Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at ui-refactor 75 lines 1.9 kB view raw
1import { createContext, useContext, useEffect, useState } from "react"; 2 3const ThemeContext = createContext({ 4 theme: "system", 5 setTheme: () => null, 6}); 7 8export function ThemeProvider({ children }) { 9 const [theme, setTheme] = useState(() => { 10 return localStorage.getItem("theme") || "system"; 11 }); 12 13 useEffect(() => { 14 localStorage.setItem("theme", theme); 15 16 const root = window.document.documentElement; 17 root.classList.remove("light", "dark"); 18 19 delete root.dataset.theme; 20 21 if (theme === "system") { 22 const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") 23 .matches 24 ? "dark" 25 : "light"; 26 27 if (systemTheme === "light") { 28 root.dataset.theme = "light"; 29 } else { 30 root.dataset.theme = "dark"; 31 } 32 return; 33 } 34 35 if (theme === "light") { 36 root.dataset.theme = "light"; 37 } 38 }, [theme]); 39 40 useEffect(() => { 41 if (theme !== "system") return; 42 43 const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); 44 const handleChange = () => { 45 const root = window.document.documentElement; 46 if (mediaQuery.matches) { 47 delete root.dataset.theme; 48 } else { 49 root.dataset.theme = "light"; 50 } 51 }; 52 53 mediaQuery.addEventListener("change", handleChange); 54 return () => mediaQuery.removeEventListener("change", handleChange); 55 }, [theme]); 56 57 const value = { 58 theme, 59 setTheme: (newTheme) => { 60 setTheme(newTheme); 61 }, 62 }; 63 64 return ( 65 <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> 66 ); 67} 68 69// eslint-disable-next-line react-refresh/only-export-components 70export function useTheme() { 71 const context = useContext(ThemeContext); 72 if (context === undefined) 73 throw new Error("useTheme must be used within a ThemeProvider"); 74 return context; 75}