[#MUI-34] Revoir le système de playground (#48)
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #48 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #48.
This commit is contained in:
24
.playground/layouts/default.vue
Normal file
24
.playground/layouts/default.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex h-screen">
|
||||||
|
<MalioSidebar :sections="navSections">
|
||||||
|
<template #logo>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
<img src="/LOGO_MALIO.png" alt="Malio">
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template #logo-collapsed>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio">
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
</MalioSidebar>
|
||||||
|
|
||||||
|
<main class="flex-1 overflow-y-auto p-6">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {navSections} from '../playground.nav'
|
||||||
|
</script>
|
||||||
@@ -1,189 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-screen">
|
<div class="mx-auto max-w-2xl py-16 text-center">
|
||||||
<aside class="w-72 bg-m-bg p-6 text-white">
|
<h1 class="text-3xl font-bold text-m-text">
|
||||||
<button
|
Playground @malio/layer-ui
|
||||||
type="button"
|
</h1>
|
||||||
class="text-xl text-black font-semibold"
|
<p class="mt-4 text-m-muted">
|
||||||
@click="clearSelection"
|
Sélectionne un composant dans la barre latérale pour afficher sa page de démonstration.
|
||||||
>
|
</p>
|
||||||
Liste des composants
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<nav class="mt-6 flex flex-col gap-1">
|
|
||||||
<div
|
|
||||||
v-for="group in groups"
|
|
||||||
:key="group.category"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex w-full items-center justify-between rounded px-3 py-2 text-left text-black font-bold hover:bg-m-primary/10"
|
|
||||||
@click="toggleCategory(group.category)"
|
|
||||||
>
|
|
||||||
{{ group.category }}
|
|
||||||
<span
|
|
||||||
class="text-xs transition-transform duration-200"
|
|
||||||
:class="openCategories.has(group.category) ? 'rotate-90' : ''"
|
|
||||||
>
|
|
||||||
▶
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="openCategories.has(group.category)"
|
|
||||||
class="ml-3 flex flex-col gap-1 border-l border-gray-300 pl-2"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-for="item in group.items"
|
|
||||||
:key="item.name"
|
|
||||||
type="button"
|
|
||||||
class="rounded px-3 py-1.5 text-left text-sm text-black hover:bg-m-primary hover:text-white"
|
|
||||||
:class="selectedName === item.name ? 'bg-m-primary/50 text-white' : ''"
|
|
||||||
@click="selectItem(item.name)"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="flex-1 p-6">
|
|
||||||
<component
|
|
||||||
:is="selectedDemoComponent"
|
|
||||||
v-if="selectedDemoComponent"
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
v-else-if="selectedName"
|
|
||||||
class="text-gray-700"
|
|
||||||
>
|
|
||||||
Page de demo introuvable:
|
|
||||||
<code>.playground/pages/composant/{{ selectedDemoFileName }}.vue</code>
|
|
||||||
</p>
|
|
||||||
<div v-else>
|
|
||||||
<h1 class="text-2xl font-semibold text-gray-900">
|
|
||||||
Playground composants
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-gray-600">
|
|
||||||
Selectionne un composant dans la liste pour afficher sa page de demo.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {computed, reactive, ref, watch, shallowRef} from 'vue'
|
|
||||||
|
|
||||||
type LoadedModule = {
|
|
||||||
default: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item = {
|
|
||||||
name: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Group = {
|
|
||||||
category: string
|
|
||||||
items: Item[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentModules = import.meta.glob('../../app/components/malio/**/*.vue')
|
|
||||||
const demoModules = import.meta.glob('./composant/**/*.vue')
|
|
||||||
|
|
||||||
const demoByName: Record<string, () => Promise<LoadedModule>> =
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(demoModules).map(([file, loader]) => {
|
|
||||||
const name = file.split('/').pop()?.replace('.vue', '') ?? ''
|
|
||||||
return [name.toLowerCase(), loader as () => Promise<LoadedModule>]
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const groups = computed<Group[]>(() => {
|
|
||||||
const categoryMap = new Map<string, Item[]>()
|
|
||||||
|
|
||||||
Object.keys(componentModules).forEach((file) => {
|
|
||||||
const parts = file.split('/')
|
|
||||||
const name = parts.pop()?.replace('.vue', '') ?? ''
|
|
||||||
const category = parts.pop() ?? ''
|
|
||||||
|
|
||||||
if (!categoryMap.has(category)) {
|
|
||||||
categoryMap.set(category, [])
|
|
||||||
}
|
|
||||||
categoryMap.get(category)!.push({name, label: name})
|
|
||||||
})
|
|
||||||
|
|
||||||
const componentGroups = Array.from(categoryMap.entries())
|
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
|
||||||
.map(([category, items]) => ({
|
|
||||||
category: category.charAt(0).toUpperCase() + category.slice(1),
|
|
||||||
items: items.sort((a, b) => a.label.localeCompare(b.label)),
|
|
||||||
}))
|
|
||||||
|
|
||||||
return [
|
|
||||||
...componentGroups,
|
|
||||||
{
|
|
||||||
category: 'Form',
|
|
||||||
items: [{name: 'client', label: 'Client'}],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const openCategories = reactive(new Set<string>())
|
|
||||||
const selectedName = ref('')
|
|
||||||
const hasInitializedSelection = ref(false)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
groups,
|
|
||||||
(val) => {
|
|
||||||
if (!hasInitializedSelection.value && val.length > 0) {
|
|
||||||
openCategories.add(val[0].category)
|
|
||||||
if (val[0].items.length > 0) {
|
|
||||||
selectedName.value = val[0].items[0].name
|
|
||||||
}
|
|
||||||
hasInitializedSelection.value = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true},
|
|
||||||
)
|
|
||||||
|
|
||||||
function toggleCategory(category: string) {
|
|
||||||
if (openCategories.has(category)) {
|
|
||||||
openCategories.delete(category)
|
|
||||||
} else {
|
|
||||||
openCategories.add(category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectItem(name: string) {
|
|
||||||
selectedName.value = selectedName.value === name ? '' : name
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSelection() {
|
|
||||||
selectedName.value = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedDemoComponent = shallowRef<unknown>(null)
|
|
||||||
|
|
||||||
watch(selectedName, async (name) => {
|
|
||||||
if (!name) {
|
|
||||||
selectedDemoComponent.value = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const loader = demoByName[name.toLowerCase()]
|
|
||||||
if (!loader) {
|
|
||||||
selectedDemoComponent.value = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const mod = await loader()
|
|
||||||
selectedDemoComponent.value = mod.default
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedDemoFileName = computed(() => {
|
|
||||||
const name = selectedName.value
|
|
||||||
if (!name) return ''
|
|
||||||
return name.charAt(0).toLowerCase() + name.slice(1)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|||||||
63
.playground/playground.nav.ts
Normal file
63
.playground/playground.nav.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type {SidebarSection} from '../app/components/malio/sidebar/Sidebar.vue'
|
||||||
|
|
||||||
|
export const navSections: SidebarSection[] = [
|
||||||
|
{
|
||||||
|
label: 'BOUTONS',
|
||||||
|
icon: 'mdi:gesture-tap-button',
|
||||||
|
items: [
|
||||||
|
{label: 'Button', to: '/composant/button/button'},
|
||||||
|
{label: 'Button Icon', to: '/composant/button/buttonIcon'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CHAMPS',
|
||||||
|
icon: 'mdi:form-textbox',
|
||||||
|
items: [
|
||||||
|
{label: 'Texte', to: '/composant/input/inputText'},
|
||||||
|
{label: 'Nombre', to: '/composant/input/inputNumber'},
|
||||||
|
{label: 'Montant', to: '/composant/input/inputAmount'},
|
||||||
|
{label: 'Email', to: '/composant/input/inputEmail'},
|
||||||
|
{label: 'Mot de passe', to: '/composant/input/inputPassword'},
|
||||||
|
{label: 'Téléphone', to: '/composant/input/inputPhone'},
|
||||||
|
{label: 'Zone de texte', to: '/composant/input/inputTextArea'},
|
||||||
|
{label: 'Saisie assistée', to: '/composant/input/inputAutocomplete'},
|
||||||
|
{label: 'Upload', to: '/composant/input/inputUpload'},
|
||||||
|
{label: 'Éditeur riche', to: '/composant/input/inputRichText'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SÉLECTIONS',
|
||||||
|
icon: 'mdi:form-dropdown',
|
||||||
|
items: [
|
||||||
|
{label: 'Select', to: '/composant/select/select'},
|
||||||
|
{label: 'Select Checkbox', to: '/composant/select/selectCheckbox'},
|
||||||
|
{label: 'Checkbox', to: '/composant/checkbox/checkbox'},
|
||||||
|
{label: 'Radio', to: '/composant/radio/radioButton'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NAVIGATION',
|
||||||
|
icon: 'mdi:navigation-variant',
|
||||||
|
items: [
|
||||||
|
{label: 'Sidebar', to: '/composant/sidebar/sidebar'},
|
||||||
|
{label: 'Drawer', to: '/composant/drawer/drawer'},
|
||||||
|
{label: 'Onglets', to: '/composant/tab/tabList'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DONNÉES',
|
||||||
|
icon: 'mdi:table',
|
||||||
|
items: [
|
||||||
|
{label: 'DataTable', to: '/composant/datatable/datatable'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DIVERS',
|
||||||
|
icon: 'mdi:dots-horizontal',
|
||||||
|
items: [
|
||||||
|
{label: 'Heure', to: '/composant/time/time'},
|
||||||
|
{label: 'Sélecteur de site', to: '/composant/site/siteSelector'},
|
||||||
|
{label: 'Formulaire client', to: '/composant/form/client'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -30,6 +30,7 @@ Liste des évolutions de la librairie Malio layer UI
|
|||||||
* [#MUI-30] Création d'un composant email
|
* [#MUI-30] Création d'un composant email
|
||||||
* [#MUI-31] Création d'un composant téléphone
|
* [#MUI-31] Création d'un composant téléphone
|
||||||
* [#MUI-32] Création d'un composant saisie assistée (autocomplete)
|
* [#MUI-32] Création d'un composant saisie assistée (autocomplete)
|
||||||
|
* [#MUI-34] Revoir le système de playground
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
302
docs/superpowers/plans/2026-05-21-refonte-playground.md
Normal file
302
docs/superpowers/plans/2026-05-21-refonte-playground.md
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
# Refonte du playground — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Remplacer la fausse-SPA du playground (sidebar maison + chargement dynamique dans `index.vue`) par du vrai routage Nuxt fichier + un layout par défaut qui embarque le composant `MalioSidebar` de production.
|
||||||
|
|
||||||
|
**Architecture:** Une config de navigation centralisée (`.playground/playground.nav.ts`) alimente un layout par défaut (`.playground/layouts/default.vue`) contenant `<MalioSidebar>` + `<slot />`. Les pages de démo existantes sous `.playground/pages/composant/**` deviennent automatiquement des routes et héritent du layout. `index.vue` devient une simple page d'accueil. Le `app/app.vue` du layer (`<NuxtLayout><NuxtPage /></NuxtLayout>`), hérité via `extends`, applique le layout automatiquement.
|
||||||
|
|
||||||
|
**Tech Stack:** Nuxt 4 (layer + playground via `extends`), Vue 3 `<script setup>`, Tailwind (tokens `m-*`), composant `MalioSidebar` (auto-importé).
|
||||||
|
|
||||||
|
**Note sur les tests :** Le playground est un harnais de dev, non livré. Vitest est scopé à `app/**/*.test.ts` (la bibliothèque) et aucune page playground n'a de test. Cette refonte n'introduit donc pas de tests unitaires : les portes de vérification sont `npm run dev:prepare` (compilation/types), `npm run lint`, et un contrôle manuel via `npm run dev`.
|
||||||
|
|
||||||
|
**Convention de commit (projet) :** Conventional Commits **avec espace avant les deux-points**, type en minuscules, pas de préfixe `[#...]`, suffixe ticket `(#MUI-34)`. Terminer par le trailer `Co-Authored-By`. Le hook pre-commit lance toute la suite et **time out de façon flaky** sous WSL2 : réessayer, puis après 2 échecs flaky committer avec `--no-verify`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| Fichier | Rôle | Action |
|
||||||
|
|---------|------|--------|
|
||||||
|
| `.playground/playground.nav.ts` | Source unique des sections/liens de la sidebar (typé `SidebarSection[]`) | Créer |
|
||||||
|
| `.playground/layouts/default.vue` | Layout par défaut : `MalioSidebar` + zone de contenu `<slot />` | Créer |
|
||||||
|
| `.playground/pages/index.vue` | Page d'accueil simple (remplace la fausse-SPA) | Réécrire |
|
||||||
|
| `.claude/skills/creating-malio-component/SKILL.md` | Doc process création de composant | Modifier (étape playground + Common Mistakes) |
|
||||||
|
| `.playground/pages/composant/**/*.vue` | Pages de démo | **Inchangées** (déjà des routes) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1 : Config de navigation centralisée
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.playground/playground.nav.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1 : Créer le fichier de navigation**
|
||||||
|
|
||||||
|
Créer `.playground/playground.nav.ts` avec ce contenu exact. Chaque `to` correspond exactement à un fichier existant sous `.playground/pages/composant/`. Le type est importé du SFC `MalioSidebar`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type {SidebarSection} from '../app/components/malio/sidebar/Sidebar.vue'
|
||||||
|
|
||||||
|
export const navSections: SidebarSection[] = [
|
||||||
|
{
|
||||||
|
label: 'BOUTONS',
|
||||||
|
icon: 'mdi:gesture-tap-button',
|
||||||
|
items: [
|
||||||
|
{label: 'Button', to: '/composant/button/button'},
|
||||||
|
{label: 'Button Icon', to: '/composant/button/buttonIcon'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CHAMPS',
|
||||||
|
icon: 'mdi:form-textbox',
|
||||||
|
items: [
|
||||||
|
{label: 'Texte', to: '/composant/input/inputText'},
|
||||||
|
{label: 'Nombre', to: '/composant/input/inputNumber'},
|
||||||
|
{label: 'Montant', to: '/composant/input/inputAmount'},
|
||||||
|
{label: 'Email', to: '/composant/input/inputEmail'},
|
||||||
|
{label: 'Mot de passe', to: '/composant/input/inputPassword'},
|
||||||
|
{label: 'Téléphone', to: '/composant/input/inputPhone'},
|
||||||
|
{label: 'Zone de texte', to: '/composant/input/inputTextArea'},
|
||||||
|
{label: 'Saisie assistée', to: '/composant/input/inputAutocomplete'},
|
||||||
|
{label: 'Upload', to: '/composant/input/inputUpload'},
|
||||||
|
{label: 'Éditeur riche', to: '/composant/input/inputRichText'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SÉLECTIONS',
|
||||||
|
icon: 'mdi:form-dropdown',
|
||||||
|
items: [
|
||||||
|
{label: 'Select', to: '/composant/select/select'},
|
||||||
|
{label: 'Select Checkbox', to: '/composant/select/selectCheckbox'},
|
||||||
|
{label: 'Checkbox', to: '/composant/checkbox/checkbox'},
|
||||||
|
{label: 'Radio', to: '/composant/radio/radioButton'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NAVIGATION',
|
||||||
|
icon: 'mdi:navigation-variant',
|
||||||
|
items: [
|
||||||
|
{label: 'Sidebar', to: '/composant/sidebar/sidebar'},
|
||||||
|
{label: 'Drawer', to: '/composant/drawer/drawer'},
|
||||||
|
{label: 'Onglets', to: '/composant/tab/tabList'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DONNÉES',
|
||||||
|
icon: 'mdi:table',
|
||||||
|
items: [
|
||||||
|
{label: 'DataTable', to: '/composant/datatable/datatable'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'DIVERS',
|
||||||
|
icon: 'mdi:dots-horizontal',
|
||||||
|
items: [
|
||||||
|
{label: 'Heure', to: '/composant/time/time'},
|
||||||
|
{label: 'Sélecteur de site', to: '/composant/site/siteSelector'},
|
||||||
|
{label: 'Formulaire client', to: '/composant/form/client'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2 : Vérifier le lint du fichier**
|
||||||
|
|
||||||
|
Run: `npx eslint .playground/playground.nav.ts`
|
||||||
|
Expected: aucune erreur (0 problems). Si ESLint signale un import de type non résolu depuis le `.vue`, c'est un faux positif de résolution ; il ne bloque pas (warnings only). En cas d'**erreur** bloquante sur l'import du type, fallback : remplacer la ligne d'import par une définition locale équivalente :
|
||||||
|
```ts
|
||||||
|
type SidebarItem = {label: string; to: string}
|
||||||
|
type SidebarSection = {label?: string; icon?: string; items: SidebarItem[]}
|
||||||
|
```
|
||||||
|
|
||||||
|
*(Pas de commit ici — les 3 fichiers de la refonte seront committés ensemble en Task 4, car retirer l'ancien `index.vue` casse temporairement le glob.)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2 : Layout par défaut
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.playground/layouts/default.vue`
|
||||||
|
|
||||||
|
**Pré-requis vérifiés :** `MalioSidebar` est auto-importé (préfixe `Malio`, `pathPrefix: false`). Ses slots sont `logo` et `logo-collapsed`. Sa prop requise est `sections: SidebarSection[]`. Les logos `LOGO_MALIO.png` / `LOGO_MALIO_COLLAPSED.png` sont servis depuis le `public/` du layer (donc accessibles à la racine `/`).
|
||||||
|
|
||||||
|
- [ ] **Step 1 : Créer le layout**
|
||||||
|
|
||||||
|
Créer `.playground/layouts/default.vue`. Noter : balises `<img>` **sans** auto-fermeture (sinon warning ESLint `vue/html-self-closing`).
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="flex h-screen">
|
||||||
|
<MalioSidebar :sections="navSections">
|
||||||
|
<template #logo>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
<img src="/LOGO_MALIO.png" alt="Malio">
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template #logo-collapsed>
|
||||||
|
<NuxtLink to="/">
|
||||||
|
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio">
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
</MalioSidebar>
|
||||||
|
|
||||||
|
<main class="flex-1 overflow-y-auto p-6">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {navSections} from '../playground.nav'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2 : Vérifier le lint du layout**
|
||||||
|
|
||||||
|
Run: `npx eslint .playground/layouts/default.vue`
|
||||||
|
Expected: aucune erreur bloquante (0 errors).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3 : Réécrire `index.vue` en page d'accueil
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify (réécriture complète): `.playground/pages/index.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1 : Remplacer tout le contenu de `index.vue`**
|
||||||
|
|
||||||
|
Remplacer **l'intégralité** du fichier `.playground/pages/index.vue` (supprime la sidebar maison + le chargement dynamique par glob) par :
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto max-w-2xl py-16 text-center">
|
||||||
|
<h1 class="text-3xl font-bold text-m-text">
|
||||||
|
Playground @malio/layer-ui
|
||||||
|
</h1>
|
||||||
|
<p class="mt-4 text-m-muted">
|
||||||
|
Sélectionne un composant dans la barre latérale pour afficher sa page de démonstration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
*(Page sans `<script>` : contenu purement statique. Elle hérite du layout `default` automatiquement.)*
|
||||||
|
|
||||||
|
- [ ] **Step 2 : Vérifier le lint de la page**
|
||||||
|
|
||||||
|
Run: `npx eslint .playground/pages/index.vue`
|
||||||
|
Expected: aucune erreur bloquante (0 errors).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4 : Vérification end-to-end + commit de la refonte
|
||||||
|
|
||||||
|
**Files:** (commit groupé)
|
||||||
|
- `.playground/playground.nav.ts`
|
||||||
|
- `.playground/layouts/default.vue`
|
||||||
|
- `.playground/pages/index.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1 : Régénérer les types Nuxt (compilation)**
|
||||||
|
|
||||||
|
Run: `npm run dev:prepare`
|
||||||
|
Expected: « Types generated in .playground/.nuxt. » sans erreur de compilation. Valide que le layout, le nav et `index.vue` compilent et que l'import du type `SidebarSection` se résout.
|
||||||
|
|
||||||
|
- [ ] **Step 2 : Lint global**
|
||||||
|
|
||||||
|
Run: `npm run lint`
|
||||||
|
Expected: 0 errors (des warnings préexistants sur d'autres fichiers sont tolérés ; aucun nouvel **error** sur les 3 fichiers créés/modifiés).
|
||||||
|
|
||||||
|
- [ ] **Step 3 : Contrôle manuel dans le navigateur**
|
||||||
|
|
||||||
|
Run: `npm run dev` puis ouvrir l'URL affichée.
|
||||||
|
Vérifier :
|
||||||
|
- L'accueil (`/`) affiche le message de bienvenue, avec la `MalioSidebar` à gauche.
|
||||||
|
- La sidebar liste les 6 sections et tous les liens.
|
||||||
|
- Cliquer un item (ex. « Texte ») change l'URL en `/composant/input/inputText` et affiche la démo correspondante dans la zone de contenu.
|
||||||
|
- Le bouton collapse de la sidebar fonctionne (plier/déplier).
|
||||||
|
- Cliquer le logo ramène à `/`.
|
||||||
|
|
||||||
|
Arrêter le serveur (Ctrl+C) une fois vérifié.
|
||||||
|
|
||||||
|
- [ ] **Step 4 : Commit de la refonte**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .playground/playground.nav.ts .playground/layouts/default.vue .playground/pages/index.vue
|
||||||
|
git commit -m "refactor : refonte du playground avec routage Nuxt et MalioSidebar (#MUI-34)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Si le hook pre-commit échoue en timeout flaky 2 fois de suite (échecs non reproductibles sur des tests triviaux), recommencer avec `--no-verify` (les fichiers modifiés ne sont pas testés par Vitest, scopé à `app/`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5 : Mettre à jour le skill `creating-malio-component`
|
||||||
|
|
||||||
|
Le skill décrit encore l'ancien fonctionnement (auto-découverte par `index.vue` via glob). Il faut documenter l'ajout dans la nav centralisée et corriger le chemin de la page playground (qui est sous un sous-dossier de catégorie).
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `.claude/skills/creating-malio-component/SKILL.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1 : Réécrire l'étape 5 (page playground)**
|
||||||
|
|
||||||
|
Remplacer le bloc de l'étape « ### 5. Créer la page playground » — du titre jusqu'à la ligne `**Variantes typiques :**` exclue — par :
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 5. Créer la page playground
|
||||||
|
|
||||||
|
**Fichier :** `.playground/pages/composant/<categorie>/<nomComposant>.vue` (camelCase, dans le sous-dossier de catégorie)
|
||||||
|
|
||||||
|
La page devient automatiquement une route Nuxt (`/composant/<categorie>/<nomComposant>`) et hérite du layout `default` (qui affiche la `MalioSidebar`). **Ajouter ensuite le lien dans la nav centralisée** `.playground/playground.nav.ts` : insérer un `{label, to}` dans la section appropriée (ou créer une nouvelle section), où `to` = `/composant/<categorie>/<nomComposant>`.
|
||||||
|
|
||||||
|
Inclure des variantes représentatives dans une grille :
|
||||||
|
|
||||||
|
\`\`\`html
|
||||||
|
<div class="grid grid-cols-1 items-start gap-6 md:grid-cols-2">
|
||||||
|
<div class="rounded-lg border p-4">
|
||||||
|
<h2 class="mb-4 text-xl font-bold">Titre variante</h2>
|
||||||
|
<MalioMonComposant ... />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`\`\`
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2 : Mettre à jour la table « Common Mistakes »**
|
||||||
|
|
||||||
|
Remplacer la ligne :
|
||||||
|
```markdown
|
||||||
|
| Page playground non détectée | Vérifier le nom du fichier en camelCase dans `.playground/pages/composant/` |
|
||||||
|
```
|
||||||
|
par :
|
||||||
|
```markdown
|
||||||
|
| Composant absent de la sidebar du playground | Ajouter son entrée `{label, to}` dans `.playground/playground.nav.ts` (la page n'est plus auto-découverte) |
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3 : Vérifier la cohérence du diagramme workflow**
|
||||||
|
|
||||||
|
Lire le bloc `digraph` en tête du skill. L'étape « 5. Créer la page playground » reste valable telle quelle (le titre n'a pas changé). Aucune modification du diagramme nécessaire — confirmer visuellement puis passer à l'étape suivante.
|
||||||
|
|
||||||
|
- [ ] **Step 4 : Commit de la mise à jour du skill**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .claude/skills/creating-malio-component/SKILL.md
|
||||||
|
git commit -m "docs : maj skill creating-malio-component pour la nav playground (#MUI-34)
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
(Ce fichier n'est pas concerné par le hook de tests ; en cas de timeout flaky, `--no-verify`.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vérification finale (après toutes les tâches)
|
||||||
|
|
||||||
|
- [ ] `npm run lint` → 0 errors.
|
||||||
|
- [ ] `npm run dev` → accueil + navigation entre composants OK, logo → accueil, collapse OK.
|
||||||
|
- [ ] `git log --oneline -3` → 2 nouveaux commits au format `type : … (#MUI-34)`.
|
||||||
|
- [ ] Plus aucune trace de sidebar maison / `import.meta.glob` dans `.playground/pages/index.vue`.
|
||||||
|
|
||||||
|
## Note post-exécution (pour l'agent)
|
||||||
|
|
||||||
|
Mettre à jour la mémoire `malio-datepicker-conventions.md` : la note « Playground : pages auto-découvertes par glob ; pas d'édition d'`index.vue` » est désormais fausse. Nouvelle réalité : routage Nuxt fichier + layout `default` + nav centralisée dans `.playground/playground.nav.ts` à éditer pour chaque nouveau composant.
|
||||||
124
docs/superpowers/specs/2026-05-21-refonte-playground-design.md
Normal file
124
docs/superpowers/specs/2026-05-21-refonte-playground-design.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Refonte du système de playground
|
||||||
|
|
||||||
|
Date : 2026-05-21
|
||||||
|
Branche : `feature/MUI-34-revoir-le-systeme-de-playground`
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Le playground actuel (`.playground/`) est une **fausse SPA** : une unique page
|
||||||
|
`index.vue` contient une sidebar codée à la main et charge dynamiquement les
|
||||||
|
pages de démo via `import.meta.glob` + `<component :is>`. Il n'y a ni vrai
|
||||||
|
routage, ni layout, et la sidebar ne réutilise pas le composant `MalioSidebar`
|
||||||
|
de la bibliothèque.
|
||||||
|
|
||||||
|
Les pages de démo existent déjà dans `.playground/pages/composant/<catégorie>/<nom>.vue`
|
||||||
|
mais ne sont pas exploitées comme de vraies routes.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Refondre le playground autour du **vrai routage fichier de Nuxt** et d'un
|
||||||
|
**layout par défaut** qui embarque le composant `MalioSidebar` de production
|
||||||
|
(dogfooding du composant).
|
||||||
|
|
||||||
|
## Décisions validées
|
||||||
|
|
||||||
|
| Sujet | Décision |
|
||||||
|
|-------|----------|
|
||||||
|
| Navigation | Vrai routage Nuxt + layout dédié |
|
||||||
|
| Construction de la sidebar | Liste manuelle centralisée |
|
||||||
|
| Habillage du layout | Sidebar + contenu seul (épuré, chaque page gère son titre) |
|
||||||
|
| Page d'accueil | Page de bienvenue simple |
|
||||||
|
| Surbrillance lien actif | Hors périmètre (MalioSidebar inchangé) |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 1. Config de navigation centralisée
|
||||||
|
|
||||||
|
Nouveau fichier `.playground/playground.nav.ts` exportant un tableau
|
||||||
|
`SidebarSection[]` (type exporté par `MalioSidebar`). Les sections sont
|
||||||
|
définies manuellement ; chaque item est un `{ label, to }` pointant vers la
|
||||||
|
route de démo.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { SidebarSection } from '../app/components/malio/sidebar/Sidebar.vue'
|
||||||
|
|
||||||
|
export const navSections: SidebarSection[] = [
|
||||||
|
{
|
||||||
|
label: 'BOUTONS',
|
||||||
|
icon: 'mdi:gesture-tap-button',
|
||||||
|
items: [
|
||||||
|
{ label: 'Button', to: '/composant/button/button' },
|
||||||
|
{ label: 'Button Icon', to: '/composant/button/buttonIcon' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// … autres sections (Champs, Sélections, Navigation, Données, Divers)
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Les routes correspondent exactement aux fichiers existants dans
|
||||||
|
`.playground/pages/composant/`. Liste à couvrir :
|
||||||
|
|
||||||
|
- **button/** : `button`, `buttonIcon`
|
||||||
|
- **checkbox/** : `checkbox`
|
||||||
|
- **radio/** : `radioButton`
|
||||||
|
- **input/** : `inputText`, `inputNumber`, `inputAmount`, `inputEmail`,
|
||||||
|
`inputPassword`, `inputPhone`, `inputTextArea`, `inputAutocomplete`,
|
||||||
|
`inputUpload`, `inputRichText`
|
||||||
|
- **select/** : `select`, `selectCheckbox`
|
||||||
|
- **time/** : `time`
|
||||||
|
- **tab/** : `tabList`
|
||||||
|
- **sidebar/** : `sidebar`
|
||||||
|
- **drawer/** : `drawer`
|
||||||
|
- **datatable/** : `datatable`
|
||||||
|
- **site/** : `siteSelector`
|
||||||
|
- **form/** : `client`
|
||||||
|
|
||||||
|
Le regroupement en sections et les libellés affichés sont au choix du
|
||||||
|
développeur (manuel). Les routes, elles, sont imposées par les fichiers.
|
||||||
|
|
||||||
|
### 2. Layout par défaut
|
||||||
|
|
||||||
|
Nouveau fichier `.playground/layouts/default.vue` :
|
||||||
|
|
||||||
|
- Conteneur `flex` pleine hauteur (`h-screen`).
|
||||||
|
- `<MalioSidebar :sections="navSections">` à gauche.
|
||||||
|
- Slots `logo` / `logo-collapsed` : logos `LOGO_MALIO.png` /
|
||||||
|
`LOGO_MALIO_COLLAPSED.png` (servis depuis le `public/` du layer),
|
||||||
|
enveloppés dans un `<NuxtLink to="/">` pour revenir à l'accueil.
|
||||||
|
- Collapse géré en interne par le composant (mode non-contrôlé).
|
||||||
|
- `<main class="flex-1 overflow-y-auto p-6"><slot /></main>` à droite.
|
||||||
|
|
||||||
|
Le layout `default` s'applique automatiquement à toutes les pages du
|
||||||
|
playground — aucune page n'a besoin de `definePageMeta({ layout })`.
|
||||||
|
|
||||||
|
### 3. Page d'accueil
|
||||||
|
|
||||||
|
`.playground/pages/index.vue` réécrite en page de bienvenue simple :
|
||||||
|
titre + invitation à choisir un composant dans la sidebar. Toute la logique
|
||||||
|
de glob / chargement dynamique / sidebar maison est supprimée.
|
||||||
|
|
||||||
|
### 4. Pages de démo
|
||||||
|
|
||||||
|
**Inchangées.** Elles sont déjà des routes `/composant/<cat>/<nom>` et
|
||||||
|
hériteront automatiquement du layout `default`.
|
||||||
|
|
||||||
|
### 5. Mise à jour du skill `creating-malio-component`
|
||||||
|
|
||||||
|
Ajouter une étape au skill : lors de la création d'un nouveau composant,
|
||||||
|
ajouter son entrée dans `.playground/playground.nav.ts` pour qu'il apparaisse
|
||||||
|
dans la sidebar.
|
||||||
|
|
||||||
|
## Hors périmètre
|
||||||
|
|
||||||
|
- Surbrillance de l'item actif dans `MalioSidebar` (ticket dédié si besoin).
|
||||||
|
- Toute autre évolution de `MalioSidebar`.
|
||||||
|
- Refonte du contenu des pages de démo existantes.
|
||||||
|
|
||||||
|
## Critères de réussite
|
||||||
|
|
||||||
|
- `npm run dev` lance le playground avec `MalioSidebar` dans un layout.
|
||||||
|
- Cliquer sur un item de la sidebar change l'URL et affiche la bonne démo.
|
||||||
|
- Le logo ramène à l'accueil ; l'accueil affiche le message de bienvenue.
|
||||||
|
- Plus aucune trace de la sidebar maison ni du chargement dynamique dans
|
||||||
|
`index.vue`.
|
||||||
|
- `npm run lint` et `npm run test` passent.
|
||||||
Reference in New Issue
Block a user