df289aa829
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
461 lines
20 KiB
Markdown
461 lines
20 KiB
Markdown
# État « obligatoire » cohérent + normalisation email — 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:** Exposer une prop `required` cohérente avec astérisque rouge dans le label sur toute la famille formulaire, et ajouter une sanitisation à la saisie (suppression des espaces + option `lowercase`) à `MalioInputEmail`.
|
||
|
||
**Architecture :** Un composant présentational partagé `MalioRequiredMark` (astérisque `aria-hidden`, token `text-m-danger`) est importé explicitement et rendu dans le `<label>` de chaque composant quand `required` est vrai. Les 4 composants sans la prop la reçoivent (+ câblage `aria-required` là où il n'y a pas de `required` natif). `MalioInputEmail.onInput` sanitise la valeur avant émission.
|
||
|
||
**Tech Stack :** Nuxt 4 layer, Vue 3 `<script setup lang="ts">`, Tailwind (palette `m-*`), `tailwind-merge`, Vitest + `@vue/test-utils` (jsdom).
|
||
|
||
**Spec :** `docs/superpowers/specs/2026-06-03-required-asterisk-email-sanitization-design.md`
|
||
|
||
**Conventions de test (rappel) :** chaque fichier `*.test.ts` définit son propre helper de montage (nom variable : `mountInput`, `mountDate`, `mountCheckbox`, `mountTime`, `mountComponent`…) ou monte en inline. Le tableau de chaque tâche indique le helper exact à réutiliser.
|
||
|
||
**⚠️ Suite flaky :** des timeouts intermittents existent sur diverses suites. Si un test échoue par timeout sans rapport avec le changement, relancer le fichier ciblé ; ne pas conclure à un échec sans relance. Le hook pre-commit lance les tests — si un timeout flaky bloque un commit déjà vérifié manuellement, utiliser `git commit --no-verify`.
|
||
|
||
**Branche :** `feature/MUI-41-props-required-asterisque-dans-le-label-sur-les-co` (rester dessus, ne pas créer de branche).
|
||
|
||
---
|
||
|
||
## Task 1 : Composant partagé `MalioRequiredMark`
|
||
|
||
**Files:**
|
||
- Create: `app/components/malio/shared/RequiredMark.vue`
|
||
- Test: `app/components/malio/shared/RequiredMark.test.ts`
|
||
|
||
- [ ] **Step 1 : Écrire le test qui échoue**
|
||
|
||
Create `app/components/malio/shared/RequiredMark.test.ts` :
|
||
|
||
```ts
|
||
import {describe, expect, it} from 'vitest'
|
||
import {mount} from '@vue/test-utils'
|
||
import RequiredMark from './RequiredMark.vue'
|
||
|
||
describe('MalioRequiredMark', () => {
|
||
it('rend un astérisque', () => {
|
||
const wrapper = mount(RequiredMark)
|
||
expect(wrapper.text()).toBe('*')
|
||
})
|
||
|
||
it('est masqué pour les technologies d’assistance', () => {
|
||
const wrapper = mount(RequiredMark)
|
||
expect(wrapper.get('[data-test="required-mark"]').attributes('aria-hidden')).toBe('true')
|
||
})
|
||
|
||
it('utilise le token de couleur danger', () => {
|
||
const wrapper = mount(RequiredMark)
|
||
expect(wrapper.get('[data-test="required-mark"]').classes()).toContain('text-m-danger')
|
||
})
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 2 : Lancer le test, vérifier l'échec**
|
||
|
||
Run: `npm run test -- app/components/malio/shared/RequiredMark.test.ts`
|
||
Expected: FAIL — `Failed to resolve import './RequiredMark.vue'` (le composant n'existe pas encore).
|
||
|
||
- [ ] **Step 3 : Créer le composant**
|
||
|
||
Create `app/components/malio/shared/RequiredMark.vue` :
|
||
|
||
```vue
|
||
<template>
|
||
<span
|
||
data-test="required-mark"
|
||
aria-hidden="true"
|
||
class="ml-0.5 select-none text-m-danger"
|
||
>*</span>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
defineOptions({name: 'MalioRequiredMark', inheritAttrs: false})
|
||
</script>
|
||
```
|
||
|
||
- [ ] **Step 4 : Lancer le test, vérifier le succès**
|
||
|
||
Run: `npm run test -- app/components/malio/shared/RequiredMark.test.ts`
|
||
Expected: PASS (3 tests).
|
||
|
||
- [ ] **Step 5 : Commit**
|
||
|
||
```bash
|
||
git add app/components/malio/shared/RequiredMark.vue app/components/malio/shared/RequiredMark.test.ts
|
||
git commit -m "feat(ui): composant partagé MalioRequiredMark (astérisque champ obligatoire)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2 : Prop `required` + a11y + astérisque sur les 4 composants sans la prop
|
||
|
||
Composants : `Select`, `SelectCheckbox`, `InputUpload`, `InputRichText`. Chacun reçoit la prop `required`, le câblage a11y adapté, l'import + le rendu de l'astérisque, et un test.
|
||
|
||
**Files:**
|
||
- Modify: `app/components/malio/select/Select.vue`, `app/components/malio/select/SelectCheckbox.vue`, `app/components/malio/input/InputUpload.vue`, `app/components/malio/input/InputRichText.vue`
|
||
- Test: `app/components/malio/select/Select.test.ts`, `app/components/malio/select/SelectCheckbox.test.ts`, `app/components/malio/input/InputUpload.test.ts`, `app/components/malio/input/InputRichText.test.ts`
|
||
|
||
- [ ] **Step 1 : Écrire les tests qui échouent (un par composant)**
|
||
|
||
Patron d'assertion (à adapter au helper de chaque fichier) :
|
||
|
||
```ts
|
||
it('affiche l’astérisque quand required est vrai', () => {
|
||
const wrapper = /* monter avec { label: 'Champ', required: true, ...props requises } */
|
||
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||
})
|
||
|
||
it('n’affiche pas l’astérisque par défaut', () => {
|
||
const wrapper = /* monter avec { label: 'Champ', ...props requises } */
|
||
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||
})
|
||
```
|
||
|
||
Montage par fichier :
|
||
|
||
| Fichier test | Montage |
|
||
|---|---|
|
||
| `select/Select.test.ts` | inline : `mount(SelectForTest, {props: {label: 'Champ', required: true, options: [{label: 'A', value: 'a'}]}})` (et sans `required` pour le 2ᵉ test) |
|
||
| `select/SelectCheckbox.test.ts` | inline : `mount(SelectCheckboxForTest, {props: {label: 'Champ', required: true, options: [{label: 'A', value: 'a'}]}})` |
|
||
| `input/InputUpload.test.ts` | helper existant `mountComponent({label: 'Champ', required: true})` |
|
||
| `input/InputRichText.test.ts` | helper existant `mountComponent({label: 'Champ', required: true})` |
|
||
|
||
> Note : pour `Select`/`SelectCheckbox`, reprendre la forme exacte des `options` et les `global.stubs` déjà utilisés dans les autres `it()` du fichier (copier un montage voisin).
|
||
|
||
- [ ] **Step 2 : Lancer les tests, vérifier l'échec**
|
||
|
||
Run: `npm run test -- app/components/malio/select/Select.test.ts app/components/malio/select/SelectCheckbox.test.ts app/components/malio/input/InputUpload.test.ts app/components/malio/input/InputRichText.test.ts`
|
||
Expected: FAIL sur les nouveaux tests « affiche l’astérisque » (la prop/le rendu n'existent pas encore).
|
||
|
||
- [ ] **Step 3 : Ajouter la prop `required` (type + défaut) dans les 4 composants**
|
||
|
||
Dans chaque `defineProps<{…}>()`, ajouter la ligne :
|
||
|
||
```ts
|
||
required?: boolean
|
||
```
|
||
|
||
Dans chaque `withDefaults(…, { … })`, ajouter :
|
||
|
||
```ts
|
||
required: false,
|
||
```
|
||
|
||
- [ ] **Step 4 : Câbler l'accessibilité (un élément interactif par composant)**
|
||
|
||
`Select.vue` — sur le `<button>` déclencheur (là où sont déjà `:aria-expanded`, `:aria-controls`), ajouter :
|
||
|
||
```vue
|
||
:aria-required="required || undefined"
|
||
```
|
||
|
||
`SelectCheckbox.vue` — idem, sur son `<button>` déclencheur :
|
||
|
||
```vue
|
||
:aria-required="required || undefined"
|
||
```
|
||
|
||
`InputUpload.vue` — sur l'`<input type="file">`, ajouter l'attribut natif :
|
||
|
||
```vue
|
||
:required="required"
|
||
```
|
||
|
||
`InputRichText.vue` — sur le wrapper éditeur identifié par `:id="editorId"` (le conteneur de `<EditorContent>` en mode éditable), ajouter :
|
||
|
||
```vue
|
||
:aria-required="required || undefined"
|
||
```
|
||
|
||
- [ ] **Step 5 : Importer et rendre l'astérisque dans les 4 composants**
|
||
|
||
Dans le `<script setup>` de chacun, ajouter l'import (chemin relatif depuis `family/Component.vue`) :
|
||
|
||
```ts
|
||
import MalioRequiredMark from '../shared/RequiredMark.vue'
|
||
```
|
||
|
||
Dans le `<template>`, remplacer le rendu du libellé `{{ label }}` (celui à l'intérieur du `<label>` du champ — **pas** un `{{ opt.label }}`) par :
|
||
|
||
```vue
|
||
{{ label }}<MalioRequiredMark v-if="required" />
|
||
```
|
||
|
||
> Respecter l'indentation existante de chaque fichier. Pour `Select`/`SelectCheckbox`, viser le `{{ label }}` du `<label>` flottant, pas le `{{ opt.label }}` des options.
|
||
|
||
- [ ] **Step 6 : Lancer les tests, vérifier le succès**
|
||
|
||
Run: `npm run test -- app/components/malio/select/Select.test.ts app/components/malio/select/SelectCheckbox.test.ts app/components/malio/input/InputUpload.test.ts app/components/malio/input/InputRichText.test.ts`
|
||
Expected: PASS (anciens + nouveaux tests). En cas de timeout flaky non lié, relancer le fichier concerné.
|
||
|
||
- [ ] **Step 7 : Commit**
|
||
|
||
```bash
|
||
git add app/components/malio/select/Select.vue app/components/malio/select/SelectCheckbox.vue app/components/malio/input/InputUpload.vue app/components/malio/input/InputRichText.vue app/components/malio/select/Select.test.ts app/components/malio/select/SelectCheckbox.test.ts app/components/malio/input/InputUpload.test.ts app/components/malio/input/InputRichText.test.ts
|
||
git commit -m "feat(ui): prop required + aria-required + astérisque sur Select/SelectCheckbox/Upload/RichText"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3 : Astérisque sur les composants ayant déjà `required`
|
||
|
||
Ces composants ont déjà la prop `required` (câblée nativement). On ajoute uniquement l'import + le rendu de l'astérisque + un test.
|
||
|
||
**Files (16 composants → 13 via CalendarField mutualisé) :**
|
||
|
||
| Composant `.vue` | Import à ajouter | Fichier test | Helper de montage |
|
||
|---|---|---|---|
|
||
| `input/InputText.vue` | `'../shared/RequiredMark.vue'` | `input/Input.test.ts` | `mountInput({label:'Champ', required:true})` |
|
||
| `input/InputEmail.vue` | `'../shared/RequiredMark.vue'` | `input/InputEmail.test.ts` | `mountComponent({label:'Champ', required:true})` |
|
||
| `input/InputPhone.vue` | `'../shared/RequiredMark.vue'` | `input/InputPhone.test.ts` | `mountComponent({label:'Champ', required:true})` |
|
||
| `input/InputPassword.vue` | `'../shared/RequiredMark.vue'` | `input/InputPassword.test.ts` | `mountComponent({label:'Champ', required:true})` |
|
||
| `input/InputTextArea.vue` | `'../shared/RequiredMark.vue'` | `input/InputTextArea.test.ts` | helper du fichier (`mount<…>` ; copier un montage voisin) |
|
||
| `input/InputAmount.vue` | `'../shared/RequiredMark.vue'` | `input/InputAmount.test.ts` | helper du fichier |
|
||
| `input/InputNumber.vue` | `'../shared/RequiredMark.vue'` | `input/InputNumber.test.ts` | helper du fichier |
|
||
| `input/InputAutocomplete.vue` | `'../shared/RequiredMark.vue'` | `input/InputAutocomplete.test.ts` | `mountComponent({label:'Champ', required:true, …props requises})` |
|
||
| `checkbox/Checkbox.vue` | `'../shared/RequiredMark.vue'` | `checkbox/Checkbox.test.ts` | `mountCheckbox({label:'Champ', required:true})` |
|
||
| `radio/RadioButton.vue` | `'../shared/RequiredMark.vue'` | `radio/RadioButton.test.ts` | helper du fichier |
|
||
| `time/Time.vue` | `'../shared/RequiredMark.vue'` | `time/Time.test.ts` | `mountTime({label:'Champ', required:true})` |
|
||
| `time/TimePicker.vue` | `'../shared/RequiredMark.vue'` | `time/TimePicker.test.ts` | helper du fichier |
|
||
| `date/internal/CalendarField.vue` | `'../../shared/RequiredMark.vue'` | `date/Date.test.ts` | `mountDate({label:'Champ', required:true})` |
|
||
|
||
> `CalendarField` rend le label de tout le date family (`Date`, `DateTime`, `DateRange`, `DateWeek`). Une seule modif + un seul test (via `Date.test.ts`) couvrent les quatre.
|
||
|
||
- [ ] **Step 1 : Écrire les tests qui échouent (un couple par fichier test du tableau)**
|
||
|
||
Pour chaque fichier test listé, ajouter :
|
||
|
||
```ts
|
||
it('affiche l’astérisque quand required est vrai', () => {
|
||
const wrapper = /* helper du tableau, avec required: true */
|
||
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(true)
|
||
})
|
||
|
||
it('n’affiche pas l’astérisque par défaut', () => {
|
||
const wrapper = /* helper du tableau, sans required */
|
||
expect(wrapper.find('[data-test="required-mark"]').exists()).toBe(false)
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 2 : Lancer les tests, vérifier l'échec**
|
||
|
||
Run: `npm run test -- app/components/malio/input app/components/malio/checkbox app/components/malio/radio app/components/malio/time app/components/malio/date/Date.test.ts`
|
||
Expected: FAIL sur les nouveaux tests « affiche l’astérisque ».
|
||
|
||
- [ ] **Step 3 : Ajouter l'import + le rendu de l'astérisque dans les 13 `.vue`**
|
||
|
||
Dans chaque `<script setup>`, ajouter l'import indiqué dans la colonne « Import à ajouter ».
|
||
|
||
Dans chaque `<template>`, transformer le libellé du champ :
|
||
|
||
```vue
|
||
{{ label }}<MalioRequiredMark v-if="required" />
|
||
```
|
||
|
||
(Le `{{ label }}` est à l'intérieur du `<label v-if="label">` du champ. Respecter l'indentation propre à chaque fichier.)
|
||
|
||
- [ ] **Step 4 : Lancer les tests, vérifier le succès**
|
||
|
||
Run: `npm run test -- app/components/malio/input app/components/malio/checkbox app/components/malio/radio app/components/malio/time app/components/malio/date/Date.test.ts`
|
||
Expected: PASS. (Vérifier notamment que `input/InputEmail.test.ts` « renders the label text » → `'Adresse email'` passe toujours : pas de `required` dans ce test, donc pas d'astérisque.) Relancer en cas de timeout flaky.
|
||
|
||
- [ ] **Step 5 : Commit**
|
||
|
||
```bash
|
||
git add app/components/malio/input app/components/malio/checkbox app/components/malio/radio app/components/malio/time app/components/malio/date/internal/CalendarField.vue app/components/malio/date/Date.test.ts
|
||
git commit -m "feat(ui): astérisque required dans le label de la famille formulaire"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4 : Sanitisation de `MalioInputEmail`
|
||
|
||
**Files:**
|
||
- Modify: `app/components/malio/input/InputEmail.vue`
|
||
- Test: `app/components/malio/input/InputEmail.test.ts`
|
||
|
||
- [ ] **Step 1 : Écrire les tests qui échouent**
|
||
|
||
Ajouter à `input/InputEmail.test.ts` :
|
||
|
||
```ts
|
||
it('supprime tous les espaces saisis', async () => {
|
||
const wrapper = mountComponent()
|
||
await wrapper.get('input').setValue(' a b @ c.com ')
|
||
const emits = wrapper.emitted('update:modelValue')!
|
||
expect(emits[emits.length - 1]).toEqual(['ab@c.com'])
|
||
expect(wrapper.get('input').element.value).toBe('ab@c.com')
|
||
})
|
||
|
||
it('conserve la casse par défaut', async () => {
|
||
const wrapper = mountComponent()
|
||
await wrapper.get('input').setValue('User@Example.COM')
|
||
const emits = wrapper.emitted('update:modelValue')!
|
||
expect(emits[emits.length - 1]).toEqual(['User@Example.COM'])
|
||
})
|
||
|
||
it('met en minuscules quand lowercase est vrai', async () => {
|
||
const wrapper = mountComponent({lowercase: true})
|
||
await wrapper.get('input').setValue('User@Example.COM')
|
||
const emits = wrapper.emitted('update:modelValue')!
|
||
expect(emits[emits.length - 1]).toEqual(['user@example.com'])
|
||
})
|
||
```
|
||
|
||
> Ajouter `lowercase?: boolean` au type `InputEmailProps` en tête du fichier de test (sinon TS refuse la prop dans le 3ᵉ test).
|
||
|
||
- [ ] **Step 2 : Lancer les tests, vérifier l'échec**
|
||
|
||
Run: `npm run test -- app/components/malio/input/InputEmail.test.ts`
|
||
Expected: FAIL — les espaces ne sont pas supprimés / `lowercase` inconnu.
|
||
|
||
- [ ] **Step 3 : Ajouter la prop `lowercase`**
|
||
|
||
Dans `defineProps<{…}>()` de `InputEmail.vue`, ajouter :
|
||
|
||
```ts
|
||
lowercase?: boolean
|
||
```
|
||
|
||
Dans `withDefaults(…, { … })`, ajouter :
|
||
|
||
```ts
|
||
lowercase: false,
|
||
```
|
||
|
||
- [ ] **Step 4 : Ajouter la fonction de sanitisation et réécrire `onInput`**
|
||
|
||
Ajouter la fonction pure (au-dessus de `onInput`) :
|
||
|
||
```ts
|
||
const sanitizeEmail = (v: string) => {
|
||
let out = v.replace(/\s+/g, '')
|
||
if (props.lowercase) out = out.toLowerCase()
|
||
return out
|
||
}
|
||
```
|
||
|
||
Remplacer le `onInput` existant par :
|
||
|
||
```ts
|
||
const onInput = (event: Event) => {
|
||
const target = event.target as HTMLInputElement
|
||
const raw = target.value
|
||
const sanitized = sanitizeEmail(raw)
|
||
|
||
if (sanitized !== raw) {
|
||
// `<input type="email">` ne supporte pas l'API de sélection :
|
||
// selectionStart vaut null, setSelectionRange lève. On garde defensivement.
|
||
const caret = target.selectionStart
|
||
target.value = sanitized
|
||
if (caret !== null) {
|
||
const newCaret = sanitizeEmail(raw.slice(0, caret)).length
|
||
try {
|
||
target.setSelectionRange(newCaret, newCaret)
|
||
} catch {
|
||
/* type d'input sans support de sélection — ignore */
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!isControlled.value) {
|
||
localValue.value = sanitized
|
||
}
|
||
emit('update:modelValue', sanitized)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5 : Lancer les tests, vérifier le succès**
|
||
|
||
Run: `npm run test -- app/components/malio/input/InputEmail.test.ts`
|
||
Expected: PASS (anciens tests inclus, dont « emits update:modelValue on input change » avec `'new@example.com'` qui n'a pas d'espace → inchangé).
|
||
|
||
- [ ] **Step 6 : Commit**
|
||
|
||
```bash
|
||
git add app/components/malio/input/InputEmail.vue app/components/malio/input/InputEmail.test.ts
|
||
git commit -m "feat(inputs): sanitisation email (suppression des espaces + option lowercase)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5 : Documentation (`COMPONENTS.md` + `CHANGELOG.md`)
|
||
|
||
**Files:**
|
||
- Modify: `COMPONENTS.md`, `CHANGELOG.md`
|
||
|
||
- [ ] **Step 1 : `COMPONENTS.md` — lignes `required` manquantes**
|
||
|
||
Pour les sections `MalioSelect`, `MalioSelectCheckbox`, `MalioInputUpload`, `MalioInputRichText`, ajouter dans le tableau des props la ligne (au même format que les autres composants) :
|
||
|
||
```
|
||
| `required` | `boolean` | `false` | Champ requis (affiche un astérisque rouge dans le label) |
|
||
```
|
||
|
||
- [ ] **Step 2 : `COMPONENTS.md` — note astérisque + prop `lowercase`**
|
||
|
||
- Dans l'introduction de la famille formulaire (ou la section des props communes), ajouter une phrase : « Lorsque `required` est vrai, un astérisque rouge est ajouté dans le label (visuel ; la sémantique est portée par l'attribut `required`/`aria-required`). »
|
||
- Dans la section `MalioInputEmail`, ajouter la ligne de prop :
|
||
|
||
```
|
||
| `lowercase` | `boolean` | `false` | Normalise la saisie en minuscules à la frappe |
|
||
```
|
||
|
||
et préciser que les espaces sont supprimés automatiquement à la saisie (pas de masque ; la validation de format reste à la couche `error`).
|
||
|
||
- [ ] **Step 3 : `CHANGELOG.md` — entrées**
|
||
|
||
Sous le `### Added` de la version en cours (format `* [#…] …`), ajouter :
|
||
|
||
```
|
||
* [#MUI-41] Prop `required` cohérente + astérisque rouge dans le label sur la famille formulaire
|
||
* [#MUI-41] InputEmail : sanitisation à la saisie (suppression des espaces, option `lowercase`)
|
||
```
|
||
|
||
- [ ] **Step 4 : Commit**
|
||
|
||
```bash
|
||
git add COMPONENTS.md CHANGELOG.md
|
||
git commit -m "docs: required/astérisque + lowercase email (COMPONENTS + CHANGELOG)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6 : Exemples playground + vérification finale
|
||
|
||
**Files:**
|
||
- Modify: page(s) playground des composants concernés (selon `.playground/` ; cf. mémoire « Architecture playground »)
|
||
|
||
- [ ] **Step 1 : Ajouter des exemples légers**
|
||
|
||
Sur la page playground d'un composant représentatif (ex. `InputText`/`Select`), ajouter une instance `:required="true"`. Sur la page `InputEmail`, ajouter une instance `:lowercase="true"`. Si le coût d'intégration dépasse quelques minutes (routage/nav à câbler), le **noter** et passer — c'est hors scope strict du ticket.
|
||
|
||
- [ ] **Step 2 : Lint**
|
||
|
||
Run: `npm run lint`
|
||
Expected: 0 erreur. Corriger le cas échéant.
|
||
|
||
- [ ] **Step 3 : Suite de tests complète des fichiers touchés**
|
||
|
||
Run: `npm run test -- app/components/malio/shared app/components/malio/input app/components/malio/select app/components/malio/checkbox app/components/malio/radio app/components/malio/time app/components/malio/date`
|
||
Expected: PASS. En cas de timeout flaky, relancer le(s) fichier(s) concerné(s) individuellement.
|
||
|
||
- [ ] **Step 4 : Commit (si exemples playground ajoutés)**
|
||
|
||
```bash
|
||
git add .playground
|
||
git commit -m "docs(playground): exemples required + email lowercase"
|
||
```
|
||
|
||
---
|
||
|
||
## Récapitulatif des commits attendus
|
||
|
||
1. `feat(ui): composant partagé MalioRequiredMark (astérisque champ obligatoire)`
|
||
2. `feat(ui): prop required + aria-required + astérisque sur Select/SelectCheckbox/Upload/RichText`
|
||
3. `feat(ui): astérisque required dans le label de la famille formulaire`
|
||
4. `feat(inputs): sanitisation email (suppression des espaces + option lowercase)`
|
||
5. `docs: required/astérisque + lowercase email (COMPONENTS + CHANGELOG)`
|
||
6. `docs(playground): exemples required + email lowercase` (optionnel)
|