feat(ui) : cohérence du mode disabled sur la famille formulaire
Calqué sur InputText (texte + label grisés, cursor-not-allowed, aucune affordance interactive). Quand disabled : - bouton « + » d'ajout masqué (InputPhone, InputEmail) - œil de révélation masqué (InputPassword) - chevron masqué (Select, SelectCheckbox, InputAutocomplete) - croix d'effacement déjà masquée (date, upload, time) - label en text-m-muted (Select, SelectCheckbox, CalendarField → famille Date, TimePicker, InputNumber aligné) - tags du SelectCheckbox + valeur du Select grisés ; icône horloge (TimePicker) et icône calendrier (date, même rempli) grisées Playground : page « Champs disabled » (DIVERS) en miroir de la page readonly, avec le SelectCheckbox en version tags. Tests par composant ajoutés/adaptés. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="space-y-6 p-4">
|
||||
<h1 class="text-2xl font-bold">Champs désactivés (disabled)</h1>
|
||||
<p class="text-sm text-m-muted">
|
||||
Tous les champs de formulaire dans leur état <code>disabled</code>, vides puis remplis.
|
||||
Règles : texte + label grisés, <code>cursor-not-allowed</code>, et <strong>aucune affordance
|
||||
interactive</strong> (pas de bouton « + », pas de croix « x », pas de chevron, pas d'œil ;
|
||||
tags et valeurs grisés).
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 items-start gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputText</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputText
|
||||
label="Référence (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputText
|
||||
model-value="Commande #A-2048"
|
||||
label="Référence (rempli)"
|
||||
icon-name="mdi:lock-outline"
|
||||
icon-size="20"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputEmail (addable → pas de « + »)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputEmail
|
||||
label="Adresse email (vide)"
|
||||
:addable="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputEmail
|
||||
model-value="contact@malio.fr"
|
||||
label="Adresse email (rempli)"
|
||||
:addable="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputPhone (addable → pas de « + »)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputPhone
|
||||
label="Téléphone (vide)"
|
||||
:addable="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputPhone
|
||||
model-value="+33 6 12 34 56 78"
|
||||
label="Téléphone (rempli)"
|
||||
:addable="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputPassword (pas d'œil)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputPassword
|
||||
label="Mot de passe (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputPassword
|
||||
model-value="motdepasse123"
|
||||
label="Mot de passe (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputAmount</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputAmount
|
||||
label="Montant (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputAmount
|
||||
model-value="1250.00"
|
||||
label="Montant (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputNumber</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputNumber
|
||||
label="Quantité (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputNumber
|
||||
model-value="42"
|
||||
label="Quantité (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputTextArea</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputTextArea
|
||||
label="Description (vide)"
|
||||
:size="3"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputTextArea
|
||||
model-value="Ce texte est désactivé et ne peut pas être modifié."
|
||||
label="Description (rempli)"
|
||||
:size="3"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputUpload (pas de croix)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputUpload
|
||||
label="Fichier (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputUpload
|
||||
model-value="document.pdf"
|
||||
label="Fichier (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioInputAutocomplete (pas de chevron)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioInputAutocomplete
|
||||
label="Pays (vide)"
|
||||
:options="countryOptions"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioInputAutocomplete
|
||||
model-value="de"
|
||||
label="Pays (rempli)"
|
||||
:options="countryOptions"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioSelect (pas de chevron)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioSelect
|
||||
label="Catégorie (vide)"
|
||||
:options="categoryOptions"
|
||||
empty-option-label="Aucune sélection"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioSelect
|
||||
:model-value="'a'"
|
||||
label="Catégorie (rempli)"
|
||||
:options="categoryOptions"
|
||||
empty-option-label="Aucune sélection"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioSelectCheckbox — version tags (grisés)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioSelectCheckbox
|
||||
label="Catégories (vide)"
|
||||
:options="categoryOptions"
|
||||
:display-tag="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioSelectCheckbox
|
||||
:model-value="['a', 'b', 'c']"
|
||||
label="Catégories (rempli)"
|
||||
:options="categoryOptions"
|
||||
:display-tag="true"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioDate (pas de croix)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioDate
|
||||
label="Date de naissance (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioDate
|
||||
model-value="2026-06-15"
|
||||
label="Date de naissance (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioDateTime</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioDateTime
|
||||
label="Date et heure (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioDateTime
|
||||
model-value="2026-12-25T09:30:00"
|
||||
label="Date et heure (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioDateRange</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioDateRange
|
||||
label="Période (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioDateRange
|
||||
:model-value="rangeValue"
|
||||
label="Période (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioDateWeek</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioDateWeek
|
||||
label="Semaine (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioDateWeek
|
||||
model-value="2026-W52"
|
||||
label="Semaine (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">MalioTimePicker (pas de croix)</h2>
|
||||
<div class="space-y-4">
|
||||
<MalioTimePicker
|
||||
label="Heure (vide)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<MalioTimePicker
|
||||
model-value="14:30"
|
||||
label="Heure (rempli)"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
|
||||
type Option = {label: string; value: string | number}
|
||||
|
||||
const countryOptions: Option[] = [
|
||||
{label: 'France', value: 'fr'},
|
||||
{label: 'Belgique', value: 'be'},
|
||||
{label: 'Canada', value: 'ca'},
|
||||
{label: 'Suisse', value: 'ch'},
|
||||
{label: 'Luxembourg', value: 'lu'},
|
||||
{label: 'Allemagne', value: 'de'},
|
||||
]
|
||||
|
||||
const categoryOptions: Option[] = [
|
||||
{label: 'Catégorie A', value: 'a'},
|
||||
{label: 'Catégorie B', value: 'b'},
|
||||
{label: 'Catégorie C', value: 'c'},
|
||||
]
|
||||
|
||||
const rangeValue = ref<{start: string; end: string}>({start: '2026-12-20', end: '2026-12-31'})
|
||||
</script>
|
||||
@@ -70,6 +70,7 @@ export const navSections: SidebarSection[] = [
|
||||
icon: 'mdi:dots-horizontal',
|
||||
items: [
|
||||
{label: 'Champs readonly', to: '/composant/divers/readonly'},
|
||||
{label: 'Champs disabled', to: '/composant/divers/disabled'},
|
||||
{label: 'Heure', to: '/composant/time/time'},
|
||||
{label: 'Sélecteur de site', to: '/composant/site/siteSelector'},
|
||||
{label: 'Formulaire client', to: '/composant/form/client'},
|
||||
|
||||
@@ -58,6 +58,7 @@ Liste des évolutions de la librairie Malio layer UI
|
||||
* [#MUI-45] MalioDate : prop `markedDates` (`Record<"YYYY-MM-DD", 'success' | 'danger'>`) appliquant un fond tokenisé par jour dans la grille (générique, fourni par le consommateur ; précédence sélection/`today` > variante marquée > défaut) + event `month-change` (`{ month: 0-11, year }`) émis à l'ouverture du popover et à chaque navigation de mois. Sert l'écran *Heures* de SIRH (jours validés en vert, chargement du mois visible à la volée).
|
||||
|
||||
### Changed
|
||||
* Cohérence du mode **`disabled`** sur toute la famille formulaire (calqué sur InputText : texte + label grisés, `cursor-not-allowed`, aucune affordance interactive). Concrètement, quand `disabled` : le **bouton « + »** d'ajout disparaît (InputPhone, InputEmail), l'**œil** de révélation disparaît (InputPassword), le **chevron** disparaît (Select, SelectCheckbox, InputAutocomplete), la **croix d'effacement** reste masquée (date, upload, time), le **label** passe en `text-m-muted` (Select, SelectCheckbox, famille Date via CalendarField, TimePicker), et les **tags** du SelectCheckbox + la valeur du Select passent en gris. (InputText, InputAmount, InputNumber, InputTextArea, InputRichText, Checkbox, RadioButton, InputUpload étaient déjà conformes.)
|
||||
* TabList : le nombre d'onglets visibles en mode fenêtré s'**adapte automatiquement à la largeur réelle** (mesure via `ResizeObserver` + ligne de mesure cachée), au lieu d'un `maxVisibleTabs` fixe qui pouvait faire déborder les onglets sur les chevrons. Les chevrons restent fixés aux bords et le nombre affiché est choisi pour que les onglets tiennent (pas de chevauchement ni de rognage). `maxVisibleTabs` devient un **plafond optionnel**. Calcul isolé dans une fonction pure testable (`tabFit.ts`, basée sur les largeurs réelles des onglets). Sans layout (SSR), repli sur le plafond / tous les onglets. **Breaking** : la prop `maxWidth` est supprimée (la barre utilise désormais toute la largeur disponible au lieu d'être plafonnée à 1100px).
|
||||
* TabList : au **survol** d'un onglet inactif, on applique désormais le même style que l'onglet actif — texte `m-primary` plein + barre soulignée `m-primary` (`hover:after:*`) — au lieu du discret `text-m-primary/70`, pour bien marquer la cible.
|
||||
* Sidebar : états visuels des liens de navigation — **survol** : highlight pleine largeur entièrement porté par le `<li>` (fond `m-primary` à 10 % + texte `m-primary` + semi-bold, `hover:bg-m-primary/10 hover:text-m-primary hover:font-semibold`, espacement `pt-1 pb-1`). La couleur de base (`text-black`) est aussi sur le `<li>` et le `<a>` ne fige plus sa couleur (il hérite) : sinon, sur les bandes `pt-1`/`pb-1` situées hors du `<a>`, le fond devenait bleu mais le texte restait noir. **Lien actif** : texte `m-primary` + semi-bold, sans fond (`active-class="!text-m-primary font-semibold"` ; `!important` car `active-class` est hors `twMerge`).
|
||||
|
||||
@@ -238,6 +238,23 @@ describe('MalioDate', () => {
|
||||
expect(wrapper.find('[data-test="popover"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('disabled : label grisé', () => {
|
||||
const wrapper = mountDate({disabled: true, label: 'Date'})
|
||||
expect(wrapper.get('label').classes()).toContain('text-m-muted')
|
||||
})
|
||||
|
||||
it('disabled : pas de croix d\'effacement même avec une valeur', () => {
|
||||
const wrapper = mountDate({disabled: true, modelValue: '2026-05-19'})
|
||||
expect(wrapper.find('[data-test="clear"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('disabled + rempli : icône calendrier grisée (pas noire)', () => {
|
||||
const wrapper = mountDate({disabled: true, modelValue: '2026-05-19'})
|
||||
const icon = wrapper.get('[data-test="calendar-icon"]')
|
||||
expect(icon.classes()).toContain('text-m-muted')
|
||||
expect(icon.classes()).not.toContain('text-black')
|
||||
})
|
||||
|
||||
it('does not open when readonly', async () => {
|
||||
const wrapper = mountDate({readonly: true, modelValue: '2026-05-19'})
|
||||
await wrapper.get('[data-test="date-input"]').trigger('click')
|
||||
|
||||
@@ -358,11 +358,13 @@ const mergedLabelClass = computed(() =>
|
||||
? 'text-m-danger'
|
||||
: hasSuccess.value
|
||||
? 'text-m-success'
|
||||
: isReadonly.value
|
||||
? isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
: isOpen.value
|
||||
? 'text-m-primary'
|
||||
: 'peer-placeholder-shown:text-m-muted text-black',
|
||||
: props.disabled
|
||||
? 'text-m-muted'
|
||||
: isReadonly.value
|
||||
? isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
: isOpen.value
|
||||
? 'text-m-primary'
|
||||
: 'peer-placeholder-shown:text-m-muted text-black',
|
||||
props.labelClass,
|
||||
),
|
||||
)
|
||||
@@ -370,6 +372,7 @@ const mergedLabelClass = computed(() =>
|
||||
const iconStateClass = computed(() => {
|
||||
if (hasError.value) return 'text-m-danger'
|
||||
if (hasSuccess.value) return 'text-m-success'
|
||||
if (props.disabled) return 'text-m-muted'
|
||||
if (isReadonly.value) return isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
if (isOpen.value) return 'text-m-primary'
|
||||
if (isFilled.value) return 'text-black'
|
||||
|
||||
@@ -375,6 +375,12 @@ describe('MalioInputAutocomplete', () => {
|
||||
expect(wrapper.get('input').classes()).toContain('cursor-not-allowed')
|
||||
})
|
||||
|
||||
it('hides the chevron when disabled', () => {
|
||||
const wrapper = mountComponent({disabled: true})
|
||||
|
||||
expect(wrapper.find('[data-test="chevron"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('sets readonly attribute', () => {
|
||||
const wrapper = mountComponent({readonly: true})
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
class="animate-spin text-m-primary"
|
||||
/>
|
||||
<IconifyIcon
|
||||
v-else
|
||||
v-else-if="!disabled"
|
||||
icon="mdi:chevron-down"
|
||||
:width="20"
|
||||
:height="20"
|
||||
|
||||
@@ -339,12 +339,10 @@ describe('MalioInputEmail', () => {
|
||||
expect(wrapper.emitted('add')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('does not emit add when disabled', async () => {
|
||||
it('hides the add button when disabled', () => {
|
||||
const wrapper = mountComponent({addable: true, disabled: true})
|
||||
|
||||
await wrapper.get('[data-test="add-button"]').trigger('click')
|
||||
|
||||
expect(wrapper.emitted('add')).toBeUndefined()
|
||||
expect(wrapper.find('[data-test="add-button"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not emit add when readonly', async () => {
|
||||
@@ -355,12 +353,6 @@ describe('MalioInputEmail', () => {
|
||||
expect(wrapper.emitted('add')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('disables add button when disabled', () => {
|
||||
const wrapper = mountComponent({addable: true, disabled: true})
|
||||
|
||||
expect(wrapper.get('[data-test="add-button"]').attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('add button is not natively disabled in readonly (onAdd guard blocks the action)', () => {
|
||||
const wrapper = mountComponent({addable: true, readonly: true})
|
||||
|
||||
|
||||
@@ -41,9 +41,8 @@
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="addable"
|
||||
v-if="addable && !disabled"
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
:aria-label="addButtonLabel"
|
||||
data-test="add-button"
|
||||
:class="mergedAddButtonClass"
|
||||
|
||||
@@ -205,7 +205,7 @@ const mergedLabelClass = computed(() =>
|
||||
'cursor-pointer text-black mr-4 text-[18px]',
|
||||
hasError.value ? 'text-m-danger' : '',
|
||||
hasSuccess.value ? 'text-m-success' : '',
|
||||
props.disabled ? 'cursor-not-allowed text-black/60' : '',
|
||||
props.disabled ? 'cursor-not-allowed text-m-muted' : '',
|
||||
props.labelClass,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -91,6 +91,12 @@ describe('MalioInputPassword', () => {
|
||||
expect(wrapper.find('[data-test="icon"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('hides the eye icon when disabled', () => {
|
||||
const wrapper = mountComponent({disabled: true})
|
||||
|
||||
expect(wrapper.find('[data-test="icon"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders icon by default', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</label>
|
||||
|
||||
<IconifyIcon
|
||||
v-if="displayIcon"
|
||||
v-if="displayIcon && !disabled"
|
||||
:icon="isPasswordVisible ? 'mdi:eye-outline' : 'mdi:eye-off-outline'"
|
||||
:width="24"
|
||||
:height="24"
|
||||
|
||||
@@ -253,12 +253,10 @@ describe('MalioInputPhone', () => {
|
||||
expect(wrapper.emitted('add')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('does not emit add when disabled', async () => {
|
||||
it('hides the add button when disabled', () => {
|
||||
const wrapper = mountComponent({addable: true, disabled: true})
|
||||
|
||||
await wrapper.get('[data-test="add-button"]').trigger('click')
|
||||
|
||||
expect(wrapper.emitted('add')).toBeUndefined()
|
||||
expect(wrapper.find('[data-test="add-button"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not emit add when readonly', async () => {
|
||||
@@ -269,12 +267,6 @@ describe('MalioInputPhone', () => {
|
||||
expect(wrapper.emitted('add')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('disables add button when disabled', () => {
|
||||
const wrapper = mountComponent({addable: true, disabled: true})
|
||||
|
||||
expect(wrapper.get('[data-test="add-button"]').attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('add button is not natively disabled in readonly (onAdd guard blocks the action)', () => {
|
||||
const wrapper = mountComponent({addable: true, readonly: true})
|
||||
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
/>
|
||||
|
||||
<button
|
||||
v-if="addable"
|
||||
v-if="addable && !disabled"
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
:aria-label="addButtonLabel"
|
||||
data-test="add-button"
|
||||
:class="mergedAddButtonClass"
|
||||
|
||||
@@ -237,12 +237,21 @@ describe('MalioSelect', () => {
|
||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-black')
|
||||
})
|
||||
|
||||
it('shows muted chevron color when disabled', () => {
|
||||
it('hides the chevron when disabled', () => {
|
||||
const wrapper = mount(SelectForTest, {
|
||||
props: {modelValue: 'fr', options, disabled: true},
|
||||
})
|
||||
|
||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-muted')
|
||||
expect(wrapper.find('[data-test="chevron"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('greys the label and the selected value when disabled', () => {
|
||||
const wrapper = mount(SelectForTest, {
|
||||
props: {modelValue: 'fr', label: 'Pays', options, disabled: true},
|
||||
})
|
||||
|
||||
expect(wrapper.get('label').classes()).toContain('text-m-muted')
|
||||
expect(wrapper.get('button span.block').classes()).toContain('text-black/60')
|
||||
})
|
||||
|
||||
it('shows danger chevron color on error even when open', async () => {
|
||||
|
||||
@@ -65,15 +65,17 @@
|
||||
? 'text-m-danger'
|
||||
: hasSuccess
|
||||
? 'text-m-success'
|
||||
: isReadonly
|
||||
? isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted'
|
||||
: isOpen
|
||||
? 'text-m-primary'
|
||||
: isOptionSelected
|
||||
: disabled
|
||||
? 'text-m-muted'
|
||||
: isReadonly
|
||||
? isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted',
|
||||
: 'text-m-muted'
|
||||
: isOpen
|
||||
? 'text-m-primary'
|
||||
: isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted',
|
||||
textLabel,
|
||||
]"
|
||||
:style="labelTransformStyle"
|
||||
@@ -85,13 +87,14 @@
|
||||
class="block truncate"
|
||||
:class="[
|
||||
textValue,
|
||||
isOptionSelected ? 'text-black' : 'select-none text-transparent'
|
||||
isOptionSelected ? (disabled ? 'text-black/60' : 'text-black') : 'select-none text-transparent'
|
||||
]"
|
||||
>
|
||||
{{ selectedLabel || '\u00A0' }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="!disabled"
|
||||
data-test="chevron"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
:class="[
|
||||
|
||||
@@ -227,12 +227,23 @@ describe('MalioSelectCheckbox', () => {
|
||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-black')
|
||||
})
|
||||
|
||||
it('shows muted chevron color when disabled', () => {
|
||||
it('hides the chevron when disabled', () => {
|
||||
const wrapper = mount(SelectCheckboxForTest, {
|
||||
props: {modelValue: ['fr'], options, disabled: true},
|
||||
})
|
||||
|
||||
expect(wrapper.get('[data-test="chevron"]').classes()).toContain('text-m-muted')
|
||||
expect(wrapper.find('[data-test="chevron"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('greys the label and the tags when disabled', () => {
|
||||
const wrapper = mount(SelectCheckboxForTest, {
|
||||
props: {modelValue: ['fr'], label: 'Pays', options, displayTag: true, disabled: true},
|
||||
})
|
||||
|
||||
expect(wrapper.get('label').classes()).toContain('text-m-muted')
|
||||
const tag = wrapper.findAll('span.inline-flex')[0]
|
||||
expect(tag.classes()).toContain('text-black/60')
|
||||
expect(tag.classes()).not.toContain('text-black')
|
||||
})
|
||||
|
||||
it('shows danger chevron color on error even when open', async () => {
|
||||
|
||||
@@ -65,15 +65,17 @@
|
||||
? 'text-m-danger'
|
||||
: hasSuccess
|
||||
? 'text-m-success'
|
||||
: isReadonly
|
||||
? isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted'
|
||||
: isOpen
|
||||
? 'text-m-primary'
|
||||
: isOptionSelected
|
||||
: disabled
|
||||
? 'text-m-muted'
|
||||
: isReadonly
|
||||
? isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted',
|
||||
: 'text-m-muted'
|
||||
: isOpen
|
||||
? 'text-m-primary'
|
||||
: isOptionSelected
|
||||
? 'text-black'
|
||||
: 'text-m-muted',
|
||||
textLabel,
|
||||
]"
|
||||
:style="labelTransformStyle"
|
||||
@@ -89,7 +91,8 @@
|
||||
<span
|
||||
v-for="option in selectedOptions"
|
||||
:key="String(option.value)"
|
||||
class="inline-flex max-w-full items-center rounded-md border border-black px-2 text-sm leading-none text-black"
|
||||
class="inline-flex max-w-full items-center rounded-md border px-2 text-sm leading-none"
|
||||
:class="disabled ? 'border-black/40 text-black/60' : 'border-black text-black'"
|
||||
>
|
||||
<span class="truncate pb-[2px]">{{ option.label }}</span>
|
||||
</span>
|
||||
@@ -113,13 +116,14 @@
|
||||
:class="[
|
||||
textValue,
|
||||
label ? 'pl-24' : '',
|
||||
isOptionSelected ? 'text-black' : 'text-m-muted'
|
||||
disabled ? 'text-black/60' : isOptionSelected ? 'text-black' : 'text-m-muted'
|
||||
]"
|
||||
>
|
||||
{{ selectionSummary }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="!disabled"
|
||||
data-test="chevron"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
:class="[
|
||||
|
||||
@@ -53,6 +53,14 @@ describe('MalioTimePicker', () => {
|
||||
expect(wrapper.find('[data-test="popover"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('disabled + rempli : label et icône horloge grisés', () => {
|
||||
const wrapper = mountPicker({disabled: true, label: 'Heure', modelValue: '14:30'})
|
||||
expect(wrapper.get('label').classes()).toContain('text-m-muted')
|
||||
const icon = wrapper.get('[data-test="clock-icon"]')
|
||||
expect(icon.classes()).toContain('text-m-muted')
|
||||
expect(icon.classes()).not.toContain('text-black')
|
||||
})
|
||||
|
||||
it('n\'ouvre pas le popover si readonly', async () => {
|
||||
const wrapper = mountPicker({readonly: true})
|
||||
await wrapper.get('[data-test="time-field"]').trigger('click')
|
||||
|
||||
@@ -219,11 +219,13 @@ const mergedLabelClass = computed(() =>
|
||||
? 'text-m-danger'
|
||||
: hasSuccess.value
|
||||
? 'text-m-success'
|
||||
: isReadonly.value
|
||||
? isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
: isOpen.value
|
||||
? 'text-m-primary'
|
||||
: 'text-black peer-placeholder-shown:text-m-muted',
|
||||
: props.disabled
|
||||
? 'text-m-muted'
|
||||
: isReadonly.value
|
||||
? isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
: isOpen.value
|
||||
? 'text-m-primary'
|
||||
: 'text-black peer-placeholder-shown:text-m-muted',
|
||||
props.labelClass,
|
||||
),
|
||||
)
|
||||
@@ -231,6 +233,7 @@ const mergedLabelClass = computed(() =>
|
||||
const iconStateClass = computed(() => {
|
||||
if (hasError.value) return 'text-m-danger'
|
||||
if (hasSuccess.value) return 'text-m-success'
|
||||
if (props.disabled) return 'text-m-muted'
|
||||
if (isReadonly.value) return isFilled.value ? 'text-black' : 'text-m-muted'
|
||||
if (isOpen.value) return 'text-m-primary'
|
||||
if (isFilled.value) return 'text-black'
|
||||
|
||||
Reference in New Issue
Block a user