UNPKG

@casoon/dragonfly

Version:

Modular, lightweight CSS framework and design system for modern web projects. Optimized for Astro JS, LightningCSS and Container Queries with @layer-based architecture and comprehensive accessibility.

225 lines (197 loc) 5.72 kB
/** * Theme Helper * * JavaScript helper functions for theme management. * Supports dynamic theme variant loading and data-theme switching. */ class ThemeHelper { constructor() { this.THEME_STORAGE_KEY = 'dragonfly-theme'; this.loadedVariants = new Set(); this.init(); } /** * Initialize the theme system */ init() { // Load saved theme on startup const savedTheme = this.getSavedTheme(); this.setTheme(savedTheme); // Listen for system color scheme changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (this.getSavedTheme() === 'auto') { this.dispatchThemeChangeEvent('auto'); } }); } /** * Get saved theme from localStorage * @returns {string} Theme name */ getSavedTheme() { try { return localStorage.getItem(this.THEME_STORAGE_KEY) || 'light'; } catch (error) { console.warn('Error reading theme from localStorage:', error); return 'light'; } } /** * Save theme to localStorage * @param {string} themeName - Theme name to save */ saveTheme(themeName) { try { localStorage.setItem(this.THEME_STORAGE_KEY, themeName); } catch (error) { console.warn('Error saving theme to localStorage:', error); } } /** * Set theme using data-theme attribute * @param {string} themeName - Theme name (light, dark, auto, or variant name) */ setTheme(themeName) { const root = document.documentElement; // Handle auto theme if (themeName === 'auto') { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; root.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); } else { root.setAttribute('data-theme', themeName); } this.saveTheme(themeName); this.dispatchThemeChangeEvent(themeName); } /** * Load a theme variant dynamically * @param {string} themeName - Name of the theme variant to load * @returns {Promise} Promise that resolves when theme is loaded */ async loadThemeVariant(themeName) { // Don't load if already loaded if (this.loadedVariants.has(themeName)) { this.setTheme(themeName); return Promise.resolve(); } return new Promise((resolve, reject) => { // Create link element for CSS const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = `./themes/variants/${themeName}.css`; link.id = `theme-variant-${themeName}`; // Handle load success link.onload = () => { this.loadedVariants.add(themeName); this.setTheme(themeName); resolve(); }; // Handle load error link.onerror = () => { console.error(`Failed to load theme variant: ${themeName}`); reject(new Error(`Theme variant '${themeName}' not found`)); }; // Add to document head document.head.appendChild(link); }); } /** * Unload a theme variant * @param {string} themeName - Name of the theme variant to unload */ unloadThemeVariant(themeName) { const link = document.getElementById(`theme-variant-${themeName}`); if (link) { link.remove(); this.loadedVariants.delete(themeName); } } /** * Get currently active theme * @returns {string} Current theme name */ getCurrentTheme() { return document.documentElement.getAttribute('data-theme') || 'light'; } /** * Check if a theme variant is loaded * @param {string} themeName - Theme name to check * @returns {boolean} True if loaded */ isVariantLoaded(themeName) { return this.loadedVariants.has(themeName); } /** * Get list of loaded theme variants * @returns {Array} Array of loaded theme names */ getLoadedVariants() { return Array.from(this.loadedVariants); } /** * Dispatch theme change event * @param {string} themeName - Theme name that was changed to */ dispatchThemeChangeEvent(themeName) { const event = new CustomEvent('theme-change', { detail: { theme: themeName, isDark: this.isDarkTheme(themeName), }, }); document.dispatchEvent(event); } /** * Check if theme is dark * @param {string} themeName - Theme name to check * @returns {boolean} True if dark theme */ isDarkTheme(themeName) { if (themeName === 'dark') return true; if (themeName === 'auto') { return window.matchMedia('(prefers-color-scheme: dark)').matches; } return false; } /** * Cycle through basic themes (light -> dark -> auto -> light) */ cycleTheme() { const current = this.getSavedTheme(); let next; switch (current) { case 'light': next = 'dark'; break; case 'dark': next = 'auto'; break; default: next = 'light'; break; } this.setTheme(next); return next; } } // Create global instance const themeHelper = new ThemeHelper(); // Export functions for global use window.themeHelper = themeHelper; // Export individual functions for convenience window.loadThemeVariant = (themeName) => themeHelper.loadThemeVariant(themeName); window.setTheme = (themeName) => themeHelper.setTheme(themeName); window.cycleTheme = () => themeHelper.cycleTheme(); // Export for module systems if (typeof module !== 'undefined' && module.exports) { module.exports = { themeHelper, loadThemeVariant: themeHelper.loadThemeVariant.bind(themeHelper), }; } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => themeHelper.init()); } else { themeHelper.init(); }