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>
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
.output/
|
||||||
|
.nuxt/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# @yvv/nuxt-base
|
||||||
|
|
||||||
|
Nuxt layer partagé pour les projets Nuxt Yvv (librodrome, GrateWizard, libreDecision, SejeteralO).
|
||||||
|
Fournit le **mécanisme** commun, jamais les couleurs (chaque projet garde son identité).
|
||||||
|
|
||||||
|
## Contenu
|
||||||
|
|
||||||
|
- `app/composables/useMood.ts` — mécanisme d'ambiances/palettes (extrait de libreDecision, généralisé).
|
||||||
|
Supporte les 2 patterns existants sans réécrire le CSS d'un projet :
|
||||||
|
- **classes `.mood-<id>`** + variables `--mood-*` définies en CSS (style libreDecision) ;
|
||||||
|
- **variables CSS injectées en JS** via `MoodDef.vars` (style librodrome `--color-*`) ;
|
||||||
|
- option `lightDarkClasses` pour basculer aussi `.palette-light/.palette-dark`.
|
||||||
|
|
||||||
|
## Adoption (à faire AVEC vérif visuelle mobile + desktop — zéro régression)
|
||||||
|
|
||||||
|
Dans `nuxt.config.ts` du projet :
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
extends: ['../yvv-nuxt-base'], // chemin relatif depuis la racine frontend du projet
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`useMood` devient auto-importé. Puis, par projet :
|
||||||
|
|
||||||
|
### libreDecision (risque quasi nul — c'est le mécanisme canonique)
|
||||||
|
Remplacer le corps de `frontend/app/composables/useMood.ts` par un appel au layer :
|
||||||
|
```ts
|
||||||
|
// frontend/app/composables/useMood.ts
|
||||||
|
const moods = [
|
||||||
|
{ id: 'peps', label: 'Peps', description: 'Chaud et tonique', icon: 'i-lucide-sun', color: '#d44a10', isDark: false },
|
||||||
|
{ id: 'zen', label: 'Zen', description: 'Nature vivante', icon: 'i-lucide-leaf', color: '#2e8b48', isDark: false },
|
||||||
|
{ id: 'chagrine', label: 'Chagrine', description: 'Nuit profonde', icon: 'i-lucide-moon', color: '#6488d8', isDark: true },
|
||||||
|
{ id: 'grave', label: 'Grave', description: 'Ambre mineral', icon: 'i-lucide-shield', color: '#d8a030', isDark: true },
|
||||||
|
]
|
||||||
|
export const useLibreMood = () => useMood(moods, { storageKey: 'libredecision_mood', defaultId: 'peps' })
|
||||||
|
```
|
||||||
|
`moods.css` (les couleurs) reste inchangé. Vérifier le switch des 4 moods + persistance.
|
||||||
|
|
||||||
|
### librodrome (via l'option vars — le CSS `--color-*` reste inchangé)
|
||||||
|
Convertir `stores/palette.ts` pour déléguer l'application DOM à `useMood`, en passant les triplets HSL dans `vars` (`--color-primary`, etc.) et `lightDarkClasses: true`. Le store peut rester pour l'API publique, mais l'application au DOM passe par le mécanisme partagé. Vérifier les 4 saisons + light/dark.
|
||||||
|
|
||||||
|
### GrateWizard / SejeteralO
|
||||||
|
Adopter après les deux premiers, même principe. SejeteralO (pas d'UnoCSS, vue-router 5) en dernier, prudemment.
|
||||||
|
|
||||||
|
## Principe
|
||||||
|
|
||||||
|
Ce layer unifie la **logique** (application DOM, persistance, SSR-guard, color-mode), pas le **contrat CSS**. Migrer le contrat CSS (renommer `--color-*` → `--mood-*`) est une étape séparée, optionnelle, à faire sous contrôle visuel.
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// @yvv/nuxt-base — Nuxt layer partagé (mécanismes communs aux projets Nuxt Yvv)
|
||||||
|
// Consommé via: export default defineNuxtConfig({ extends: ['../yvv-nuxt-base'] })
|
||||||
|
// Fournit (auto-importés chez le consommateur) : composables/useMood.ts
|
||||||
|
// N'impose AUCUN module ni CSS — les projets gardent leur stack et leurs couleurs.
|
||||||
|
export default defineNuxtConfig({})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "@yvv/nuxt-base",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Nuxt layer partagé (composables communs : useMood) pour les projets Nuxt Yvv",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./nuxt.config.ts"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user