Files
yvv-nuxt-base/app/composables/useMood.ts
T
YvvandClaude Opus 4.8 a9ec5c49e4 feat: socle initial du package mutualisé
- primitives de design partagées / mécanisme moods commun
- isolé, opt-in, sans impact sur les projets existants

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 05:28:15 +02:00

114 lines
3.4 KiB
TypeScript

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 <html> quand ce mood est actif.
* Optionnel — pour les projets qui injectent les variables en JS
* (style librodrome) plutôt que via des classes .mood-<id> en CSS
* (style libreDecision). Les deux approches sont supportées.
*/
vars?: Record<string, string>
}
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
}
const currentMood: Ref<string> = ref('')
/**
* Mécanisme moods partagé (extrait de libreDecision, généralisé).
* Applique sur <html> : classe .mood-<id>, 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,
} = options
function applyMood(moodId: string) {
if (import.meta.server) return
const mood = moods.find(m => m.id === moodId)
if (!mood) return
const html = document.documentElement
// classe .mood-<id>
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)
}
}
// 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 */
}
}
localStorage.setItem(storageKey, moodId)
currentMood.value = moodId
}
function setMood(moodId: string) {
applyMood(moodId)
}
function initMood() {
if (import.meta.server) return
const saved = localStorage.getItem(storageKey)
const moodId = saved && moods.some(m => m.id === saved) ? saved : defaultId
applyMood(moodId)
}
return {
moods,
currentMood: readonly(currentMood),
setMood,
initMood,
}
}