web/src/lib/theme.tsx
1.3 KB · sha256:ac9bf7bd9798e466801cba979ed4eb0862fa80bc8760150f39eb1de8e61601e1
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeCtx {
theme: Theme;
toggle: () => void;
set: (t: Theme) => void;
}
const Ctx = createContext<ThemeCtx | null>(null);
function initial(): Theme {
if (typeof localStorage !== "undefined") {
const stored = localStorage.getItem("ward.theme");
if (stored === "light" || stored === "dark") return stored;
}
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
return "dark";
}
return "light";
}
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>(initial);
useEffect(() => {
const root = document.documentElement;
root.classList.toggle("dark", theme === "dark");
try { localStorage.setItem("ward.theme", theme); } catch {}
}, [theme]);
const value: ThemeCtx = {
theme,
toggle: () => setTheme((t) => (t === "dark" ? "light" : "dark")),
set: setTheme,
};
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}
export function useTheme(): ThemeCtx {
const ctx = useContext(Ctx);
if (!ctx) throw new Error("useTheme must be used inside ThemeProvider");
return ctx;
}