feat: add persistent responsive color themes

This commit is contained in:
donghyeon-ka
2026-07-26 00:06:49 +09:00
parent bee5c158c6
commit 4cfbe5a29e
9 changed files with 372 additions and 51 deletions
+36
View File
@@ -0,0 +1,36 @@
import {
normalizeColorSchemePreference,
resolveColorScheme,
} from "../application/policies/color-scheme.js";
/**
* Applies the persisted public preference before React paints.
*
* @param {import("../application/ports/storage-port.js").StoragePort} storage
* @param {{
* documentElement?: HTMLElement,
* matchMedia?: (query: string) => MediaQueryList
* }} [browser]
*/
export function initializeColorScheme(storage, browser = {}) {
const documentElement = browser.documentElement ?? document.documentElement;
const matchMedia =
browser.matchMedia ??
(typeof window.matchMedia === "function"
? window.matchMedia.bind(window)
: () => /** @type {MediaQueryList} */ ({ matches: false }));
const stored = storage.read("COLOR_SCHEME");
const preference = normalizeColorSchemePreference(
stored.ok ? stored.value : undefined,
);
const resolved = resolveColorScheme(
preference,
matchMedia("(prefers-color-scheme: dark)").matches,
);
documentElement.dataset.theme = resolved;
documentElement.dataset.themePreference = preference;
documentElement.style.colorScheme = resolved;
return Object.freeze({ preference, resolved });
}
+3
View File
@@ -5,6 +5,7 @@ import { QueryClientProvider } from "@tanstack/react-query";
import { BootErrorShell } from "../presentation/boundaries/boot-error-shell.jsx";
import { AppRouter } from "../presentation/routes/app-router.jsx";
import { createRuntimeComposition } from "./create-runtime-composition.js";
import { initializeColorScheme } from "./initialize-color-scheme.js";
import { BootConfigError } from "./load-runtime-config.js";
import { ReleaseManifestError } from "./load-release-manifest.js";
import "../presentation/styles/theme.css";
@@ -20,6 +21,7 @@ const root = createRoot(rootElement);
async function boot() {
try {
const composition = await createRuntimeComposition();
initializeColorScheme(composition.ports.storage);
root.render(
<StrictMode>
<QueryClientProvider client={composition.ports.queryClient}>
@@ -27,6 +29,7 @@ async function boot() {
authSession={composition.ports.authSession}
basename={composition.config.build.routerBasePath}
buildId={composition.release.buildId}
storage={composition.ports.storage}
telemetry={composition.ports.telemetry}
/>
</QueryClientProvider>