- darkClass: bascule une classe unique sur <html> selon isDark
(variants dark: UnoCSS/Tailwind — besoin GrateWizard)
- setMood(id, { persist: false }): application éphémère (embed, prefers-color-scheme)
- initMood ne persiste plus le défaut, seulement un choix déjà stocké
- README: section GrateWizard + avertissement distribution (extends relatif
hors repo = build Docker/CI cassé, à trancher avant merge)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
133 lines
4.2 KiB
TypeScript
133 lines
4.2 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
|
|
/**
|
|
* Classe unique à basculer sur <html> 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<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,
|
|
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-<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)
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|