@gv-tech/design-system 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.github/CONTRIBUTING.md +14 -10
  2. package/.github/RELEASING.md +1 -1
  3. package/.github/copilot-instructions.md +30 -62
  4. package/.release-please-manifest.json +1 -1
  5. package/CHANGELOG.md +19 -0
  6. package/README.md +8 -9
  7. package/dist/components/ui/theme-toggle.test.d.ts +2 -0
  8. package/dist/components/ui/theme-toggle.test.d.ts.map +1 -0
  9. package/dist/design-system.css +1 -1
  10. package/dist/favicon.png +0 -0
  11. package/dist/hooks/use-theme.d.ts +52 -0
  12. package/dist/hooks/use-theme.d.ts.map +1 -0
  13. package/dist/hooks/use-theme.test.d.ts +2 -0
  14. package/dist/hooks/use-theme.test.d.ts.map +1 -0
  15. package/dist/index.cjs.js +2 -2
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.es.js +829 -773
  20. package/dist/index.es.js.map +1 -1
  21. package/dist/logo192.png +0 -0
  22. package/dist/logo512.png +0 -0
  23. package/dist/manifest.json +3 -3
  24. package/dist/pages/ColorTokensDocs.d.ts.map +1 -1
  25. package/dist/pages/components/ThemeToggleDocs.d.ts.map +1 -1
  26. package/dist/registry/alert-dialog.test.json +1 -1
  27. package/dist/registry/index.json +7 -0
  28. package/dist/registry/theme-toggle.json +1 -1
  29. package/dist/registry/theme-toggle.test.json +13 -0
  30. package/dist/theme/tokens.d.ts +115 -0
  31. package/dist/theme/tokens.d.ts.map +1 -0
  32. package/index.html +1 -1
  33. package/package.json +9 -9
  34. package/public/favicon.png +0 -0
  35. package/public/logo192.png +0 -0
  36. package/public/logo512.png +0 -0
  37. package/public/manifest.json +3 -3
  38. package/scripts/validate.js +1 -0
  39. package/src/components/ui/alert-dialog.test.tsx +2 -0
  40. package/src/components/ui/theme-toggle.test.tsx +49 -0
  41. package/src/components/ui/theme-toggle.tsx +1 -1
  42. package/src/globals.css +2 -1
  43. package/src/hooks/use-theme.test.tsx +27 -0
  44. package/src/hooks/use-theme.ts +15 -0
  45. package/src/index.ts +2 -1
  46. package/src/pages/ColorTokensDocs.tsx +173 -136
  47. package/src/pages/components/ThemeToggleDocs.tsx +35 -8
  48. package/src/theme/tokens.ts +68 -0
  49. package/.nvmrc +0 -1
  50. package/babel.config.js +0 -3
  51. package/dist/favicon.ico +0 -0
  52. package/dist/lib/tokens.d.ts +0 -54
  53. package/dist/lib/tokens.d.ts.map +0 -1
  54. package/netlify.toml +0 -6
  55. package/public/favicon.ico +0 -0
  56. package/serve.json +0 -4
  57. package/src/lib/tokens.ts +0 -54
  58. package/temp.md +0 -292
package/dist/logo192.png CHANGED
Binary file
package/dist/logo512.png CHANGED
Binary file
@@ -3,9 +3,9 @@
3
3
  "name": "GV Tech Design System",
