Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2aded80971 |
@@ -17,6 +17,27 @@
|
|||||||
empty-option-label=" "
|
empty-option-label=" "
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="rounded-lg border p-4">
|
||||||
|
<h2 class="mb-4 text-xl font-bold">Avec tag + max tags (3)</h2>
|
||||||
|
<MalioSelectCheckbox
|
||||||
|
v-model="maxTagsValue"
|
||||||
|
:options="options"
|
||||||
|
:display-tag="true"
|
||||||
|
:max-tags="3"
|
||||||
|
label="Pays"
|
||||||
|
empty-option-label=" "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg border p-4">
|
||||||
|
<h2 class="mb-4 text-xl font-bold">Avec tag + couleurs</h2>
|
||||||
|
<MalioSelectCheckbox
|
||||||
|
v-model="colorValue"
|
||||||
|
:options="colorOptions"
|
||||||
|
:display-tag="true"
|
||||||
|
label="Pays"
|
||||||
|
empty-option-label=" "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="rounded-lg border p-4">
|
<div class="rounded-lg border p-4">
|
||||||
<h2 class="mb-4 text-xl font-bold">Avec tag + label</h2>
|
<h2 class="mb-4 text-xl font-bold">Avec tag + label</h2>
|
||||||
<MalioSelectCheckbox
|
<MalioSelectCheckbox
|
||||||
@@ -185,6 +206,15 @@ const options = [
|
|||||||
{label: 'Portugal', value: 'pt'},
|
{label: 'Portugal', value: 'pt'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const colorOptions = [
|
||||||
|
{label: 'France', value: 'fr', color: '#fde2e2'},
|
||||||
|
{label: 'Belgique', value: 'be', color: '#fff3cd'},
|
||||||
|
{label: 'Suisse', value: 'ch', color: '#d1e7dd'},
|
||||||
|
{label: 'Canada', value: 'ca', color: '#cfe2ff'},
|
||||||
|
{label: 'Allemagne', value: 'de'},
|
||||||
|
{label: 'Espagne', value: 'es', color: '#e2d9f3'},
|
||||||
|
]
|
||||||
|
|
||||||
const longOptions = [
|
const longOptions = [
|
||||||
...options,
|
...options,
|
||||||
{label: 'Pays-Bas', value: 'nl'},
|
{label: 'Pays-Bas', value: 'nl'},
|
||||||
@@ -203,6 +233,8 @@ const longOptions = [
|
|||||||
const basicValue = ref<Array<string | number>>([])
|
const basicValue = ref<Array<string | number>>([])
|
||||||
const labelValue = ref<Array<string | number>>([])
|
const labelValue = ref<Array<string | number>>([])
|
||||||
const labelValue1 = ref<Array<string | number>>([])
|
const labelValue1 = ref<Array<string | number>>([])
|
||||||
|
const maxTagsValue = ref<Array<string | number>>(['fr', 'be', 'ch', 'ca', 'de'])
|
||||||
|
const colorValue = ref<Array<string | number>>(['fr', 'be', 'de'])
|
||||||
const selectedValue = ref<Array<string | number>>(['fr'])
|
const selectedValue = ref<Array<string | number>>(['fr'])
|
||||||
const hintValue = ref<Array<string | number>>([])
|
const hintValue = ref<Array<string | number>>([])
|
||||||
const errorValue = ref<Array<string | number>>([])
|
const errorValue = ref<Array<string | number>>([])
|
||||||
|
|||||||
@@ -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).
|
* [#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`.
|
* 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.
|
* 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
|
### 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.)
|
* 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.)
|
||||||
|
|||||||
+4
-1
@@ -427,8 +427,9 @@ Liste déroulante multi-sélection avec checkboxes.
|
|||||||
| Prop | Type | Défaut | Description |
|
| Prop | Type | Défaut | Description |
|
||||||
|------|------|--------|-------------|
|
|------|------|--------|-------------|
|
||||||
| `modelValue` | `(string \| number)[]` | `[]` | Valeurs sélectionnées (v-model) |
|
| `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 |
|
| `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" |
|
| `displaySelectAll` | `boolean` | `false` | Afficher "Tout sélectionner" |
|
||||||
| `selectAllLabel` | `string` | `'Tout sélectionner'` | Texte du sélecteur global |
|
| `selectAllLabel` | `string` | `'Tout sélectionner'` | Texte du sélecteur global |
|
||||||
| `label` | `string` | `''` | Label |
|
| `label` | `string` | `''` | Label |
|
||||||
@@ -445,6 +446,8 @@ Liste déroulante multi-sélection avec checkboxes.
|
|||||||
```vue
|
```vue
|
||||||
<MalioSelectCheckbox v-model="competences" label="Compétences" :options="skills" :display-tag="true" />
|
<MalioSelectCheckbox v-model="competences" label="Compétences" :options="skills" :display-tag="true" />
|
||||||
<MalioSelectCheckbox v-model="sites" label="Sites" :options="sitesList" :display-select-all="true" />
|
<MalioSelectCheckbox v-model="sites" label="Sites" :options="sitesList" :display-select-all="true" />
|
||||||
|
<!-- maxTags : 3 tags max + badge "+N" ; color par option pour le fond du tag -->
|
||||||
|
<MalioSelectCheckbox v-model="pays" label="Pays" :options="paysColorés" :display-tag="true" :max-tags="3" />
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SelectCheckbox from './SelectCheckbox.vue'
|
|||||||
type Option = {
|
type Option = {
|
||||||
label: string
|
label: string
|
||||||
value: string | number
|
value: string | number
|
||||||
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectCheckboxProps = {
|
type SelectCheckboxProps = {
|
||||||
@@ -21,6 +22,7 @@ type SelectCheckboxProps = {
|
|||||||
textLabel?: string
|
textLabel?: string
|
||||||
rounded?: string
|
rounded?: string
|
||||||
displayTag?: boolean
|
displayTag?: boolean
|
||||||
|
maxTags?: number
|
||||||
displaySelectAll?: boolean
|
displaySelectAll?: boolean
|
||||||
selectAllLabel?: string
|
selectAllLabel?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@@ -380,4 +382,63 @@ describe('MalioSelectCheckbox', () => {
|
|||||||
expect(msg.exists()).toBe(true)
|
expect(msg.exists()).toBe(true)
|
||||||
expect(msg.classes()).not.toContain('min-h-[1rem]')
|
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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -89,13 +89,25 @@
|
|||||||
:class="[label ? 'pt-1' : '']"
|
:class="[label ? 'pt-1' : '']"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-for="option in selectedOptions"
|
v-for="option in visibleTags"
|
||||||
:key="String(option.value)"
|
:key="String(option.value)"
|
||||||
class="inline-flex max-w-full items-center rounded-md border px-2 text-sm leading-none"
|
class="inline-flex max-w-full items-center rounded-md px-2 pt-[2px] pb-0 text-lg font-medium leading-[normal]"
|
||||||
:class="disabled ? 'border-black/40 text-black/60' : 'border-black text-black'"
|
:class="[
|
||||||
|
option.color ? '' : 'bg-m-bg',
|
||||||
|
disabled ? 'text-black/60' : 'text-black',
|
||||||
|
]"
|
||||||
|
:style="option.color ? { backgroundColor: option.color } : undefined"
|
||||||
>
|
>
|
||||||
<span class="truncate pb-[2px]">{{ option.label }}</span>
|
<span class="truncate pb-[2px]">{{ option.label }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="hiddenTagsCount > 0"
|
||||||
|
data-test="tags-overflow"
|
||||||
|
class="inline-flex items-center rounded-md bg-m-bg px-2 pt-[2px] pb-0 text-lg font-medium leading-[normal]"
|
||||||
|
:class="disabled ? 'text-black/60' : 'text-black'"
|
||||||
|
>
|
||||||
|
<span class="pb-[2px]">+{{ hiddenTagsCount }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -269,7 +281,8 @@ const {keyboardFocused, onFocus: onKbdFocus, onBlur: onKbdBlur} = useKbdFocusRin
|
|||||||
|
|
||||||
type Option = {
|
type Option = {
|
||||||
label: string;
|
label: string;
|
||||||
value: string | number
|
value: string | number;
|
||||||
|
color?: string
|
||||||
}
|
}
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
modelValue?: Array<string | number>
|
modelValue?: Array<string | number>
|
||||||
@@ -284,6 +297,7 @@ const props = withDefaults(defineProps<{
|
|||||||
textLabel?: string
|
textLabel?: string
|
||||||
rounded?: string
|
rounded?: string
|
||||||
displayTag?: boolean
|
displayTag?: boolean
|
||||||
|
maxTags?: number
|
||||||
displaySelectAll?: boolean
|
displaySelectAll?: boolean
|
||||||
selectAllLabel?: string
|
selectAllLabel?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@@ -305,6 +319,7 @@ const props = withDefaults(defineProps<{
|
|||||||
textLabel: 'text-sm',
|
textLabel: 'text-sm',
|
||||||
rounded: 'rounded-md',
|
rounded: 'rounded-md',
|
||||||
displayTag: false,
|
displayTag: false,
|
||||||
|
maxTags: 0,
|
||||||
displaySelectAll: false,
|
displaySelectAll: false,
|
||||||
selectAllLabel: 'Tout sélectionner',
|
selectAllLabel: 'Tout sélectionner',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
@@ -347,6 +362,12 @@ const selectedOptions = computed(() =>
|
|||||||
const displayTags = computed(() =>
|
const displayTags = computed(() =>
|
||||||
props.displayTag && selectedOptions.value.length > 0,
|
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(() =>
|
const shouldFloatLabel = computed(() =>
|
||||||
isReadonly.value ? isOptionSelected.value : (isOpen.value || displayTags.value)
|
isReadonly.value ? isOptionSelected.value : (isOpen.value || displayTags.value)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user