55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
export type ThemePref = "light" | "dark" | "system";
|
|
|
|
const KEY = "vantage-hq-theme";
|
|
|
|
/*
|
|
* globals.css has always defined the dark tokens under BOTH
|
|
* :root[data-theme="dark"] and :root[data-theme="light"], specifically so an
|
|
* in-page control can win over the OS preference in either direction. Nothing
|
|
* ever set the attribute. This is that missing half.
|
|
*
|
|
* "system" removes the attribute rather than writing a value, which hands the
|
|
* decision back to the prefers-color-scheme block.
|
|
*/
|
|
export function applyTheme(pref: ThemePref) {
|
|
const root = document.documentElement;
|
|
if (pref === "system") root.removeAttribute("data-theme");
|
|
else root.setAttribute("data-theme", pref);
|
|
}
|
|
|
|
export function readTheme(): ThemePref {
|
|
if (typeof localStorage === "undefined") return "system";
|
|
const v = localStorage.getItem(KEY);
|
|
return v === "light" || v === "dark" ? v : "system";
|
|
}
|
|
|
|
export function useTheme(): [ThemePref, (p: ThemePref) => void] {
|
|
// Starts at "system" on both server and first client render so hydration
|
|
// matches; the real value lands in the effect below. The inline script in
|
|
// app/layout.tsx has already painted the correct colours by then, so there
|
|
// is no flash only this control's own highlight settles a tick late.
|
|
const [pref, setPref] = useState<ThemePref>("system");
|
|
|
|
useEffect(() => setPref(readTheme()), []);
|
|
|
|
return [
|
|
pref,
|
|
(next: ThemePref) => {
|
|
setPref(next);
|
|
if (next === "system") localStorage.removeItem(KEY);
|
|
else localStorage.setItem(KEY, next);
|
|
applyTheme(next);
|
|
},
|
|
];
|
|
}
|
|
|
|
/*
|
|
* Runs before first paint, so a dark-preferring user never sees a white flash.
|
|
* Inlined as a string because it has to execute ahead of React.
|
|
*/
|
|
export const THEME_BOOT_SCRIPT = `try{var t=localStorage.getItem(${JSON.stringify(KEY)});if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}`;
|