'use client' import { useEffect, useState } from 'react' /** * App theme preference. 'system' follows the OS (prefers-color-scheme) and is * the default; 'light' / 'dark' are explicit overrides. The resolved theme is * applied by toggling the `dark` class on (Tailwind darkMode via * @custom-variant in globals.css). * * The same localStorage key is read by the inline FOUC guard in layout.tsx so * the correct class is set before React mounts — keep THEME_STORAGE_KEY in sync there. */ export type ThemePref = 'system' | 'light' | 'dark' export const THEME_STORAGE_KEY = 'iamcavalli:theme' function systemPrefersDark(): boolean { return ( typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ) } export function readThemePref(): ThemePref { if (typeof window === 'undefined') return 'system' const v = window.localStorage.getItem(THEME_STORAGE_KEY) return v === 'light' || v === 'dark' ? v : 'system' } export function resolveTheme(pref: ThemePref): 'light' | 'dark' { return pref === 'system' ? (systemPrefersDark() ? 'dark' : 'light') : pref } export function applyTheme(pref: ThemePref): void { if (typeof document === 'undefined') return document.documentElement.classList.toggle('dark', resolveTheme(pref) === 'dark') } /** * Theme state hook. Initialises to 'system' on both server and first client * render (avoids hydration mismatch — the FOUC guard already set the * class), then reconciles from localStorage after mount. Applies the resolved * theme on change and, while in 'system' mode, re-applies live when the OS * theme flips. Returns the raw preference, the resolved light/dark value, and a * setter that persists (clearing the key for 'system' so the OS stays * authoritative). */ export function useTheme() { const [pref, setPref] = useState('system') useEffect(() => { setPref(readThemePref()) }, []) useEffect(() => { applyTheme(pref) if (pref !== 'system') return const mq = window.matchMedia('(prefers-color-scheme: dark)') const onChange = () => applyTheme('system') mq.addEventListener('change', onChange) return () => mq.removeEventListener('change', onChange) }, [pref]) function setTheme(next: ThemePref) { if (next === 'system') window.localStorage.removeItem(THEME_STORAGE_KEY) else window.localStorage.setItem(THEME_STORAGE_KEY, next) setPref(next) } return { pref, resolved: resolveTheme(pref), setTheme } }