| 1 | import { useCallback, useEffect, useState } from "react"; |
| 2 | |
| 3 | type Theme = "light" | "dark"; |
| 4 | const STORAGE_KEY = "nanobot-webui.theme"; |
| 5 | |
| 6 | function readStored(): Theme | null { |
| 7 | try { |
| 8 | const v = localStorage.getItem(STORAGE_KEY); |
| 9 | return v === "light" || v === "dark" ? v : null; |
| 10 | } catch { |
| 11 | return null; |
| 12 | } |
| 13 | } |
| 14 | |
| 15 | function applyTheme(theme: Theme): void { |
| 16 | const root = document.documentElement; |
| 17 | if (theme === "dark") root.classList.add("dark"); |
| 18 | else root.classList.remove("dark"); |
| 19 | } |
| 20 | |
| 21 | export function useTheme(): { theme: Theme; toggle: () => void; setTheme: (t: Theme) => void } { |
| 22 | const [theme, setThemeState] = useState<Theme>(() => { |
| 23 | const stored = readStored(); |
| 24 | if (stored) return stored; |
| 25 | if (typeof window !== "undefined" && window.matchMedia) { |
| 26 | return window.matchMedia("(prefers-color-scheme: dark)").matches |
| 27 | ? "dark" |
| 28 | : "light"; |
| 29 | } |
| 30 | return "light"; |
| 31 | }); |
| 32 | |
| 33 | useEffect(() => { |
| 34 | applyTheme(theme); |
| 35 | try { |
| 36 | localStorage.setItem(STORAGE_KEY, theme); |
| 37 | } catch { |
| 38 | // ignore |
| 39 | } |
| 40 | }, [theme]); |
| 41 | |
| 42 | const setTheme = useCallback((t: Theme) => setThemeState(t), []); |
| 43 | const toggle = useCallback( |
| 44 | () => setThemeState((t) => (t === "dark" ? "light" : "dark")), |
| 45 | [], |
| 46 | ); |
| 47 | return { theme, toggle, setTheme }; |
| 48 | } |
| 49 |