import { useState, useEffect } from "react"; /** * Shared singleton observer for data-theme + --primary-color. * Only one MutationObserver runs regardless of how many components subscribe. */ type ThemeListener = (isDark: boolean, primaryColor: string) => void; const listeners = new Set(); let observer: MutationObserver | null = null; let cachedDark = document.documentElement.getAttribute("data-theme") === "dark"; let cachedColor = getComputedStyle(document.documentElement) .getPropertyValue("--primary-color") .trim() || "#4e79a7"; function notify() { const dark = document.documentElement.getAttribute("data-theme") === "dark"; const color = getComputedStyle(document.documentElement) .getPropertyValue("--primary-color") .trim() || "#4e79a7"; const changed = dark !== cachedDark || color !== cachedColor; cachedDark = dark; cachedColor = color; if (changed) { for (const fn of listeners) fn(dark, color); } } function subscribe(fn: ThemeListener): () => void { listeners.add(fn); if (!observer) { observer = new MutationObserver(notify); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "style"], }); } return () => { listeners.delete(fn); if (listeners.size === 0 && observer) { observer.disconnect(); observer = null; } }; } export function useTheme(): { isDark: boolean; primaryColor: string } { const [isDark, setIsDark] = useState(cachedDark); const [primaryColor, setPrimaryColor] = useState(cachedColor); useEffect(() => { return subscribe((dark, color) => { setIsDark(dark); setPrimaryColor(color); }); }, []); return { isDark, primaryColor }; } export function useIsDark(): boolean { const { isDark } = useTheme(); return isDark; }