UNPKG

@lucasmiqueias/blip-tokens

Version:

Design token system inspired by BLiP's design language

185 lines (184 loc) 5.83 kB
/** * BLiP Design System - Theme Management * Enhanced theme manager with React hooks and TypeScript support */ import * as React from "react"; class ThemeManager { constructor() { Object.defineProperty(this, "currentTheme", { enumerable: true, configurable: true, writable: true, value: "auto" }); Object.defineProperty(this, "systemPreference", { enumerable: true, configurable: true, writable: true, value: "light" }); Object.defineProperty(this, "listeners", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "handleSystemPreferenceChange", { enumerable: true, configurable: true, writable: true, value: (e) => { this.systemPreference = e.matches ? "dark" : "light"; if (this.currentTheme === "auto") { this.applyTheme(); this.notifyListeners(); } } }); this.init(); } init() { // Detect system preference this.detectSystemPreference(); // Listen for system preference changes if (typeof window !== "undefined" && window.matchMedia) { const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); mediaQuery.addListener(this.handleSystemPreferenceChange.bind(this)); } // Load saved theme from localStorage this.loadSavedTheme(); // Apply initial theme this.applyTheme(); } detectSystemPreference() { if (typeof window !== "undefined" && window.matchMedia) { this.systemPreference = window.matchMedia("(prefers-color-scheme: dark)") .matches ? "dark" : "light"; } } loadSavedTheme() { if (typeof window !== "undefined" && window.localStorage) { const savedTheme = localStorage.getItem("blip-theme"); if (savedTheme && ["light", "dark", "auto"].includes(savedTheme)) { this.currentTheme = savedTheme; } } } saveTheme(theme) { if (typeof window !== "undefined" && window.localStorage) { localStorage.setItem("blip-theme", theme); } } applyTheme() { if (typeof document === "undefined") return; const root = document.documentElement; if (this.currentTheme === "auto") { // Remove explicit theme and let CSS media queries handle it root.removeAttribute("data-theme"); root.setAttribute("data-theme-mode", "auto"); } else { // Set explicit theme root.setAttribute("data-theme", this.currentTheme); root.removeAttribute("data-theme-mode"); } } notifyListeners() { this.listeners.forEach((listener) => listener(this.currentTheme)); } /** * Set the current theme */ setTheme(theme) { this.currentTheme = theme; this.saveTheme(theme); this.applyTheme(); this.notifyListeners(); } /** * Get the current theme setting */ getTheme() { return this.currentTheme; } /** * Get the effective theme (resolves 'auto' to actual theme) */ getEffectiveTheme() { if (this.currentTheme === "auto") { return this.systemPreference; } return this.currentTheme; } /** * Get the system preference */ getSystemPreference() { return this.systemPreference; } /** * Toggle between light and dark themes */ toggleTheme() { const effectiveTheme = this.getEffectiveTheme(); this.setTheme(effectiveTheme === "light" ? "dark" : "light"); } /** * Subscribe to theme changes */ subscribe(listener) { this.listeners.push(listener); // Return unsubscribe function return () => { const index = this.listeners.indexOf(listener); if (index > -1) { this.listeners.splice(index, 1); } }; } /** * Check if dark mode is currently active */ isDarkMode() { return this.getEffectiveTheme() === "dark"; } /** * Check if light mode is currently active */ isLightMode() { return this.getEffectiveTheme() === "light"; } } // Export singleton instance export const themeManager = new ThemeManager(); // React hook for theme management export function useTheme() { const [theme, setTheme] = React.useState(themeManager.getTheme()); const [effectiveTheme, setEffectiveTheme] = React.useState(themeManager.getEffectiveTheme()); React.useEffect(() => { const unsubscribe = themeManager.subscribe((newTheme) => { setTheme(newTheme); setEffectiveTheme(themeManager.getEffectiveTheme()); }); return unsubscribe; }, []); return { theme, effectiveTheme, setTheme: themeManager.setTheme.bind(themeManager), toggleTheme: themeManager.toggleTheme.bind(themeManager), isDarkMode: themeManager.isDarkMode.bind(themeManager), isLightMode: themeManager.isLightMode.bind(themeManager), systemPreference: themeManager.getSystemPreference(), }; } // Utility function to get CSS variable value export const getCSSVariable = (variable) => { if (typeof document === "undefined") return ""; return getComputedStyle(document.documentElement).getPropertyValue(variable); }; export default themeManager;