This commit is contained in:
@@ -1,24 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
|
||||
const STORAGE_KEY = "theme-preference";
|
||||
const THEME_CHANGE_EVENT = "theme-preference-change";
|
||||
|
||||
function getSystemTheme(): Theme {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
function applyTheme(theme: Theme) {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
window.localStorage.setItem(STORAGE_KEY, theme);
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, theme);
|
||||
} catch {
|
||||
}
|
||||
window.dispatchEvent(new Event(THEME_CHANGE_EVENT));
|
||||
}
|
||||
|
||||
function getCurrentTheme(): Theme {
|
||||
if (typeof window === "undefined") return "light";
|
||||
|
||||
const currentTheme = document.documentElement.dataset.theme;
|
||||
if (currentTheme === "light" || currentTheme === "dark") {
|
||||
return currentTheme;
|
||||
}
|
||||
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
return getSystemTheme();
|
||||
}
|
||||
|
||||
function subscribeToTheme(callback: () => void) {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handleSystemThemeChange = () => {
|
||||
try {
|
||||
if (!window.localStorage.getItem(STORAGE_KEY)) {
|
||||
document.documentElement.dataset.theme = getSystemTheme();
|
||||
}
|
||||
} catch {
|
||||
document.documentElement.dataset.theme = getSystemTheme();
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
mediaQuery.addEventListener("change", handleSystemThemeChange);
|
||||
window.addEventListener("storage", callback);
|
||||
window.addEventListener(THEME_CHANGE_EVENT, callback);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeEventListener("change", handleSystemThemeChange);
|
||||
window.removeEventListener("storage", callback);
|
||||
window.removeEventListener(THEME_CHANGE_EVENT, callback);
|
||||
};
|
||||
}
|
||||
|
||||
export function ThemeToggle() {
|
||||
const theme = useSyncExternalStore(subscribeToTheme, getCurrentTheme, () => "light");
|
||||
|
||||
const toggleTheme = () => {
|
||||
const nextTheme = getCurrentTheme() === "dark" ? "light" : "dark";
|
||||
applyTheme(nextTheme);
|
||||
@@ -29,8 +69,9 @@ export function ThemeToggle() {
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
title="Toggle color theme"
|
||||
aria-label="Toggle color theme"
|
||||
className="theme-toggle inline-flex h-8 w-8 items-center justify-center rounded-full border border-transparent text-muted-strong transition-colors hover:border-line hover:text-ink"
|
||||
aria-label={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
|
||||
aria-pressed={theme === "dark"}
|
||||
className="theme-toggle inline-flex h-8 w-8 items-center justify-center rounded-full border border-transparent text-muted-strong transition-colors hover:border-line hover:bg-accent-soft hover:text-ink"
|
||||
>
|
||||
<span className="sr-only">Toggle color theme</span>
|
||||
<svg
|
||||
|
||||
Reference in New Issue
Block a user