Skill : Créer un composant Vue 3
Quand l'utilisateur demande de créer un composant Vue, applique ce template et ces règles.
Ressources disponibles
templates/CardItem.vue — composant carte fidélité complet (modes merchant + client)
templates/CardForm.vue — formulaire avec validation Zod, UForm, UInputNumber
Template de base
<script setup lang="ts">
// 1. Types importés (import type uniquement)
import type { Card } from '~/composables/useDB'
// 2. Props typées avec générics
const props = defineProps<{
requiredProp: string
optionalProp?: number
complexProp: Card
}>()
// 3. Emits typés avec tuple syntax
const emit = defineEmits<{
updated: [id: string, value: number]
deleted: [id: string]
}>()
// 4. Composables Nuxt (auto-importés, pas d'import manuel)
const toast = useToast()
// 5. State local
const isLoading = ref(false)
// 6. Computed (logique hors template)
const displayValue = computed(() => props.optionalProp ?? 0)
// 7. Fonctions (async avec try/catch + feedback toast)
async function handleAction() {
isLoading.value = true
try {
// logique métier
emit('updated', 'id', displayValue.value)
toast.add({ title: 'Succès', color: 'success' })
}
catch {
toast.add({ title: 'Erreur', color: 'error' })
}
finally {
isLoading.value = false
}
}
</script>
<template>
<UCard class="w-full">
<!-- Contenu principal -->
<p class="font-semibold text-lg">{{ props.requiredProp }}</p>
<!-- Actions : UButton avec size="lg" (min touch target 48px) -->
<UButton
color="primary"
size="lg"
:loading="isLoading"
@click="handleAction"
>
Action
</UButton>
</UCard>
</template>
Règles obligatoires
Structure complète, ordre des blocs, props/emits, composables et JSDoc dans coding-standards.
Composants Nuxt UI disponibles :
- Layout :
UCard, UContainer, USeparator
- Actions :
UButton, UButtonGroup
- Formulaire :
UForm, UFormField, UInput, UInputNumber, UTextarea, USelect, UCheckbox
- Feedback :
UAlert, UBadge, UProgress, UIcon
- Overlay :
UModal, UDrawer, UTooltip
- Icônes :
i-lucide-* en priorité (ex: i-lucide-trash-2, i-lucide-plus, i-lucide-gift)
UX mobile :
- Boutons interactifs :
size="lg" minimum (touch target 48px)
- Textes tronqués dans les listes : classe
truncate
- Layouts flexibles :
flex flex-col gap-4 ou flex gap-2
SSR safety :
- Si le composant accède à
localStorage, indexedDB, document, ou window → envelopper dans onMounted
- Si le composant utilise
qrcode ou html5-qrcode → <ClientOnly> + import dynamique dans onMounted
Composable associé (si logique complexe)
Si la logique dépasse ~20 lignes ou est réutilisable, extraire dans app/composables/useNomFeature.ts :
// app/composables/useNomFeature.ts
export function useNomFeature() {
const state = ref<Type>(initialValue)
async function doSomething(): Promise<void> {
// logique
}
return { state, doSomething }
}
Nommage des fichiers
- Composant :
PascalCase.vue → app/components/MonComposant.vue
- Page :
kebab-case.vue → app/pages/ma-page.vue
- Route dynamique :
[param].vue → app/pages/client/[id].vue
Confirmation en fin de tâche
✅ vue-component appliqué
- Structure : [ en premier, ordre des blocs respecté]
- Props/Emits : [générics, tuple syntax]
- UX mobile : [size="lg" sur boutons interactifs, truncate sur textes longs]
- SSR safety : [ClientOnly / onMounted si browser API]
- Tests : [fichier .test.ts co-localisé créé ou mis à jour]
1---2name: vue-component3description: Crée ou modifie un composant Vue 3 conforme aux standards Fidely. Déclencher quand : création composant (CardItem, CardForm, Scanner, QRCodeDisplay ou nouveau), ajout prop/emit, refactoring template, UX mobile (touch target), choix composant Nuxt UI. Mots-clés : CardItem.vue, CardForm.vue, Scanner.vue, QRCodeDisplay.vue, defineProps, defineEmits, UCard, UButton, UForm, UFormField, UInput, UInputNumber, UProgress, UModal, size="lg", truncate, flex gap, ClientOnly, onMounted, onBeforeUnmount.4---56# Skill : Créer un composant Vue 378Quand l'utilisateur demande de créer un composant Vue, applique ce template et ces règles.910## Ressources disponibles1112- [`templates/CardItem.vue`](templates/CardItem.vue) — composant carte fidélité complet (modes merchant + client)13- [`templates/CardForm.vue`](templates/CardForm.vue) — formulaire avec validation Zod, UForm, UInputNumber1415## Template de base1617```vue18<script setup lang="ts">19// 1. Types importés (import type uniquement)20import type { Card } from '~/composables/useDB'2122// 2. Props typées avec générics23const props = defineProps<{24 requiredProp: string25 optionalProp?: number26 complexProp: Card27}>()2829// 3. Emits typés avec tuple syntax30const emit = defineEmits<{31 updated: [id: string, value: number]32 deleted: [id: string]33}>()3435// 4. Composables Nuxt (auto-importés, pas d'import manuel)36const toast = useToast()3738// 5. State local39const isLoading = ref(false)4041// 6. Computed (logique hors template)42const displayValue = computed(() => props.optionalProp ?? 0)4344// 7. Fonctions (async avec try/catch + feedback toast)45async function handleAction() {46 isLoading.value = true47 try {48 // logique métier49 emit('updated', 'id', displayValue.value)50 toast.add({ title: 'Succès', color: 'success' })51 }52 catch {53 toast.add({ title: 'Erreur', color: 'error' })54 }55 finally {56 isLoading.value = false57 }58}59</script>6061<template>62 <UCard class="w-full">63 <!-- Contenu principal -->64 <p class="font-semibold text-lg">{{ props.requiredProp }}</p>6566 <!-- Actions : UButton avec size="lg" (min touch target 48px) -->67 <UButton68 color="primary"69 size="lg"70 :loading="isLoading"71 @click="handleAction"72 >73 Action74 </UButton>75 </UCard>76</template>77```7879## Règles obligatoires8081> Structure complète, ordre des blocs, props/emits, composables et JSDoc dans **`coding-standards`**.8283**Composants Nuxt UI disponibles :**84- Layout : `UCard`, `UContainer`, `USeparator`85- Actions : `UButton`, `UButtonGroup`86- Formulaire : `UForm`, `UFormField`, `UInput`, `UInputNumber`, `UTextarea`, `USelect`, `UCheckbox`87- Feedback : `UAlert`, `UBadge`, `UProgress`, `UIcon`88- Overlay : `UModal`, `UDrawer`, `UTooltip`89- Icônes : `i-lucide-*` en priorité (ex: `i-lucide-trash-2`, `i-lucide-plus`, `i-lucide-gift`)9091**UX mobile :**92- Boutons interactifs : `size="lg"` minimum (touch target 48px)93- Textes tronqués dans les listes : classe `truncate`94- Layouts flexibles : `flex flex-col gap-4` ou `flex gap-2`9596**SSR safety :**97- Si le composant accède à `localStorage`, `indexedDB`, `document`, ou `window` → envelopper dans `onMounted`98- Si le composant utilise `qrcode` ou `html5-qrcode` → `<ClientOnly>` + import dynamique dans `onMounted`99100## Composable associé (si logique complexe)101102Si la logique dépasse ~20 lignes ou est réutilisable, extraire dans `app/composables/useNomFeature.ts` :103104```ts105// app/composables/useNomFeature.ts106export function useNomFeature() {107 const state = ref<Type>(initialValue)108109 async function doSomething(): Promise<void> {110 // logique111 }112113 return { state, doSomething }114}115```116117## Nommage des fichiers118119- Composant : `PascalCase.vue` → `app/components/MonComposant.vue`120- Page : `kebab-case.vue` → `app/pages/ma-page.vue`121- Route dynamique : `[param].vue` → `app/pages/client/[id].vue`122123---124125## Confirmation en fin de tâche126127> ✅ **vue-component appliqué**128> - Structure : [<script setup lang="ts"> en premier, ordre des blocs respecté]129> - Props/Emits : [générics, tuple syntax]130> - UX mobile : [size="lg" sur boutons interactifs, truncate sur textes longs]131> - SSR safety : [ClientOnly / onMounted si browser API]132> - Tests : [fichier .test.ts co-localisé créé ou mis à jour]