RAG_project_frontend / components /theme-toggle.tsx
anhkhoiphan's picture
deploy: 2026-06-04 13:41:54
f104cdd
Raw
History Blame Contribute Delete
1.77 kB
"use client";
import { useSyncExternalStore } from "react";
import { Moon, Sun } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
// The theme lives on <html class="dark"> (set pre-paint by the script in
// layout.tsx). Reading it via useSyncExternalStore keeps the toggle in sync
// without a setState-in-effect and without a hydration mismatch.
function subscribe(callback: () => void) {
window.addEventListener("themechange", callback);
window.addEventListener("storage", callback);
return () => {
window.removeEventListener("themechange", callback);
window.removeEventListener("storage", callback);
};
}
function getSnapshot() {
return document.documentElement.classList.contains("dark");
}
function getServerSnapshot() {
return false;
}
export function ThemeToggle({ className }: { className?: string }) {
const dark = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
function toggle() {
const next = !dark;
document.documentElement.classList.toggle("dark", next);
try {
localStorage.setItem("theme", next ? "dark" : "light");
} catch {
/* ignore storage errors (private mode, etc.) */
}
window.dispatchEvent(new Event("themechange"));
}
return (
<button
type="button"
onClick={toggle}
aria-label={dark ? "Chuyển sang giao diện sáng" : "Chuyển sang giao diện tối"}
className={cn(
"inline-flex size-9 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-2 hover:text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
className,
)}
>
{dark ? <Sun size={18} weight="bold" /> : <Moon size={18} weight="bold" />}
</button>
);
}