Files
malio-layer-ui/.playground/pages/composant/input/inputPassword.vue
T
tristan 887ebdebd7 feat(ui) : required cohérent + astérisque label + sanitisation email (MUI-41) (#60)
## Résumé (MUI-41)

Harmonise l'état « obligatoire » des composants de formulaire et normalise le champ email.

### `required` + astérisque
- Nouveau composant partagé `MalioRequiredMark` : astérisque rouge (`text-m-danger`, **16px**), `aria-hidden`.
- Prop `required` désormais cohérente sur toute la famille formulaire ; quand vraie, l'astérisque s'affiche **dans le label**.
- Prop ajoutée à `Select`, `SelectCheckbox`, `InputUpload`, `InputRichText` (les autres l'avaient déjà).
- Accessibilité : `required` natif là où l'élément le supporte, sinon `aria-required` (Select/SelectCheckbox sur le `<button>`, RichText sur le wrapper éditeur, Upload sur le champ visible).
- `MalioSiteSelector` **exclu** volontairement (segmented control, pas de label de champ).

### Sanitisation email (`MalioInputEmail`)
- Suppression de **tous les espaces** à la saisie (pas de masque).
- Nouvelle prop opt-in `lowercase` (défaut `false`) : normalise en minuscules à la frappe (cohérent RG-1.21 Starseed).
- Garde défensive curseur : l'API de sélection est interdite sur `type="email"` → repositionnement best-effort sans jamais lever.
- La validation de format reste à la couche `error`.

### Docs & playground
- `COMPONENTS.md` (doc `required` cohérente + note famille + `lowercase`) et `CHANGELOG.md` mis à jour.
- Exemples playground `required` et email `lowercase` ajoutés.

## Test plan
- [x] Suite complète : 42 fichiers / 771 tests verts
- [x] Lint : 0 erreur
- [x] Tests `aria-required` sur Select/SelectCheckbox/RichText
- [ ] Vérif visuelle playground : astérisque 16px dans le label, email qui retire les espaces / minuscule

Spec & plan : `docs/superpowers/specs/` et `docs/superpowers/plans/`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #60
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-04 06:42:19 +00:00

118 lines
3.2 KiB
Vue

<template>
<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">Simple</h2>
<MalioInputPassword />
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec label</h2>
<MalioInputPassword
v-model="passwordValue"
label="Mot de passe"
name="password"
autocomplete="current-password"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Sans icône</h2>
<MalioInputPassword
label="Mot de passe"
:display-icon="false"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Désactivé</h2>
<MalioInputPassword
model-value="motdepasse123"
disabled
label="Mot de passe"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Readonly</h2>
<MalioInputPassword
model-value="lectureseule"
readonly
label="Mot de passe"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
<MalioInputPassword
label="Mot de passe (readonly vide)"
:readonly="true"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
<MalioInputPassword
v-model="readonlyFilledPassword"
label="Mot de passe (readonly rempli)"
:readonly="true"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Avec hint</h2>
<MalioInputPassword
label="Mot de passe"
hint="8 caractères minimum"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Erreur</h2>
<MalioInputPassword
model-value="abc"
label="Mot de passe"
error="Le mot de passe est trop court"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Succès</h2>
<MalioInputPassword
model-value="motdepasse123"
label="Mot de passe"
success="Mot de passe valide"
/>
</div>
<div class="rounded-lg border p-4">
<h2 class="mb-4 text-xl font-bold">Validation dynamique</h2>
<MalioInputPassword
v-model="dynamicPassword"
label="Mot de passe"
hint="8 caractères minimum, 1 majuscule, 1 chiffre"
:error="dynamicError"
:success="dynamicSuccess"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const readonlyFilledPassword = ref('motdepasse123')
const passwordValue = ref('')
const dynamicPassword = ref('')
const passwordRegex = /^(?=.*[A-Z])(?=.*\d).{8,}$/
const isDynamicValid = computed(() => passwordRegex.test(dynamicPassword.value))
const dynamicError = computed(() => {
if (!dynamicPassword.value) return ''
return isDynamicValid.value ? '' : 'Mot de passe invalide'
})
const dynamicSuccess = computed(() => {
if (!dynamicPassword.value) return ''
return isDynamicValid.value ? 'Mot de passe valide' : ''
})
</script>