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>
This commit was merged in pull request #60.
This commit is contained in:
@@ -36,6 +36,23 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputAmount
|
||||
label="Montant (readonly vide)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputAmount
|
||||
v-model="readonlyFilledAmount"
|
||||
label="Montant (readonly rempli)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Erreur et succès</h2>
|
||||
<div class="mt-4">
|
||||
@@ -57,4 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const readonlyFilledAmount = ref('1250.00')
|
||||
</script>
|
||||
|
||||
@@ -82,6 +82,25 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputAutocomplete
|
||||
label="Pays (readonly vide)"
|
||||
:options="staticOptions"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputAutocomplete
|
||||
v-model="readonlyFilledAutocomplete"
|
||||
label="Pays (readonly rempli)"
|
||||
:options="staticOptions"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Avec hint</h2>
|
||||
<MalioInputAutocomplete
|
||||
@@ -140,6 +159,7 @@ const staticOptions: Option[] = [
|
||||
{label: 'Italie', value: 'it'},
|
||||
]
|
||||
|
||||
const readonlyFilledAutocomplete = ref<string | number | null>('de')
|
||||
const simpleValue = ref<string | number | null>(null)
|
||||
const leftIconValue = ref<string | number | null>(null)
|
||||
const createValue = ref<string | number | null>(null)
|
||||
|
||||
@@ -48,6 +48,23 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputEmail
|
||||
label="Adresse email (readonly vide)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputEmail
|
||||
v-model="readonlyFilledEmail"
|
||||
label="Adresse email (readonly rempli)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Avec hint</h2>
|
||||
<MalioInputEmail
|
||||
@@ -84,14 +101,35 @@
|
||||
:success="dynamicSuccess"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Email obligatoire</h2>
|
||||
<MalioInputEmail
|
||||
v-model="requiredEmail"
|
||||
label="Email obligatoire"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Email normalisé (minuscules)</h2>
|
||||
<MalioInputEmail
|
||||
v-model="lowercaseEmail"
|
||||
label="Email normalisé (minuscules)"
|
||||
:lowercase="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const readonlyFilledEmail = ref('contact@malio.fr')
|
||||
const emailValue = ref('')
|
||||
const dynamicEmail = ref('')
|
||||
const requiredEmail = ref('')
|
||||
const lowercaseEmail = ref('')
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const isDynamicValid = computed(() => emailRegex.test(dynamicEmail.value))
|
||||
|
||||
@@ -41,6 +41,23 @@
|
||||
/>
|
||||
</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
|
||||
@@ -83,6 +100,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const readonlyFilledPassword = ref('motdepasse123')
|
||||
const passwordValue = ref('')
|
||||
const dynamicPassword = ref('')
|
||||
|
||||
|
||||
@@ -73,6 +73,23 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputPhone
|
||||
label="Téléphone (readonly vide)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputPhone
|
||||
v-model="readonlyFilledPhone"
|
||||
label="Téléphone (readonly rempli)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Avec hint</h2>
|
||||
<MalioInputPhone
|
||||
@@ -121,6 +138,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const readonlyFilledPhone = ref('+33 6 12 34 56 78')
|
||||
const phoneValue = ref('')
|
||||
const phoneAddable = ref('')
|
||||
const phoneFrench = ref('')
|
||||
|
||||
@@ -108,6 +108,33 @@
|
||||
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
|
||||
@@ -154,6 +181,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
const readonlyFilledValue = ref('Commande #A-2048')
|
||||
const nameValue = ref('')
|
||||
const searchValue = ref('')
|
||||
const codeValue = ref('')
|
||||
|
||||
@@ -61,6 +61,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputTextArea
|
||||
label="Description (readonly vide)"
|
||||
:readonly="true"
|
||||
:size="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputTextArea
|
||||
v-model="readonlyFilledTextArea"
|
||||
label="Description (readonly rempli)"
|
||||
:readonly="true"
|
||||
:size="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Resize avec limites</h2>
|
||||
<MalioInputTextArea
|
||||
@@ -94,6 +113,7 @@
|
||||
import {ref} from 'vue'
|
||||
import MalioInputTextArea from '../../../../app/components/malio/input/InputTextArea.vue'
|
||||
|
||||
const readonlyFilledTextArea = ref('Ce texte est en lecture seule et ne peut pas être modifié.')
|
||||
const hintValue = ref('')
|
||||
const iconValue = ref('')
|
||||
const errorValue = ref('abc')
|
||||
|
||||
@@ -31,6 +31,23 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly vide)</h2>
|
||||
<MalioInputUpload
|
||||
label="Fichier (readonly vide)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Readonly (readonly rempli)</h2>
|
||||
<MalioInputUpload
|
||||
v-model="readonlyFilledUpload"
|
||||
label="Fichier (readonly rempli)"
|
||||
:readonly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Avec hint</h2>
|
||||
<MalioInputUpload
|
||||
@@ -74,6 +91,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const readonlyFilledUpload = ref('document.pdf')
|
||||
const uploadValue = ref('')
|
||||
const dynamicUpload = ref('')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user