"use client"; import { useCallback, useEffect, useState } from "react"; import { DEFAULT_THEME_ID, getThemeById } from "@/lib/themes"; const STORAGE_KEY = "wfdash-theme"; /** * useThemeId — reads / writes the active visual theme id. * * The theme id is applied to via the `data-theme` attribute; the * matching tokens live in src/styles/themes.css. * * On the first render the value is `DEFAULT_THEME_ID` — the bootstrap * script in app/layout.tsx applies the persisted value before hydration, * then a tiny effect in this hook syncs the React state to whatever the * DOM already says. This is the only safe way to avoid a hydration * mismatch without rendering nothing on the first frame. */ export function useThemeId() { const [themeId, setThemeIdState] = useState(DEFAULT_THEME_ID); useEffect(() => { // Pull whatever the bootstrap script already set on so the // first React render matches the DOM. Intentionally a setState-in-effect: // we need to read the DOM after hydration, not before. const fromDom = document.documentElement.getAttribute("data-theme"); if (fromDom && fromDom !== themeId) { // eslint-disable-next-line react-hooks/set-state-in-effect setThemeIdState(fromDom); } }, []); // eslint-disable-line react-hooks/exhaustive-deps const setThemeId = useCallback((id: string) => { const t = getThemeById(id); setThemeIdState(t.id); try { localStorage.setItem(STORAGE_KEY, t.id); } catch { /* ignore */ } if (typeof document !== "undefined") { const root = document.documentElement; root.setAttribute("data-theme", t.id); root.style.setProperty("--radius", t.radius); root.style.setProperty("--font-heading", t.fontHeading); root.style.setProperty("--font-body", t.fontBody); } }, []); return { themeId, setThemeId }; }