Skip to content

Commit afdeee4

Browse files
committed
feat: ✨ add multi-theme color support
1 parent 872a451 commit afdeee4

7 files changed

Lines changed: 389 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ cd new_directory_name
2222
#### Install dependencies.
2323

2424
```bash
25-
pnpm install
25+
pnpm install
2626
```
2727

2828
#### Start the server

src/components/setting-drawer.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Settings } from 'lucide-react';
2+
import { Button } from '@/components/ui/button';
3+
import {
4+
Sheet,
5+
SheetContent,
6+
SheetDescription,
7+
SheetHeader,
8+
SheetTitle,
9+
SheetTrigger,
10+
} from '@/components/ui/sheet';
11+
12+
import { useTheme } from '@/hooks/use-theme';
13+
import type { ThemeScheme, ThemeColor } from '@/hooks/use-theme';
14+
15+
const schemes: ThemeScheme[] = ['light', 'dark', 'system'];
16+
const colors: ThemeColor[] = [
17+
'neutral',
18+
'red',
19+
'blue',
20+
'green',
21+
'amber',
22+
'rose',
23+
'purple',
24+
'orange',
25+
'teal',
26+
'yellow',
27+
'violet',
28+
];
29+
30+
export function SettingDrawer() {
31+
const { scheme, setScheme, color, setColor } = useTheme();
32+
33+
return (
34+
<Sheet>
35+
<SheetTrigger asChild>
36+
<Button
37+
size='icon'
38+
variant='outline'
39+
aria-label='Open settings'
40+
aria-describedby='config-drawer-description'
41+
className='rounded-full fixed right-4 top-40 z-40'
42+
>
43+
<Settings aria-hidden='true' />
44+
</Button>
45+
</SheetTrigger>
46+
<SheetContent className='flex flex-col'>
47+
<SheetHeader className='pb-0 text-start'>
48+
<SheetTitle>Settings</SheetTitle>
49+
<SheetDescription className='text-sm text-muted-foreground'>
50+
Configure your preferences for the application.
51+
</SheetDescription>
52+
</SheetHeader>
53+
<div className='space-y-6 overflow-y-auto px-4'>
54+
<div>
55+
<div className='font-semibold'>Scheme:</div>
56+
<div className='mt-4 grid grid-cols-3 gap-3'>
57+
{schemes.map((s) => (
58+
<Button
59+
key={s}
60+
variant='outline'
61+
className='capitalize'
62+
onClick={() => setScheme(s)}
63+
>
64+
{s}
65+
</Button>
66+
))}
67+
</div>
68+
</div>
69+
<div>
70+
<div className='font-semibold'>Theme:</div>
71+
<div className='mt-4 grid grid-cols-3 gap-3'>
72+
{colors.map((c) => (
73+
<Button
74+
key={c}
75+
variant='outline'
76+
onClick={() => setColor(c)}
77+
>
78+
{c}
79+
</Button>
80+
))}
81+
</div>
82+
</div>
83+
</div>
84+
</SheetContent>
85+
</Sheet>
86+
);
87+
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import { Button } from '@/components/ui/button';
12
import { createFileRoute } from '@tanstack/react-router';
23

34
export const Route = createFileRoute('/(admin)/dashboard/')({
45
component: RouteComponent,
56
});
67

78
function RouteComponent() {
8-
return <div>Hello "/(admin)/(dashboard)/"!</div>;
9+
return (
10+
<div>
11+
<div>Hello "/(admin)/(dashboard)/"!</div>
12+
<div>
13+
<Button>OK nè</Button>
14+
</div>
15+
</div>
16+
);
917
}

src/hooks/use-theme.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useEffect, useState } from 'react';
2+
3+
export type ThemeScheme = 'light' | 'dark' | 'system';
4+
export type ThemeColor =
5+
| 'neutral'
6+
| 'blue'
7+
| 'green'
8+
| 'amber'
9+
| 'rose'
10+
| 'purple'
11+
| 'orange'
12+
| 'teal'
13+
| 'red'
14+
| 'yellow'
15+
| 'violet';
16+
17+
const THEME_SCHEME_KEY = 'theme-scheme';
18+
const THEME_COLOR_KEY = 'theme-color';
19+
20+
function getSystemScheme(): ThemeScheme {
21+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
22+
return 'light';
23+
}
24+
25+
export function useTheme() {
26+
const [scheme, setScheme] = useState<ThemeScheme>(() => {
27+
return (localStorage.getItem(THEME_SCHEME_KEY) as ThemeScheme) || 'system';
28+
});
29+
30+
const [color, setColor] = useState<ThemeColor>(() => {
31+
return (localStorage.getItem(THEME_COLOR_KEY) as ThemeColor) || 'neutral';
32+
});
33+
34+
useEffect(() => {
35+
const appliedScheme = scheme === 'system' ? getSystemScheme() : scheme;
36+
document.documentElement.classList.toggle('dark', appliedScheme === 'dark');
37+
document.documentElement.classList.toggle(
38+
'light',
39+
appliedScheme === 'light'
40+
);
41+
localStorage.setItem(THEME_SCHEME_KEY, scheme);
42+
}, [scheme]);
43+
44+
useEffect(() => {
45+
Array.from(document.body.classList)
46+
.filter((className) => className.startsWith('theme-'))
47+
.forEach((className) => {
48+
document.body.classList.remove(className);
49+
});
50+
51+
document.body.classList.add(`theme-${color}`);
52+
localStorage.setItem(THEME_COLOR_KEY, color);
53+
}, [color]);
54+
55+
return {
56+
scheme,
57+
setScheme,
58+
color,
59+
setColor,
60+
};
61+
}

src/index.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
@import 'tailwindcss';
22
@import 'tw-animate-css';
33

4+
@import "./styles/themes.css";
5+
46
@custom-variant dark (&:is(.dark *));
57

68
@theme inline {

src/layouts/classic/classic-layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SidebarTrigger,
1414
} from '@/components/ui/sidebar';
1515
import { AppSidebar } from '../components/app-sidebar';
16+
import { SettingDrawer } from '@/components/setting-drawer';
1617

1718
export default function ClassicLayout({
1819
children,
@@ -45,6 +46,7 @@ export default function ClassicLayout({
4546
</Breadcrumb>
4647
</div>
4748
</header>
49+
<SettingDrawer />
4850
<div className='flex flex-1 flex-col gap-4 p-4 pt-0'>{children}</div>
4951
</SidebarInset>
5052
</SidebarProvider>

0 commit comments

Comments
 (0)