Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 8x 8x 8x 8x 265x 128x 128x 76x 76x 137x 137x 128x 128x 76x 1x 21x 21x 21x 21x 21x 21x 21x 21x 21x 23x 23x 1x 1x 1x 47x 47x 22x 47x 47x 1x | "use client";
import {Moon, Sun} from "lucide-react";
import {useEffect, useSyncExternalStore} from "react";
import {Button} from "@/components/ui/button/button";
type Theme = "light" | "dark";
const themeDatasetKey = "dashboardTheme";
const themeStorageKey = "dashboard-theme";
const themeChangeEvent = "dashboard-theme-change";
function isTheme(value: string | undefined | null): value is Theme {
return value === "light" || value === "dark";
}
function getDomTheme(): Theme | null {
const datasetTheme = document.documentElement.dataset[themeDatasetKey];
if (isTheme(datasetTheme)) return datasetTheme;
Iif (document.documentElement.classList.contains("dark")) return "dark";
return null;
}
function getBrowserTheme(): Theme {
const stored = window.localStorage.getItem(themeStorageKey);
if (isTheme(stored)) return stored;
const domTheme = getDomTheme();
if (domTheme) return domTheme;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function getServerTheme(): Theme {
return "light";
}
function subscribeToThemeChanges(onStoreChange: () => void) {
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
const synchronizeSystemTheme = () => {
if (!isTheme(window.localStorage.getItem(themeStorageKey))) {
// Update the DOM-backed snapshot before notifying React.
applyTheme(colorSchemeQuery.matches ? "dark" : "light");
}
onStoreChange();
};
window.addEventListener("storage", onStoreChange);
window.addEventListener(themeChangeEvent, onStoreChange);
colorSchemeQuery.addEventListener("change", synchronizeSystemTheme);
return () => {
window.removeEventListener("storage", onStoreChange);
window.removeEventListener(themeChangeEvent, onStoreChange);
colorSchemeQuery.removeEventListener("change", synchronizeSystemTheme);
};
}
function applyTheme(theme: Theme) {
document.documentElement.classList.toggle("dark", theme === "dark");
document.documentElement.dataset[themeDatasetKey] = theme;
}
function persistTheme(theme: Theme) {
applyTheme(theme);
window.localStorage.setItem(themeStorageKey, theme);
window.dispatchEvent(new Event(themeChangeEvent));
}
/** Persisted light/dark theme toggle used in the dashboard header. */
export function ThemeToggle() {
const theme = useSyncExternalStore(subscribeToThemeChanges, getBrowserTheme, getServerTheme);
// The layout script handles first paint; this keeps later browser changes in sync.
useEffect(() => {
applyTheme(getBrowserTheme());
}, [theme]);
const isDark = theme === "dark";
return (
<Button
aria-label={isDark ? "Use light theme" : "Use dark theme"}
onClick={() => persistTheme(isDark ? "light" : "dark")}
size="icon"
title={isDark ? "Use light theme" : "Use dark theme"}
variant="secondary"
>
{isDark ? <Sun aria-hidden="true" size={17}/> : <Moon aria-hidden="true" size={17}/>}
</Button>
);
}
|