4
4
  "icons": [
5
5
  {
6
- "src": "favicon.ico",
7
- "sizes": "64x64 32x32 24x24 16x16",
8
- "type": "image/x-icon"
6
+ "src": "favicon.png",
7
+ "sizes": "512x512",
8
+ "type": "image/png"
9
9
  },
10
10
  {
11
11
  "src": "logo192.png",
@@ -1 +1 @@
1
- {"version":3,"file":"ColorTokensDocs.d.ts","sourceRoot":"","sources":["../../src/pages/ColorTokensDocs.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,4CA+K9B"}
1
+ {"version":3,"file":"ColorTokensDocs.d.ts","sourceRoot":"","sources":["../../src/pages/ColorTokensDocs.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,4CAoN9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeToggleDocs.d.ts","sourceRoot":"","sources":["../../../src/pages/components/ThemeToggleDocs.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,4CAoH9B"}
1
+ {"version":3,"file":"ThemeToggleDocs.d.ts","sourceRoot":"","sources":["../../../src/pages/components/ThemeToggleDocs.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,4CA+I9B"}
@@ -6,7 +6,7 @@
6
6
  "files": [
7
7
  {
8
8
  "path": "ui/alert-dialog.test.tsx",
9
- "content": "import { render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { describe, expect, it } from 'vitest';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from './alert-dialog';\n\ndescribe('AlertDialog', () => {\n it('renders correctly', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n expect(screen.getByText('Open')).toBeInTheDocument();\n expect(screen.queryByText('Are you sure?')).not.toBeInTheDocument();\n\n await userEvent.click(screen.getByText('Open'));\n\n await waitFor(() => {\n expect(screen.getByRole('alertdialog')).toBeInTheDocument();\n expect(screen.getByText('Are you sure?')).toBeInTheDocument();\n expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();\n });\n });\n\n it('closes when cancel is clicked', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n await userEvent.click(screen.getByText('Open'));\n await waitFor(() => expect(screen.getByRole('alertdialog')).toBeInTheDocument());\n\n await userEvent.click(screen.getByText('Cancel'));\n await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());\n });\n\n it('closes when action is clicked', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n await userEvent.click(screen.getByText('Open'));\n await waitFor(() => expect(screen.getByRole('alertdialog')).toBeInTheDocument());\n\n await userEvent.click(screen.getByText('Continue'));\n await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());\n });\n});\n",
9
+ "content": "import { render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { describe, expect, it } from 'vitest';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from './alert-dialog';\n\ndescribe('AlertDialog', () => {\n it('renders correctly', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n expect(screen.getByText('Open')).toBeInTheDocument();\n expect(screen.queryByText('Are you sure?')).not.toBeInTheDocument();\n\n await userEvent.click(screen.getByText('Open'));\n\n await waitFor(() => {\n expect(screen.getByRole('alertdialog')).toBeInTheDocument();\n expect(screen.getByText('Are you sure?')).toBeInTheDocument();\n expect(screen.getByText('This action cannot be undone.')).toBeInTheDocument();\n });\n });\n\n it('closes when cancel is clicked', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n await userEvent.click(screen.getByText('Open'));\n await waitFor(() => expect(screen.getByRole('alertdialog')).toBeInTheDocument());\n\n await userEvent.click(screen.getByText('Cancel'));\n await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());\n });\n\n it('closes when action is clicked', async () => {\n render(\n <AlertDialog>\n <AlertDialogTrigger>Open</AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogAction>Continue</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>,\n );\n\n await userEvent.click(screen.getByText('Open'));\n await waitFor(() => expect(screen.getByRole('alertdialog')).toBeInTheDocument());\n\n await userEvent.click(screen.getByText('Continue'));\n await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());\n });\n});\n",
10
10
  "type": "registry:ui"
11
11
  }
12
12
  ]
@@ -587,6 +587,13 @@
587
587
  ],
588
588
  "type": "registry:ui"
589
589
  },
590
+ {
591
+ "name": "theme-toggle.test",
592
+ "files": [
593
+ "ui/theme-toggle.test.tsx"
594
+ ],
595
+ "type": "registry:ui"
596
+ },
590
597
  {
591
598
  "name": "theme-toggle",
592
599
  "files": [
@@ -6,7 +6,7 @@
6
6
  "files": [
7
7
  {
8
8
  "path": "ui/theme-toggle.tsx",
9
- "content": "import { Button } from '@/components/ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { cn } from '@/lib/utils';\nimport { Moon, Sun, SunMoon } from 'lucide-react';\nimport { useTheme } from 'next-themes';\n\nexport interface ThemeToggleProps {\n /**\n * The mode of the theme toggle. 'binary' allows toggling between light and dark. 'ternary' allows choosing between\n * light, dark, and system.\n *\n * @default 'binary'\n */\n variant?: 'binary' | 'ternary';\n /** Optional callback when the theme changes. */\n onThemeChange?: (theme: string) => void;\n /** Optional current theme value for external control. */\n customTheme?: string;\n /** Optional className for the button. */\n className?: string;\n}\n\nexport function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, className }: ThemeToggleProps) {\n const { theme: nextTheme, setTheme: setNextTheme, resolvedTheme } = useTheme();\n\n // Use customTheme if provided, otherwise fallback to next-themes\n const currentTheme = customTheme ?? nextTheme;\n\n // Determine the effective theme for icon rendering\n const effectiveTheme = customTheme ? customTheme : resolvedTheme;\n const isDark = effectiveTheme === 'dark';\n const isSystem = currentTheme === 'system';\n\n const handleThemeChange = (newTheme: string) => {\n if (onThemeChange) {\n onThemeChange(newTheme);\n } else {\n setNextTheme(newTheme);\n }\n };\n\n const IconToggle = () => (\n <>\n <Sun\n className={cn(\n 'h-[1.2rem] w-[1.2rem] transition-all',\n !isSystem && !isDark ? 'rotate-0 scale-100' : '-rotate-90 scale-0',\n )}\n />\n <Moon\n className={cn(\n 'absolute h-[1.2rem] w-[1.2rem] transition-all',\n !isSystem && isDark ? 'rotate-0 scale-100' : 'rotate-90 scale-0',\n )}\n />\n <SunMoon\n className={cn(\n 'absolute h-[1.2rem] w-[1.2rem] transition-all',\n isSystem ? 'rotate-0 scale-100' : 'rotate-90 scale-0',\n )}\n />\n <span className=\"sr-only\">Toggle theme</span>\n </>\n );\n\n if (variant === 'ternary') {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className={cn('relative h-9 w-9', className)}>\n <IconToggle />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => handleThemeChange('light')}>\n <Sun className=\"mr-2 h-4 w-4\" />\n <span>Light</span>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => handleThemeChange('dark')}>\n <Moon className=\"mr-2 h-4 w-4\" />\n <span>Dark</span>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => handleThemeChange('system')}>\n <SunMoon className=\"mr-2 h-4 w-4\" />\n <span>System</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n }\n\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn('relative h-9 w-9', className)}\n onClick={() => handleThemeChange(currentTheme === 'dark' ? 'light' : 'dark')}\n aria-label=\"Toggle theme\"\n >\n <IconToggle />\n </Button>\n );\n}\n",
9
+ "content": "import { Button } from '@/components/ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { useTheme } from '@/hooks/use-theme';\nimport { cn } from '@/lib/utils';\nimport { Moon, Sun, SunMoon } from 'lucide-react';\n\nexport interface ThemeToggleProps {\n /**\n * The mode of the theme toggle. 'binary' allows toggling between light and dark. 'ternary' allows choosing between\n * light, dark, and system.\n *\n * @default 'binary'\n */\n variant?: 'binary' | 'ternary';\n /** Optional callback when the theme changes. */\n onThemeChange?: (theme: string) => void;\n /** Optional current theme value for external control. */\n customTheme?: string;\n /** Optional className for the button. */\n className?: string;\n}\n\nexport function ThemeToggle({ variant = 'binary', onThemeChange, customTheme, className }: ThemeToggleProps) {\n const { theme: nextTheme, setTheme: setNextTheme, resolvedTheme } = useTheme();\n\n // Use customTheme if provided, otherwise fallback to next-themes\n const currentTheme = customTheme ?? nextTheme;\n\n // Determine the effective theme for icon rendering\n const effectiveTheme = customTheme ? customTheme : resolvedTheme;\n const isDark = effectiveTheme === 'dark';\n const isSystem = currentTheme === 'system';\n\n const handleThemeChange = (newTheme: string) => {\n if (onThemeChange) {\n onThemeChange(newTheme);\n } else {\n setNextTheme(newTheme);\n }\n };\n\n const IconToggle = () => (\n <>\n <Sun\n className={cn(\n 'h-[1.2rem] w-[1.2rem] transition-all',\n !isSystem && !isDark ? 'rotate-0 scale-100' : '-rotate-90 scale-0',\n )}\n />\n <Moon\n className={cn(\n 'absolute h-[1.2rem] w-[1.2rem] transition-all',\n !isSystem && isDark ? 'rotate-0 scale-100' : 'rotate-90 scale-0',\n )}\n />\n <SunMoon\n className={cn(\n 'absolute h-[1.2rem] w-[1.2rem] transition-all',\n isSystem ? 'rotate-0 scale-100' : 'rotate-90 scale-0',\n )}\n />\n <span className=\"sr-only\">Toggle theme</span>\n </>\n );\n\n if (variant === 'ternary') {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className={cn('relative h-9 w-9', className)}>\n <IconToggle />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => handleThemeChange('light')}>\n <Sun className=\"mr-2 h-4 w-4\" />\n <span>Light</span>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => handleThemeChange('dark')}>\n <Moon className=\"mr-2 h-4 w-4\" />\n <span>Dark</span>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => handleThemeChange('system')}>\n <SunMoon className=\"mr-2 h-4 w-4\" />\n <span>System</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n }\n\n return (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn('relative h-9 w-9', className)}\n onClick={() => handleThemeChange(currentTheme === 'dark' ? 'light' : 'dark')}\n aria-label=\"Toggle theme\"\n >\n <IconToggle />\n </Button>\n );\n}\n",
10
10
  "type": "registry:ui"
11
11
  }
12
12
  ]
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "theme-toggle.test",
3
+ "type": "registry:ui",
4
+ "dependencies": [],
5
+ "registryDependencies": [],
6
+ "files": [
7
+ {
8
+ "path": "ui/theme-toggle.test.tsx",
9
+ "content": "import { ThemeToggle } from '@/components/ui/theme-toggle';\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { ThemeProvider } from 'next-themes';\nimport { describe, expect, it, vi } from 'vitest';\n\n// Mock the useTheme hook to control its return values\nvi.mock('@/hooks/use-theme', async () => {\n const actual = await vi.importActual('@/hooks/use-theme');\n return {\n ...actual,\n useTheme: () => ({\n theme: 'light',\n setTheme: vi.fn(),\n resolvedTheme: 'light',\n tokens: {},\n }),\n };\n});\n\ndescribe('ThemeToggle', () => {\n it('renders binary toggle by default', () => {\n render(\n <ThemeProvider>\n <ThemeToggle />\n </ThemeProvider>,\n );\n // Use role button which is accessible\n const button = screen.getByRole('button', { name: /toggle theme/i });\n expect(button).toBeInTheDocument();\n });\n\n it('renders ternary toggle with dropdown', async () => {\n const user = userEvent.setup();\n render(\n <ThemeProvider>\n <ThemeToggle variant=\"ternary\" />\n </ThemeProvider>,\n );\n const button = screen.getByRole('button'); // Dropdown trigger\n expect(button).toBeInTheDocument();\n\n // Open dropdown\n await user.click(button);\n expect(await screen.findByText('Light')).toBeInTheDocument();\n expect(screen.getByText('Dark')).toBeInTheDocument();\n expect(screen.getByText('System')).toBeInTheDocument();\n });\n});\n",
10
+ "type": "registry:ui"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Design system tokens for Garcia Ventures.
3
+ * This file defines the core palette and semantic tokens for the theme system.
4
+ */
5
+ export declare const palette: {
6
+ readonly brand: {
7
+ readonly blue: "hsl(225 73% 57%)";
8
+ readonly green: "hsl(151 66% 27%)";
9
+ readonly floralWhite: "hsl(40 100% 97%)";
10
+ };
11
+ };
12
+ export declare const theme: {
13
+ readonly light: {
14
+ readonly background: "hsl(0 0% 96%)";
15
+ readonly foreground: "hsl(222 47% 11%)";
16
+ readonly card: "hsl(0 0% 100%)";
17
+ readonly cardForeground: "hsl(222 47% 11%)";
18
+ readonly popover: "hsl(0 0% 100%)";
19
+ readonly popoverForeground: "hsl(222 47% 11%)";
20
+ readonly primary: "hsl(225 73% 57%)";
21
+ readonly primaryForeground: "hsl(0 0% 100%)";
22
+ readonly secondary: "hsl(93 28% 54%)";
23
+ readonly secondaryForeground: "hsl(0 0% 100%)";
24
+ readonly muted: "hsl(0 0% 92%)";
25
+ readonly mutedForeground: "hsl(215 16% 47%)";
26
+ readonly accent: "hsl(0 0% 88%)";
27
+ readonly accentForeground: "hsl(222 47% 11%)";
28
+ readonly destructive: "hsl(0 84.2% 60.2%)";
29
+ readonly destructiveForeground: "hsl(0 0% 100%)";
30
+ readonly border: "hsl(0 0% 89%)";
31
+ readonly input: "hsl(0 0% 89%)";
32
+ readonly ring: "hsl(222 47% 11%)";
33
+ readonly radius: "0.5rem";
34
+ };
35
+ readonly dark: {
36
+ readonly background: "hsl(0 0% 9%)";
37
+ readonly foreground: "hsl(0 0% 100%)";
38
+ readonly card: "hsl(0 0% 14%)";
39
+ readonly cardForeground: "hsl(0 0% 100%)";
40
+ readonly popover: "hsl(0 0% 11%)";
41
+ readonly popoverForeground: "hsl(0 0% 100%)";
42
+ readonly primary: "hsl(227 96% 71%)";
43
+ readonly primaryForeground: "hsl(0 0% 9%)";
44
+ readonly secondary: "hsl(96 44% 61%)";
45
+ readonly secondaryForeground: "hsl(0 0% 9%)";
46
+ readonly muted: "hsl(0 0% 6%)";
47
+ readonly mutedForeground: "hsl(0 0% 70%)";
48
+ readonly accent: "hsl(0 0% 15%)";
49
+ readonly accentForeground: "hsl(0 0% 100%)";
50
+ readonly destructive: "hsl(0 62.8% 30.6%)";
51
+ readonly destructiveForeground: "hsl(0 0% 100%)";
52
+ readonly border: "hsl(0 0% 18%)";
53
+ readonly input: "hsl(0 0% 18%)";
54
+ readonly ring: "hsl(0 0% 90%)";
55
+ readonly radius: "0.5rem";
56
+ };
57
+ };
58
+ export declare const tokens: {
59
+ readonly palette: {
60
+ readonly brand: {
61
+ readonly blue: "hsl(225 73% 57%)";
62
+ readonly green: "hsl(151 66% 27%)";
63
+ readonly floralWhite: "hsl(40 100% 97%)";
64
+ };
65
+ };
66
+ readonly theme: {
67
+ readonly light: {
68
+ readonly background: "hsl(0 0% 96%)";
69
+ readonly foreground: "hsl(222 47% 11%)";
70
+ readonly card: "hsl(0 0% 100%)";
71
+ readonly cardForeground: "hsl(222 47% 11%)";
72
+ readonly popover: "hsl(0 0% 100%)";
73
+ readonly popoverForeground: "hsl(222 47% 11%)";
74
+ readonly primary: "hsl(225 73% 57%)";
75
+ readonly primaryForeground: "hsl(0 0% 100%)";
76
+ readonly secondary: "hsl(93 28% 54%)";
77
+ readonly secondaryForeground: "hsl(0 0% 100%)";
78
+ readonly muted: "hsl(0 0% 92%)";
79
+ readonly mutedForeground: "hsl(215 16% 47%)";
80
+ readonly accent: "hsl(0 0% 88%)";
81
+ readonly accentForeground: "hsl(222 47% 11%)";
82
+ readonly destructive: "hsl(0 84.2% 60.2%)";
83
+ readonly destructiveForeground: "hsl(0 0% 100%)";
84
+ readonly border: "hsl(0 0% 89%)";
85
+ readonly input: "hsl(0 0% 89%)";
86
+ readonly ring: "hsl(222 47% 11%)";
87
+ readonly radius: "0.5rem";
88
+ };
89
+ readonly dark: {
90
+ readonly background: "hsl(0 0% 9%)";
91
+ readonly foreground: "hsl(0 0% 100%)";
92
+ readonly card: "hsl(0 0% 14%)";
93
+ readonly cardForeground: "hsl(0 0% 100%)";
94
+ readonly popover: "hsl(0 0% 11%)";
95
+ readonly popoverForeground: "hsl(0 0% 100%)";
96
+ readonly primary: "hsl(227 96% 71%)";
97
+ readonly primaryForeground: "hsl(0 0% 9%)";
98
+ readonly secondary: "hsl(96 44% 61%)";
99
+ readonly secondaryForeground: "hsl(0 0% 9%)";
100
+ readonly muted: "hsl(0 0% 6%)";
101
+ readonly mutedForeground: "hsl(0 0% 70%)";
102
+ readonly accent: "hsl(0 0% 15%)";
103
+ readonly accentForeground: "hsl(0 0% 100%)";
104
+ readonly destructive: "hsl(0 62.8% 30.6%)";
105
+ readonly destructiveForeground: "hsl(0 0% 100%)";
106
+ readonly border: "hsl(0 0% 18%)";
107
+ readonly input: "hsl(0 0% 18%)";
108
+ readonly ring: "hsl(0 0% 90%)";
109
+ readonly radius: "0.5rem";
110
+ };
111
+ };
112
+ };
113
+ export type ThemeTokens = typeof theme.light;
114
+ export type PaletteTokens = typeof palette;
115
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/theme/tokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAEX,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CR,CAAC;AAEX,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAGT,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,CAAC,KAAK,CAAC;AAC7C,MAAM,MAAM,aAAa,GAAG,OAAO,OAAO,CAAC"}
package/index.html CHANGED
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/x-icon" href="/favicon.ico" />
5
+ <link rel="icon" type="image/png" href="/favicon.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>GV Tech Design System Playground</title>
8
8
  </head>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gv-tech/design-system",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Garcia Ventures react design system",
5
5
  "repository": "git@github.com:Garcia-Ventures/gvtech-design.git",
6
6
  "license": "MIT",
@@ -9,14 +9,20 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.es.js",
12
- "require": "./dist/index.cjs.js"
12
+ "require": "./dist/index.cjs.js",
13
+ "default": "./dist/index.es.js"
14
+ },
15
+ "./style.css": {
16
+ "style": "./dist/design-system.css",
17
+ "import": "./dist/design-system.css",
18
+ "default": "./dist/design-system.css"
13
19
  },
14
- "./style.css": "./dist/design-system.css",
15
20
  "./package.json": "./package.json"
16
21
  },
17
22
  "main": "dist/index.cjs.js",
18
23
  "module": "dist/index.es.js",
19
24
  "types": "dist/index.d.ts",
25
+ "style": "dist/design-system.css",
20
26
  "scripts": {
21
27
  "build": "yarn build:registry && tsc -p tsconfig.build.json && VITE_LIB=true vite build",
22
28
  "build:registry": "tsx scripts/build-registry.ts",
@@ -45,12 +51,6 @@
45
51
  "prettier --write"
46
52
  ]
47
53
  },
48
- "babel": {
49
- "presets": [
50
- "@babel/preset-env",
51
- "@babel/preset-react"
52
- ]
53
- },
54
54
  "browserslist": {
55
55
  "development": [
56
56
  "last 1 chrome version",
Binary file
Binary file
Binary file
@@ -3,9 +3,9 @@
3
3
  "name": "GV Tech Design System",
4
4
  "icons": [
5
5
  {
6
- "src": "favicon.ico",
7
- "sizes": "64x64 32x32 24x24 16x16",
8
- "type": "image/x-icon"
6
+ "src": "favicon.png",
7
+ "sizes": "512x512",
8
+ "type": "image/png"
9
9
  },
10
10
  {
11
11
  "src": "logo192.png",
@@ -15,6 +15,7 @@ const steps = [
15
15
  cmd: fix ? 'yarn lint:fix' : 'yarn lint',
16
16
  },
17
17
  { name: 'TypeScript type check', cmd: 'npx tsc --noEmit' },
18
+ { name: 'Test (vitest)', cmd: 'yarn test:ci' },
18
19
  { name: 'Build (vite)', cmd: 'yarn build' },
19
20
  ];
20
21
 
@@ -50,6 +50,7 @@ describe('AlertDialog', () => {
50
50
  <AlertDialogContent>
51
51
  <AlertDialogHeader>
52
52
  <AlertDialogTitle>Are you sure?</AlertDialogTitle>
53
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
53
54
  </AlertDialogHeader>
54
55
  <AlertDialogFooter>
55
56
  <AlertDialogCancel>Cancel</AlertDialogCancel>
@@ -72,6 +73,7 @@ describe('AlertDialog', () => {
72
73
  <AlertDialogContent>
73
74
  <AlertDialogHeader>
74
75
  <AlertDialogTitle>Are you sure?</AlertDialogTitle>
76
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
75
77
  </AlertDialogHeader>
76
78
  <AlertDialogFooter>
77
79
  <AlertDialogAction>Continue</AlertDialogAction>
@@ -0,0 +1,49 @@
1
+ import { ThemeToggle } from '@/components/ui/theme-toggle';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { ThemeProvider } from 'next-themes';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+
7
+ // Mock the useTheme hook to control its return values
8
+ vi.mock('@/hooks/use-theme', async () => {
9
+ const actual = await vi.importActual('@/hooks/use-theme');
10
+ return {
11
+ ...actual,
12
+ useTheme: () => ({
13
+ theme: 'light',
14
+ setTheme: vi.fn(),
15
+ resolvedTheme: 'light',
16
+ tokens: {},
17
+ }),
18
+ };
19
+ });
20
+
21
+ describe('ThemeToggle', () => {
22
+ it('renders binary toggle by default', () => {
23
+ render(
24
+ <ThemeProvider>
25
+ <ThemeToggle />
26
+ </ThemeProvider>,
27
+ );
28
+ // Use role button which is accessible
29
+ const button = screen.getByRole('button', { name: /toggle theme/i });
30
+ expect(button).toBeInTheDocument();
31
+ });
32
+
33
+ it('renders ternary toggle with dropdown', async () => {
34
+ const user = userEvent.setup();
35
+ render(
36
+ <ThemeProvider>
37
+ <ThemeToggle variant="ternary" />
38
+ </ThemeProvider>,
39
+ );
40
+ const button = screen.getByRole('button'); // Dropdown trigger
41
+ expect(button).toBeInTheDocument();
42
+
43
+ // Open dropdown
44
+ await user.click(button);
45
+ expect(await screen.findByText('Light')).toBeInTheDocument();
46
+ expect(screen.getByText('Dark')).toBeInTheDocument();
47
+ expect(screen.getByText('System')).toBeInTheDocument();
48
+ });
49
+ });
@@ -5,9 +5,9 @@ import {
5
5
  DropdownMenuItem,
6
6
  DropdownMenuTrigger,
7
7
  } from '@/components/ui/dropdown-menu';
8
+ import { useTheme } from '@/hooks/use-theme';
8
9
  import { cn } from '@/lib/utils';
9
10
  import { Moon, Sun, SunMoon } from 'lucide-react';
10
- import { useTheme } from 'next-themes';
11
11
 
12
12
  export interface ThemeToggleProps {
13
13
  /**
package/src/globals.css CHANGED
@@ -56,8 +56,9 @@
56
56
  --radius: 0.5rem;
57
57
 
58
58
  /* Brand Tokens */
59
- --brand-green: 151 66% 27%;
60
59
  --brand-blue: 225 73% 57%;
60
+ --brand-green: 151 66% 27%;
61
+ --brand-floral-white: 40 100% 97%;
61
62
  }
62
63
 
63
64
  .dark {
@@ -0,0 +1,27 @@
1
+ import { useTheme } from '@/hooks/use-theme';
2
+ import { theme } from '@/theme/tokens';
3
+ import { renderHook } from '@testing-library/react';
4
+ import { ThemeProvider } from 'next-themes';
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ describe('useTheme', () => {
8
+ it('returns default light tokens when no theme is set', () => {
9
+ const { result } = renderHook(() => useTheme(), {
10
+ wrapper: ({ children }) => <ThemeProvider>{children}</ThemeProvider>,
11
+ });
12
+
13
+ expect(result.current.tokens).toEqual(theme.light);
14
+ });
15
+
16
+ it('returns dark tokens when theme is dark', () => {
17
+ const { result } = renderHook(() => useTheme(), {
18
+ wrapper: ({ children }) => (
19
+ <ThemeProvider defaultTheme="dark" enableSystem={false}>
20
+ {children}
21
+ </ThemeProvider>
22
+ ),
23
+ });
24
+
25
+ expect(result.current.tokens).toEqual(theme.dark);
26
+ });
27
+ });
@@ -0,0 +1,15 @@
1
+ import { theme } from '@/theme/tokens';
2
+ import { useTheme as useNextTheme } from 'next-themes';
3
+
4
+ export function useTheme() {
5
+ const context = useNextTheme();
6
+ const { resolvedTheme } = context;
7
+
8
+ // Default to light theme tokens if resolvedTheme is undefined or invalid
9
+ const activeTokens = resolvedTheme === 'dark' ? theme.dark : theme.light;
10
+
11
+ return {
12
+ ...context,
13
+ tokens: activeTokens,
14
+ };
15
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import './globals.css';
2
2
 
3
- export * from './lib/tokens';
4
3
  export * from './lib/utils';
4
+ export * from './theme/tokens';
5
5
 
6
6
  // UI Components
7
7
  export * from './components/ui/accordion';
@@ -53,4 +53,5 @@ export * from './components/ui/toggle-group';
53
53
  export * from './components/ui/tooltip';
54
54
 
55
55
  // Hooks
56
+ export * from './hooks/use-theme';
56
57
  export * from './hooks/use-toast';