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>
202 lines
5.7 KiB
Vue
202 lines
5.7 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>
|
|
<MalioSelect
|
|
v-model="basicValue"
|
|
:options="options"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Avec label</h2>
|
|
<MalioSelect
|
|
v-model="labelValue"
|
|
:options="options"
|
|
label="Pays"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Valeur preselectionnee</h2>
|
|
<MalioSelect
|
|
v-model="selectedValue"
|
|
:options="options"
|
|
label="Pays"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Hint</h2>
|
|
<MalioSelect
|
|
v-model="hintValue"
|
|
:options="options"
|
|
label="Pays"
|
|
hint="Choisissez votre pays"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Erreur</h2>
|
|
<MalioSelect
|
|
v-model="errorValue"
|
|
:options="options"
|
|
label="Pays"
|
|
error="Ce champ est obligatoire"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Succes</h2>
|
|
<MalioSelect
|
|
v-model="successValue"
|
|
:options="options"
|
|
label="Pays"
|
|
success="Selection validee"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Desactive</h2>
|
|
<MalioSelect
|
|
v-model="disabledValue"
|
|
:options="options"
|
|
label="Pays"
|
|
disabled
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Sans options</h2>
|
|
<MalioSelect
|
|
v-model="emptyValue"
|
|
label="Pays"
|
|
empty-option-label="Aucun pays disponible"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Sélection obligatoire</h2>
|
|
<MalioSelect
|
|
v-model="requiredValue"
|
|
:options="options"
|
|
label="Sélection obligatoire"
|
|
:required="true"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Peu d'elements (2)</h2>
|
|
<MalioSelect
|
|
v-model="shortListValue"
|
|
:options="shortOptions"
|
|
label="Civilite"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Lecture seule (vide)</h2>
|
|
<MalioSelect
|
|
v-model="readonlyEmptyValue"
|
|
:options="options"
|
|
label="Pays"
|
|
empty-option-label="Aucune selection"
|
|
:readonly="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4">
|
|
<h2 class="mb-4 text-xl font-bold">Lecture seule (rempli)</h2>
|
|
<MalioSelect
|
|
v-model="readonlyFilledValue"
|
|
:options="options"
|
|
label="Pays"
|
|
empty-option-label="Aucune selection"
|
|
:readonly="true"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4 md:col-span-2">
|
|
<h2 class="mb-4 text-xl font-bold">Liste longue</h2>
|
|
<MalioSelect
|
|
v-model="longListValue"
|
|
:options="longOptions"
|
|
label="Pays"
|
|
hint="Permet de verifier la scrollbar"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
|
|
<div class="rounded-lg border p-4 md:col-span-2">
|
|
<h2 class="mb-4 text-xl font-bold">Ouverture en bas de page</h2>
|
|
<div class="h-64" />
|
|
<MalioSelect
|
|
v-model="bottomValue"
|
|
:options="longOptions"
|
|
label="Ouverture adaptative"
|
|
hint="A ouvrir pres du bas de la page"
|
|
empty-option-label="Aucune selection"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {ref} from 'vue'
|
|
|
|
const options = [
|
|
{label: 'France', value: 'fr'},
|
|
{label: 'Belgique', value: 'be'},
|
|
{label: 'Suisse', value: 'ch'},
|
|
{label: 'Canada', value: 'ca'},
|
|
{label: 'Allemagne', value: 'de'},
|
|
{label: 'Espagne', value: 'es'},
|
|
{label: 'Italie', value: 'it'},
|
|
{label: 'Portugal', value: 'pt'},
|
|
]
|
|
|
|
const shortOptions = [
|
|
{label: 'Monsieur', value: 'M'},
|
|
{label: 'Madame', value: 'Mme'},
|
|
]
|
|
|
|
const longOptions = [
|
|
...options,
|
|
{label: 'Pays-Bas', value: 'nl'},
|
|
{label: 'Suede', value: 'se'},
|
|
{label: 'Norvege', value: 'no'},
|
|
{label: 'Danemark', value: 'dk'},
|
|
{label: 'Finlande', value: 'fi'},
|
|
{label: 'Autriche', value: 'at'},
|
|
{label: 'Irlande', value: 'ie'},
|
|
{label: 'Grece', value: 'gr'},
|
|
{label: 'Pologne', value: 'pl'},
|
|
{label: 'Hongrie', value: 'hu'},
|
|
{label: 'Republique tcheque', value: 'cz'},
|
|
]
|
|
|
|
const requiredValue = ref<string | number | null>(null)
|
|
const basicValue = ref<string | number | null>(null)
|
|
const labelValue = ref<string | number | null>(null)
|
|
const selectedValue = ref<string | number | null>('fr')
|
|
const hintValue = ref<string | number | null>(null)
|
|
const errorValue = ref<string | number | null>(null)
|
|
const successValue = ref<string | number | null>('be')
|
|
const disabledValue = ref<string | number | null>('ca')
|
|
const emptyValue = ref<string | number | null>(null)
|
|
const shortListValue = ref<string | number | null>(null)
|
|
const longListValue = ref<string | number | null>(null)
|
|
const bottomValue = ref<string | number | null>(null)
|
|
const readonlyEmptyValue = ref<string | number | null>(null)
|
|
const readonlyFilledValue = ref<string | number | null>('fr')
|
|
</script>
|