From d67428b043a909035a99b63c0f0d934db0c558ce Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Wed, 4 May 2022 10:48:41 -0400 Subject: [PATCH] rename `resolvedTheme` -> `activeTheme` --- components/Captcha/Captcha.tsx | 4 +-- components/Comments/Comments.tsx | 4 +-- components/Layout/Layout.tsx | 4 +-- components/ThemeScript/ThemeScript.tsx | 4 +-- components/ThemeToggle/ThemeToggle.tsx | 16 +++++----- components/TweetEmbed/TweetEmbed.tsx | 4 +-- contexts/ThemeContext.tsx | 42 ++++++++++++-------------- lib/config/themes.ts | 3 -- 8 files changed, 38 insertions(+), 43 deletions(-) diff --git a/components/Captcha/Captcha.tsx b/components/Captcha/Captcha.tsx index 8dae73b2..da0013a7 100644 --- a/components/Captcha/Captcha.tsx +++ b/components/Captcha/Captcha.tsx @@ -22,7 +22,7 @@ export type CaptchaProps = { const Captcha = ({ size = "normal", theme, className, ...rest }: CaptchaProps) => { const hasMounted = useHasMounted(); - const { resolvedTheme } = useTheme(); + const { activeTheme } = useTheme(); return (
@@ -32,7 +32,7 @@ const Captcha = ({ size = "normal", theme, className, ...rest }: CaptchaProps) = reCaptchaCompat={false} tabIndex={0} size={size} - theme={theme || (resolvedTheme === "dark" ? "dark" : "light")} + theme={theme || (activeTheme === "dark" ? "dark" : "light")} {...rest} /> )} diff --git a/components/Comments/Comments.tsx b/components/Comments/Comments.tsx index 357bf303..b4c37805 100644 --- a/components/Comments/Comments.tsx +++ b/components/Comments/Comments.tsx @@ -18,7 +18,7 @@ export type CommentsProps = ComponentProps & { }; const Comments = ({ title, ...rest }: CommentsProps) => { - const { resolvedTheme } = useTheme(); + const { activeTheme } = useTheme(); // TODO: use custom `` spinner component during suspense return ( @@ -29,7 +29,7 @@ const Comments = ({ title, ...rest }: CommentsProps) => { mapping="specific" reactionsEnabled="1" emitMetadata="0" - theme={resolvedTheme === "dark" ? "dark" : "light"} + theme={activeTheme === "dark" ? "dark" : "light"} /> ); diff --git a/components/Layout/Layout.tsx b/components/Layout/Layout.tsx index 9e6f48c2..23cc46e0 100644 --- a/components/Layout/Layout.tsx +++ b/components/Layout/Layout.tsx @@ -33,13 +33,13 @@ export type LayoutProps = ComponentProps & { }; const Layout = ({ container = true, children, ...rest }: LayoutProps) => { - const { resolvedTheme } = useTheme(); + const { activeTheme } = useTheme(); return ( <> {/* dynamically set browser theme color to match the background color; default to light for SSR */} - + diff --git a/components/ThemeScript/ThemeScript.tsx b/components/ThemeScript/ThemeScript.tsx index 0eddbf71..a38c6236 100644 --- a/components/ThemeScript/ThemeScript.tsx +++ b/components/ThemeScript/ThemeScript.tsx @@ -1,13 +1,13 @@ import { useMemo } from "react"; import { minify } from "uglify-js"; import { clientScript } from "./client"; -import { darkModeQuery, themeStorageKey, themeClassNames } from "../../lib/config/themes"; +import { themeClassNames, themeStorageKey } from "../../lib/config/themes"; const ThemeScript = () => { const minified = useMemo(() => { // since the client function will end up being injected as a plain dumb string, we need to set dynamic values here: const functionString = String(clientScript) - .replace('"__MEDIA_QUERY__"', `"${darkModeQuery}"`) + .replace('"__MEDIA_QUERY__"', `"(prefers-color-scheme: dark)"`) .replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`) .replace('"__CLASS_NAMES__"', JSON.stringify(themeClassNames)); diff --git a/components/ThemeToggle/ThemeToggle.tsx b/components/ThemeToggle/ThemeToggle.tsx index 138e9590..57576a05 100644 --- a/components/ThemeToggle/ThemeToggle.tsx +++ b/components/ThemeToggle/ThemeToggle.tsx @@ -1,4 +1,4 @@ -import { useEffect, memo } from "react"; +import { useEffect, useId, memo } from "react"; import { useMedia } from "react-use"; import { useSpring, animated, Globals } from "@react-spring/web"; import { useTheme } from "../../hooks/use-theme"; @@ -19,17 +19,17 @@ const Button = styled("button", { }); export type ThemeToggleProps = { - id?: string; className?: string; }; -const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => { +const ThemeToggle = ({ className }: ThemeToggleProps) => { const hasMounted = useHasMounted(); + const { activeTheme, setTheme } = useTheme(); const prefersReducedMotion = useMedia("(prefers-reduced-motion: reduce)", false); - const { resolvedTheme, setTheme } = useTheme(); + const maskId = useId(); // SSR-safe ID to cross-reference areas of the SVG - // default to light since `resolvedTheme` might be undefined - const safeTheme = resolvedTheme === "dark" ? "dark" : "light"; + // default to light since `activeTheme` might be undefined + const safeTheme = activeTheme === "dark" ? "dark" : "light"; // accessibility: skip animation if user prefers reduced motion useEffect(() => { @@ -120,7 +120,7 @@ const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => { }} className={className} > - + { cx="12" cy="12" fill="currentColor" - mask={`url(#moon-mask-${id})`} + mask={`url(#mask-${maskId})`} // @ts-ignore style={centerCircleProps} /> diff --git a/components/TweetEmbed/TweetEmbed.tsx b/components/TweetEmbed/TweetEmbed.tsx index 6ffcc9c3..535f06fd 100644 --- a/components/TweetEmbed/TweetEmbed.tsx +++ b/components/TweetEmbed/TweetEmbed.tsx @@ -8,7 +8,7 @@ export type TweetEmbedProps = { }; const TweetEmbed = ({ id, options }: TweetEmbedProps) => { - const { resolvedTheme } = useTheme(); + const { activeTheme } = useTheme(); return ( { options={{ dnt: true, align: "center", - theme: resolvedTheme === "dark" ? "dark" : "light", + theme: activeTheme === "dark" ? "dark" : "light", ...options, }} /> diff --git a/contexts/ThemeContext.tsx b/contexts/ThemeContext.tsx index 20f2711f..493eed1d 100644 --- a/contexts/ThemeContext.tsx +++ b/contexts/ThemeContext.tsx @@ -1,20 +1,16 @@ import { createContext, useCallback, useEffect, useState } from "react"; import { useLocalStorage, useMedia } from "react-use"; -import { darkModeQuery, themeStorageKey } from "../lib/config/themes"; +import { themeStorageKey } from "../lib/config/themes"; import type { Context, PropsWithChildren } from "react"; export const ThemeContext: Context<{ - /** Update the theme manually. */ - setTheme?: (theme: string) => void; - /** The user's website theme setting ("light" or "dark", or undefined if unset). */ - preferredTheme?: string; /** - * If the theme setting is undefined, this returns whether the system preference resolved to "light" or "dark". If the - * preference is set, the value is identical to `preferredTheme`. - * - * Note to self: you probably want this. + * If the user's theme preference is unset, this returns whether the system preference resolved to "light" or "dark". + * If the user's theme preference is set, the preference is returned instead, regardless of their system's theme. */ - resolvedTheme?: string; + activeTheme?: "light" | "dark"; + /** Update the theme manually and save to local storage. */ + setTheme?: (theme: string) => void; }> = createContext({}); // provider used once in _app.tsx to wrap entire app @@ -27,12 +23,13 @@ export const ThemeProvider = ({ [themeName: string]: string; }; }>) => { - // keep track of if/when the user has set their theme *here*: + // keep track of if/when the user has set their theme *on this site* 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(""); + // keep track of changes to the user's OS/browser dark mode setting + const [systemTheme, setSystemTheme] = useState(""); // hook into system `prefers-dark-mode` setting - const isSystemDark = useMedia(darkModeQuery, false); + // https://web.dev/prefers-color-scheme/#the-prefers-color-scheme-media-query + const isSystemDark = useMedia("(prefers-color-scheme: dark)", false); // get the theme names (light, dark) via passed-in classnames' keys const themeNames = Object.keys(classNames); @@ -54,28 +51,31 @@ export const ThemeProvider = ({ // listen for changes in OS preference useEffect(() => { - const systemTheme = isSystemDark ? "dark" : "light"; + // translate boolean to theme string + const systemResolved = isSystemDark ? "dark" : "light"; - // keep track of the resolved theme whether or not we change it below - setResolvedTheme(systemTheme); + // keep track of the system theme whether or not we override it manually + setSystemTheme(systemResolved); // only actually change the theme if preference is unset (and *don't* save new theme to storage) if (!preferredTheme || !themeNames.includes(preferredTheme)) { - changeTheme(systemTheme, false); + changeTheme(systemResolved, false); } }, [changeTheme, themeNames, preferredTheme, isSystemDark]); // 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; + // https://web.dev/color-scheme/#the-color-scheme-css-property + const colorScheme = ["light", "dark"].includes(preferredTheme) ? preferredTheme : systemTheme; document.documentElement.style?.setProperty("color-scheme", colorScheme); - }, [preferredTheme, resolvedTheme]); + }, [preferredTheme, systemTheme]); return ( { // force save to local storage @@ -83,8 +83,6 @@ export const ThemeProvider = ({ }, [changeTheme] ), - preferredTheme: themeNames.includes(preferredTheme) ? preferredTheme : undefined, - resolvedTheme: themeNames.includes(preferredTheme) ? preferredTheme : resolvedTheme, }} > {children} diff --git a/lib/config/themes.ts b/lib/config/themes.ts index 265e3f01..2f6ff238 100644 --- a/lib/config/themes.ts +++ b/lib/config/themes.ts @@ -12,8 +12,5 @@ export const themeColors = { dark: darkTheme.colors.backgroundOuter?.value, }; -// https://web.dev/prefers-color-scheme/#the-prefers-color-scheme-media-query -export const darkModeQuery = "(prefers-color-scheme: dark)"; - // local storage key export const themeStorageKey = "preferred-theme";