From 3d6b7537cc9eca1d95f614f4c2fc1992eac0b508 Mon Sep 17 00:00:00 2001 From: tristan Date: Sun, 28 Jun 2026 11:00:40 +0000 Subject: [PATCH] =?UTF-8?q?feat(select)=20:=20tags=20SelectCheckbox=20rest?= =?UTF-8?q?yl=C3=A9s=20+=20props=20maxTags=20et=20color=20(MUI-49)=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## MUI-49 — Revoir le style des tags dans le multi-select ### Style des tags - Fond `m-bg`, **sans bordure**, padding `2px / 8px` - Texte **18px / weight 500 / line-height normal** - Correction du rognage des jambages (`g` de « Belgique ») sans agrandir le tag ### Nouvelles props - `maxTags` (`number`, défaut `0`) : limite le nombre de tags affichés ; au-delà un tag **`+N`** résume le surplus. `0` = tous les tags. - Champ `color?` par option : couleur de fond du tag (sinon `m-bg`). ### Divers - 5 nouveaux tests (maxTags, badge +N, color, fallback m-bg) - Playground : exemples « max tags (3) » et « couleurs » - `COMPONENTS.md` et `CHANGELOG.md` mis à jour 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://gitea.malio.fr/MALIO-DEV/malio-layer-ui/pulls/89 Co-authored-by: tristan Co-committed-by: tristan --- .../pages/composant/select/selectCheckbox.vue | 32 ++++++++++ CHANGELOG.md | 1 + COMPONENTS.md | 5 +- .../malio/select/SelectCheckbox.test.ts | 61 +++++++++++++++++++ .../malio/select/SelectCheckbox.vue | 29 +++++++-- 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/.playground/pages/composant/select/selectCheckbox.vue b/.playground/pages/composant/select/selectCheckbox.vue index 6ab5f00..d435acd 100644 --- a/.playground/pages/composant/select/selectCheckbox.vue +++ b/.playground/pages/composant/select/selectCheckbox.vue @@ -17,6 +17,27 @@ empty-option-label=" " /> +
+

Avec tag + max tags (3)

+ +
+
+

Avec tag + couleurs

+ +

Avec tag + label

