"use client";
import { useState, useSyncExternalStore } from "react";
import { Switcher } from "@unlocalhosted/metalui";
import { THEME_COOKIE, colorwayOf, type Theme } from "@/lib/theme";
const options = [
{ value: "light", label: "Bone" },
{ value: "system", label: "Auto" },
{ value: "dark", label: "Graphite" },
] satisfies { value: Theme; label: string }[];
function applyTheme(theme: Theme) {
const root = document.documentElement;
const colorway = colorwayOf(theme);
if (colorway) root.dataset.muColorway = colorway;
else delete root.dataset.muColorway;
document.cookie = `${THEME_COOKIE}=${theme}; path=/; max-age=31536000; samesite=lax`;
}
/** Bone / auto / graphite, as a compact switcher. The choice is stored in a cookie so the server renders it without a flash. */
export function ThemeToggle({ initial }: { initial: Theme }) {
const [theme, setTheme] = useState<Theme>(initial);
return (
<Switcher<Theme>
aria-label="Colorway"
size="compact"
className="w-fit"
value={theme}
onValueChange={(next) => {
setTheme(next);
applyTheme(next);
}}
options={options}
/>
);
}
function subscribe(onChange: () => void) {
const mq = window.matchMedia("(prefers-color-scheme: dark)");
const observer = new MutationObserver(onChange);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-mu-colorway"] });
mq.addEventListener("change", onChange);
return () => {
observer.disconnect();
mq.removeEventListener("change", onChange);
};
}
function isDark() {
const forced = document.documentElement.dataset.muColorway;
return forced ? forced === "graphite" : window.matchMedia("(prefers-color-scheme: dark)").matches;
}
/** Whether the page is currently in the graphite colorway, honoring the switcher. */
export function useDarkMode() {
return useSyncExternalStore(subscribe, isDark, () => false);
}