detatch theme provider from stitches logic

This commit is contained in:
2024-02-15 10:48:45 -05:00
parent b5f0f56702
commit 6f5c066525
11 changed files with 41 additions and 144 deletions
+1 -13
View File
@@ -1,9 +1,7 @@
import Head from "next/head";
import Header from "../Header";
import Footer from "../Footer";
import { SkipToContentLink, SkipToContentTarget } from "../SkipToContent";
import useTheme from "../../hooks/useTheme";
import { styled, theme, darkTheme } from "../../lib/styles/stitches.config";
import { styled, theme } from "../../lib/styles/stitches.config";
import type { ComponentPropsWithoutRef } from "react";
const Flex = styled("div", {
@@ -39,18 +37,8 @@ export type LayoutProps = ComponentPropsWithoutRef<typeof Flex> & {
};
const Layout = ({ container = true, children, ...rest }: LayoutProps) => {
const { activeTheme } = useTheme();
return (
<>
<Head>
<meta
// dynamically set browser theme color to match the background color; default to light for SSR
name="theme-color"
content={(activeTheme === "dark" ? darkTheme : theme)?.colors?.backgroundOuter?.value}
/>
</Head>
<SkipToContentLink />
<Flex {...rest}>
-72
View File
@@ -1,72 +0,0 @@
import { memo } from "react";
import { minify } from "uglify-js";
import type { MinifyOutput } from "uglify-js";
import { restoreTheme as clientFn } from "./client.js";
export type ThemeScriptProps = {
themeClassNames: {
[themeName: string]: string;
};
themeStorageKey: string;
};
// eslint-disable-next-line react/display-name
const ThemeScript = memo<ThemeScriptProps>(({ themeClassNames, themeStorageKey }) => {
const minified = (() => {
// since the client function will end up being injected as a static hard-coded string, we need to determine all of
// the dynamic values within it *before* generating the final script.
const source = String(clientFn)
.replaceAll("__MEDIA_QUERY__", "(prefers-color-scheme: dark)")
.replaceAll("__STORAGE_KEY__", themeStorageKey)
.replaceAll("__CLASS_NAMES__", Object.values(themeClassNames).join('","'));
// turn the raw function into an iife
const unminified = `(${source})()`;
// minify the final code. this approach is a bit janky but is ONLY used at build time, so there's essentially no
// risk of breaking the entire site and/or accidentally bundling uglify-js clientside (bad).
let minified: MinifyOutput | undefined;
try {
minified = minify(unminified, {
toplevel: true,
compress: {
negate_iife: false,
},
parse: {
bare_returns: true,
},
});
} catch (error) {
// fail somewhat silenty by returning the unminified version
console.warn("Failed to minify inline theme script:", error);
return unminified;
}
// same as the catch block above, but in some cases (not really sure when), minify() doesn't throw an actual error
// and instead just returns undefined and an "error" string, so we need to check for both.
if (!minified || minified.error) {
console.warn("Failed to minify inline theme script. uglify-js output:", minified.error);
return unminified;
}
return minified.code;
})();
// the script tag injected manually into `<head>` in _document.tsx to prevent FARTing:
// https://css-tricks.com/flash-of-inaccurate-color-theme-fart/
// even though it's the proper method, using next/script with `strategy="beforeInteractive"` still causes flash of
// white on load. injecting a normal script tag lets us prioritize setting the `<html>` class even more urgently.
// TODO: using next/script *might* be possible after https://github.com/vercel/next.js/pull/36364 is merged.
return (
<script
id="restore-theme"
dangerouslySetInnerHTML={{
// make it an IIFE:
__html: `(function(){${minified}})()`,
}}
/>
);
});
export default ThemeScript;
-31
View File
@@ -1,31 +0,0 @@
// @ts-check
// this function is converted to a string verbatim, substitutions are made to insert dynamic values, minified, and then
// finally exported as an inline `<script>` tag in ThemeScript.tsx for _document.tsx to use.
export const restoreTheme = () => {
// `try/catch` in case I messed something up here bigly... (will default to light theme)
try {
// help minifier minify
const htmlRoot = document.documentElement;
// the list of <html>'s current class(es)...
// eslint-disable-next-line prefer-destructuring
const classList = htmlRoot.classList;
// map of themes -> classnames ([0]=light, [1]=dark)
const classNames = ["__CLASS_NAMES__"];
// user's saved preference
const pref = typeof Storage !== "undefined" ? window.localStorage.getItem("__STORAGE_KEY__") : null;
// restore the local storage preference if it's set, otherwise test CSS media query for browser dark mode preference
// https://stackoverflow.com/a/57795495/1438024
const newTheme = (pref && pref === "dark") ?? window.matchMedia("__MEDIA_QUERY__").matches ? 1 : 0;
// remove both `classNames` to start fresh...
classList.remove(...classNames);
// ...and then FINALLY set the root class
classList.add(classNames[newTheme] || "");
// set "color-scheme" inline css property
htmlRoot.style.colorScheme = newTheme === 1 ? "dark" : "light";
} catch (error) {} // eslint-disable-line no-empty
};
-2
View File
@@ -1,2 +0,0 @@
export * from "./ThemeScript";
export { default } from "./ThemeScript";