refactor: follow system color scheme via matchMedia listener
This commit is contained in:
@@ -1,62 +1,20 @@
|
|||||||
type Theme = "light" | "dark";
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
const STORAGE_KEY = "theme";
|
const colorSchemeQuery = window.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
|
||||||
|
|
||||||
const getLocalStorage = (): Storage | null => {
|
|
||||||
try {
|
|
||||||
return window.localStorage;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPreferredTheme = (): Theme => {
|
const getPreferredTheme = (): Theme => {
|
||||||
const prefersDark = window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false;
|
const prefersDark = colorSchemeQuery?.matches ?? false;
|
||||||
return prefersDark ? "dark" : "light";
|
return prefersDark ? "dark" : "light";
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeTheme = (raw: string | null): Theme | null => {
|
|
||||||
if (raw === "light" || raw === "dark") return raw;
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSavedTheme = (): Theme => {
|
|
||||||
const storage = getLocalStorage();
|
|
||||||
const fromStorage = normalizeTheme(storage?.getItem(STORAGE_KEY) ?? null);
|
|
||||||
if (fromStorage) return fromStorage;
|
|
||||||
|
|
||||||
return getPreferredTheme();
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyTheme = (theme: Theme): void => {
|
const applyTheme = (theme: Theme): void => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
document.documentElement.style.colorScheme = theme;
|
document.documentElement.style.colorScheme = theme;
|
||||||
const storage = getLocalStorage();
|
|
||||||
try {
|
|
||||||
storage?.setItem(STORAGE_KEY, theme);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const cycleTheme = (): void => {
|
|
||||||
const current = getSavedTheme();
|
|
||||||
const next: Theme = current === "light" ? "dark" : "light";
|
|
||||||
applyTheme(next);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const initTheme = (): void => {
|
const initTheme = (): void => {
|
||||||
const saved = getSavedTheme();
|
applyTheme(getPreferredTheme());
|
||||||
applyTheme(saved);
|
colorSchemeQuery?.addEventListener("change", () => applyTheme(getPreferredTheme()));
|
||||||
|
|
||||||
// delegated click handler on theme buttons
|
|
||||||
document.addEventListener("click", (e) => {
|
|
||||||
const target = e.target;
|
|
||||||
if (!(target instanceof Element)) return;
|
|
||||||
const btn = target.closest("#theme-toggle, #footer-theme-toggle");
|
|
||||||
if (!(btn instanceof HTMLButtonElement)) return;
|
|
||||||
cycleTheme();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|||||||
Reference in New Issue
Block a user