Skip to content

Commit 50a44b6

Browse files
standardize theme management on next-themes
1 parent d5265c0 commit 50a44b6

File tree

4 files changed

+48
-74
lines changed

4 files changed

+48
-74
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: 24 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,15 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
3+
import { useTheme } from 'next-themes';
44
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;
29-
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-
}
5+
import { cn } from '@/lib/utils';
436

447
interface ThemeToggleProps {
458
className?: string;
469
}
4710

48-
export function ApplyThemeScript() {
49-
return <script id="theme-script">{THEME_SCRIPT}</script>;
50-
}
51-
5211
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-
}
12+
const { theme, setTheme } = useTheme();
6513

6614
return (
6715
<div
@@ -71,29 +19,40 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
7119
)}
7220
>
7321
<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-
>
22+
<button type="button" onClick={() => setTheme('dark')} className="cursor-pointer p-1 pl-1.5">
7923
<span className="sr-only">Enable dark color scheme</span>
80-
<MoonIcon size={16} weight="bold" className={cn(theme !== 'dark' && 'opacity-25')} />
24+
<MoonIcon
25+
suppressHydrationWarning
26+
size={16}
27+
weight="bold"
28+
className={cn(theme !== 'dark' && 'opacity-25')}
29+
/>
8130
</button>
8231
<button
8332
type="button"
84-
onClick={() => handleThemeChange('light')}
33+
onClick={() => setTheme('light')}
8534
className="cursor-pointer px-1.5 py-1"
8635
>
8736
<span className="sr-only">Enable light color scheme</span>
88-
<SunIcon size={16} weight="bold" className={cn(theme !== 'light' && 'opacity-25')} />
37+
<SunIcon
38+
suppressHydrationWarning
39+
size={16}
40+
weight="bold"
41+
className={cn(theme !== 'light' && 'opacity-25')}
42+
/>
8943
</button>
9044
<button
9145
type="button"
92-
onClick={() => handleThemeChange('system')}
46+
onClick={() => setTheme('system')}
9347
className="cursor-pointer p-1 pr-1.5"
9448
>
9549
<span className="sr-only">Enable system color scheme</span>
96-
<MonitorIcon size={16} weight="bold" className={cn(theme !== 'system' && 'opacity-25')} />
50+
<MonitorIcon
51+
suppressHydrationWarning
52+
size={16}
53+
weight="bold"
54+
className={cn(theme !== 'system' && 'opacity-25')}
55+
/>
9756
</button>
9857
</div>
9958
);

lib/utils.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import type { AppConfig } from '@/app-config';
77
export const CONFIG_ENDPOINT = process.env.NEXT_PUBLIC_APP_CONFIG_ENDPOINT;
88
export const SANDBOX_ID = process.env.SANDBOX_ID;
99

10-
export const THEME_STORAGE_KEY = 'theme-mode';
11-
export const THEME_MEDIA_QUERY = '(prefers-color-scheme: dark)';
12-
1310
export interface SandboxConfig {
1411
[key: string]:
1512
| { type: 'string'; value: string }

0 commit comments

Comments
 (0)