Files
contexted-v3/src/components/ui/UIModal.vue
2023-06-08 22:22:29 +02:00

91 lines
2.1 KiB
Vue

<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { vibrate } from '@/composables/useHaptics'
const props = withDefaults(
defineProps<{
open?: boolean
persistent?: boolean
size?: 'sm' | 'md' | 'lg'
icon?: string
}>(),
{
open: false,
persistent: false,
size: 'md'
}
)
const show = ref<boolean>(false)
watch(
() => props.open,
() => {
if (show.value) vibrate()
show.value = props.open
},
{ immediate: true }
)
const modal = ref<HTMLElement | null>(null)
const modalBox = ref(null)
const open = () => (show.value = true)
const close = (): Promise<boolean> => {
return new Promise((resolve) => {
modal.value?.addEventListener('transitionend', () => resolve(true))
show.value = false
})
}
const slotProps = { open, close }
if (!props.persistent) onClickOutside(modalBox, () => close())
const onEnter = (el: Element, done: () => void): void => {
setTimeout(() => {
el.classList.add('dui-modal-open')
done()
})
}
const onLeave = (el: Element, done: () => void): void => {
el.classList.remove('dui-modal-open')
el.addEventListener('transitionend', () => done())
}
const styleClass = computed(() => {
const sizeVariants = {
sm: 'max-w-xs',
md: 'max-w-md',
lg: 'max-w-2xl'
}
const sizeClass = sizeVariants[props.size]
return [sizeClass]
})
defineExpose({ open, close })
</script>
<template>
<slot name="activator" v-bind="slotProps"></slot>
<Teleport to="body">
<Transition @enter="onEnter" @leave="onLeave" appear>
<div class="dui-modal bg-neutral-800 bg-opacity-60" v-if="show" ref="modal">
<div class="dui-modal-box" :class="styleClass" ref="modalBox">
<h3 class="mb-4 flex items-center text-xl font-bold" v-if="$slots.title">
<slot name="title" />
</h3>
<div>
<slot v-bind="slotProps" />
</div>
<div class="dui-modal-action mt-4">
<slot name="actions" v-bind="slotProps">
<UIButton size="sm" @click="close">Close</UIButton>
</slot>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>