Margin is an open annotation layer for the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
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}