140 lines
3.1 KiB
Vue
140 lines
3.1 KiB
Vue
<template>
|
|
<Teleport to="body">
|
|
<Transition
|
|
name="drawer"
|
|
appear
|
|
@after-leave="isRendered = false"
|
|
>
|
|
<div
|
|
v-if="isRendered && isOpen"
|
|
:id="componentId"
|
|
class="fixed inset-0 z-50 flex justify-end"
|
|
v-bind="attrs"
|
|
>
|
|
<div
|
|
class="absolute inset-0 bg-black/40"
|
|
data-test="backdrop"
|
|
@click="close"
|
|
/>
|
|
|
|
<div
|
|
:class="twMerge(
|
|
'relative z-50 flex h-full w-full max-w-md flex-col bg-white shadow-xl',
|
|
drawerClass,
|
|
)"
|
|
role="dialog"
|
|
:aria-modal="true"
|
|
:aria-labelledby="titleId"
|
|
data-test="panel"
|
|
>
|
|
<div class="flex items-center justify-between px-5 pb-8 pt-8">
|
|
<h2
|
|
:id="titleId"
|
|
class="text-[32px] font-semibold text-m-primary"
|
|
>
|
|
{{ title }}
|
|
</h2>
|
|
<button
|
|
v-if="showClose"
|
|
type="button"
|
|
aria-label="Fermer"
|
|
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full transition-colors hover:bg-m-surface"
|
|
data-test="close-button"
|
|
@click="close"
|
|
>
|
|
<IconifyIcon
|
|
icon="mdi:close"
|
|
:width="24"
|
|
:height="24"
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
class="flex-1 overflow-y-auto px-5"
|
|
>
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, useAttrs, useId, watch } from 'vue'
|
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
defineOptions({ name: 'MalioDrawer', inheritAttrs: false })
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
id?: string
|
|
modelValue?: boolean
|
|
title?: string
|
|
showClose?: boolean
|
|
drawerClass?: string
|
|
}>(),
|
|
{
|
|
id: '',
|
|
modelValue: undefined,
|
|
title: '',
|
|
showClose: true,
|
|
drawerClass: '',
|
|
},
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void
|
|
}>()
|
|
|
|
const attrs = useAttrs()
|
|
const generatedId = useId()
|
|
|
|
const componentId = computed(() => props.id || `malio-drawer-${generatedId}`)
|
|
const titleId = computed(() => `${componentId.value}-title`)
|
|
|
|
const isControlled = computed(() => props.modelValue !== undefined)
|
|
const localValue = ref(false)
|
|
|
|
const isOpen = computed(() =>
|
|
isControlled.value ? props.modelValue! : localValue.value,
|
|
)
|
|
|
|
const isRendered = ref(isOpen.value)
|
|
|
|
watch(isOpen, (val) => {
|
|
if (val) isRendered.value = true
|
|
})
|
|
|
|
function close() {
|
|
if (!isControlled.value) {
|
|
localValue.value = false
|
|
}
|
|
emit('update:modelValue', false)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.drawer-enter-active,
|
|
.drawer-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.drawer-enter-active > div:last-child,
|
|
.drawer-leave-active > div:last-child {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.drawer-enter-from,
|
|
.drawer-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.drawer-enter-from > div:last-child,
|
|
.drawer-leave-to > div:last-child {
|
|
transform: translateX(100%);
|
|
}
|
|
</style>
|