import { ref, readonly, type Ref } from 'vue' /** * Définition d'une ambiance ("mood") / palette. * Chaque projet fournit SA propre liste (ses couleurs = son identité). */ export interface MoodDef { id: string label?: string description?: string icon?: string color?: string isDark: boolean /** * Variables CSS à poser sur quand ce mood est actif. * Optionnel — pour les projets qui injectent les variables en JS * (style librodrome) plutôt que via des classes .mood- en CSS * (style libreDecision). Les deux approches sont supportées. */ vars?: Record } export interface UseMoodOptions { /** clé localStorage (défaut: 'yvv_mood') */ storageKey?: string /** id du mood par défaut (défaut: premier de la liste) */ defaultId?: string /** synchronise useColorMode().preference depuis isDark (défaut: true) */ syncColorMode?: boolean /** bascule aussi les classes .palette-light / .palette-dark (style librodrome) (défaut: false) */ lightDarkClasses?: boolean /** * Classe unique à basculer sur selon mood.isDark (ex. 'dark' pour * les variants dark: d'UnoCSS/Tailwind — style GrateWizard). Défaut: aucune. */ darkClass?: string } export interface SetMoodOptions { /** écrit le choix en localStorage (défaut: true). false = application * éphémère (ex. thème imposé en mode embed, suivi de prefers-color-scheme) */ persist?: boolean } const currentMood: Ref = ref('') /** * Mécanisme moods partagé (extrait de libreDecision, généralisé). * Applique sur : classe .mood-, variables CSS éventuelles, * synchro color-mode, persistance localStorage. SSR-safe. * * @example * const moods: MoodDef[] = [ * { id: 'peps', label: 'Peps', icon: 'i-lucide-sun', color: '#d44a10', isDark: false }, * { id: 'grave', label: 'Grave', icon: 'i-lucide-shield', color: '#d8a030', isDark: true }, * ] * const { moods: list, currentMood, setMood, initMood } = useMood(moods) */ export function useMood(moods: MoodDef[], options: UseMoodOptions = {}) { const { storageKey = 'yvv_mood', defaultId = moods[0]?.id ?? '', syncColorMode = true, lightDarkClasses = false, darkClass, } = options function applyMood(moodId: string, { persist = true }: SetMoodOptions = {}) { if (import.meta.server) return const mood = moods.find(m => m.id === moodId) if (!mood) return const html = document.documentElement // classe .mood- moods.forEach(m => html.classList.remove(`mood-${m.id}`)) html.classList.add(`mood-${moodId}`) // variables CSS injectées (si le projet en fournit) if (mood.vars) { for (const [k, v] of Object.entries(mood.vars)) { html.style.setProperty(k, v) } } // classe dark unique (compat variants dark: UnoCSS/Tailwind) if (darkClass) { html.classList.toggle(darkClass, mood.isDark) } // classes palette-light / palette-dark (compat style librodrome) if (lightDarkClasses) { html.classList.toggle('palette-light', !mood.isDark) html.classList.toggle('palette-dark', mood.isDark) html.style.setProperty('color-scheme', mood.isDark ? 'dark' : 'light') } // synchro color-mode Nuxt (si dispo et demandé) if (syncColorMode) { try { // @ts-expect-error auto-import Nuxt (résolu chez le consommateur) const colorMode = useColorMode() colorMode.preference = mood.isDark ? 'dark' : 'light' } catch { /* @nuxtjs/color-mode absent : on ignore */ } } if (persist) localStorage.setItem(storageKey, moodId) currentMood.value = moodId } function setMood(moodId: string, options: SetMoodOptions = {}) { applyMood(moodId, options) } function initMood() { if (import.meta.server) return const saved = localStorage.getItem(storageKey) const moodId = saved && moods.some(m => m.id === saved) ? saved : defaultId // ne persiste que ce qui était déjà stocké : appliquer le défaut // ne doit pas verrouiller un choix que l'utilisateur n'a pas fait applyMood(moodId, { persist: saved != null }) } return { moods, currentMood: readonly(currentMood), setMood, initMood, } }