feat : barre header, slot #header, bouton fermer et ARIA du MalioDrawer
This commit is contained in:
@@ -74,4 +74,75 @@ describe('MalioDrawer', () => {
|
|||||||
const wrapper = mountComponent({ modelValue: true, drawerClass: 'max-w-2xl' })
|
const wrapper = mountComponent({ modelValue: true, drawerClass: 'max-w-2xl' })
|
||||||
expect(wrapper.find('[data-test="panel"]').classes()).toContain('max-w-2xl')
|
expect(wrapper.find('[data-test="panel"]').classes()).toContain('max-w-2xl')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders the #header slot inside the header bar', () => {
|
||||||
|
const wrapper = mountComponent(
|
||||||
|
{ modelValue: true },
|
||||||
|
{ header: '<h2 data-test="title">Titre</h2>' },
|
||||||
|
)
|
||||||
|
expect(wrapper.find('[data-test="header"] [data-test="title"]').text()).toBe('Titre')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the header bar when showClose is true even without #header', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true })
|
||||||
|
expect(wrapper.find('[data-test="header"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render the header bar when no #header and showClose is false', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true, showClose: false })
|
||||||
|
expect(wrapper.find('[data-test="header"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the close button by default', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true })
|
||||||
|
expect(wrapper.find('[data-test="close-button"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides the close button when showClose is false', () => {
|
||||||
|
const wrapper = mountComponent(
|
||||||
|
{ modelValue: true, showClose: false },
|
||||||
|
{ header: '<h2>Titre</h2>' },
|
||||||
|
)
|
||||||
|
expect(wrapper.find('[data-test="close-button"]').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('close button renders mdi:cancel-bold icon', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true })
|
||||||
|
const icon = wrapper.findComponent(IconifyIcon)
|
||||||
|
expect(icon.props('icon')).toBe('mdi:cancel-bold')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('close button has aria-label "Fermer"', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true })
|
||||||
|
expect(wrapper.find('[data-test="close-button"]').attributes('aria-label')).toBe('Fermer')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits update:modelValue false and close on close button click', async () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true })
|
||||||
|
await wrapper.find('[data-test="close-button"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([false])
|
||||||
|
expect(wrapper.emitted('close')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets aria-labelledby to the header id when #header is provided', () => {
|
||||||
|
const wrapper = mountComponent(
|
||||||
|
{ modelValue: true, id: 'test-drawer' },
|
||||||
|
{ header: '<h2>Titre</h2>' },
|
||||||
|
)
|
||||||
|
const panel = wrapper.find('[data-test="panel"]')
|
||||||
|
expect(panel.attributes('aria-labelledby')).toBe('test-drawer-header')
|
||||||
|
expect(wrapper.find('[data-test="header-content"]').attributes('id')).toBe('test-drawer-header')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets aria-label from ariaLabel when no #header is provided', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true, ariaLabel: 'Panneau latéral' })
|
||||||
|
const panel = wrapper.find('[data-test="panel"]')
|
||||||
|
expect(panel.attributes('aria-label')).toBe('Panneau latéral')
|
||||||
|
expect(panel.attributes('aria-labelledby')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies headerClass to the header bar', () => {
|
||||||
|
const wrapper = mountComponent({ modelValue: true, headerClass: 'bg-m-primary' })
|
||||||
|
expect(wrapper.find('[data-test="header"]').classes()).toContain('bg-m-primary')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,9 +25,38 @@
|
|||||||
)"
|
)"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
|
:aria-labelledby="hasHeader ? headerId : undefined"
|
||||||
|
:aria-label="hasHeader ? undefined : (ariaLabel || undefined)"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
data-test="panel"
|
data-test="panel"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="hasHeader || showClose"
|
||||||
|
:class="twMerge('flex items-center justify-between gap-4 px-5 py-[25px]', headerClass)"
|
||||||
|
data-test="header"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:id="headerId"
|
||||||
|
class="min-w-0 flex-1"
|
||||||
|
data-test="header-content"
|
||||||
|
>
|
||||||
|
<slot name="header" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="showClose"
|
||||||
|
type="button"
|
||||||
|
aria-label="Fermer"
|
||||||
|
class="flex h-8 w-8 shrink-0 cursor-pointer items-center justify-center rounded-full transition-colors hover:bg-m-surface"
|
||||||
|
data-test="close-button"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
<IconifyIcon
|
||||||
|
icon="mdi:cancel-bold"
|
||||||
|
:width="16"
|
||||||
|
:height="16"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="twMerge('flex-1 overflow-y-auto px-5', bodyClass)"
|
:class="twMerge('flex-1 overflow-y-auto px-5', bodyClass)"
|
||||||
data-test="body"
|
data-test="body"
|
||||||
@@ -41,7 +70,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useAttrs, useId } from 'vue'
|
import { computed, ref, useAttrs, useId, useSlots } from 'vue'
|
||||||
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
defineOptions({ name: 'MalioDrawer', inheritAttrs: false })
|
defineOptions({ name: 'MalioDrawer', inheritAttrs: false })
|
||||||
@@ -87,6 +117,10 @@ const generatedId = useId()
|
|||||||
|
|
||||||
const componentId = computed(() => props.id || `malio-drawer-${generatedId}`)
|
const componentId = computed(() => props.id || `malio-drawer-${generatedId}`)
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const headerId = computed(() => `${componentId.value}-header`)
|
||||||
|
const hasHeader = computed(() => !!slots.header)
|
||||||
|
|
||||||
const isControlled = computed(() => props.modelValue !== undefined)
|
const isControlled = computed(() => props.modelValue !== undefined)
|
||||||
const localValue = ref(false)
|
const localValue = ref(false)
|
||||||
const isOpen = computed(() =>
|
const isOpen = computed(() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user