>([]) const labelValue = ref>([]) const labelValue1 = ref>([]) +const maxTagsValue = ref>(['fr', 'be', 'ch', 'ca', 'de']) +const colorValue = ref>(['fr', 'be', 'de']) const selectedValue = ref>(['fr']) const hintValue = ref>([]) const errorValue = ref>([]) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d277ea..e26c0fa 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). * Calendrier (Date/DateRange/DateTime/DateWeek) : sélecteur d'année (3ᵉ niveau de navigation — jours → mois → années) et grisage des mois et années hors `min`/`max`. * MalioSidebar : slots `footer` / `footer-collapsed` pour ajouter un contenu en bas de la sidebar (profil, déconnexion, version…). Toujours collé en bas (la nav `flex-1` le pousse), reste visible quand la liste de liens scrolle ; bordure haute `m-primary` en mode déplié, à l'image du bloc logo. +* [#MUI-49] SelectCheckbox : refonte du style des tags (fond `m-bg`, sans bordure, texte 18px/500). Nouvelle prop `maxTags` (nombre max de tags affichés, `0` = tous ; au-delà un tag `+N` résume le surplus) et champ `color` optionnel par option (couleur de fond du tag, sinon `m-bg`). ### 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.) diff --git a/COMPONENTS.md b/COMPONENTS.md index 2b527ce..26bed7e 100644 --- a/COMPONENTS.md +++ b/COMPONENTS.md @@ -427,8 +427,9 @@ Liste déroulante multi-sélection avec checkboxes. | Prop | Type | Défaut | Description | |------|------|--------|-------------| | `modelValue` | `(string \| number)[]` | `[]` | Valeurs sélectionnées (v-model) | -| `options` | `{ value: string \| number, text: string }[]` | `[]` | Options | +| `options` | `{ value: string \| number, label: string, color?: string }[]` | `[]` | Options. `color` optionnel = couleur de fond du tag (sinon `m-bg`). | | `displayTag` | `boolean` | `false` | Afficher les tags sélectionnés | +| `maxTags` | `number` | `0` | Nombre max de tags affichés ; au-delà un tag `+N` résume le surplus. `0` = tous les tags. | | `displaySelectAll` | `boolean` | `false` | Afficher "Tout sélectionner" | | `selectAllLabel` | `string` | `'Tout sélectionner'` | Texte du sélecteur global | | `label` | `string` | `''` | Label | @@ -445,6 +446,8 @@ Liste déroulante multi-sélection avec checkboxes. ```vue + + ``` --- diff --git a/app/components/malio/select/SelectCheckbox.test.ts b/app/components/malio/select/SelectCheckbox.test.ts index b30f7e3..19443b8 100644 --- a/app/components/malio/select/SelectCheckbox.test.ts +++ b/app/components/malio/select/SelectCheckbox.test.ts @@ -6,6 +6,7 @@ import SelectCheckbox from './SelectCheckbox.vue' type Option = { label: string value: string | number + color?: string } type SelectCheckboxProps = { @@ -21,6 +22,7 @@ type SelectCheckboxProps = { textLabel?: string rounded?: string displayTag?: boolean + maxTags?: number displaySelectAll?: boolean selectAllLabel?: string disabled?: boolean @@ -380,4 +382,63 @@ describe('MalioSelectCheckbox', () => { expect(msg.exists()).toBe(true) expect(msg.classes()).not.toContain('min-h-[1rem]') }) + + it('affiche tous les tags par défaut (maxTags non fourni)', () => { + const wrapper = mount(SelectCheckboxForTest, { + props: {modelValue: ['fr', 'be', 'ca'], options, displayTag: true}, + }) + + expect(wrapper.find('[data-test="tags-overflow"]').exists()).toBe(false) + expect(wrapper.text()).toContain('France') + expect(wrapper.text()).toContain('Belgique') + expect(wrapper.text()).toContain('Canada') + }) + + it('limite le nombre de tags affichés et ajoute un badge +N', () => { + const manyOptions: Option[] = [ + {label: 'France', value: 'fr'}, + {label: 'Belgique', value: 'be'}, + {label: 'Canada', value: 'ca'}, + {label: 'Suisse', value: 'ch'}, + {label: 'Allemagne', value: 'de'}, + ] + const wrapper = mount(SelectCheckboxForTest, { + props: {modelValue: ['fr', 'be', 'ca', 'ch', 'de'], options: manyOptions, displayTag: true, maxTags: 3}, + }) + + expect(wrapper.text()).toContain('France') + expect(wrapper.text()).toContain('Belgique') + expect(wrapper.text()).toContain('Canada') + expect(wrapper.text()).not.toContain('Suisse') + expect(wrapper.text()).not.toContain('Allemagne') + expect(wrapper.get('[data-test="tags-overflow"]').text()).toBe('+2') + }) + + it('n’affiche pas de badge +N quand le nombre de tags est sous la limite', () => { + const wrapper = mount(SelectCheckboxForTest, { + props: {modelValue: ['fr', 'be'], options, displayTag: true, maxTags: 3}, + }) + + expect(wrapper.find('[data-test="tags-overflow"]').exists()).toBe(false) + }) + + it('applique la couleur de fond fournie par l’option', () => { + const wrapper = mount(SelectCheckboxForTest, { + props: {modelValue: ['fr'], options: [{label: 'France', value: 'fr', color: '#fde2e2'}], displayTag: true}, + }) + + const tag = wrapper.findAll('span.inline-flex')[0] + expect(tag.attributes('style')).toContain('background-color') + expect(tag.classes()).not.toContain('bg-m-bg') + }) + + it('utilise bg-m-bg quand l’option n’a pas de couleur', () => { + const wrapper = mount(SelectCheckboxForTest, { + props: {modelValue: ['fr'], options, displayTag: true}, + }) + + const tag = wrapper.findAll('span.inline-flex')[0] + expect(tag.classes()).toContain('bg-m-bg') + expect(tag.attributes('style')).toBeFalsy() + }) }) diff --git a/app/components/malio/select/SelectCheckbox.vue b/app/components/malio/select/SelectCheckbox.vue index 5c16dd5..e06c329 100644 --- a/app/components/malio/select/SelectCheckbox.vue +++ b/app/components/malio/select/SelectCheckbox.vue @@ -89,13 +89,25 @@ :class="[label ? 'pt-1' : '']" > {{ option.label }} + + +{{ hiddenTagsCount }} +
@@ -284,6 +297,7 @@ const props = withDefaults(defineProps<{ textLabel?: string rounded?: string displayTag?: boolean + maxTags?: number displaySelectAll?: boolean selectAllLabel?: string disabled?: boolean @@ -305,6 +319,7 @@ const props = withDefaults(defineProps<{ textLabel: 'text-sm', rounded: 'rounded-md', displayTag: false, + maxTags: 0, displaySelectAll: false, selectAllLabel: 'Tout sélectionner', disabled: false, @@ -347,6 +362,12 @@ const selectedOptions = computed(() => const displayTags = computed(() => props.displayTag && selectedOptions.value.length > 0, ) +const visibleTags = computed(() => + props.maxTags > 0 ? selectedOptions.value.slice(0, props.maxTags) : selectedOptions.value, +) +const hiddenTagsCount = computed(() => + props.maxTags > 0 ? Math.max(selectedOptions.value.length - props.maxTags, 0) : 0, +) const shouldFloatLabel = computed(() => isReadonly.value ? isOptionSelected.value : (isOpen.value || displayTags.value) )