mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-06-30 07:16:37 -04:00
rename resolvedTheme
-> activeTheme
This commit is contained in:
@ -22,7 +22,7 @@ export type CaptchaProps = {
|
|||||||
|
|
||||||
const Captcha = ({ size = "normal", theme, className, ...rest }: CaptchaProps) => {
|
const Captcha = ({ size = "normal", theme, className, ...rest }: CaptchaProps) => {
|
||||||
const hasMounted = useHasMounted();
|
const hasMounted = useHasMounted();
|
||||||
const { resolvedTheme } = useTheme();
|
const { activeTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@ -32,7 +32,7 @@ const Captcha = ({ size = "normal", theme, className, ...rest }: CaptchaProps) =
|
|||||||
reCaptchaCompat={false}
|
reCaptchaCompat={false}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
size={size}
|
size={size}
|
||||||
theme={theme || (resolvedTheme === "dark" ? "dark" : "light")}
|
theme={theme || (activeTheme === "dark" ? "dark" : "light")}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -18,7 +18,7 @@ export type CommentsProps = ComponentProps<typeof Wrapper> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Comments = ({ title, ...rest }: CommentsProps) => {
|
const Comments = ({ title, ...rest }: CommentsProps) => {
|
||||||
const { resolvedTheme } = useTheme();
|
const { activeTheme } = useTheme();
|
||||||
|
|
||||||
// TODO: use custom `<Loading />` spinner component during suspense
|
// TODO: use custom `<Loading />` spinner component during suspense
|
||||||
return (
|
return (
|
||||||
@ -29,7 +29,7 @@ const Comments = ({ title, ...rest }: CommentsProps) => {
|
|||||||
mapping="specific"
|
mapping="specific"
|
||||||
reactionsEnabled="1"
|
reactionsEnabled="1"
|
||||||
emitMetadata="0"
|
emitMetadata="0"
|
||||||
theme={resolvedTheme === "dark" ? "dark" : "light"}
|
theme={activeTheme === "dark" ? "dark" : "light"}
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -33,13 +33,13 @@ export type LayoutProps = ComponentProps<typeof Flex> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Layout = ({ container = true, children, ...rest }: LayoutProps) => {
|
const Layout = ({ container = true, children, ...rest }: LayoutProps) => {
|
||||||
const { resolvedTheme } = useTheme();
|
const { activeTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
{/* dynamically set browser theme color to match the background color; default to light for SSR */}
|
{/* dynamically set browser theme color to match the background color; default to light for SSR */}
|
||||||
<meta name="theme-color" content={themeColors[resolvedTheme === "dark" ? "dark" : "light"]} />
|
<meta name="theme-color" content={themeColors[activeTheme === "dark" ? "dark" : "light"]} />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<Flex {...rest}>
|
<Flex {...rest}>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { minify } from "uglify-js";
|
import { minify } from "uglify-js";
|
||||||
import { clientScript } from "./client";
|
import { clientScript } from "./client";
|
||||||
import { darkModeQuery, themeStorageKey, themeClassNames } from "../../lib/config/themes";
|
import { themeClassNames, themeStorageKey } from "../../lib/config/themes";
|
||||||
|
|
||||||
const ThemeScript = () => {
|
const ThemeScript = () => {
|
||||||
const minified = useMemo(() => {
|
const minified = useMemo(() => {
|
||||||
// since the client function will end up being injected as a plain dumb string, we need to set dynamic values here:
|
// 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)
|
const functionString = String(clientScript)
|
||||||
.replace('"__MEDIA_QUERY__"', `"${darkModeQuery}"`)
|
.replace('"__MEDIA_QUERY__"', `"(prefers-color-scheme: dark)"`)
|
||||||
.replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`)
|
.replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`)
|
||||||
.replace('"__CLASS_NAMES__"', JSON.stringify(themeClassNames));
|
.replace('"__CLASS_NAMES__"', JSON.stringify(themeClassNames));
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, memo } from "react";
|
import { useEffect, useId, memo } from "react";
|
||||||
import { useMedia } from "react-use";
|
import { useMedia } from "react-use";
|
||||||
import { useSpring, animated, Globals } from "@react-spring/web";
|
import { useSpring, animated, Globals } from "@react-spring/web";
|
||||||
import { useTheme } from "../../hooks/use-theme";
|
import { useTheme } from "../../hooks/use-theme";
|
||||||
@ -19,17 +19,17 @@ const Button = styled("button", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type ThemeToggleProps = {
|
export type ThemeToggleProps = {
|
||||||
id?: string;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => {
|
const ThemeToggle = ({ className }: ThemeToggleProps) => {
|
||||||
const hasMounted = useHasMounted();
|
const hasMounted = useHasMounted();
|
||||||
|
const { activeTheme, setTheme } = useTheme();
|
||||||
const prefersReducedMotion = useMedia("(prefers-reduced-motion: reduce)", false);
|
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
|
// default to light since `activeTheme` might be undefined
|
||||||
const safeTheme = resolvedTheme === "dark" ? "dark" : "light";
|
const safeTheme = activeTheme === "dark" ? "dark" : "light";
|
||||||
|
|
||||||
// accessibility: skip animation if user prefers reduced motion
|
// accessibility: skip animation if user prefers reduced motion
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -120,7 +120,7 @@ const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => {
|
|||||||
}}
|
}}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
<mask id={`moon-mask-${id}`}>
|
<mask id={`mask-${maskId}`}>
|
||||||
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
<rect x="0" y="0" width="100%" height="100%" fill="white" />
|
||||||
<animated.circle
|
<animated.circle
|
||||||
r="9"
|
r="9"
|
||||||
@ -135,7 +135,7 @@ const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => {
|
|||||||
cx="12"
|
cx="12"
|
||||||
cy="12"
|
cy="12"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
mask={`url(#moon-mask-${id})`}
|
mask={`url(#mask-${maskId})`}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
style={centerCircleProps}
|
style={centerCircleProps}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ export type TweetEmbedProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TweetEmbed = ({ id, options }: TweetEmbedProps) => {
|
const TweetEmbed = ({ id, options }: TweetEmbedProps) => {
|
||||||
const { resolvedTheme } = useTheme();
|
const { activeTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TwitterTweetEmbed
|
<TwitterTweetEmbed
|
||||||
@ -16,7 +16,7 @@ const TweetEmbed = ({ id, options }: TweetEmbedProps) => {
|
|||||||
options={{
|
options={{
|
||||||
dnt: true,
|
dnt: true,
|
||||||
align: "center",
|
align: "center",
|
||||||
theme: resolvedTheme === "dark" ? "dark" : "light",
|
theme: activeTheme === "dark" ? "dark" : "light",
|
||||||
...options,
|
...options,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import { createContext, useCallback, useEffect, useState } from "react";
|
import { createContext, useCallback, useEffect, useState } from "react";
|
||||||
import { useLocalStorage, useMedia } from "react-use";
|
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";
|
import type { Context, PropsWithChildren } from "react";
|
||||||
|
|
||||||
export const ThemeContext: Context<{
|
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
|
* If the user's theme preference is unset, this returns whether the system preference resolved to "light" or "dark".
|
||||||
* preference is set, the value is identical to `preferredTheme`.
|
* If the user's theme preference is set, the preference is returned instead, regardless of their system's theme.
|
||||||
*
|
|
||||||
* Note to self: you probably want this.
|
|
||||||
*/
|
*/
|
||||||
resolvedTheme?: string;
|
activeTheme?: "light" | "dark";
|
||||||
|
/** Update the theme manually and save to local storage. */
|
||||||
|
setTheme?: (theme: string) => void;
|
||||||
}> = createContext({});
|
}> = createContext({});
|
||||||
|
|
||||||
// provider used once in _app.tsx to wrap entire app
|
// provider used once in _app.tsx to wrap entire app
|
||||||
@ -27,12 +23,13 @@ export const ThemeProvider = ({
|
|||||||
[themeName: string]: string;
|
[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 });
|
const [preferredTheme, setPreferredTheme] = useLocalStorage(themeStorageKey, null, { raw: true });
|
||||||
// save the end result no matter how we got there (by preference or by system):
|
// keep track of changes to the user's OS/browser dark mode setting
|
||||||
const [resolvedTheme, setResolvedTheme] = useState("");
|
const [systemTheme, setSystemTheme] = useState("");
|
||||||
// hook into system `prefers-dark-mode` setting
|
// 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
|
// get the theme names (light, dark) via passed-in classnames' keys
|
||||||
const themeNames = Object.keys(classNames);
|
const themeNames = Object.keys(classNames);
|
||||||
@ -54,28 +51,31 @@ export const ThemeProvider = ({
|
|||||||
|
|
||||||
// listen for changes in OS preference
|
// listen for changes in OS preference
|
||||||
useEffect(() => {
|
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
|
// keep track of the system theme whether or not we override it manually
|
||||||
setResolvedTheme(systemTheme);
|
setSystemTheme(systemResolved);
|
||||||
|
|
||||||
// only actually change the theme if preference is unset (and *don't* save new theme to storage)
|
// only actually change the theme if preference is unset (and *don't* save new theme to storage)
|
||||||
if (!preferredTheme || !themeNames.includes(preferredTheme)) {
|
if (!preferredTheme || !themeNames.includes(preferredTheme)) {
|
||||||
changeTheme(systemTheme, false);
|
changeTheme(systemResolved, false);
|
||||||
}
|
}
|
||||||
}, [changeTheme, themeNames, preferredTheme, isSystemDark]);
|
}, [changeTheme, themeNames, preferredTheme, isSystemDark]);
|
||||||
|
|
||||||
// color-scheme handling (tells browser how to render built-in elements like forms, scrollbars, etc.)
|
// color-scheme handling (tells browser how to render built-in elements like forms, scrollbars, etc.)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// only "light" and "dark" are valid here
|
// 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);
|
document.documentElement.style?.setProperty("color-scheme", colorScheme);
|
||||||
}, [preferredTheme, resolvedTheme]);
|
}, [preferredTheme, systemTheme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider
|
<ThemeContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
activeTheme: themeNames.includes(preferredTheme) ? preferredTheme : systemTheme,
|
||||||
setTheme: useCallback(
|
setTheme: useCallback(
|
||||||
(theme: string) => {
|
(theme: string) => {
|
||||||
// force save to local storage
|
// force save to local storage
|
||||||
@ -83,8 +83,6 @@ export const ThemeProvider = ({
|
|||||||
},
|
},
|
||||||
[changeTheme]
|
[changeTheme]
|
||||||
),
|
),
|
||||||
preferredTheme: themeNames.includes(preferredTheme) ? preferredTheme : undefined,
|
|
||||||
resolvedTheme: themeNames.includes(preferredTheme) ? preferredTheme : resolvedTheme,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -12,8 +12,5 @@ export const themeColors = {
|
|||||||
dark: darkTheme.colors.backgroundOuter?.value,
|
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
|
// local storage key
|
||||||
export const themeStorageKey = "preferred-theme";
|
export const themeStorageKey = "preferred-theme";
|
||||||
|
Reference in New Issue
Block a user