import { useEffect, useState } from "react"; /** * Possible user-facing color scheme preferences. */ export type ColorSchemePreference = "light" | "dark" | "system"; const MEDIA_QUERY = "(prefers-color-scheme: dark)"; /** * Resolves a persisted preference into an explicit light/dark value. * * @param pref - Stored preference value (`light`, `dark`, or `system`). * @returns Explicit light/dark scheme suitable for rendering. */ function resolveScheme(pref: ColorSchemePreference): "light" | "dark" { if (pref === "light" || pref === "dark") return pref; if ( typeof window === "undefined" || typeof window.matchMedia !== "function" ) { return "light"; } return window.matchMedia(MEDIA_QUERY).matches ? "dark" : "light"; } /** * React hook that returns the effective light/dark scheme, respecting system preferences. * * @param preference - User preference; defaults to following the OS setting. * @returns {'light' | 'dark'} Explicit scheme that should be used for rendering. */ export function useColorScheme( preference: ColorSchemePreference = "system", ): "light" | "dark" { const [scheme, setScheme] = useState<"light" | "dark">(() => resolveScheme(preference), ); useEffect(() => { if (preference === "light" || preference === "dark") { setScheme(preference); return; } if ( typeof window === "undefined" || typeof window.matchMedia !== "function" ) { setScheme("light"); return; } const media = window.matchMedia(MEDIA_QUERY); const update = (event: MediaQueryListEvent | MediaQueryList) => { setScheme(event.matches ? "dark" : "light"); }; update(media); if (typeof media.addEventListener === "function") { media.addEventListener("change", update); return () => media.removeEventListener("change", update); } media.addListener(update); return () => media.removeListener(update); }, [preference]); return scheme; }