| import { useState, useEffect } from "react"; |
|
|
| |
| |
| |
| |
|
|
| type ThemeListener = (isDark: boolean, primaryColor: string) => void; |
| const listeners = new Set<ThemeListener>(); |
| 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; |
| } |
|
|