File size: 1,702 Bytes
e154491 | 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 | import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
export type ThemeMode = "light" | "dark";
const STORAGE_KEY = "signalmod.theme";
type ThemeValue = {
theme: ThemeMode;
setTheme: (t: ThemeMode) => void;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeValue | null>(null);
function detectInitialTheme(): ThemeMode {
if (typeof window === "undefined") return "light";
const stored = window.localStorage.getItem(STORAGE_KEY);
if (stored === "light" || stored === "dark") return stored;
const prefersDark =
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
return prefersDark ? "dark" : "light";
}
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<ThemeMode>(() => detectInitialTheme());
useEffect(() => {
if (typeof document !== "undefined") {
document.documentElement.dataset.theme = theme;
}
if (typeof window !== "undefined") {
window.localStorage.setItem(STORAGE_KEY, theme);
}
}, [theme]);
const setTheme = useCallback((t: ThemeMode) => setThemeState(t), []);
const toggleTheme = useCallback(
() => setThemeState((prev) => (prev === "light" ? "dark" : "light")),
[]
);
const value = useMemo<ThemeValue>(
() => ({ theme, setTheme, toggleTheme }),
[theme, setTheme, toggleTheme]
);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
|