@gv-tech/design-system 2.1.1 → 2.3.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 (47) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/dist/components/ui/theme-provider.d.ts +4 -0
  4. package/dist/components/ui/theme-provider.d.ts.map +1 -0
  5. package/dist/components/ui/theme-provider.test.d.ts +2 -0
  6. package/dist/components/ui/theme-provider.test.d.ts.map +1 -0
  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/hooks/use-theme.d.ts +52 -0
  11. package/dist/hooks/use-theme.d.ts.map +1 -0
  12. package/dist/hooks/use-theme.test.d.ts +2 -0
  13. package/dist/hooks/use-theme.test.d.ts.map +1 -0
  14. package/dist/index.cjs.js +2 -2
  15. package/dist/index.cjs.js.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.es.js +736 -723
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/pages/GettingStarted.d.ts.map +1 -1
  21. package/dist/pages/components/ThemeToggleDocs.d.ts.map +1 -1
  22. package/dist/registry/alert-dialog.test.json +1 -1
  23. package/dist/registry/index.json +21 -0
  24. package/dist/registry/theme-provider.json +13 -0
  25. package/dist/registry/theme-provider.test.json +13 -0
  26. package/dist/registry/theme-toggle.json +1 -1
  27. package/dist/registry/theme-toggle.test.json +13 -0
  28. package/dist/{vendor-BLvpSabH.mjs → vendor-Bcg_ARLM.mjs} +1111 -1140
  29. package/dist/vendor-Bcg_ARLM.mjs.map +1 -0
  30. package/dist/{vendor-n4WFhtJT.js → vendor-BrqPND3G.js} +12 -12
  31. package/dist/vendor-BrqPND3G.js.map +1 -0
  32. package/package.json +3 -2
  33. package/scripts/validate.js +1 -0
  34. package/src/App.tsx +2 -2
  35. package/src/components/ui/alert-dialog.test.tsx +2 -0
  36. package/src/components/ui/theme-provider.test.tsx +47 -0
  37. package/src/components/ui/theme-provider.tsx +12 -0
  38. package/src/components/ui/theme-toggle.test.tsx +49 -0
  39. package/src/components/ui/theme-toggle.tsx +1 -1
  40. package/src/hooks/use-theme.test.tsx +27 -0
  41. package/src/hooks/use-theme.ts +15 -0
  42. package/src/index.ts +2 -0
  43. package/src/pages/GettingStarted.tsx +37 -6
  44. package/src/pages/components/ThemeToggleDocs.tsx +187 -13
  45. package/vite.config.ts +2 -1
  46. package/dist/vendor-BLvpSabH.mjs.map +0 -1
  47. package/dist/vendor-n4WFhtJT.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GettingStarted.d.ts","sourceRoot":"","sources":["../../src/pages/GettingStarted.tsx"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,4CA4FjC;AAED,wBAAgB,gBAAgB,4CA4K/B"}
1
+ {"version":3,"file":"GettingStarted.d.ts","sourceRoot":"","sources":["../../src/pages/GettingStarted.tsx"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,4CA4FjC;AAED,wBAAgB,gBAAgB,4CA2M/B"}
@@ -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,4CAkS9B"}
@@ -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,27 @@
587
587
  ],
588
588
  "type": "registry:ui"
589
589
  },
590
+ {
591
+ "name": "theme-provider.test",
592
+ "files": [
593
+ "ui/theme-provider.test.tsx"
594
+ ],
595
+ "type": "registry:ui"
596
+ },
597
+ {
598
+ "name": "theme-provider",
599
+ "files": [
600
+ "ui/theme-provider.tsx"
601
+ ],
602
+ "type": "registry:ui"
603
+ },
604
+ {
605
+ "name": "theme-toggle.test",
606
+ "files": [
607
+ "ui/theme-toggle.test.tsx"
608
+ ],
609
+ "type": "registry:ui"
610
+ },
590
611
  {
591
612
  "name": "theme-toggle",
592
613
  "files": [
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "theme-provider",
3
+ "type": "registry:ui",
4
+ "dependencies": [],
5
+ "registryDependencies": [],
6
+ "files": [
7
+ {
8
+ "path": "ui/theme-provider.tsx",
9
+ "content": "import type { ThemeProviderProps as NextThemesProviderProps } from 'next-themes';\nimport { ThemeProvider as NextThemesProvider } from 'next-themes';\n\nexport type ThemeProviderProps = NextThemesProviderProps;\n\nexport function ThemeProvider({ children, ...props }: ThemeProviderProps) {\n return (\n <NextThemesProvider attribute=\"class\" defaultTheme=\"system\" enableSystem {...props}>\n {children}\n </NextThemesProvider>\n );\n}\n",
10
+ "type": "registry:ui"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "theme-provider.test",
3
+ "type": "registry:ui",
4
+ "dependencies": [],
5
+ "registryDependencies": [],
6
+ "files": [
7
+ {
8
+ "path": "ui/theme-provider.test.tsx",
9
+ "content": "import { ThemeProvider } from '@/components/ui/theme-provider';\nimport { render, screen } from '@testing-library/react';\nimport { describe, expect, it } from 'vitest';\n\ndescribe('ThemeProvider', () => {\n it('renders children correctly', () => {\n render(\n <ThemeProvider>\n <div data-testid=\"child\">Hello</div>\n </ThemeProvider>,\n );\n expect(screen.getByTestId('child')).toBeInTheDocument();\n expect(screen.getByTestId('child')).toHaveTextContent('Hello');\n });\n\n it('applies class attribute to html element by default', () => {\n render(\n <ThemeProvider>\n <div>Content</div>\n </ThemeProvider>,\n );\n // next-themes applies the attribute to the html element;\n // in jsdom the attribute should be set to 'class' by default\n const html = document.documentElement;\n // The attribute prop controls how the theme class is toggled.\n // We verify the provider renders without error, which confirms the defaults are valid.\n expect(html).toBeDefined();\n });\n\n it('allows overriding default props', () => {\n render(\n <ThemeProvider defaultTheme=\"dark\" enableSystem={false}>\n <div data-testid=\"child\">Dark Mode</div>\n </ThemeProvider>,\n );\n expect(screen.getByTestId('child')).toBeInTheDocument();\n });\n\n it('passes additional next-themes props through', () => {\n render(\n <ThemeProvider storageKey=\"my-app-theme\" themes={['light', 'dark', 'ocean']}>\n <div data-testid=\"child\">Custom themes</div>\n </ThemeProvider>,\n );\n expect(screen.getByTestId('child')).toBeInTheDocument();\n });\n});\n",
10
+ "type": "registry:ui"
11
+ }
12
+ ]
13
+ }
@@ -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
+ }