<template> <dialog v-if="isOpen" ref="dialogEl" class="tw-shadow-xl tw-border tw-border-gray-200 tw-rounded tw-p-0 tw-bg-white tw-z-30 tw-flex tw-flex-col" style="max-width: 95%" @close.stop="close" @click="maybeCloseFromClickOutside" > <header class="tw-flex tw-justify-between tw-p-4 flex-none tw-items-center"> <div> <slot name="header" :title-class="titleClass"> <p :class="titleClass">{{ title }}</p> </slot> </div> <slot name="header-extra" /> <button type="button" class="btn btn-default tw-w-8 tw-h-8 tw-ml-auto tw-p-0 tw-justify-center tw-rounded-full" tabindex="-1" @click="close" > <icon-system-uicons-close class="tw-w-6 tw-h-6" /> </button> </header> <div class="tw-flex-1 tw-p-4 tw-overflow-y-auto tw-shadow-inner"> <slot /> </div> <footer v-if="slots.footer" class="tw-flex-none tw-p-4 tw-border-0 tw-border-t tw-border-solid tw-border-gray-200" > <slot name="footer" /> </footer> </dialog> </template> <script lang="ts" setup> import { computed, nextTick, ref, useSlots, watch } from 'vue' defineOptions({ compatConfig: { MODE: 3 } }) const titleClass = 'tw-text-lg tw-font-semibold tw-select-none tw-m-0' const props = withDefaults( defineProps<{ modelValue?: boolean | undefined isModal?: boolean title?: string }>(), { modelValue: undefined, title: '', isModal: false, }, ) const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>() const slots = useSlots() const localIsOpen = ref(false) const dialogEl = ref<HTMLDialogElement>() const isOpen = computed({ get() { return props.modelValue !== undefined ? props.modelValue : localIsOpen.value }, set(value: boolean) { if (props.modelValue !== undefined) { emit('update:modelValue', value) } else { localIsOpen.value = value } }, }) function close() { isOpen.value = false } function open() { isOpen.value = true } function maybeCloseFromClickOutside(event: MouseEvent | PointerEvent) { // @ts-expect-error TS EventTarget does not contain nodeName if (event.target?.nodeName === 'DIALOG') close() } defineExpose({ close, open, hide: close, show: open, }) watch( isOpen, async (isOpen) => { if (isOpen && !dialogEl.value) { await nextTick() } if (dialogEl.value) { if (isOpen) { if (props.isModal) { dialogEl.value.showModal() } else { dialogEl.value.show() } } else if (dialogEl.value.open) { dialogEl.value.close() } } }, { immediate: true }, ) </script> <style lang="postcss" scoped> dialog::backdrop { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(3px); } </style>