diff --git a/.playground/pages/composant/divers/disabled.vue b/.playground/pages/composant/divers/disabled.vue new file mode 100644 index 0000000..47c2896 --- /dev/null +++ b/.playground/pages/composant/divers/disabled.vue @@ -0,0 +1,294 @@ + + + diff --git a/.playground/playground.nav.ts b/.playground/playground.nav.ts index c10706f..8d8d165 100644 --- a/.playground/playground.nav.ts +++ b/.playground/playground.nav.ts @@ -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'}, diff --git a/CHANGELOG.md b/CHANGELOG.md index 6271e43..5fa6dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `
  • ` (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 `
  • ` et le `` ne fige plus sa couleur (il hérite) : sinon, sur les bandes `pt-1`/`pb-1` situées hors du ``, 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`). diff --git a/app/components/malio/date/Date.test.ts b/app/components/malio/date/Date.test.ts index cf9a2b0..0ada59b 100644 --- a/app/components/malio/date/Date.test.ts +++ b/app/components/malio/date/Date.test.ts @@ -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') diff --git a/app/components/malio/date/internal/CalendarField.vue b/app/components/malio/date/internal/CalendarField.vue index 4405384..4d8af60 100644 --- a/app/components/malio/date/internal/CalendarField.vue +++ b/app/components/malio/date/internal/CalendarField.vue @@ -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' diff --git a/app/components/malio/input/InputAutocomplete.test.ts b/app/components/malio/input/InputAutocomplete.test.ts index c3e60db..ebc59fe 100644 --- a/app/components/malio/input/InputAutocomplete.test.ts +++ b/app/components/malio/input/InputAutocomplete.test.ts @@ -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}) diff --git a/app/components/malio/input/InputAutocomplete.vue b/app/components/malio/input/InputAutocomplete.vue index 362a97f..1d907c2 100644 --- a/app/components/malio/input/InputAutocomplete.vue +++ b/app/components/malio/input/InputAutocomplete.vue @@ -64,7 +64,7 @@ class="animate-spin text-m-primary" /> { 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}) diff --git a/app/components/malio/input/InputEmail.vue b/app/components/malio/input/InputEmail.vue index 7ed98dc..9af7aa5 100644 --- a/app/components/malio/input/InputEmail.vue +++ b/app/components/malio/input/InputEmail.vue @@ -41,9 +41,8 @@ />