@gv-tech/design-system 2.1.1 → 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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/components/ui/theme-toggle.test.d.ts +2 -0
- package/dist/components/ui/theme-toggle.test.d.ts.map +1 -0
- package/dist/hooks/use-theme.d.ts +52 -0
- package/dist/hooks/use-theme.d.ts.map +1 -0
- package/dist/hooks/use-theme.test.d.ts +2 -0
- package/dist/hooks/use-theme.test.d.ts.map +1 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +413 -405
- package/dist/index.es.js.map +1 -1
- package/dist/pages/components/ThemeToggleDocs.d.ts.map +1 -1
- package/dist/registry/alert-dialog.test.json +1 -1
- package/dist/registry/index.json +7 -0
- package/dist/registry/theme-toggle.json +1 -1
- package/dist/registry/theme-toggle.test.json +13 -0
- package/package.json +1 -1
- package/scripts/validate.js +1 -0
- package/src/components/ui/alert-dialog.test.tsx +2 -0
- package/src/components/ui/theme-toggle.test.tsx +49 -0
- package/src/components/ui/theme-toggle.tsx +1 -1
- package/src/hooks/use-theme.test.tsx +27 -0
- package/src/hooks/use-theme.ts +15 -0
- package/src/index.ts +1 -0
- package/src/pages/components/ThemeToggleDocs.tsx +35 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeToggleDocs.d.ts","sourceRoot":"","sources":["../../../src/pages/components/ThemeToggleDocs.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,
|
|
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
|
]
|
package/dist/registry/index.json
CHANGED
|
@@ -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';\
|
|
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
|
+
}
|
package/package.json
CHANGED
package/scripts/validate.js
CHANGED
|
@@ -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
|
/**
|
|
@@ -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
|
@@ -30,23 +30,48 @@ export function ThemeToggleDocs() {
|
|
|
30
30
|
</ComponentShowcase>
|
|
31
31
|
|
|
32
32
|
<ComponentShowcase
|
|
33
|
-
title="
|
|
34
|
-
description="You can control the theme externally by passing customTheme and onThemeChange props."
|
|
33
|
+
title="Controlled Mode"
|
|
34
|
+
description="You can control the theme externally by passing customTheme and onThemeChange props. This is useful for testing or when using a different theme provider."
|
|
35
35
|
code={`const [theme, setTheme] = useState('light');
|
|
36
36
|
|
|
37
37
|
<ThemeToggle
|
|
38
38
|
customTheme={theme}
|
|
39
|
-
onThemeChange={
|
|
39
|
+
onThemeChange={setTheme}
|
|
40
40
|
/>
|
|
41
41
|
|
|
42
42
|
<p>Current Theme: {theme}</p>`}
|
|
43
43
|
>
|
|
44
44
|
<div className="flex flex-col items-center gap-4">
|
|
45
|
-
<ThemeToggle customTheme={customTheme} onThemeChange={
|
|
45
|
+
<ThemeToggle customTheme={customTheme} onThemeChange={setCustomTheme} />
|
|
46
46
|
<p className="text-sm font-medium">Current Selection: {customTheme}</p>
|
|
47
47
|
</div>
|
|
48
48
|
</ComponentShowcase>
|
|
49
49
|
|
|
50
|
+
<div className="space-y-4">
|
|
51
|
+
<h3 className="text-xl font-semibold">useTheme Hook</h3>
|
|
52
|
+
<p className="text-sm text-muted-foreground">
|
|
53
|
+
The `useTheme` hook provides access to the current theme and the active design tokens.
|
|
54
|
+
</p>
|
|
55
|
+
<div className="rounded-md border bg-muted p-4">
|
|
56
|
+
<pre className="text-xs">
|
|
57
|
+
<code>
|
|
58
|
+
{`import { useTheme } from '@gv-tech/design-system';
|
|
59
|
+
|
|
60
|
+
export function MyComponent() {
|
|
61
|
+
const { theme, setTheme, tokens } = useTheme();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div style={{ backgroundColor: tokens.background }}>
|
|
65
|
+
<p>Current theme: {theme}</p>
|
|
66
|
+
<button onClick={() => setTheme('dark')}>Dark Mode</button>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}`}
|
|
70
|
+
</code>
|
|
71
|
+
</pre>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
50
75
|
<div className="space-y-4">
|
|
51
76
|
<h3 className="text-xl font-semibold">ThemeToggle Props</h3>
|
|
52
77
|
<PropsTable
|
|
@@ -104,14 +129,16 @@ export function ThemeToggleDocs() {
|
|
|
104
129
|
</div>
|
|
105
130
|
|
|
106
131
|
<div className="rounded-lg border bg-muted/50 p-6">
|
|
107
|
-
<h4 className="font-medium text-foreground">Custom
|
|
132
|
+
<h4 className="font-medium text-foreground">Controlled / Custom State</h4>
|
|
108
133
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
109
134
|
Pass your own theme state and change handler to integrate with custom logic or external storage.
|
|
110
135
|
</p>
|
|
111
136
|
<pre className="mt-4 overflow-x-auto rounded-md bg-background p-4 text-xs">
|
|
112
|
-
<code>{
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
<code>{`const [theme, setTheme] = useState("light")
|
|
138
|
+
|
|
139
|
+
<ThemeToggle
|
|
140
|
+
customTheme={theme}
|
|
141
|
+
onThemeChange={setTheme}
|
|
115
142
|
/>`}</code>
|
|
116
143
|
</pre>
|
|
117
144
|
</div>
|