feat: Ajout de composant (#23)
All checks were successful
Release / release (push) Successful in 1m14s
All checks were successful
Release / release (push) Successful in 1m14s
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [x] Pas de régression - [x] TU/TI/TF rédigée - [x] TU/TI/TF OK - [x] CHANGELOG modifié Co-authored-by: kevin <kevin@yuno.malio.fr> Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr> Reviewed-on: #23 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #23.
This commit is contained in:
18
.claude/settings.local.json
Normal file
18
.claude/settings.local.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run:*)",
|
||||
"Bash(npx vitest:*)",
|
||||
"Bash(sed -i \"s|from ''../../../app/components/malio/Checkbox.vue''|from ''../../../app/components/malio/checkbox/Checkbox.vue''|\" .playground/pages/composant/checkbox.vue)",
|
||||
"Bash(sed -i \"s|from ''../../../app/components/malio/RadioButton.vue''|from ''../../../app/components/malio/radio/RadioButton.vue''|\" .playground/pages/composant/radioButton.vue)",
|
||||
"Bash(sed -i \"s|from ''../../../app/components/malio/Time.vue''|from ''../../../app/components/malio/time/Time.vue''|\" .playground/pages/composant/time.vue)",
|
||||
"Bash(sed -i \"s|from ''../../../app/components/malio/InputTextArea.vue''|from ''../../../app/components/malio/input/InputTextArea.vue''|\" .playground/pages/composant/inputTextArea.vue)",
|
||||
"Bash(npx nuxi:*)",
|
||||
"Bash(mkdir -p button input select checkbox radio time)",
|
||||
"Bash(mv buttonIcon.story.vue button/)",
|
||||
"Bash(mv inputText.story.vue inputAmount.story.vue inputNumber.story.vue inputPassword.story.vue inputTextArea.story.vue inputUpload.story.vue input/)",
|
||||
"Bash(mv InputSelect.story.vue selectCheckbox.story.vue select/)",
|
||||
"Bash(mv inputCheckbox.story.vue checkbox/)"
|
||||
]
|
||||
}
|
||||
}
|
||||
223
.claude/skills/creating-malio-component/SKILL.md
Normal file
223
.claude/skills/creating-malio-component/SKILL.md
Normal file
@@ -0,0 +1,223 @@
|
||||
---
|
||||
name: creating-malio-component
|
||||
description: Use when creating a new UI component in the @malio/layer-ui Nuxt layer — covers component, tests, playground page, and Histoire story
|
||||
---
|
||||
|
||||
# Creating a Malio Component
|
||||
|
||||
## Overview
|
||||
|
||||
Step-by-step process for creating a component in `@malio/layer-ui`. Each component requires 6 deliverables : le `.vue`, les tests, la page playground, la story Histoire, la mise à jour du CHANGELOG, et la mise à jour du `COMPONENTS.md`.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Création d'un nouveau composant dans `app/components/malio/`
|
||||
- Ajout d'une variante d'un composant existant (ex: InputPassword basé sur InputText)
|
||||
|
||||
## Workflow
|
||||
|
||||
```dot
|
||||
digraph create_component {
|
||||
rankdir=TB;
|
||||
"1. Lire les fichiers de référence" -> "2. Créer le composant .vue";
|
||||
"2. Créer le composant .vue" -> "3. Créer les tests .test.ts";
|
||||
"3. Créer les tests .test.ts" -> "4. npm run test + npm run lint";
|
||||
"4. npm run test + npm run lint" -> "Tests OK?" [shape=diamond];
|
||||
"Tests OK?" -> "5. Créer la page playground" [label="oui"];
|
||||
"Tests OK?" -> "3. Créer les tests .test.ts" [label="non, corriger"];
|
||||
"5. Créer la page playground" -> "6. Créer la story Histoire";
|
||||
"6. Créer la story Histoire" -> "7. Mettre à jour CHANGELOG.md";
|
||||
"7. Mettre à jour CHANGELOG.md" -> "8. Mettre à jour COMPONENTS.md";
|
||||
}
|
||||
```
|
||||
|
||||
## Étapes
|
||||
|
||||
### 1. Lire les fichiers de référence
|
||||
|
||||
Identifier le composant le plus proche comme base (ex: `InputText.vue` pour `InputPassword.vue`). Lire :
|
||||
- Le composant de référence : `app/components/malio/<Ref>.vue`
|
||||
- Ses tests : `app/components/malio/<Ref>.test.ts`
|
||||
|
||||
### 2. Créer le composant `.vue`
|
||||
|
||||
**Fichier :** `app/components/malio/<NomComposant>.vue`
|
||||
|
||||
**Checklist obligatoire :**
|
||||
|
||||
| Élément | Pattern |
|
||||
|---------|---------|
|
||||
| `defineOptions` | `{ name: 'Malio<Nom>', inheritAttrs: false }` |
|
||||
| Props | `defineProps<T>()` + `withDefaults()` — props communes : `id`, `label`, `modelValue`, `inputClass`, `labelClass`, `groupClass`, `disabled`, `readonly`, `hint`, `error`, `success` |
|
||||
| Contrôlé / non-contrôlé | `isControlled = computed(() => props.modelValue !== undefined)` + `localValue` en fallback |
|
||||
| Classes CSS | Fusionnées via `twMerge()` pour permettre l'override consommateur |
|
||||
| Accessibilité | `aria-invalid`, `aria-describedby`, `label[for]` lié à `input[id]` |
|
||||
| Icônes | `Icon as IconifyIcon` depuis `@iconify/vue` (pas `@nuxt/icon`) |
|
||||
| ID généré | `useId()` + prefix unique (ex: `malio-input-password-${generatedId}`) |
|
||||
|
||||
### 3. Créer les tests `.test.ts`
|
||||
|
||||
**Fichier :** `app/components/malio/<NomComposant>.test.ts` (colocalisé)
|
||||
|
||||
**Pattern de montage :**
|
||||
|
||||
```ts
|
||||
import { mount } from '@vue/test-utils'
|
||||
import type { DefineComponent } from 'vue'
|
||||
import MonComposant from './MonComposant.vue'
|
||||
|
||||
const ComposantForTest = MonComposant as DefineComponent<MonComposantProps>
|
||||
|
||||
const mountComponent = (props: MonComposantProps = {}) =>
|
||||
mount(ComposantForTest, {
|
||||
props,
|
||||
global: {
|
||||
stubs: {
|
||||
IconifyIcon: {
|
||||
template: '<span data-test="icon" v-bind="$attrs" />',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Tests minimum à couvrir :**
|
||||
- Rendu initial avec valeur
|
||||
- Rendu du label
|
||||
- Emit `update:modelValue`
|
||||
- Props `disabled`, `readonly`
|
||||
- États `error`, `success`, `hint` (messages + classes CSS)
|
||||
- Accessibilité (`aria-invalid`, `label[for]` / `input[id]`)
|
||||
- Comportements spécifiques au composant
|
||||
|
||||
**Attention stub IconifyIcon :** Le stub basé sur le nom `IconifyIcon` ne remplace pas toujours le vrai composant `@iconify/vue`. Pour tester les props du composant Icon (ex: `icon`), utiliser `findComponent` avec l'import réel :
|
||||
|
||||
```ts
|
||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||
// ...
|
||||
const iconComponent = wrapper.findComponent(IconifyIcon)
|
||||
expect(iconComponent.props('icon')).toBe('mdi:eye-outline')
|
||||
```
|
||||
|
||||
### 4. Vérification
|
||||
|
||||
```bash
|
||||
npm run test # Tous les tests passent
|
||||
npm run lint # Pas d'erreurs
|
||||
```
|
||||
|
||||
### 5. Créer la page playground
|
||||
|
||||
**Fichier :** `.playground/pages/composant/<nomComposant>.vue` (camelCase)
|
||||
|
||||
La page est auto-détectée par `index.vue` via `import.meta.glob`. 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>
|
||||
```
|
||||
|
||||
**Variantes typiques :** simple, avec label, désactivé, readonly, hint, erreur, succès, validation dynamique.
|
||||
|
||||
### 6. Créer la story Histoire
|
||||
|
||||
**Fichier :** `app/story/<nomComposant>.story.vue` (camelCase)
|
||||
|
||||
**Structure :**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Story title="Category/Name">
|
||||
<!-- Variantes avec v-model et valeurs initiales -->
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<docs lang="md">
|
||||
# MalioNomComposant
|
||||
Description courte.
|
||||
## Props détaillées
|
||||
<!-- Documenter chaque prop : type, description, défaut, comportement -->
|
||||
## Comportement
|
||||
## Accessibilité
|
||||
## Events
|
||||
</docs>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import MalioMonComposant from '../components/malio/MonComposant.vue'
|
||||
// refs pour chaque variante avec valeurs initiales
|
||||
</script>
|
||||
```
|
||||
|
||||
**Important : initial state avec variantes.** La story doit contenir des exemples visuels directement visibles (pas un composant vide). Chaque variante a un `v-model` avec une `ref` initialisée. Variantes typiques à inclure :
|
||||
- Simple (avec label)
|
||||
- Sans icône (`display-icon="false"`) si applicable
|
||||
- Avec hint
|
||||
- Désactivé (avec valeur pré-remplie)
|
||||
- Readonly (avec valeur pré-remplie)
|
||||
- Erreur (avec valeur + message d'erreur)
|
||||
- Succès (avec valeur + message de succès)
|
||||
|
||||
### 7. Mettre à jour le CHANGELOG
|
||||
|
||||
**Fichier :** `CHANGELOG.md` à la racine du projet.
|
||||
|
||||
Ajouter une ligne dans la section `### Added` de la version courante. Le numéro de ticket se trouve dans le nom de la branche Git (ex: branche `feat/MUI-8-composant-password` → ticket `MUI-8`).
|
||||
|
||||
**Format :**
|
||||
- Avec numéro de ticket : `* [#MUI-8] Création d'un composant mot de passe`
|
||||
- Sans numéro de ticket : `* Création d'un composant textarea`
|
||||
|
||||
Pour extraire le numéro de ticket depuis la branche courante :
|
||||
```bash
|
||||
git branch --show-current | grep -oP '(MUI-\d+|\d{3,})' | head -1
|
||||
```
|
||||
|
||||
### 8. Mettre à jour COMPONENTS.md
|
||||
|
||||
**Fichier :** `COMPONENTS.md` à la racine du projet.
|
||||
|
||||
Ce fichier sert de documentation de référence pour les projets qui consomment `@malio/layer-ui`. Il est lu par Claude dans les projets consommateurs pour connaître les composants disponibles et leurs props.
|
||||
|
||||
**Ajouter une section pour le nouveau composant** en suivant le format existant :
|
||||
|
||||
```markdown
|
||||
## MalioNomComposant
|
||||
|
||||
Description courte du composant.
|
||||
|
||||
| Prop | Type | Défaut | Description |
|
||||
|------|------|--------|-------------|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
**Events :** `update:modelValue(value: string)`
|
||||
|
||||
\`\`\`vue
|
||||
<MalioNomComposant v-model="val" label="Exemple" />
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
**Checklist :**
|
||||
- Toutes les props documentées avec type, défaut et description
|
||||
- Events listés
|
||||
- Slots listés si applicable
|
||||
- 2-5 exemples d'utilisation couvrant les cas courants (simple, avec options, disabled, erreur)
|
||||
- Section placée par ordre logique (inputs ensemble, boutons ensemble, etc.)
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
Cette section est alimentée au fur et à mesure des retours utilisateur et des problèmes rencontrés. **Si un retour ou un bug est identifié lors de la création d'un composant, ajouter une ligne dans ce tableau.**
|
||||
|
||||
| Erreur | Solution |
|
||||
|--------|----------|
|
||||
| Stub IconifyIcon ne fonctionne pas dans les tests | Utiliser `findComponent(IconifyIcon)` avec l'import réel pour tester les props |
|
||||
| Oubli de `inheritAttrs: false` | Toujours dans `defineOptions` — sinon les attrs se dupliquent |
|
||||
| Page playground non détectée | Vérifier le nom du fichier en camelCase dans `.playground/pages/composant/` |
|
||||
| Padding input pas ajusté avec icône | Ajouter `!pr-10` (ou équivalent) quand une icône est présente à droite |
|
||||
| Story sans initial state | Toujours initialiser les `ref` avec des valeurs pour que les variantes soient visibles dès le chargement |
|
||||
| CHANGELOG oublié | Toujours ajouter la ligne dans `### Added` avant de commit |
|
||||
| COMPONENTS.md pas mis à jour | Ajouter la doc du composant dans `COMPONENTS.md` — c'est la référence pour les projets consommateurs |
|
||||
Reference in New Issue
Block a user