SignalMod / frontend /src /context /ThemeContext.tsx
Ruperth's picture
feat: add theme context with light and dark modes
e154491
Raw
History Blame Contribute Delete
1.7 kB
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;
}