-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy paththeme.tsx
More file actions
82 lines (71 loc) · 2.37 KB
/
theme.tsx
File metadata and controls
82 lines (71 loc) · 2.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
"use client";
import { createContext, useContext, useEffect, useState } from "react";
export type ThemePreference = "light" | "dark" | "system";
export type ResolvedTheme = "light" | "dark";
interface ThemeContextValue {
preference: ThemePreference;
resolved: ResolvedTheme;
setPreference: (pref: ThemePreference) => void;
}
const STORAGE_KEY = "memo-theme";
const DARK_MQ = "(prefers-color-scheme:dark)";
const ThemeContext = createContext<ThemeContextValue | null>(null);
function getSystemTheme(): ResolvedTheme {
if (typeof window === "undefined") return "dark";
return window.matchMedia(DARK_MQ).matches ? "dark" : "light";
}
function applyTheme(theme: ResolvedTheme) {
document.documentElement.setAttribute("data-theme", theme);
document.documentElement.classList.toggle("dark", theme === "dark");
}
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [preference, setPreferenceState] = useState<ThemePreference>(() => {
if (typeof window === "undefined") return "dark";
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored === "light" || stored === "dark" || stored === "system"
? stored
: "dark";
} catch {
return "dark";
}
});
const [resolved, setResolved] = useState<ResolvedTheme>(() =>
preference === "system" ? getSystemTheme() : preference,
);
function setPreference(pref: ThemePreference) {
try {
localStorage.setItem(STORAGE_KEY, pref);
} catch {
// Storage unavailable (SecurityError in restricted browsers)
}
setPreferenceState(pref);
const next = pref === "system" ? getSystemTheme() : pref;
setResolved(next);
applyTheme(next);
}
useEffect(() => {
applyTheme(resolved);
if (preference !== "system") return;
const mq = window.matchMedia(DARK_MQ);
function onChange() {
const next = getSystemTheme();
setResolved(next);
applyTheme(next);
}
mq.addEventListener("change", onChange);
return () => mq.removeEventListener("change", onChange);
}, [preference, resolved]);
return (
<ThemeContext value={{ preference, resolved, setPreference }}>
{children}
</ThemeContext>
);
}
export function useTheme(): ThemeContextValue {
const ctx = useContext(ThemeContext);
if (!ctx) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return ctx;
}