2 Commits

Author SHA1 Message Date
a95cf8cdfb fix: select checkbox (#35)
All checks were successful
Release / release (push) Successful in 1m10s
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr>
Reviewed-on: #35
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-27 10:09:24 +00:00
ba2ecb5768 fix: suppression de la marge top sur la Checkbox (#34)
All checks were successful
Release / release (push) Successful in 1m12s
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Co-authored-by: kevin <kevin@yuno.malio.fr>
Co-authored-by: Kevin Boudet <kevin@yuno.malio.fr>
Reviewed-on: #34
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-04-27 09:31:14 +00:00
3 changed files with 46 additions and 21 deletions

View File

@@ -94,7 +94,7 @@ const describedBy = computed(() => {
const mergedGroupClass = computed(() =>
twMerge(
'checkbox-wrapper-4 mt-4 w-full',
'checkbox-wrapper-4 w-full',
props.groupClass,
),
)

View File

@@ -26,6 +26,7 @@ type SelectCheckboxProps = {
displaySelectAll?: boolean
selectAllLabel?: string
disabled?: boolean
groupClass?: string
}
const SelectCheckboxForTest = SelectCheckbox as DefineComponent<SelectCheckboxProps>
@@ -175,4 +176,21 @@ describe('MalioSelectCheckbox', () => {
const checkboxes = wrapper.findAll('input[type="checkbox"]')
expect((checkboxes[0].element as HTMLInputElement).checked).toBe(false)
})
it('applies minWidth via twMerge so it overrides w-full (parity with MalioSelect)', () => {
const wrapper = mount(SelectCheckboxForTest, {
props: {modelValue: [], options: [], minWidth: 'w-80'},
})
const root = wrapper.find('button').element.parentElement
expect(root?.className).toContain('w-80')
expect(root?.className).not.toContain('w-full')
})
it('applies groupClass via twMerge', () => {
const wrapper = mount(SelectCheckboxForTest, {
props: {modelValue: [], options: [], groupClass: 'mt-4'},
})
const root = wrapper.find('button').element.parentElement
expect(root?.className).toContain('mt-4')
})
})

View File

@@ -2,8 +2,7 @@
<div>
<div
ref="root"
class="relative w-full"
:class="[minWidth, maxWidth]"
:class="mergedGroupClass"
>
<button
:id="buttonId"
@@ -26,7 +25,7 @@
? openDirection === 'down'
? 'rounded-b-none !border-2 !border-m-primary !border-b-0'
: 'rounded-t-none !border-2 !border-m-primary !border-t-0'
: isOptionSelected
: isOptionSelected
? 'border-black'
: 'border-m-muted',
disabled ? 'cursor-not-allowed border-m-muted text-black/60' : 'cursor-pointer',
@@ -45,7 +44,7 @@
v-if="label"
class="floating-label pointer-events-none absolute left-3 inline-block origin-left transition-transform duration-150 font-medium"
:class="[
shouldFloatLabel ? 'top-2 z-30' : 'top-1/2 -translate-y-1/2',
isOpen ? 'top-2 z-30' : 'top-2',
hasError
? 'text-m-danger'
: hasSuccess
@@ -206,6 +205,7 @@
<script setup lang="ts">
import {computed, onBeforeUnmount, onMounted, ref, useId, nextTick} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue'
import {twMerge} from 'tailwind-merge'
import Checkbox from '../checkbox/Checkbox.vue'
defineOptions({name: 'MalioSelectCheckbox', inheritAttrs: false})
@@ -232,6 +232,7 @@ const props = withDefaults(defineProps<{
displaySelectAll?: boolean
selectAllLabel?: string
disabled?: boolean
groupClass?: string
}>(), {
options: () => [],
emptyOptionLabel: '',
@@ -249,6 +250,7 @@ const props = withDefaults(defineProps<{
displaySelectAll: false,
selectAllLabel: 'Tout sélectionner',
disabled: false,
groupClass: '',
})
const emit = defineEmits<{
@@ -264,6 +266,9 @@ const listboxId = `custom-select-listbox-${uid}`
const listRef = ref<HTMLElement | null>(null)
const listHeight = ref(0)
const normalizedOptions = computed<Option[]>(() => props.options)
const mergedGroupClass = computed(() =>
twMerge('relative w-full', props.minWidth, props.maxWidth, props.groupClass),
)
const hasError = computed(() => !!props.error)
const hasSuccess = computed(() => !!props.success && !hasError.value)
const isOptionSelected = computed(() =>
@@ -281,6 +286,10 @@ const shouldFloatLabel = computed(() =>
const selectionSummary = computed(() =>
`${props.modelValue.length}/${normalizedOptions.value.length}`
)
const allSelected = computed(() =>
normalizedOptions.value.length > 0
&& normalizedOptions.value.every(opt => props.modelValue.includes(opt.value)),
)
const describedBy = computed(() =>
(hasError.value || hasSuccess.value || !!props.hint) ? `${buttonId}-describedby` : undefined,
)
@@ -320,18 +329,22 @@ function open() {
}
const labelTransformStyle = computed(() => {
// label non flottant
if (!shouldFloatLabel.value) {
return undefined
return {}
}
// fermé ou ouverture vers le bas : comportement classique
if (!isOpen.value || openDirection.value === 'down') {
return {
transform: 'translateY(-1.15rem) scale(0.9)',
}
}
// ouverture vers le haut : on remonte en fonction de la hauteur de la liste
const extraOffset = 8 // marge visuelle au-dessus de la liste en px
const total = 4 + listHeight.value + extraOffset
const total = 4 +listHeight.value + extraOffset
// 18 ≈ 1.15rem pour garder la même base que votre flottant actuel
return {
transform: `translateY(-${total}px) scale(0.9)`,
@@ -351,19 +364,6 @@ function toggle() {
open()
}
const allSelected = computed(() =>
normalizedOptions.value.length > 0
&& normalizedOptions.value.every(opt => props.modelValue.includes(opt.value)),
)
function toggleAll() {
if (allSelected.value) {
emit('update:modelValue', [])
} else {
emit('update:modelValue', normalizedOptions.value.map(opt => opt.value))
}
}
function isChecked(value: string | number) {
return props.modelValue.includes(value)
}
@@ -373,10 +373,17 @@ function toggleOption(value: string | number) {
emit('update:modelValue', props.modelValue.filter(item => item !== value))
return
}
emit('update:modelValue', [...props.modelValue, value])
}
function toggleAll() {
if (allSelected.value) {
emit('update:modelValue', [])
} else {
emit('update:modelValue', normalizedOptions.value.map(opt => opt.value))
}
}
function onClickOutside(e: MouseEvent) {
if (!root.value) return
if (!root.value.contains(e.target as Node)) close()