71 lines
1.7 KiB
Vue
71 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
import { onClickOutside } from '@vueuse/core'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
open?: boolean
|
|
persistent?: boolean
|
|
}>(),
|
|
{
|
|
open: false,
|
|
persistent: false
|
|
}
|
|
)
|
|
|
|
const show = ref<boolean>(false)
|
|
watch(
|
|
() => props.open,
|
|
() => (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('modal-open')
|
|
done()
|
|
})
|
|
}
|
|
|
|
const onLeave = (el: Element, done: () => void): void => {
|
|
el.classList.remove('modal-open')
|
|
el.addEventListener('transitionend', () => done())
|
|
}
|
|
|
|
defineExpose({ open, close })
|
|
</script>
|
|
<template>
|
|
<slot name="activator" v-bind="slotProps"></slot>
|
|
<Teleport to="body">
|
|
<Transition @enter="onEnter" @leave="onLeave" appear>
|
|
<div class="modal bg-neutral-800 bg-opacity-60" v-if="show" ref="modal">
|
|
<div class="modal-box" ref="modalBox">
|
|
<h3 class="text-lg font-bold" v-if="$slots.title"><slot name="title" /></h3>
|
|
<p class="py-4">
|
|
<slot v-bind="slotProps" />
|
|
</p>
|
|
<div class="modal-action">
|
|
<slot name="actions" v-bind="slotProps">
|
|
<button class="btn-sm btn" @click="close">Close</button>
|
|
</slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</template>
|