A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import { useEffect, useState } from "react"; 2 3/** 4 * Possible user-facing color scheme preferences. 5 */ 6export type ColorSchemePreference = "light" | "dark" | "system"; 7 8const MEDIA_QUERY = "(prefers-color-scheme: dark)"; 9 10/** 11 * Resolves a persisted preference into an explicit light/dark value. 12 * 13 * @param pref - Stored preference value (`light`, `dark`, or `system`). 14 * @returns Explicit light/dark scheme suitable for rendering. 15 */ 16function resolveScheme(pref: ColorSchemePreference): "light" | "dark" { 17 if (pref === "light" || pref === "dark") return pref; 18 if ( 19 typeof window === "undefined" || 20 typeof window.matchMedia !== "function" 21 ) { 22 return "light"; 23 } 24 return window.matchMedia(MEDIA_QUERY).matches ? "dark" : "light"; 25} 26 27/** 28 * React hook that returns the effective light/dark scheme, respecting system preferences. 29 * 30 * @param preference - User preference; defaults to following the OS setting. 31 * @returns {'light' | 'dark'} Explicit scheme that should be used for rendering. 32 */ 33export function useColorScheme( 34 preference: ColorSchemePreference = "system", 35): "light" | "dark" { 36 const [scheme, setScheme] = useState<"light" | "dark">(() => 37 resolveScheme(preference), 38 ); 39 40 useEffect(() => { 41 if (preference === "light" || preference === "dark") { 42 setScheme(preference); 43 return; 44 } 45 if ( 46 typeof window === "undefined" || 47 typeof window.matchMedia !== "function" 48 ) { 49 setScheme("light"); 50 return; 51 } 52 const media = window.matchMedia(MEDIA_QUERY); 53 const update = (event: MediaQueryListEvent | MediaQueryList) => { 54 setScheme(event.matches ? "dark" : "light"); 55 }; 56 update(media); 57 if (typeof media.addEventListener === "function") { 58 media.addEventListener("change", update); 59 return () => media.removeEventListener("change", update); 60 } 61 media.addListener(update); 62 return () => media.removeListener(update); 63 }, [preference]); 64 65 return scheme; 66}