Skip to content

Commit 357a1e8

Browse files
standardize theme management on next-themes
1 parent d5265c0 commit 357a1e8

File tree

4 files changed

+50
-76
lines changed

4 files changed

+50
-76
lines changed

app/layout.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Public_Sans } from 'next/font/google';
22
import localFont from 'next/font/local';
33
import { headers } from 'next/headers';
4-
import { ApplyThemeScript, ThemeToggle } from '@/components/app/theme-toggle';
4+
import { ThemeProvider } from '@/components/app/theme-provider';
5+
import { ThemeToggle } from '@/components/app/theme-toggle';
56
import { cn, getAppConfig, getStyles } from '@/lib/utils';
67
import '@/styles/globals.css';
78

@@ -61,13 +62,19 @@ export default async function RootLayout({ children }: RootLayoutProps) {
6162
{styles && <style>{styles}</style>}
6263
<title>{pageTitle}</title>
6364
<meta name="description" content={pageDescription} />
64-
<ApplyThemeScript />
6565
</head>
6666
<body className="overflow-x-hidden">
67-
{children}
68-
<div className="group fixed bottom-0 left-1/2 z-50 mb-2 -translate-x-1/2">
69-
<ThemeToggle className="translate-y-20 transition-transform delay-150 duration-300 group-hover:translate-y-0" />
70-
</div>
67+
<ThemeProvider
68+
attribute="class"
69+
defaultTheme="system"
70+
enableSystem
71+
disableTransitionOnChange
72+
>
73+
{children}
74+
<div className="group fixed bottom-0 left-1/2 z-50 mb-2 -translate-x-1/2">
75+
<ThemeToggle className="translate-y-20 transition-transform delay-150 duration-300 group-hover:translate-y-0" />
76+
</div>
77+
</ThemeProvider>
7178
</body>
7279
</html>
7380
);

components/app/theme-provider.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { ThemeProvider as NextThemesProvider } from 'next-themes';
5+
6+
export function ThemeProvider({
7+
children,
8+
...props
9+
}: React.ComponentProps<typeof NextThemesProvider>) {
10+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11+
}

components/app/theme-toggle.tsx

Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,16 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
4-
import { MonitorIcon, MoonIcon, SunIcon } from '@phosphor-icons/react';
5-
import { THEME_MEDIA_QUERY, THEME_STORAGE_KEY, cn } from '@/lib/utils';
6-
7-
const THEME_SCRIPT = `
8-
const doc = document.documentElement;
9-
const theme = localStorage.getItem("${THEME_STORAGE_KEY}") ?? "system";
10-
11-
if (theme === "system") {
12-
if (window.matchMedia("${THEME_MEDIA_QUERY}").matches) {
13-
doc.classList.add("dark");
14-
} else {
15-
doc.classList.add("light");
16-
}
17-
} else {
18-
doc.classList.add(theme);
19-
}
20-
`
21-
.trim()
22-
.replace(/\n/g, '')
23-
.replace(/\s+/g, ' ');
24-
25-
export type ThemeMode = 'dark' | 'light' | 'system';
26-
27-
function applyTheme(theme: ThemeMode) {
28-
const doc = document.documentElement;
293

30-
doc.classList.remove('dark', 'light');
31-
localStorage.setItem(THEME_STORAGE_KEY, theme);
32-
33-
if (theme === 'system') {
34-
if (window.matchMedia(THEME_MEDIA_QUERY).matches) {
35-
doc.classList.add('dark');
36-
} else {
37-
doc.classList.add('light');
38-
}
39-
} else {
40-
doc.classList.add(theme);
41-
}
42-
}
4+
import { useTheme } from 'next-themes';
5+
import { MonitorIcon, MoonIcon, SunIcon } from '@phosphor-icons/react';
6+
import { cn } from '@/lib/utils';
437

448
interface ThemeToggleProps {
459
className?: string;
4610
}
4711

48-
export function ApplyThemeScript() {
49-
return <script id="theme-script">{THEME_SCRIPT}</script>;
50-
}
51-
5212
export function ThemeToggle({ className }: ThemeToggleProps) {
53-
const [theme, setTheme] = useState<ThemeMode | undefined>(undefined);
54-
55-
useEffect(() => {
56-
const storedTheme = (localStorage.getItem(THEME_STORAGE_KEY) as ThemeMode) ?? 'system';
57-
58-
setTheme(storedTheme);
59-
}, []);
60-
61-
function handleThemeChange(theme: ThemeMode) {
62-
applyTheme(theme);
63-
setTheme(theme);
64-
}
13+
const { theme, setTheme } = useTheme();
6514

6615
return (
6716
<div
@@ -71,30 +20,40 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
7120
)}
7221
>
7322
<span className="sr-only">Color scheme toggle</span>
74-
<button
75-
type="button"
76-
onClick={() => handleThemeChange('dark')}
77-
className="cursor-pointer p-1 pl-1.5"
78-
>
23+
<button type="button" onClick={() => setTheme('dark')} className="cursor-pointer p-1 pl-1.5">
7924
<span className="sr-only">Enable dark color scheme</span>
80-
<MoonIcon size={16} weight="bold" className={cn(theme !== 'dark' && 'opacity-25')} />
25+
<MoonIcon
26+
suppressHydrationWarning
27+
size={16}
28+
weight="bold"
29+
className={cn(theme !== 'dark' && 'opacity-25')}
30+
/>
8131
</button>
8232
<button
8333
type="button"
84-
onClick={() => handleThemeChange('light')}
34+
onClick={() => setTheme('light')}
8535
className="cursor-pointer px-1.5 py-1"
8636
>
8737
<span className="sr-only">Enable light color scheme</span>
88-
<SunIcon size={16} weight="bold" className={cn(theme !== 'light' && 'opacity-25')} />
38+
<SunIcon
39+
suppressHydrationWarning
40+
size={16}
41+
weight="bold"
42+
className={cn(theme !== 'light' && 'opacity-25')}
43+
/>
8944
</button>
9045
<button
9146
type="button"
92-
onClick={() => handleThemeChange('system')}
47+
onClick={() => setTheme('system')}
9348
className="cursor-pointer p-1 pr-1.5"
9449
>
9550
<span className="sr-only">Enable system color scheme</span>
96-
<MonitorIcon size={16} weight="bold" className={cn(theme !== 'system' && 'opacity-25')} />
51+
<MonitorIcon
52+
suppressHydrationWarning
53+
size={16}
54+
weight="bold"
55+
className={cn(theme !== 'system' && 'opacity-25')}
56+
/>
9757
</button>
9858
</div>
99-
);
100-
}
59+
);

lib/utils.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import { twMerge } from 'tailwind-merge';
44
import { APP_CONFIG_DEFAULTS } from '@/app-config';
55
import type { AppConfig } from '@/app-config';
66

7-
export const CONFIG_ENDPOINT = process.env.NEXT_PUBLIC_APP_CONFIG_ENDPOINT;
8-
export const SANDBOX_ID = process.env.SANDBOX_ID;
9-
107
export const THEME_STORAGE_KEY = 'theme-mode';
118
export const THEME_MEDIA_QUERY = '(prefers-color-scheme: dark)';
129

0 commit comments

Comments
 (0)