From bc07855ff6cde8026bd4c25123f537db1334cf59 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Thu, 20 Mar 2025 12:56:25 +0100 Subject: [PATCH] Cleanup the registry. --- apps/ui-library/registry/.eslintrc.json | 6 - apps/ui-library/registry/blocks.ts | 26 + apps/ui-library/registry/clients.ts | 7 + apps/ui-library/registry/colors.ts | 1556 ----------------- .../blocks/dropzone/components/dropzone.tsx | 166 ++ .../dropzone/hooks/use-supabase-upload.ts | 122 ++ .../blocks/dropzone/registry-item.json | 19 + .../app/confirm/route.ts | 31 + .../app/error/page.tsx | 3 + .../app/forgot-password/page.tsx | 11 + .../app/login/page.tsx | 11 + .../app/protected/page.tsx | 14 + .../app/sign-up-success/page.tsx | 3 + .../app/sign-up/page.tsx | 11 + .../app/update-password/page.tsx | 11 + .../components/forgot-password-form.tsx | 81 + .../components/login-form.tsx | 102 ++ .../components/sign-up-form.tsx | 114 ++ .../components/update-password-form.tsx | 79 + .../password-based-auth-nextjs/middleware.ts | 19 + .../registry-item.json | 71 + .../components/forgot-password-form.tsx | 82 + .../components/login-form.tsx | 99 ++ .../components/sign-up-form.tsx | 111 ++ .../components/update-password-form.tsx | 77 + .../registry-item.json | 30 + .../realtime-cursor/components/cursor.tsx | 27 + .../components/realtime-cursors.tsx | 26 + .../hooks/use-realtime-cursors.ts | 104 ++ .../blocks/realtime-cursor/registry-item.json | 23 + .../clients/nextjs/lib/supabase/client.ts | 8 + .../clients/nextjs/lib/supabase/middleware.ts | 65 + .../clients/nextjs/lib/supabase/server.ts | 29 + .../default/clients/nextjs/registry-item.json | 23 + .../clients/react/lib/supabase/client.ts | 8 + .../default/clients/react/registry-item.json | 15 + .../clients/remix/lib/supabase.client.ts | 7 + .../clients/remix/lib/supabase.server.ts | 23 + .../default/clients/remix/registry-item.json | 19 + .../clients/tanstack/lib/supabase/client.ts | 5 + .../clients/tanstack/lib/supabase/server.ts | 23 + .../clients/tanstack/registry-item.json | 19 + .../registry/default/components/ui/button.tsx | 50 + .../registry/default/components/ui/card.tsx | 55 + .../registry/default/components/ui/input.tsx | 22 + .../registry/default/components/ui/label.tsx | 21 + .../default/components/ui/progress.tsx | 28 + .../default/components/ui/textarea.tsx | 21 + .../default/components/ui/tooltip.tsx | 32 + .../default/examples/dropzone-demo.tsx | 28 + .../default/examples/realtime-cursor-demo.tsx | 26 + apps/ui-library/registry/examples.ts | 1244 +------------ apps/ui-library/registry/index.ts | 17 + apps/ui-library/registry/registry.ts | 6 - apps/ui-library/registry/schema.ts | 68 - apps/ui-library/registry/styles.ts | 4 - apps/ui-library/registry/themes.ts | 636 ------- apps/ui-library/registry/utils.ts | 23 + 58 files changed, 2070 insertions(+), 3497 deletions(-) delete mode 100644 apps/ui-library/registry/.eslintrc.json create mode 100644 apps/ui-library/registry/blocks.ts create mode 100644 apps/ui-library/registry/clients.ts delete mode 100644 apps/ui-library/registry/colors.ts create mode 100644 apps/ui-library/registry/default/blocks/dropzone/components/dropzone.tsx create mode 100644 apps/ui-library/registry/default/blocks/dropzone/hooks/use-supabase-upload.ts create mode 100644 apps/ui-library/registry/default/blocks/dropzone/registry-item.json create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react/components/update-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react/registry-item.json create mode 100644 apps/ui-library/registry/default/blocks/realtime-cursor/components/cursor.tsx create mode 100644 apps/ui-library/registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx create mode 100644 apps/ui-library/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts create mode 100644 apps/ui-library/registry/default/blocks/realtime-cursor/registry-item.json create mode 100644 apps/ui-library/registry/default/clients/nextjs/lib/supabase/client.ts create mode 100644 apps/ui-library/registry/default/clients/nextjs/lib/supabase/middleware.ts create mode 100644 apps/ui-library/registry/default/clients/nextjs/lib/supabase/server.ts create mode 100644 apps/ui-library/registry/default/clients/nextjs/registry-item.json create mode 100644 apps/ui-library/registry/default/clients/react/lib/supabase/client.ts create mode 100644 apps/ui-library/registry/default/clients/react/registry-item.json create mode 100644 apps/ui-library/registry/default/clients/remix/lib/supabase.client.ts create mode 100644 apps/ui-library/registry/default/clients/remix/lib/supabase.server.ts create mode 100644 apps/ui-library/registry/default/clients/remix/registry-item.json create mode 100644 apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts create mode 100644 apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts create mode 100644 apps/ui-library/registry/default/clients/tanstack/registry-item.json create mode 100644 apps/ui-library/registry/default/components/ui/button.tsx create mode 100644 apps/ui-library/registry/default/components/ui/card.tsx create mode 100644 apps/ui-library/registry/default/components/ui/input.tsx create mode 100644 apps/ui-library/registry/default/components/ui/label.tsx create mode 100644 apps/ui-library/registry/default/components/ui/progress.tsx create mode 100644 apps/ui-library/registry/default/components/ui/textarea.tsx create mode 100644 apps/ui-library/registry/default/components/ui/tooltip.tsx create mode 100644 apps/ui-library/registry/default/examples/dropzone-demo.tsx create mode 100644 apps/ui-library/registry/default/examples/realtime-cursor-demo.tsx create mode 100644 apps/ui-library/registry/index.ts delete mode 100644 apps/ui-library/registry/registry.ts delete mode 100644 apps/ui-library/registry/schema.ts delete mode 100644 apps/ui-library/registry/themes.ts create mode 100644 apps/ui-library/registry/utils.ts diff --git a/apps/ui-library/registry/.eslintrc.json b/apps/ui-library/registry/.eslintrc.json deleted file mode 100644 index 6579e1b264d..00000000000 --- a/apps/ui-library/registry/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/eslintrc", - "rules": { - "react/no-unescaped-entities": "off" - } -} diff --git a/apps/ui-library/registry/blocks.ts b/apps/ui-library/registry/blocks.ts new file mode 100644 index 00000000000..dce4bc3b8f7 --- /dev/null +++ b/apps/ui-library/registry/blocks.ts @@ -0,0 +1,26 @@ +import { type Registry } from 'shadcn/registry' +import { clients } from './clients' +import dropzone from './default/blocks/dropzone/registry-item.json' assert { type: 'json' } +import passwordBasedAuthNextjs from './default/blocks/password-based-auth-nextjs/registry-item.json' assert { type: 'json' } +import passwordBasedAuthReact from './default/blocks/password-based-auth-react/registry-item.json' assert { type: 'json' } +import realtimeCursor from './default/blocks/realtime-cursor/registry-item.json' assert { type: 'json' } +import { registryItemAppend } from './utils' + +const combine = (component: Registry['items'][number]) => { + return clients.flatMap((client) => { + return registryItemAppend( + { + ...component, + name: `${component.name}-${client.name.replace('supabase-client-', '')}`, + }, + [client] + ) + }) +} + +export const blocks = [ + passwordBasedAuthNextjs, + passwordBasedAuthReact, + ...combine(dropzone as Registry['items'][number]), + ...combine(realtimeCursor as Registry['items'][number]), +] as Registry['items'] diff --git a/apps/ui-library/registry/clients.ts b/apps/ui-library/registry/clients.ts new file mode 100644 index 00000000000..7ff4c722361 --- /dev/null +++ b/apps/ui-library/registry/clients.ts @@ -0,0 +1,7 @@ +import { type Registry } from 'shadcn/registry' +import nextjs from './default/clients/nextjs/registry-item.json' assert { type: 'json' } +import react from './default/clients/react/registry-item.json' assert { type: 'json' } +import remix from './default/clients/remix/registry-item.json' assert { type: 'json' } +import tanstack from './default/clients/tanstack/registry-item.json' assert { type: 'json' } + +export const clients = [nextjs, react, remix, tanstack] as Registry['items'] diff --git a/apps/ui-library/registry/colors.ts b/apps/ui-library/registry/colors.ts deleted file mode 100644 index 2589cf4f69f..00000000000 --- a/apps/ui-library/registry/colors.ts +++ /dev/null @@ -1,1556 +0,0 @@ -export const colors = { - inherit: 'inherit', - current: 'currentColor', - transparent: 'transparent', - black: { - hex: '#000000', - rgb: 'rgb(0,0,0)', - hsl: 'hsl(0,0%,0%)', - }, - white: { - hex: '#ffffff', - rgb: 'rgb(255,255,255)', - hsl: 'hsl(0,0%,100%)', - }, - slate: [ - { - scale: 50, - hex: '#f8fafc', - rgb: 'rgb(248,250,252)', - hsl: 'hsl(210,40%,98%)', - }, - { - scale: 100, - hex: '#f1f5f9', - rgb: 'rgb(241,245,249)', - hsl: 'hsl(210,40%,96.1%)', - }, - { - scale: 200, - hex: '#e2e8f0', - rgb: 'rgb(226,232,240)', - hsl: 'hsl(214.3,31.8%,91.4%)', - }, - { - scale: 300, - hex: '#cbd5e1', - rgb: 'rgb(203,213,225)', - hsl: 'hsl(212.7,26.8%,83.9%)', - }, - { - scale: 400, - hex: '#94a3b8', - rgb: 'rgb(148,163,184)', - hsl: 'hsl(215,20.2%,65.1%)', - }, - { - scale: 500, - hex: '#64748b', - rgb: 'rgb(100,116,139)', - hsl: 'hsl(215.4,16.3%,46.9%)', - }, - { - scale: 600, - hex: '#475569', - rgb: 'rgb(71,85,105)', - hsl: 'hsl(215.3,19.3%,34.5%)', - }, - { - scale: 700, - hex: '#334155', - rgb: 'rgb(51,65,85)', - hsl: 'hsl(215.3,25%,26.7%)', - }, - { - scale: 800, - hex: '#1e293b', - rgb: 'rgb(30,41,59)', - hsl: 'hsl(217.2,32.6%,17.5%)', - }, - { - scale: 900, - hex: '#0f172a', - rgb: 'rgb(15,23,42)', - hsl: 'hsl(222.2,47.4%,11.2%)', - }, - { - scale: 950, - hex: '#020617', - rgb: 'rgb(2,6,23)', - hsl: 'hsl(222.2,84%,4.9%)', - }, - ], - gray: [ - { - scale: 50, - hex: '#f9fafb', - rgb: 'rgb(249,250,251)', - hsl: 'hsl(210,20%,98%)', - }, - { - scale: 100, - hex: '#f3f4f6', - rgb: 'rgb(243,244,246)', - hsl: 'hsl(220,14.3%,95.9%)', - }, - { - scale: 200, - hex: '#e5e7eb', - rgb: 'rgb(229,231,235)', - hsl: 'hsl(220,13%,91%)', - }, - { - scale: 300, - hex: '#d1d5db', - rgb: 'rgb(209,213,219)', - hsl: 'hsl(216,12.2%,83.9%)', - }, - { - scale: 400, - hex: '#9ca3af', - rgb: 'rgb(156,163,175)', - hsl: 'hsl(217.9,10.6%,64.9%)', - }, - { - scale: 500, - hex: '#6b7280', - rgb: 'rgb(107,114,128)', - hsl: 'hsl(220,8.9%,46.1%)', - }, - { - scale: 600, - hex: '#4b5563', - rgb: 'rgb(75,85,99)', - hsl: 'hsl(215,13.8%,34.1%)', - }, - { - scale: 700, - hex: '#374151', - rgb: 'rgb(55,65,81)', - hsl: 'hsl(216.9,19.1%,26.7%)', - }, - { - scale: 800, - hex: '#1f2937', - rgb: 'rgb(31,41,55)', - hsl: 'hsl(215,27.9%,16.9%)', - }, - { - scale: 900, - hex: '#111827', - rgb: 'rgb(17,24,39)', - hsl: 'hsl(220.9,39.3%,11%)', - }, - { - scale: 950, - hex: '#030712', - rgb: 'rgb(3,7,18)', - hsl: 'hsl(224,71.4%,4.1%)', - }, - ], - zinc: [ - { - scale: 50, - hex: '#fafafa', - rgb: 'rgb(250,250,250)', - hsl: 'hsl(0,0%,98%)', - }, - { - scale: 100, - hex: '#f4f4f5', - rgb: 'rgb(244,244,245)', - hsl: 'hsl(240,4.8%,95.9%)', - }, - { - scale: 200, - hex: '#e4e4e7', - rgb: 'rgb(228,228,231)', - hsl: 'hsl(240,5.9%,90%)', - }, - { - scale: 300, - hex: '#d4d4d8', - rgb: 'rgb(212,212,216)', - hsl: 'hsl(240,4.9%,83.9%)', - }, - { - scale: 400, - hex: '#a1a1aa', - rgb: 'rgb(161,161,170)', - hsl: 'hsl(240,5%,64.9%)', - }, - { - scale: 500, - hex: '#71717a', - rgb: 'rgb(113,113,122)', - hsl: 'hsl(240,3.8%,46.1%)', - }, - { - scale: 600, - hex: '#52525b', - rgb: 'rgb(82,82,91)', - hsl: 'hsl(240,5.2%,33.9%)', - }, - { - scale: 700, - hex: '#3f3f46', - rgb: 'rgb(63,63,70)', - hsl: 'hsl(240,5.3%,26.1%)', - }, - { - scale: 800, - hex: '#27272a', - rgb: 'rgb(39,39,42)', - hsl: 'hsl(240,3.7%,15.9%)', - }, - { - scale: 900, - hex: '#18181b', - rgb: 'rgb(24,24,27)', - hsl: 'hsl(240,5.9%,10%)', - }, - { - scale: 950, - hex: '#09090b', - rgb: 'rgb(9,9,11)', - hsl: 'hsl(240,10%,3.9%)', - }, - ], - neutral: [ - { - scale: 50, - hex: '#fafafa', - rgb: 'rgb(250,250,250)', - hsl: 'hsl(0,0%,98%)', - }, - { - scale: 100, - hex: '#f5f5f5', - rgb: 'rgb(245,245,245)', - hsl: 'hsl(0,0%,96.1%)', - }, - { - scale: 200, - hex: '#e5e5e5', - rgb: 'rgb(229,229,229)', - hsl: 'hsl(0,0%,89.8%)', - }, - { - scale: 300, - hex: '#d4d4d4', - rgb: 'rgb(212,212,212)', - hsl: 'hsl(0,0%,83.1%)', - }, - { - scale: 400, - hex: '#a3a3a3', - rgb: 'rgb(163,163,163)', - hsl: 'hsl(0,0%,63.9%)', - }, - { - scale: 500, - hex: '#737373', - rgb: 'rgb(115,115,115)', - hsl: 'hsl(0,0%,45.1%)', - }, - { - scale: 600, - hex: '#525252', - rgb: 'rgb(82,82,82)', - hsl: 'hsl(0,0%,32.2%)', - }, - { - scale: 700, - hex: '#404040', - rgb: 'rgb(64,64,64)', - hsl: 'hsl(0,0%,25.1%)', - }, - { - scale: 800, - hex: '#262626', - rgb: 'rgb(38,38,38)', - hsl: 'hsl(0,0%,14.9%)', - }, - { - scale: 900, - hex: '#171717', - rgb: 'rgb(23,23,23)', - hsl: 'hsl(0,0%,9%)', - }, - { - scale: 950, - hex: '#0a0a0a', - rgb: 'rgb(10,10,10)', - hsl: 'hsl(0,0%,3.9%)', - }, - ], - stone: [ - { - scale: 50, - hex: '#fafaf9', - rgb: 'rgb(250,250,249)', - hsl: 'hsl(60,9.1%,97.8%)', - }, - { - scale: 100, - hex: '#f5f5f4', - rgb: 'rgb(245,245,244)', - hsl: 'hsl(60,4.8%,95.9%)', - }, - { - scale: 200, - hex: '#e7e5e4', - rgb: 'rgb(231,229,228)', - hsl: 'hsl(20,5.9%,90%)', - }, - { - scale: 300, - hex: '#d6d3d1', - rgb: 'rgb(214,211,209)', - hsl: 'hsl(24,5.7%,82.9%)', - }, - { - scale: 400, - hex: '#a8a29e', - rgb: 'rgb(168,162,158)', - hsl: 'hsl(24,5.4%,63.9%)', - }, - { - scale: 500, - hex: '#78716c', - rgb: 'rgb(120,113,108)', - hsl: 'hsl(25,5.3%,44.7%)', - }, - { - scale: 600, - hex: '#57534e', - rgb: 'rgb(87,83,78)', - hsl: 'hsl(33.3,5.5%,32.4%)', - }, - { - scale: 700, - hex: '#44403c', - rgb: 'rgb(68,64,60)', - hsl: 'hsl(30,6.3%,25.1%)', - }, - { - scale: 800, - hex: '#292524', - rgb: 'rgb(41,37,36)', - hsl: 'hsl(12,6.5%,15.1%)', - }, - { - scale: 900, - hex: '#1c1917', - rgb: 'rgb(28,25,23)', - hsl: 'hsl(24,9.8%,10%)', - }, - { - scale: 950, - hex: '#0c0a09', - rgb: 'rgb(12,10,9)', - hsl: 'hsl(20,14.3%,4.1%)', - }, - ], - red: [ - { - scale: 50, - hex: '#fef2f2', - rgb: 'rgb(254,242,242)', - hsl: 'hsl(0,85.7%,97.3%)', - }, - { - scale: 100, - hex: '#fee2e2', - rgb: 'rgb(254,226,226)', - hsl: 'hsl(0,93.3%,94.1%)', - }, - { - scale: 200, - hex: '#fecaca', - rgb: 'rgb(254,202,202)', - hsl: 'hsl(0,96.3%,89.4%)', - }, - { - scale: 300, - hex: '#fca5a5', - rgb: 'rgb(252,165,165)', - hsl: 'hsl(0,93.5%,81.8%)', - }, - { - scale: 400, - hex: '#f87171', - rgb: 'rgb(248,113,113)', - hsl: 'hsl(0,90.6%,70.8%)', - }, - { - scale: 500, - hex: '#ef4444', - rgb: 'rgb(239,68,68)', - hsl: 'hsl(0,84.2%,60.2%)', - }, - { - scale: 600, - hex: '#dc2626', - rgb: 'rgb(220,38,38)', - hsl: 'hsl(0,72.2%,50.6%)', - }, - { - scale: 700, - hex: '#b91c1c', - rgb: 'rgb(185,28,28)', - hsl: 'hsl(0,73.7%,41.8%)', - }, - { - scale: 800, - hex: '#991b1b', - rgb: 'rgb(153,27,27)', - hsl: 'hsl(0,70%,35.3%)', - }, - { - scale: 900, - hex: '#7f1d1d', - rgb: 'rgb(127,29,29)', - hsl: 'hsl(0,62.8%,30.6%)', - }, - { - scale: 950, - hex: '#450a0a', - rgb: 'rgb(69,10,10)', - hsl: 'hsl(0,74.7%,15.5%)', - }, - ], - orange: [ - { - scale: 50, - hex: '#fff7ed', - rgb: 'rgb(255,247,237)', - hsl: 'hsl(33.3,100%,96.5%)', - }, - { - scale: 100, - hex: '#ffedd5', - rgb: 'rgb(255,237,213)', - hsl: 'hsl(34.3,100%,91.8%)', - }, - { - scale: 200, - hex: '#fed7aa', - rgb: 'rgb(254,215,170)', - hsl: 'hsl(32.1,97.7%,83.1%)', - }, - { - scale: 300, - hex: '#fdba74', - rgb: 'rgb(253,186,116)', - hsl: 'hsl(30.7,97.2%,72.4%)', - }, - { - scale: 400, - hex: '#fb923c', - rgb: 'rgb(251,146,60)', - hsl: 'hsl(27,96%,61%)', - }, - { - scale: 500, - hex: '#f97316', - rgb: 'rgb(249,115,22)', - hsl: 'hsl(24.6,95%,53.1%)', - }, - { - scale: 600, - hex: '#ea580c', - rgb: 'rgb(234,88,12)', - hsl: 'hsl(20.5,90.2%,48.2%)', - }, - { - scale: 700, - hex: '#c2410c', - rgb: 'rgb(194,65,12)', - hsl: 'hsl(17.5,88.3%,40.4%)', - }, - { - scale: 800, - hex: '#9a3412', - rgb: 'rgb(154,52,18)', - hsl: 'hsl(15,79.1%,33.7%)', - }, - { - scale: 900, - hex: '#7c2d12', - rgb: 'rgb(124,45,18)', - hsl: 'hsl(15.3,74.6%,27.8%)', - }, - { - scale: 950, - hex: '#431407', - rgb: 'rgb(67,20,7)', - hsl: 'hsl(13,81.1%,14.5%)', - }, - ], - amber: [ - { - scale: 50, - hex: '#fffbeb', - rgb: 'rgb(255,251,235)', - hsl: 'hsl(48,100%,96.1%)', - }, - { - scale: 100, - hex: '#fef3c7', - rgb: 'rgb(254,243,199)', - hsl: 'hsl(48,96.5%,88.8%)', - }, - { - scale: 200, - hex: '#fde68a', - rgb: 'rgb(253,230,138)', - hsl: 'hsl(48,96.6%,76.7%)', - }, - { - scale: 300, - hex: '#fcd34d', - rgb: 'rgb(252,211,77)', - hsl: 'hsl(45.9,96.7%,64.5%)', - }, - { - scale: 400, - hex: '#fbbf24', - rgb: 'rgb(251,191,36)', - hsl: 'hsl(43.3,96.4%,56.3%)', - }, - { - scale: 500, - hex: '#f59e0b', - rgb: 'rgb(245,158,11)', - hsl: 'hsl(37.7,92.1%,50.2%)', - }, - { - scale: 600, - hex: '#d97706', - rgb: 'rgb(217,119,6)', - hsl: 'hsl(32.1,94.6%,43.7%)', - }, - { - scale: 700, - hex: '#b45309', - rgb: 'rgb(180,83,9)', - hsl: 'hsl(26,90.5%,37.1%)', - }, - { - scale: 800, - hex: '#92400e', - rgb: 'rgb(146,64,14)', - hsl: 'hsl(22.7,82.5%,31.4%)', - }, - { - scale: 900, - hex: '#78350f', - rgb: 'rgb(120,53,15)', - hsl: 'hsl(21.7,77.8%,26.5%)', - }, - { - scale: 950, - hex: '#451a03', - rgb: 'rgb(69,26,3)', - hsl: 'hsl(20.9,91.7%,14.1%)', - }, - ], - yellow: [ - { - scale: 50, - hex: '#fefce8', - rgb: 'rgb(254,252,232)', - hsl: 'hsl(54.5,91.7%,95.3%)', - }, - { - scale: 100, - hex: '#fef9c3', - rgb: 'rgb(254,249,195)', - hsl: 'hsl(54.9,96.7%,88%)', - }, - { - scale: 200, - hex: '#fef08a', - rgb: 'rgb(254,240,138)', - hsl: 'hsl(52.8,98.3%,76.9%)', - }, - { - scale: 300, - hex: '#fde047', - rgb: 'rgb(253,224,71)', - hsl: 'hsl(50.4,97.8%,63.5%)', - }, - { - scale: 400, - hex: '#facc15', - rgb: 'rgb(250,204,21)', - hsl: 'hsl(47.9,95.8%,53.1%)', - }, - { - scale: 500, - hex: '#eab308', - rgb: 'rgb(234,179,8)', - hsl: 'hsl(45.4,93.4%,47.5%)', - }, - { - scale: 600, - hex: '#ca8a04', - rgb: 'rgb(202,138,4)', - hsl: 'hsl(40.6,96.1%,40.4%)', - }, - { - scale: 700, - hex: '#a16207', - rgb: 'rgb(161,98,7)', - hsl: 'hsl(35.5,91.7%,32.9%)', - }, - { - scale: 800, - hex: '#854d0e', - rgb: 'rgb(133,77,14)', - hsl: 'hsl(31.8,81%,28.8%)', - }, - { - scale: 900, - hex: '#713f12', - rgb: 'rgb(113,63,18)', - hsl: 'hsl(28.4,72.5%,25.7%)', - }, - { - scale: 950, - hex: '#422006', - rgb: 'rgb(66,32,6)', - hsl: 'hsl(26,83.3%,14.1%)', - }, - ], - lime: [ - { - scale: 50, - hex: '#f7fee7', - rgb: 'rgb(247,254,231)', - hsl: 'hsl(78.3,92%,95.1%)', - }, - { - scale: 100, - hex: '#ecfccb', - rgb: 'rgb(236,252,203)', - hsl: 'hsl(79.6,89.1%,89.2%)', - }, - { - scale: 200, - hex: '#d9f99d', - rgb: 'rgb(217,249,157)', - hsl: 'hsl(80.9,88.5%,79.6%)', - }, - { - scale: 300, - hex: '#bef264', - rgb: 'rgb(190,242,100)', - hsl: 'hsl(82,84.5%,67.1%)', - }, - { - scale: 400, - hex: '#a3e635', - rgb: 'rgb(163,230,53)', - hsl: 'hsl(82.7,78%,55.5%)', - }, - { - scale: 500, - hex: '#84cc16', - rgb: 'rgb(132,204,22)', - hsl: 'hsl(83.7,80.5%,44.3%)', - }, - { - scale: 600, - hex: '#65a30d', - rgb: 'rgb(101,163,13)', - hsl: 'hsl(84.8,85.2%,34.5%)', - }, - { - scale: 700, - hex: '#4d7c0f', - rgb: 'rgb(77,124,15)', - hsl: 'hsl(85.9,78.4%,27.3%)', - }, - { - scale: 800, - hex: '#3f6212', - rgb: 'rgb(63,98,18)', - hsl: 'hsl(86.3,69%,22.7%)', - }, - { - scale: 900, - hex: '#365314', - rgb: 'rgb(54,83,20)', - hsl: 'hsl(87.6,61.2%,20.2%)', - }, - { - scale: 950, - hex: '#1a2e05', - rgb: 'rgb(26,46,5)', - hsl: 'hsl(89.3,80.4%,10%)', - }, - ], - green: [ - { - scale: 50, - hex: '#f0fdf4', - rgb: 'rgb(240,253,244)', - hsl: 'hsl(138.5,76.5%,96.7%)', - }, - { - scale: 100, - hex: '#dcfce7', - rgb: 'rgb(220,252,231)', - hsl: 'hsl(140.6,84.2%,92.5%)', - }, - { - scale: 200, - hex: '#bbf7d0', - rgb: 'rgb(187,247,208)', - hsl: 'hsl(141,78.9%,85.1%)', - }, - { - scale: 300, - hex: '#86efac', - rgb: 'rgb(134,239,172)', - hsl: 'hsl(141.7,76.6%,73.1%)', - }, - { - scale: 400, - hex: '#4ade80', - rgb: 'rgb(74,222,128)', - hsl: 'hsl(141.9,69.2%,58%)', - }, - { - scale: 500, - hex: '#22c55e', - rgb: 'rgb(34,197,94)', - hsl: 'hsl(142.1,70.6%,45.3%)', - }, - { - scale: 600, - hex: '#16a34a', - rgb: 'rgb(22,163,74)', - hsl: 'hsl(142.1,76.2%,36.3%)', - }, - { - scale: 700, - hex: '#15803d', - rgb: 'rgb(21,128,61)', - hsl: 'hsl(142.4,71.8%,29.2%)', - }, - { - scale: 800, - hex: '#166534', - rgb: 'rgb(22,101,52)', - hsl: 'hsl(142.8,64.2%,24.1%)', - }, - { - scale: 900, - hex: '#14532d', - rgb: 'rgb(20,83,45)', - hsl: 'hsl(143.8,61.2%,20.2%)', - }, - { - scale: 950, - hex: '#052e16', - rgb: 'rgb(5,46,22)', - hsl: 'hsl(144.9,80.4%,10%)', - }, - ], - emerald: [ - { - scale: 50, - hex: '#ecfdf5', - rgb: 'rgb(236,253,245)', - hsl: 'hsl(151.8,81%,95.9%)', - }, - { - scale: 100, - hex: '#d1fae5', - rgb: 'rgb(209,250,229)', - hsl: 'hsl(149.3,80.4%,90%)', - }, - { - scale: 200, - hex: '#a7f3d0', - rgb: 'rgb(167,243,208)', - hsl: 'hsl(152.4,76%,80.4%)', - }, - { - scale: 300, - hex: '#6ee7b7', - rgb: 'rgb(110,231,183)', - hsl: 'hsl(156.2,71.6%,66.9%)', - }, - { - scale: 400, - hex: '#34d399', - rgb: 'rgb(52,211,153)', - hsl: 'hsl(158.1,64.4%,51.6%)', - }, - { - scale: 500, - hex: '#10b981', - rgb: 'rgb(16,185,129)', - hsl: 'hsl(160.1,84.1%,39.4%)', - }, - { - scale: 600, - hex: '#059669', - rgb: 'rgb(5,150,105)', - hsl: 'hsl(161.4,93.5%,30.4%)', - }, - { - scale: 700, - hex: '#047857', - rgb: 'rgb(4,120,87)', - hsl: 'hsl(162.9,93.5%,24.3%)', - }, - { - scale: 800, - hex: '#065f46', - rgb: 'rgb(6,95,70)', - hsl: 'hsl(163.1,88.1%,19.8%)', - }, - { - scale: 900, - hex: '#064e3b', - rgb: 'rgb(6,78,59)', - hsl: 'hsl(164.2,85.7%,16.5%)', - }, - { - scale: 950, - hex: '#022c22', - rgb: 'rgb(2,44,34)', - hsl: 'hsl(165.7,91.3%,9%)', - }, - ], - teal: [ - { - scale: 50, - hex: '#f0fdfa', - rgb: 'rgb(240,253,250)', - hsl: 'hsl(166.2,76.5%,96.7%)', - }, - { - scale: 100, - hex: '#ccfbf1', - rgb: 'rgb(204,251,241)', - hsl: 'hsl(167.2,85.5%,89.2%)', - }, - { - scale: 200, - hex: '#99f6e4', - rgb: 'rgb(153,246,228)', - hsl: 'hsl(168.4,83.8%,78.2%)', - }, - { - scale: 300, - hex: '#5eead4', - rgb: 'rgb(94,234,212)', - hsl: 'hsl(170.6,76.9%,64.3%)', - }, - { - scale: 400, - hex: '#2dd4bf', - rgb: 'rgb(45,212,191)', - hsl: 'hsl(172.5,66%,50.4%)', - }, - { - scale: 500, - hex: '#14b8a6', - rgb: 'rgb(20,184,166)', - hsl: 'hsl(173.4,80.4%,40%)', - }, - { - scale: 600, - hex: '#0d9488', - rgb: 'rgb(13,148,136)', - hsl: 'hsl(174.7,83.9%,31.6%)', - }, - { - scale: 700, - hex: '#0f766e', - rgb: 'rgb(15,118,110)', - hsl: 'hsl(175.3,77.4%,26.1%)', - }, - { - scale: 800, - hex: '#115e59', - rgb: 'rgb(17,94,89)', - hsl: 'hsl(176.1,69.4%,21.8%)', - }, - { - scale: 900, - hex: '#134e4a', - rgb: 'rgb(19,78,74)', - hsl: 'hsl(175.9,60.8%,19%)', - }, - { - scale: 950, - hex: '#042f2e', - rgb: 'rgb(4,47,46)', - hsl: 'hsl(178.6,84.3%,10%)', - }, - ], - cyan: [ - { - scale: 50, - hex: '#ecfeff', - rgb: 'rgb(236,254,255)', - hsl: 'hsl(183.2,100%,96.3%)', - }, - { - scale: 100, - hex: '#cffafe', - rgb: 'rgb(207,250,254)', - hsl: 'hsl(185.1,95.9%,90.4%)', - }, - { - scale: 200, - hex: '#a5f3fc', - rgb: 'rgb(165,243,252)', - hsl: 'hsl(186.2,93.5%,81.8%)', - }, - { - scale: 300, - hex: '#67e8f9', - rgb: 'rgb(103,232,249)', - hsl: 'hsl(187,92.4%,69%)', - }, - { - scale: 400, - hex: '#22d3ee', - rgb: 'rgb(34,211,238)', - hsl: 'hsl(187.9,85.7%,53.3%)', - }, - { - scale: 500, - hex: '#06b6d4', - rgb: 'rgb(6,182,212)', - hsl: 'hsl(188.7,94.5%,42.7%)', - }, - { - scale: 600, - hex: '#0891b2', - rgb: 'rgb(8,145,178)', - hsl: 'hsl(191.6,91.4%,36.5%)', - }, - { - scale: 700, - hex: '#0e7490', - rgb: 'rgb(14,116,144)', - hsl: 'hsl(192.9,82.3%,31%)', - }, - { - scale: 800, - hex: '#155e75', - rgb: 'rgb(21,94,117)', - hsl: 'hsl(194.4,69.6%,27.1%)', - }, - { - scale: 900, - hex: '#164e63', - rgb: 'rgb(22,78,99)', - hsl: 'hsl(196.4,63.6%,23.7%)', - }, - { - scale: 950, - hex: '#083344', - rgb: 'rgb(8,51,68)', - hsl: 'hsl(197,78.9%,14.9%)', - }, - ], - sky: [ - { - scale: 50, - hex: '#f0f9ff', - rgb: 'rgb(240,249,255)', - hsl: 'hsl(204,100%,97.1%)', - }, - { - scale: 100, - hex: '#e0f2fe', - rgb: 'rgb(224,242,254)', - hsl: 'hsl(204,93.8%,93.7%)', - }, - { - scale: 200, - hex: '#bae6fd', - rgb: 'rgb(186,230,253)', - hsl: 'hsl(200.6,94.4%,86.1%)', - }, - { - scale: 300, - hex: '#7dd3fc', - rgb: 'rgb(125,211,252)', - hsl: 'hsl(199.4,95.5%,73.9%)', - }, - { - scale: 400, - hex: '#38bdf8', - rgb: 'rgb(56,189,248)', - hsl: 'hsl(198.4,93.2%,59.6%)', - }, - { - scale: 500, - hex: '#0ea5e9', - rgb: 'rgb(14,165,233)', - hsl: 'hsl(198.6,88.7%,48.4%)', - }, - { - scale: 600, - hex: '#0284c7', - rgb: 'rgb(2,132,199)', - hsl: 'hsl(200.4,98%,39.4%)', - }, - { - scale: 700, - hex: '#0369a1', - rgb: 'rgb(3,105,161)', - hsl: 'hsl(201.3,96.3%,32.2%)', - }, - { - scale: 800, - hex: '#075985', - rgb: 'rgb(7,89,133)', - hsl: 'hsl(201,90%,27.5%)', - }, - { - scale: 900, - hex: '#0c4a6e', - rgb: 'rgb(12,74,110)', - hsl: 'hsl(202,80.3%,23.9%)', - }, - { - scale: 950, - hex: '#082f49', - rgb: 'rgb(8,47,73)', - hsl: 'hsl(204,80.2%,15.9%)', - }, - ], - blue: [ - { - scale: 50, - hex: '#eff6ff', - rgb: 'rgb(239,246,255)', - hsl: 'hsl(213.8,100%,96.9%)', - }, - { - scale: 100, - hex: '#dbeafe', - rgb: 'rgb(219,234,254)', - hsl: 'hsl(214.3,94.6%,92.7%)', - }, - { - scale: 200, - hex: '#bfdbfe', - rgb: 'rgb(191,219,254)', - hsl: 'hsl(213.3,96.9%,87.3%)', - }, - { - scale: 300, - hex: '#93c5fd', - rgb: 'rgb(147,197,253)', - hsl: 'hsl(211.7,96.4%,78.4%)', - }, - { - scale: 400, - hex: '#60a5fa', - rgb: 'rgb(96,165,250)', - hsl: 'hsl(213.1,93.9%,67.8%)', - }, - { - scale: 500, - hex: '#3b82f6', - rgb: 'rgb(59,130,246)', - hsl: 'hsl(217.2,91.2%,59.8%)', - }, - { - scale: 600, - hex: '#2563eb', - rgb: 'rgb(37,99,235)', - hsl: 'hsl(221.2,83.2%,53.3%)', - }, - { - scale: 700, - hex: '#1d4ed8', - rgb: 'rgb(29,78,216)', - hsl: 'hsl(224.3,76.3%,48%)', - }, - { - scale: 800, - hex: '#1e40af', - rgb: 'rgb(30,64,175)', - hsl: 'hsl(225.9,70.7%,40.2%)', - }, - { - scale: 900, - hex: '#1e3a8a', - rgb: 'rgb(30,58,138)', - hsl: 'hsl(224.4,64.3%,32.9%)', - }, - { - scale: 950, - hex: '#172554', - rgb: 'rgb(23,37,84)', - hsl: 'hsl(226.2,57%,21%)', - }, - ], - indigo: [ - { - scale: 50, - hex: '#eef2ff', - rgb: 'rgb(238,242,255)', - hsl: 'hsl(225.9,100%,96.7%)', - }, - { - scale: 100, - hex: '#e0e7ff', - rgb: 'rgb(224,231,255)', - hsl: 'hsl(226.5,100%,93.9%)', - }, - { - scale: 200, - hex: '#c7d2fe', - rgb: 'rgb(199,210,254)', - hsl: 'hsl(228,96.5%,88.8%)', - }, - { - scale: 300, - hex: '#a5b4fc', - rgb: 'rgb(165,180,252)', - hsl: 'hsl(229.7,93.5%,81.8%)', - }, - { - scale: 400, - hex: '#818cf8', - rgb: 'rgb(129,140,248)', - hsl: 'hsl(234.5,89.5%,73.9%)', - }, - { - scale: 500, - hex: '#6366f1', - rgb: 'rgb(99,102,241)', - hsl: 'hsl(238.7,83.5%,66.7%)', - }, - { - scale: 600, - hex: '#4f46e5', - rgb: 'rgb(79,70,229)', - hsl: 'hsl(243.4,75.4%,58.6%)', - }, - { - scale: 700, - hex: '#4338ca', - rgb: 'rgb(67,56,202)', - hsl: 'hsl(244.5,57.9%,50.6%)', - }, - { - scale: 800, - hex: '#3730a3', - rgb: 'rgb(55,48,163)', - hsl: 'hsl(243.7,54.5%,41.4%)', - }, - { - scale: 900, - hex: '#312e81', - rgb: 'rgb(49,46,129)', - hsl: 'hsl(242.2,47.4%,34.3%)', - }, - { - scale: 950, - hex: '#1e1b4b', - rgb: 'rgb(30,27,75)', - hsl: 'hsl(243.8,47.1%,20%)', - }, - ], - violet: [ - { - scale: 50, - hex: '#f5f3ff', - rgb: 'rgb(245,243,255)', - hsl: 'hsl(250,100%,97.6%)', - }, - { - scale: 100, - hex: '#ede9fe', - rgb: 'rgb(237,233,254)', - hsl: 'hsl(251.4,91.3%,95.5%)', - }, - { - scale: 200, - hex: '#ddd6fe', - rgb: 'rgb(221,214,254)', - hsl: 'hsl(250.5,95.2%,91.8%)', - }, - { - scale: 300, - hex: '#c4b5fd', - rgb: 'rgb(196,181,253)', - hsl: 'hsl(252.5,94.7%,85.1%)', - }, - { - scale: 400, - hex: '#a78bfa', - rgb: 'rgb(167,139,250)', - hsl: 'hsl(255.1,91.7%,76.3%)', - }, - { - scale: 500, - hex: '#8b5cf6', - rgb: 'rgb(139,92,246)', - hsl: 'hsl(258.3,89.5%,66.3%)', - }, - { - scale: 600, - hex: '#7c3aed', - rgb: 'rgb(124,58,237)', - hsl: 'hsl(262.1,83.3%,57.8%)', - }, - { - scale: 700, - hex: '#6d28d9', - rgb: 'rgb(109,40,217)', - hsl: 'hsl(263.4,70%,50.4%)', - }, - { - scale: 800, - hex: '#5b21b6', - rgb: 'rgb(91,33,182)', - hsl: 'hsl(263.4,69.3%,42.2%)', - }, - { - scale: 900, - hex: '#4c1d95', - rgb: 'rgb(76,29,149)', - hsl: 'hsl(263.5,67.4%,34.9%)', - }, - { - scale: 950, - hex: '#1e1b4b', - rgb: 'rgb(46,16,101)', - hsl: 'hsl(261.2,72.6%,22.9%)', - }, - ], - purple: [ - { - scale: 50, - hex: '#faf5ff', - rgb: 'rgb(250,245,255)', - hsl: 'hsl(270,100%,98%)', - }, - { - scale: 100, - hex: '#f3e8ff', - rgb: 'rgb(243,232,255)', - hsl: 'hsl(268.7,100%,95.5%)', - }, - { - scale: 200, - hex: '#e9d5ff', - rgb: 'rgb(233,213,255)', - hsl: 'hsl(268.6,100%,91.8%)', - }, - { - scale: 300, - hex: '#d8b4fe', - rgb: 'rgb(216,180,254)', - hsl: 'hsl(269.2,97.4%,85.1%)', - }, - { - scale: 400, - hex: '#c084fc', - rgb: 'rgb(192,132,252)', - hsl: 'hsl(270,95.2%,75.3%)', - }, - { - scale: 500, - hex: '#a855f7', - rgb: 'rgb(168,85,247)', - hsl: 'hsl(270.7,91%,65.1%)', - }, - { - scale: 600, - hex: '#9333ea', - rgb: 'rgb(147,51,234)', - hsl: 'hsl(271.5,81.3%,55.9%)', - }, - { - scale: 700, - hex: '#7e22ce', - rgb: 'rgb(126,34,206)', - hsl: 'hsl(272.1,71.7%,47.1%)', - }, - { - scale: 800, - hex: '#6b21a8', - rgb: 'rgb(107,33,168)', - hsl: 'hsl(272.9,67.2%,39.4%)', - }, - { - scale: 900, - hex: '#581c87', - rgb: 'rgb(88,28,135)', - hsl: 'hsl(273.6,65.6%,32%)', - }, - { - scale: 950, - hex: '#3b0764', - rgb: 'rgb(59,7,100)', - hsl: 'hsl(273.5,86.9%,21%)', - }, - ], - fuchsia: [ - { - scale: 50, - hex: '#fdf4ff', - rgb: 'rgb(253,244,255)', - hsl: 'hsl(289.1,100%,97.8%)', - }, - { - scale: 100, - hex: '#fae8ff', - rgb: 'rgb(250,232,255)', - hsl: 'hsl(287,100%,95.5%)', - }, - { - scale: 200, - hex: '#f5d0fe', - rgb: 'rgb(245,208,254)', - hsl: 'hsl(288.3,95.8%,90.6%)', - }, - { - scale: 300, - hex: '#f0abfc', - rgb: 'rgb(240,171,252)', - hsl: 'hsl(291.1,93.1%,82.9%)', - }, - { - scale: 400, - hex: '#e879f9', - rgb: 'rgb(232,121,249)', - hsl: 'hsl(292,91.4%,72.5%)', - }, - { - scale: 500, - hex: '#d946ef', - rgb: 'rgb(217,70,239)', - hsl: 'hsl(292.2,84.1%,60.6%)', - }, - { - scale: 600, - hex: '#c026d3', - rgb: 'rgb(192,38,211)', - hsl: 'hsl(293.4,69.5%,48.8%)', - }, - { - scale: 700, - hex: '#a21caf', - rgb: 'rgb(162,28,175)', - hsl: 'hsl(294.7,72.4%,39.8%)', - }, - { - scale: 800, - hex: '#86198f', - rgb: 'rgb(134,25,143)', - hsl: 'hsl(295.4,70.2%,32.9%)', - }, - { - scale: 900, - hex: '#701a75', - rgb: 'rgb(112,26,117)', - hsl: 'hsl(296.7,63.6%,28%)', - }, - { - scale: 950, - hex: '#4a044e', - rgb: 'rgb(74,4,78)', - hsl: 'hsl(296.8,90.2%,16.1%)', - }, - ], - pink: [ - { - scale: 50, - hex: '#fdf2f8', - rgb: 'rgb(253,242,248)', - hsl: 'hsl(327.3,73.3%,97.1%)', - }, - { - scale: 100, - hex: '#fce7f3', - rgb: 'rgb(252,231,243)', - hsl: 'hsl(325.7,77.8%,94.7%)', - }, - { - scale: 200, - hex: '#fbcfe8', - rgb: 'rgb(251,207,232)', - hsl: 'hsl(325.9,84.6%,89.8%)', - }, - { - scale: 300, - hex: '#f9a8d4', - rgb: 'rgb(249,168,212)', - hsl: 'hsl(327.4,87.1%,81.8%)', - }, - { - scale: 400, - hex: '#f472b6', - rgb: 'rgb(244,114,182)', - hsl: 'hsl(328.6,85.5%,70.2%)', - }, - { - scale: 500, - hex: '#ec4899', - rgb: 'rgb(236,72,153)', - hsl: 'hsl(330.4,81.2%,60.4%)', - }, - { - scale: 600, - hex: '#db2777', - rgb: 'rgb(219,39,119)', - hsl: 'hsl(333.3,71.4%,50.6%)', - }, - { - scale: 700, - hex: '#be185d', - rgb: 'rgb(190,24,93)', - hsl: 'hsl(335.1,77.6%,42%)', - }, - { - scale: 800, - hex: '#9d174d', - rgb: 'rgb(157,23,77)', - hsl: 'hsl(335.8,74.4%,35.3%)', - }, - { - scale: 900, - hex: '#831843', - rgb: 'rgb(131,24,67)', - hsl: 'hsl(335.9,69%,30.4%)', - }, - { - scale: 950, - hex: '#500724', - rgb: 'rgb(80,7,36)', - hsl: 'hsl(336.2,83.9%,17.1%)', - }, - ], - rose: [ - { - scale: 50, - hex: '#fff1f2', - rgb: 'rgb(255,241,242)', - hsl: 'hsl(355.7,100%,97.3%)', - }, - { - scale: 100, - hex: '#ffe4e6', - rgb: 'rgb(255,228,230)', - hsl: 'hsl(355.6,100%,94.7%)', - }, - { - scale: 200, - hex: '#fecdd3', - rgb: 'rgb(254,205,211)', - hsl: 'hsl(352.7,96.1%,90%)', - }, - { - scale: 300, - hex: '#fda4af', - rgb: 'rgb(253,164,175)', - hsl: 'hsl(352.6,95.7%,81.8%)', - }, - { - scale: 400, - hex: '#fb7185', - rgb: 'rgb(251,113,133)', - hsl: 'hsl(351.3,94.5%,71.4%)', - }, - { - scale: 500, - hex: '#f43f5e', - rgb: 'rgb(244,63,94)', - hsl: 'hsl(349.7,89.2%,60.2%)', - }, - { - scale: 600, - hex: '#e11d48', - rgb: 'rgb(225,29,72)', - hsl: 'hsl(346.8,77.2%,49.8%)', - }, - { - scale: 700, - hex: '#be123c', - rgb: 'rgb(190,18,60)', - hsl: 'hsl(345.3,82.7%,40.8%)', - }, - { - scale: 800, - hex: '#9f1239', - rgb: 'rgb(159,18,57)', - hsl: 'hsl(343.4,79.7%,34.7%)', - }, - { - scale: 900, - hex: '#881337', - rgb: 'rgb(136,19,55)', - hsl: 'hsl(341.5,75.5%,30.4%)', - }, - { - scale: 950, - hex: '#4c0519', - rgb: 'rgb(76,5,25)', - hsl: 'hsl(343.1,87.7%,15.9%)', - }, - ], -} - -export const colorMapping = { - light: { - background: 'white', - foreground: '{{base}}-950', - card: 'white', - 'card-foreground': '{{base}}-950', - popover: 'white', - 'popover-foreground': '{{base}}-950', - primary: '{{base}}-900', - 'primary-foreground': '{{base}}-50', - secondary: '{{base}}-100', - 'secondary-foreground': '{{base}}-900', - muted: '{{base}}-100', - 'muted-foreground': '{{base}}-500', - accent: '{{base}}-100', - 'accent-foreground': '{{base}}-900', - destructive: 'red-500', - 'destructive-foreground': '{{base}}-50', - border: '{{base}}-200', - input: '{{base}}-200', - ring: '{{base}}-950', - }, - dark: { - background: '{{base}}-950', - foreground: '{{base}}-50', - card: '{{base}}-950', - 'card-foreground': '{{base}}-50', - popover: '{{base}}-950', - 'popover-foreground': '{{base}}-50', - primary: '{{base}}-50', - 'primary-foreground': '{{base}}-900', - secondary: '{{base}}-800', - 'secondary-foreground': '{{base}}-50', - muted: '{{base}}-800', - 'muted-foreground': '{{base}}-400', - accent: '{{base}}-800', - 'accent-foreground': '{{base}}-50', - destructive: 'red-900', - 'destructive-foreground': '{{base}}-50', - border: '{{base}}-800', - input: '{{base}}-800', - ring: '{{base}}-300', - }, -} as const diff --git a/apps/ui-library/registry/default/blocks/dropzone/components/dropzone.tsx b/apps/ui-library/registry/default/blocks/dropzone/components/dropzone.tsx new file mode 100644 index 00000000000..7004b3889ad --- /dev/null +++ b/apps/ui-library/registry/default/blocks/dropzone/components/dropzone.tsx @@ -0,0 +1,166 @@ +'use client' + +import { cn } from '@/lib/utils' +import { UseSupabaseUploadReturn } from '@/registry/default/blocks/dropzone/hooks/use-supabase-upload' +import { Button } from '@/registry/default/components/ui/button' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/registry/default/components/ui/tooltip' +import { CheckCircle, CircleAlert, File, Loader2, Trash, Upload } from 'lucide-react' +import { createContext, PropsWithChildren, useCallback, useContext } from 'react' + +type DropzoneContextType = Omit + +const DropzoneContext = createContext(undefined) + +type DropzoneProps = UseSupabaseUploadReturn & { + className?: string +} + +const Dropzone = ({ + className, + children, + getRootProps, + getInputProps, + ...restProps +}: PropsWithChildren) => { + const isActive = restProps.isDragActive + const isInvalid = + (restProps.isDragActive && restProps.isDragReject) || + restProps.error || + restProps.files.some((file) => file.errors.length !== 0) + + return ( + +
+ + {children} +
+
+ ) +} +const DropzoneContent = ({ className }: { className?: string }) => { + const { error, files, setFiles, onUpload, loading, success } = useDropzoneContext() + + const handleRemoveFile = useCallback( + (fileName: string) => { + setFiles(files.filter((file) => file.name !== fileName)) + }, + [files, setFiles] + ) + + if (success) { + return ( +
+ +

Files uploaded successfully

+
+ ) + } + + return ( +
+ {files.map((file) => { + return ( +
+ {file.type.startsWith('image/') ? ( +
+ {file.name} +
+ ) : ( +
+ +
+ )} +
+ {file.name} + {file.errors.length !== 0 && ( + + + + + {file.errors.map((e) => e.message).join(', ')} + + )} +
+ + +
+ ) + })} + {files.length > 0 && ( +
+ +
+ )} + {error &&

{error}

} +
+ ) +} + +const DropzoneEmptyState = ({ className }: { className?: string }) => { + const { files, maxFiles, maxFileSize, inputRef, success } = useDropzoneContext() + + if ((maxFiles !== 0 && files.length >= maxFiles) || success) { + return null + } + + return ( +
+
+ +
+

+ Add or{' '} + inputRef.current?.click()} className="underline cursor-pointer"> + drop {maxFiles === 1 ? `file` : 'files'} + +

+ {maxFileSize !== Number.POSITIVE_INFINITY && ( +

Maximum file size: {maxFileSize} MB

+ )} +
+ ) +} + +const useDropzoneContext = () => { + const context = useContext(DropzoneContext) + + if (!context) { + throw new Error('useDropzoneContext must be used within a Dropzone') + } + + return context +} + +export { Dropzone, DropzoneContent, DropzoneEmptyState, useDropzoneContext } diff --git a/apps/ui-library/registry/default/blocks/dropzone/hooks/use-supabase-upload.ts b/apps/ui-library/registry/default/blocks/dropzone/hooks/use-supabase-upload.ts new file mode 100644 index 00000000000..437218d495c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/dropzone/hooks/use-supabase-upload.ts @@ -0,0 +1,122 @@ +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { useCallback, useEffect, useState } from 'react' +import { FileError, FileRejection, useDropzone } from 'react-dropzone' + +const supabase = createClient() + +interface FileWithPreview extends File { + preview?: string + errors: readonly FileError[] +} + +type UseSupabaseUploadOptions = { + bucketName: string + path: string + allowedMimeTypes?: string[] + maxFileSize?: number + maxFiles?: number +} + +type UseSupabaseUploadReturn = ReturnType + +const useSupabaseUpload = (options: UseSupabaseUploadOptions) => { + const { + bucketName, + path, + allowedMimeTypes = [], + maxFileSize = Number.POSITIVE_INFINITY, + maxFiles = 0, + } = options + + const [files, setFiles] = useState([]) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(false) + + const onDrop = useCallback( + (acceptedFiles: File[], fileRejections: FileRejection[]) => { + const validFiles = acceptedFiles.map((file) => { + ;(file as FileWithPreview).preview = URL.createObjectURL(file) + ;(file as FileWithPreview).errors = [] + return file as FileWithPreview + }) + + const invalidFiles = fileRejections.map(({ file, errors }) => { + ;(file as FileWithPreview).preview = URL.createObjectURL(file) + ;(file as FileWithPreview).errors = errors + return file as FileWithPreview + }) + + const newFiles = [...files, ...validFiles, ...invalidFiles] + + setFiles(newFiles) + }, + [files, setFiles] + ) + + const dropzoneProps = useDropzone({ + onDrop, + noClick: true, + accept: allowedMimeTypes.reduce((acc, type) => ({ ...acc, [type]: [] }), {}), + maxSize: maxFileSize, + maxFiles: maxFiles, + multiple: maxFiles !== 1, + }) + + const onUpload = useCallback(async () => { + setError(null) + setLoading(true) + await Promise.all( + files.map(async (file) => { + const { error } = await supabase.storage + .from(bucketName) + .upload(`${path}/${file.name}`, file, { + cacheControl: '3600', + upsert: false, + }) + if (error) { + setError(error.message) + } else { + setSuccess(true) + } + }) + ) + setLoading(false) + }, [files, path, bucketName, setError, setLoading]) + + useEffect(() => { + if (files.length === 0) { + setError(null) + } + + // If the number of files doesn't exceed the maxFiles parameter, remove the error 'Too many files' from each file + if (files.length <= maxFiles) { + let changed = false + const newFiles = files.map((file) => { + if (file.errors.some((e) => e.code === 'too-many-files')) { + file.errors = file.errors.filter((e) => e.code !== 'too-many-files') + changed = true + } + return file + }) + if (changed) { + setFiles(newFiles) + } + } + }, [files.length, setFiles, maxFiles]) + + return { + files, + setFiles, + success, + loading, + error, + setError, + onUpload, + maxFileSize: maxFileSize, + maxFiles: maxFiles, + ...dropzoneProps, + } +} + +export { useSupabaseUpload, type UseSupabaseUploadOptions, type UseSupabaseUploadReturn } diff --git a/apps/ui-library/registry/default/blocks/dropzone/registry-item.json b/apps/ui-library/registry/default/blocks/dropzone/registry-item.json new file mode 100644 index 00000000000..757f3e940a6 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/dropzone/registry-item.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dropzone", + "type": "registry:component", + "title": "Dropzone (File Upload)", + "description": "Displays a control for easier uploading of files directly to Supabase Storage.", + "registryDependencies": ["button", "tooltip", "progress"], + "dependencies": ["react-dropzone", "lucide-react"], + "files": [ + { + "path": "registry/default/blocks/dropzone/components/dropzone.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/dropzone/hooks/use-supabase-upload.ts", + "type": "registry:hook" + } + ] +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts new file mode 100644 index 00000000000..21611a1d60f --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts @@ -0,0 +1,31 @@ +import { type EmailOtpType } from '@supabase/supabase-js' +import { type NextRequest } from 'next/server' + +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/server' +import { redirect } from 'next/navigation' + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const token_hash = searchParams.get('token_hash') + const type = searchParams.get('type') as EmailOtpType | null + const next = searchParams.get('next') ?? '/' + + if (token_hash && type) { + const supabase = await createClient() + + const { error } = await supabase.auth.verifyOtp({ + type, + token_hash, + }) + if (!error) { + // redirect user to specified redirect URL or root of app + redirect(next) + } else { + // redirect the user to an error page with some instructions + redirect(`/auth/error?error=${error?.message}`) + } + } + + // redirect the user to an error page with some instructions + redirect(`/auth/error?error=No token hash or type`) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx new file mode 100644 index 00000000000..32a1d119c5e --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx @@ -0,0 +1,3 @@ +export default function Page({ searchParams }: { searchParams: { error: string } }) { + return
Sorry, something went wrong. Code error: {searchParams.error}
+} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx new file mode 100644 index 00000000000..5f6726990d5 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx @@ -0,0 +1,11 @@ +import { ForgotPasswordForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx new file mode 100644 index 00000000000..0cd66abb285 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx @@ -0,0 +1,11 @@ +import { LoginForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/login-form' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx new file mode 100644 index 00000000000..08b2d019578 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx @@ -0,0 +1,14 @@ +import { redirect } from 'next/navigation' + +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/server' + +export default async function ProtectedPage() { + const supabase = await createClient() + + const { data, error } = await supabase.auth.getUser() + if (error || !data?.user) { + redirect('/login') + } + + return

Hello {data.user.email}

+} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx new file mode 100644 index 00000000000..5243588092d --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Sign up success
+} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx new file mode 100644 index 00000000000..3b37c7ed777 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx @@ -0,0 +1,11 @@ +import { SignUpForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx new file mode 100644 index 00000000000..8ce3c98205e --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx @@ -0,0 +1,11 @@ +import { UpdatePasswordForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/update-password-form' + +export default function Page() { + return ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx new file mode 100644 index 00000000000..5f2ef4852d8 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx @@ -0,0 +1,81 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import Link from 'next/link' +import { useState } from 'react' + +export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const supabase = createClient() + + const handleForgotPassword = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: 'http://localhost:3000/update-password', + }) + if (error) throw error + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Reset Your Password + + Type in your email and we'll send you a link to reset your password + + + +
+
+
+ + setEmail(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx new file mode 100644 index 00000000000..eb81206ea2e --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx @@ -0,0 +1,102 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + const supabase = createClient() + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + if (error) throw error + // Update this route to redirect to an authenticated route. The user already has an active session. + router.push('/protected') + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Login + Enter your email below to login to your account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ + + Forgot your password? + +
+ setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Don't have an account?{' '} + + Sign up + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx new file mode 100644 index 00000000000..6e231393361 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx @@ -0,0 +1,114 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [repeatPassword, setRepeatPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + const supabase = createClient() + + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + if (password !== repeatPassword) { + setError('Passwords do not match') + setIsLoading(false) + return + } + + try { + const { error } = await supabase.auth.signUp({ + email, + password, + }) + if (error) throw error + router.push('/sign-up-success') + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Sign up + Create a new account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ +
+ setPassword(e.target.value)} + /> +
+
+
+ +
+ setRepeatPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx new file mode 100644 index 00000000000..a0e194a133c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx @@ -0,0 +1,79 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + const supabase = createClient() + + const handleForgotPassword = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.updateUser({ password }) + if (error) throw error + // Update this route to redirect to an authenticated route. The user already has an active session. + router.push('/protected') + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Reset Your Password + Please enter your new password below. + + +
+
+
+ + setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts new file mode 100644 index 00000000000..c04d54bc883 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts @@ -0,0 +1,19 @@ +import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware' +import { type NextRequest } from 'next/server' + +export async function middleware(request: NextRequest) { + return await updateSession(request) +} + +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + * Feel free to modify this pattern to include more paths. + */ + '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', + ], +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json new file mode 100644 index 00000000000..0ac33ee531c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json @@ -0,0 +1,71 @@ +{ + "name": "password-based-auth-nextjs", + "type": "registry:block", + "title": "Password Based Auth flow for Nextjs and Supabase", + "description": "Password Based Auth flow for Nextjs and Supabase", + "registryDependencies": ["button", "card", "input", "label"], + "dependencies": ["@supabase/ssr@latest"], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx", + "type": "registry:page", + "target": "app/login/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts", + "type": "registry:page", + "target": "app/auth/confirm/route.ts" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx", + "type": "registry:page", + "target": "app/sign-up/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx", + "type": "registry:page", + "target": "app/sign-up-success/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx", + "type": "registry:page", + "target": "app/forgot-password/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx", + "type": "registry:page", + "target": "app/update-password/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx", + "type": "registry:component" + } + ] +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx new file mode 100644 index 00000000000..a91ee765794 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx @@ -0,0 +1,82 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/react/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { useState } from 'react' + +export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [success, setSuccess] = useState(false) + const supabase = createClient() + + const handleForgotPassword = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: 'http://localhost:3000/update-password', + }) + if (error) throw error + setSuccess(true) + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Reset Your Password + + Type in your email and we'll send you a link to reset your password + + + +
+
+
+ + setEmail(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx new file mode 100644 index 00000000000..3ccb407cb73 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/login-form.tsx @@ -0,0 +1,99 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/react/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { useState } from 'react' + +export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const supabase = createClient() + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + if (error) throw error + + // Update this route to redirect to an authenticated route. The user already has an active session. + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Login + Enter your email below to login to your account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Don't have an account?{' '} + + Sign up + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx new file mode 100644 index 00000000000..7b94b6fa4a5 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx @@ -0,0 +1,111 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/react/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { useState } from 'react' + +export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [repeatPassword, setRepeatPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const supabase = createClient() + + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + if (password !== repeatPassword) { + setError('Passwords do not match') + setIsLoading(false) + return + } + + try { + const { error } = await supabase.auth.signUp({ + email, + password, + }) + if (error) throw error + // TODO: Redirect to sign-up success page + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Sign up + Create a new account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ +
+ setPassword(e.target.value)} + /> +
+
+
+ +
+ setRepeatPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/components/update-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/update-password-form.tsx new file mode 100644 index 00000000000..ddfb70d9947 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/components/update-password-form.tsx @@ -0,0 +1,77 @@ +'use client' + +import { cn } from '@/lib/utils' +import { createClient } from '@/registry/default/clients/react/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { useState } from 'react' + +export function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const supabase = createClient() + + const handleForgotPassword = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.updateUser({ password }) + if (error) throw error + + // TODO:Update this route to redirect to an authenticated route. The user already has an active session. + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+ + + Reset Your Password + Please enter your new password below. + + +
+
+
+ + setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-react/registry-item.json new file mode 100644 index 00000000000..39d20486c85 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react/registry-item.json @@ -0,0 +1,30 @@ +{ + "name": "password-based-auth-react", + "type": "registry:block", + "title": "Password Based Auth flow for React and Supabase", + "description": "Password Based Auth flow for React and Supabase", + "registryDependencies": ["button", "card", "input", "label"], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-react/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/update-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "dependencies": ["@supabase/supabase-js@latest"] +} diff --git a/apps/ui-library/registry/default/blocks/realtime-cursor/components/cursor.tsx b/apps/ui-library/registry/default/blocks/realtime-cursor/components/cursor.tsx new file mode 100644 index 00000000000..e87775eaf12 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/realtime-cursor/components/cursor.tsx @@ -0,0 +1,27 @@ +import { cn } from '@/lib/utils' +import { MousePointer2 } from 'lucide-react' + +export const Cursor = ({ + className, + style, + color, + name, +}: { + className?: string + style?: React.CSSProperties + color: string + name: string +}) => { + return ( +
+ + +
+ {name} +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx b/apps/ui-library/registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx new file mode 100644 index 00000000000..a0db3751b45 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx @@ -0,0 +1,26 @@ +'use client' + +import { Cursor } from '@/registry/default/blocks/realtime-cursor/components/cursor' +import { useRealtimeCursors } from '@/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors' + +export const RealtimeCursors = ({ roomName, username }: { roomName: string; username: string }) => { + const { cursors } = useRealtimeCursors({ roomName, username }) + + return ( +
+ {Object.keys(cursors).map((id) => ( + + ))} +
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts b/apps/ui-library/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts new file mode 100644 index 00000000000..2b2a5c7baa8 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts @@ -0,0 +1,104 @@ +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { RealtimeChannel } from '@supabase/supabase-js' +import { useCallback, useEffect, useRef, useState } from 'react' + +const supabase = createClient() + +const generateRandomColor = () => `hsl(${Math.floor(Math.random() * 360)}, 100%, 70%)` + +const generateRandomNumber = () => Math.floor(Math.random() * 100) + +const EVENT_NAME = 'realtime-cursor-move' + +type CursorEventPayload = { + position: { + x: number + y: number + } + user: { + id: number + name: string + } + color: string + timestamp: number +} + +export const useRealtimeCursors = ({ + roomName, + username, +}: { + roomName: string + username: string +}) => { + const [color] = useState(generateRandomColor()) + const [userId] = useState(generateRandomNumber()) + const [cursors, setCursors] = useState>({}) + + const channelRef = useRef(null) + + const handleMouseMove = useCallback( + (event: MouseEvent) => { + const { clientX, clientY } = event + + const payload: CursorEventPayload = { + position: { + x: clientX, + y: clientY, + }, + user: { + id: userId, + name: username, + }, + color: color, + timestamp: new Date().getTime(), + } + + channelRef.current?.send({ + type: 'broadcast', + event: EVENT_NAME, + payload: payload, + }) + }, + [color, userId, username] + ) + + useEffect(() => { + const channel = supabase.channel(roomName) + channelRef.current = channel + + channel + .on('broadcast', { event: EVENT_NAME }, (data: { payload: CursorEventPayload }) => { + const { user } = data.payload + // Don't render your own cursor + if (user.id === userId) return + + setCursors((prev) => { + if (prev[userId]) { + delete prev[userId] + } + + return { + ...prev, + [user.id]: data.payload, + } + }) + }) + .subscribe() + + return () => { + channel.unsubscribe() + } + }, []) + + useEffect(() => { + // Add event listener for mousemove + window.addEventListener('mousemove', handleMouseMove) + + // Cleanup on unmount + return () => { + window.removeEventListener('mousemove', handleMouseMove) + } + }, [handleMouseMove]) + + return { cursors } +} diff --git a/apps/ui-library/registry/default/blocks/realtime-cursor/registry-item.json b/apps/ui-library/registry/default/blocks/realtime-cursor/registry-item.json new file mode 100644 index 00000000000..a71f785045a --- /dev/null +++ b/apps/ui-library/registry/default/blocks/realtime-cursor/registry-item.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-cursor", + "type": "registry:component", + "title": "Realtime Cursor", + "description": "Component which renders realtime cursors from other users in a room.", + "registryDependencies": [], + "dependencies": ["lucide-react"], + "files": [ + { + "path": "registry/default/blocks/realtime-cursor/components/cursor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts", + "type": "registry:hook" + } + ] +} diff --git a/apps/ui-library/registry/default/clients/nextjs/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/client.ts new file mode 100644 index 00000000000..792b4570b2c --- /dev/null +++ b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/client.ts @@ -0,0 +1,8 @@ +import { createBrowserClient } from '@supabase/ssr' + +export function createClient() { + return createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) +} diff --git a/apps/ui-library/registry/default/clients/nextjs/lib/supabase/middleware.ts b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/middleware.ts new file mode 100644 index 00000000000..cc9299648b7 --- /dev/null +++ b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/middleware.ts @@ -0,0 +1,65 @@ +import { createServerClient } from '@supabase/ssr' +import { NextResponse, type NextRequest } from 'next/server' + +export async function updateSession(request: NextRequest) { + let supabaseResponse = NextResponse.next({ + request, + }) + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + getAll() { + return request.cookies.getAll() + }, + setAll(cookiesToSet) { + cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value)) + supabaseResponse = NextResponse.next({ + request, + }) + cookiesToSet.forEach(({ name, value, options }) => + supabaseResponse.cookies.set(name, value, options) + ) + }, + }, + } + ) + + // Do not run code between createServerClient and + // supabase.auth.getUser(). A simple mistake could make it very hard to debug + // issues with users being randomly logged out. + + // IMPORTANT: DO NOT REMOVE auth.getUser() + + const { + data: { user }, + } = await supabase.auth.getUser() + + if ( + !user && + !request.nextUrl.pathname.startsWith('/login') && + !request.nextUrl.pathname.startsWith('/auth') + ) { + // no user, potentially respond by redirecting the user to the login page + const url = request.nextUrl.clone() + url.pathname = '/login' + return NextResponse.redirect(url) + } + + // IMPORTANT: You *must* return the supabaseResponse object as it is. + // If you're creating a new response object with NextResponse.next() make sure to: + // 1. Pass the request in it, like so: + // const myNewResponse = NextResponse.next({ request }) + // 2. Copy over the cookies, like so: + // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll()) + // 3. Change the myNewResponse object to fit your needs, but avoid changing + // the cookies! + // 4. Finally: + // return myNewResponse + // If this is not done, you may be causing the browser and server to go out + // of sync and terminate the user's session prematurely! + + return supabaseResponse +} diff --git a/apps/ui-library/registry/default/clients/nextjs/lib/supabase/server.ts b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/server.ts new file mode 100644 index 00000000000..40633b0c5b8 --- /dev/null +++ b/apps/ui-library/registry/default/clients/nextjs/lib/supabase/server.ts @@ -0,0 +1,29 @@ +import { createServerClient } from '@supabase/ssr' +import { cookies } from 'next/headers' + +export async function createClient() { + const cookieStore = await cookies() + + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + getAll() { + return cookieStore.getAll() + }, + setAll(cookiesToSet) { + try { + cookiesToSet.forEach(({ name, value, options }) => + cookieStore.set(name, value, options) + ) + } catch { + // The `setAll` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + }, + } + ) +} diff --git a/apps/ui-library/registry/default/clients/nextjs/registry-item.json b/apps/ui-library/registry/default/clients/nextjs/registry-item.json new file mode 100644 index 00000000000..0c0c3247dfc --- /dev/null +++ b/apps/ui-library/registry/default/clients/nextjs/registry-item.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-nextjs", + "type": "registry:lib", + "title": "Supabase Client for Next.js", + "description": "", + "registryDependencies": [], + "dependencies": ["@supabase/ssr@latest"], + "files": [ + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ] +} diff --git a/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts new file mode 100644 index 00000000000..1ca4c8ce35f --- /dev/null +++ b/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts @@ -0,0 +1,8 @@ +import { createClient as createSupabaseClient } from '@supabase/supabase-js' + +export function createClient() { + return createSupabaseClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) +} diff --git a/apps/ui-library/registry/default/clients/react/registry-item.json b/apps/ui-library/registry/default/clients/react/registry-item.json new file mode 100644 index 00000000000..64e69750305 --- /dev/null +++ b/apps/ui-library/registry/default/clients/react/registry-item.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-react", + "type": "registry:lib", + "title": "Supabase Client for React", + "description": "", + "registryDependencies": [], + "dependencies": ["@supabase/supabase-js@latest"], + "files": [ + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ] +} diff --git a/apps/ui-library/registry/default/clients/remix/lib/supabase.client.ts b/apps/ui-library/registry/default/clients/remix/lib/supabase.client.ts new file mode 100644 index 00000000000..13b5e5307bf --- /dev/null +++ b/apps/ui-library/registry/default/clients/remix/lib/supabase.client.ts @@ -0,0 +1,7 @@ +import { createBrowserClient } from '@supabase/ssr' + +// Use this function to create a client for the browser. You should pass the env variables through a loader and use them +// to instantiate the client. +export function createClient(supabaseUrl: string, supabaseAnonKey: string) { + return createBrowserClient(supabaseUrl, supabaseAnonKey) +} diff --git a/apps/ui-library/registry/default/clients/remix/lib/supabase.server.ts b/apps/ui-library/registry/default/clients/remix/lib/supabase.server.ts new file mode 100644 index 00000000000..dc67a0fdd41 --- /dev/null +++ b/apps/ui-library/registry/default/clients/remix/lib/supabase.server.ts @@ -0,0 +1,23 @@ +import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr' + +export function createClient(request: Request) { + const headers = new Headers() + + const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { + cookies: { + getAll() { + return parseCookieHeader(request.headers.get('Cookie') ?? '') as { + name: string + value: string + }[] + }, + setAll(cookiesToSet) { + cookiesToSet.forEach(({ name, value, options }) => + headers.append('Set-Cookie', serializeCookieHeader(name, value, options)) + ) + }, + }, + }) + + return { supabase, headers } +} diff --git a/apps/ui-library/registry/default/clients/remix/registry-item.json b/apps/ui-library/registry/default/clients/remix/registry-item.json new file mode 100644 index 00000000000..94b3072834a --- /dev/null +++ b/apps/ui-library/registry/default/clients/remix/registry-item.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-remix", + "type": "registry:lib", + "title": "Supabase Client for Remix", + "description": "", + "registryDependencies": [], + "dependencies": ["@supabase/ssr@latest"], + "files": [ + { + "path": "registry/default/clients/remix/lib/supabase.client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/remix/lib/supabase.server.ts", + "type": "registry:lib" + } + ] +} diff --git a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts new file mode 100644 index 00000000000..8b0f3fe4181 --- /dev/null +++ b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts @@ -0,0 +1,5 @@ +import { createBrowserClient } from '@supabase/ssr' + +export function createClient() { + return createBrowserClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!) +} diff --git a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts new file mode 100644 index 00000000000..fa17b6272ab --- /dev/null +++ b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts @@ -0,0 +1,23 @@ +import { createServerClient } from '@supabase/ssr' +import { parseCookies, setCookie } from '@tanstack/react-start/server' + +export function getSupabaseServerClient() { + return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, { + cookies: { + getAll() { + return Object.entries(parseCookies()).map( + ([name, value]) => + ({ + name, + value, + }) as { name: string; value: string } + ) + }, + setAll(cookies) { + cookies.forEach((cookie) => { + setCookie(cookie.name, cookie.value) + }) + }, + }, + }) +} diff --git a/apps/ui-library/registry/default/clients/tanstack/registry-item.json b/apps/ui-library/registry/default/clients/tanstack/registry-item.json new file mode 100644 index 00000000000..30e09ce8236 --- /dev/null +++ b/apps/ui-library/registry/default/clients/tanstack/registry-item.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-tanstack", + "type": "registry:lib", + "title": "Supabase Client for Tanstack Start", + "description": "", + "registryDependencies": [], + "dependencies": ["@supabase/ssr@latest"], + "files": [ + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ] +} diff --git a/apps/ui-library/registry/default/components/ui/button.tsx b/apps/ui-library/registry/default/components/ui/button.tsx new file mode 100644 index 00000000000..a5c07b2acaa --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/button.tsx @@ -0,0 +1,50 @@ +import * as React from 'react' +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: + 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + } +) +Button.displayName = 'Button' + +export { Button, buttonVariants } diff --git a/apps/ui-library/registry/default/components/ui/card.tsx b/apps/ui-library/registry/default/components/ui/card.tsx new file mode 100644 index 00000000000..bcf3d6f26ce --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/card.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +Card.displayName = 'Card' + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardHeader.displayName = 'CardHeader' + +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardTitle.displayName = 'CardTitle' + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardDescription.displayName = 'CardDescription' + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardContent.displayName = 'CardContent' + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardFooter.displayName = 'CardFooter' + +export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } diff --git a/apps/ui-library/registry/default/components/ui/input.tsx b/apps/ui-library/registry/default/components/ui/input.tsx new file mode 100644 index 00000000000..b1406898526 --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = 'Input' + +export { Input } diff --git a/apps/ui-library/registry/default/components/ui/label.tsx b/apps/ui-library/registry/default/components/ui/label.tsx new file mode 100644 index 00000000000..59ec99946bf --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/label.tsx @@ -0,0 +1,21 @@ +'use client' + +import * as React from 'react' +import * as LabelPrimitive from '@radix-ui/react-label' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const labelVariants = cva( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/apps/ui-library/registry/default/components/ui/progress.tsx b/apps/ui-library/registry/default/components/ui/progress.tsx new file mode 100644 index 00000000000..4863cedd061 --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/progress.tsx @@ -0,0 +1,28 @@ +'use client' + +import * as React from 'react' +import * as ProgressPrimitive from '@radix-ui/react-progress' + +import { cn } from '@/lib/utils' + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/apps/ui-library/registry/default/components/ui/textarea.tsx b/apps/ui-library/registry/default/components/ui/textarea.tsx new file mode 100644 index 00000000000..2fe59d95881 --- /dev/null +++ b/apps/ui-library/registry/default/components/ui/textarea.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Textarea = React.forwardRef>( + ({ className, ...props }, ref) => { + return ( +