mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-06-30 01:26:37 -04:00
clean up ThemeContext
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { createContext, useCallback, useEffect, useState, useRef } from "react";
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
import { useLocalStorage } from "react-use";
|
||||
import { darkModeQuery, themeStorageKey } from "../lib/config/themes";
|
||||
import type { Context, PropsWithChildren } from "react";
|
||||
@ -26,69 +26,64 @@ export const ThemeProvider = ({
|
||||
classNames: {
|
||||
[themeName: string]: string;
|
||||
};
|
||||
/** Optionally set `color-scheme` CSS property to change browser appearance. */
|
||||
enableColorScheme?: boolean;
|
||||
}>) => {
|
||||
// keep track of if/when the user has set their theme *here*:
|
||||
const [preferredTheme, setPreferredTheme] = useLocalStorage(themeStorageKey, null, { raw: true });
|
||||
// save the end result no matter how we got there (by preference or by system):
|
||||
const [resolvedTheme, setResolvedTheme] = useState("");
|
||||
// TODO: remove this and do related stuff more gracefully
|
||||
const validThemes = Object.keys(classNames);
|
||||
// get the theme names (light, dark) via passed-in classnames' keys
|
||||
const themeNames = Object.keys(classNames);
|
||||
|
||||
// updates the DOM and optionally saves the new theme to local storage
|
||||
const changeTheme = useCallback(
|
||||
(theme: string, updateStorage: boolean) => {
|
||||
(theme: string, updateStorage?: boolean) => {
|
||||
if (updateStorage) {
|
||||
setPreferredTheme(theme);
|
||||
}
|
||||
|
||||
// remove all theme classes first to start fresh
|
||||
const all = Object.values(classNames);
|
||||
const d = document.documentElement;
|
||||
d.classList.remove(...all);
|
||||
d.classList.add(classNames[theme]);
|
||||
document.documentElement.classList.remove(...all);
|
||||
document.documentElement.classList.add(classNames[theme]);
|
||||
},
|
||||
[classNames, setPreferredTheme]
|
||||
);
|
||||
|
||||
// memoize browser media query handler
|
||||
const handleMediaQuery = useCallback(
|
||||
(e?: MediaQueryList) => {
|
||||
(e: MediaQueryListEvent | MediaQueryList) => {
|
||||
// get the user's preferred theme via their OS/browser settings
|
||||
const media = e || window.matchMedia(darkModeQuery);
|
||||
const systemTheme = media.matches ? "dark" : "light";
|
||||
const systemTheme = e.matches ? "dark" : "light";
|
||||
|
||||
// keep track of the resolved theme whether or not we change it below
|
||||
setResolvedTheme(systemTheme);
|
||||
|
||||
// only actually change the theme if preference is unset (and *don't* save new theme to storage)
|
||||
if (!preferredTheme || !validThemes.includes(preferredTheme)) changeTheme(systemTheme, false);
|
||||
if (!preferredTheme || !themeNames.includes(preferredTheme)) {
|
||||
changeTheme(systemTheme, false);
|
||||
}
|
||||
},
|
||||
[changeTheme, preferredTheme, validThemes]
|
||||
[changeTheme, preferredTheme, themeNames]
|
||||
);
|
||||
// ref hack to avoid adding handleMediaQuery as a dependency
|
||||
const mediaListener = useRef(handleMediaQuery);
|
||||
mediaListener.current = handleMediaQuery;
|
||||
|
||||
// listen for changes in OS preference
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handler = (...args: any) => mediaListener.current(...args);
|
||||
const media = window.matchMedia(darkModeQuery);
|
||||
|
||||
media.addEventListener("change", handler);
|
||||
handler(media);
|
||||
media.addEventListener("change", handleMediaQuery);
|
||||
handleMediaQuery(media);
|
||||
|
||||
// clean up the event listener
|
||||
return () => {
|
||||
media.removeEventListener("change", handler);
|
||||
media.removeEventListener("change", handleMediaQuery);
|
||||
};
|
||||
}, []);
|
||||
}, [handleMediaQuery]);
|
||||
|
||||
// color-scheme handling (tells browser how to render built-in elements like forms, scrollbars, etc.)
|
||||
useEffect(() => {
|
||||
// only "light" and "dark" are valid here
|
||||
const colorScheme = ["light", "dark"].includes(preferredTheme) ? preferredTheme : resolvedTheme;
|
||||
|
||||
document.documentElement.style.setProperty("color-scheme", colorScheme);
|
||||
document.documentElement.style?.setProperty("color-scheme", colorScheme);
|
||||
}, [preferredTheme, resolvedTheme]);
|
||||
|
||||
return (
|
||||
@ -101,8 +96,8 @@ export const ThemeProvider = ({
|
||||
},
|
||||
[changeTheme]
|
||||
),
|
||||
preferredTheme: validThemes.includes(preferredTheme) ? preferredTheme : undefined,
|
||||
resolvedTheme: validThemes.includes(preferredTheme) ? preferredTheme : resolvedTheme,
|
||||
preferredTheme: themeNames.includes(preferredTheme) ? preferredTheme : undefined,
|
||||
resolvedTheme: themeNames.includes(preferredTheme) ? preferredTheme : resolvedTheme,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
Reference in New Issue
Block a user