887ebdebd7
## 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>
213 lines
5.8 KiB
Vue
213 lines
5.8 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>
|
|
<MalioInputText
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Avec label</h2>
|
|
<MalioInputText
|
|
v-model="nameValue"
|
|
label="Nom d'utilisateur"
|
|
name="username"
|
|
autocomplete="username"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Avec icône à droite</h2>
|
|
<MalioInputText
|
|
v-model="searchValue"
|
|
label="Recherche"
|
|
icon-name="mdi:magnify"
|
|
icon-size="20"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Avec icône à gauche</h2>
|
|
<MalioInputText
|
|
label="Recherche"
|
|
icon-name="mdi:magnify"
|
|
icon-size="20"
|
|
icon-position="left"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Désactivé</h2>
|
|
<MalioInputText
|
|
model-value="Valeur verrouillée"
|
|
disabled
|
|
label="Champ désactivé"
|
|
/>
|
|
<MalioInputText
|
|
disabled
|
|
label="Champ désactivé"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Readonly</h2>
|
|
<MalioInputText
|
|
model-value="Lecture seule"
|
|
readonly
|
|
label="Champ readonly"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Hint + icône</h2>
|
|
<MalioInputText
|
|
v-model="cityValue"
|
|
label="Ville"
|
|
icon-name="mdi:map-marker-outline"
|
|
icon-size="20"
|
|
hint="Commencez à taper le nom de la ville"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Erreur + icône</h2>
|
|
<MalioInputText
|
|
model-value="ab"
|
|
label="Code promo"
|
|
icon-name="mdi:alert-circle-outline"
|
|
icon-size="20"
|
|
error="Le code est invalide"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Succès + icône</h2>
|
|
<MalioInputText
|
|
label="Code"
|
|
success="Code valide"
|
|
icon-name="mdi:alert-circle-outline"
|
|
icon-size="20"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Readonly + icône</h2>
|
|
<MalioInputText
|
|
model-value="Commande #A-2048"
|
|
label="Référence"
|
|
readonly
|
|
icon-name="mdi:lock-outline"
|
|
icon-size="20"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Désactivé + icône</h2>
|
|
<MalioInputText
|
|
model-value="Compte indisponible"
|
|
label="Compte"
|
|
disabled
|
|
icon-name="mdi:account-off-outline"
|
|
icon-size="20"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
|
<MalioInputText
|
|
label="Référence (readonly vide)"
|
|
:readonly="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
|
<MalioInputText
|
|
v-model="readonlyFilledValue"
|
|
label="Référence (readonly rempli)"
|
|
icon-name="mdi:lock-outline"
|
|
icon-size="20"
|
|
:readonly="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Champ obligatoire</h2>
|
|
<MalioInputText
|
|
label="Champ obligatoire"
|
|
:required="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Avec masque</h2>
|
|
<MalioInputText
|
|
label="Plaque d'immatriculation"
|
|
:mask="maskOptions"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Code dynamique</h2>
|
|
<MalioInputText
|
|
v-model="dynamicCodeValue"
|
|
label="Code d'accès"
|
|
hint="Format attendu: 6 à 10 caractères alphanumériques"
|
|
:error="dynamicCodeError"
|
|
:success="dynamicCodeSuccess"
|
|
icon-name="mdi:key-outline"
|
|
icon-size="20"
|
|
/>
|
|
</div>
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Hint,erreur et succès</h2>
|
|
<MalioInputText
|
|
v-model="codeValue"
|
|
label="Code"
|
|
hint="Format attendu: 6 à 10 caractères alphanumériques"
|
|
/>
|
|
<div class="mt-4">
|
|
<MalioInputText
|
|
model-value="invalide"
|
|
label="Code"
|
|
error="Le code doit contenir au moins 6 caractères"
|
|
/>
|
|
</div>
|
|
<div class="mt-4">
|
|
<MalioInputText
|
|
model-value="valide"
|
|
label="Code"
|
|
success="Le code est valide"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
const readonlyFilledValue = ref('Commande #A-2048')
|
|
const nameValue = ref('')
|
|
const searchValue = ref('')
|
|
const codeValue = ref('')
|
|
const cityValue = ref('')
|
|
const dynamicCodeValue = ref('')
|
|
|
|
const codeRegex = /^[A-Z0-9]{6,10}$/
|
|
const normalizedDynamicCode = computed(() => dynamicCodeValue.value.toUpperCase())
|
|
const isDynamicCodeValid = computed(() => codeRegex.test(normalizedDynamicCode.value))
|
|
const dynamicCodeError = computed(() => {
|
|
if (!dynamicCodeValue.value) return ''
|
|
return isDynamicCodeValid.value ? '' : 'Code invalide (6 à 10 caractères alphanumériques)'
|
|
})
|
|
const dynamicCodeSuccess = computed(() => {
|
|
if (!dynamicCodeValue.value) return ''
|
|
return isDynamicCodeValid.value ? 'Code valide' : ''
|
|
})
|
|
|
|
const maskOptions = {
|
|
mask: '@@-###-@@',
|
|
tokens: {
|
|
'@': {
|
|
pattern: /[A-Za-z]/,
|
|
transform: (char: string) => char.toUpperCase()
|
|
}
|
|
}
|
|
}
|
|
</script>
|