Ruperth commited on
Commit
e154491
·
1 Parent(s): 21e8095

feat: add theme context with light and dark modes

Browse files

Tracks the active palette as a data-theme attribute on the root element so the CSS token system can switch instantly. Detects the user's prefers-color-scheme on first load and remembers later toggles in localStorage.

frontend/src/context/ThemeContext.tsx ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode,
9
+ } from "react";
10
+
11
+ export type ThemeMode = "light" | "dark";
12
+ const STORAGE_KEY = "signalmod.theme";
13
+
14
+ type ThemeValue = {
15
+ theme: ThemeMode;
16
+ setTheme: (t: ThemeMode) => void;
17
+ toggleTheme: () => void;
18
+ };
19
+
20
+ const ThemeContext = createContext<ThemeValue | null>(null);
21
+
22
+ function detectInitialTheme(): ThemeMode {
23
+ if (typeof window === "undefined") return "light";
24
+ const stored = window.localStorage.getItem(STORAGE_KEY);
25
+ if (stored === "light" || stored === "dark") return stored;
26
+ const prefersDark =
27
+ window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
28
+ return prefersDark ? "dark" : "light";
29
+ }
30
+
31
+ export function ThemeProvider({ children }: { children: ReactNode }) {
32
+ const [theme, setThemeState] = useState<ThemeMode>(() => detectInitialTheme());
33
+
34
+ useEffect(() => {
35
+ if (typeof document !== "undefined") {
36
+ document.documentElement.dataset.theme = theme;
37
+ }
38
+ if (typeof window !== "undefined") {
39
+ window.localStorage.setItem(STORAGE_KEY, theme);
40
+ }
41
+ }, [theme]);
42
+
43
+ const setTheme = useCallback((t: ThemeMode) => setThemeState(t), []);
44
+ const toggleTheme = useCallback(
45
+ () => setThemeState((prev) => (prev === "light" ? "dark" : "light")),
46
+ []
47
+ );
48
+
49
+ const value = useMemo<ThemeValue>(
50
+ () => ({ theme, setTheme, toggleTheme }),
51
+ [theme, setTheme, toggleTheme]
52
+ );
53
+
54
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
55
+ }
56
+
57
+ export function useTheme() {
58
+ const ctx = useContext(ThemeContext);
59
+ if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
60
+ return ctx;
61
+ }