<template> <dialog v-if="modelValue" ref="dialogEl" class="tw-shadow-xl tw-border tw-border-gray-200 tw-rounded tw-max-w-[95%] tw-p-0 tw-bg-white tw-z-30 tw-flex tw-flex-col" @click="maybeClose" @close="close" > <header class="tw-flex tw-justify-between tw-p-4 flex-none"> <div> <slot name="header" :title-class="titleClass"> <p :class="titleClass">{{ title }}</p> </slot> </div> <button type="button" class="tw-w-8 tw-h-8 tw-ml-auto tw-p-0 tw-flex tw-items-center tw-justify-center tw-bg-gray-100 tw-rounded-full tw-border-none" 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 { onClickOutside } from '@vueuse/core' import { nextTick, ref, useSlots, watch } from 'vue' const titleClass = 'tw-text-lg tw-font-semibold tw-m-0' const props = withDefaults( defineProps<{ modelValue: boolean isModal?: boolean title?: string }>(), { title: '', isModal: false, }, ) const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>() const slots = useSlots() const dialogEl = ref<HTMLDialogElement>() function close() { emit('update:modelValue', false) } function maybeClose(event: MouseEvent) { // when dialog is a modal the backdrop of it is still considered the dialog element // so the onClickOutside handler below won’t be triggered if (dialogEl.value) { const rect = dialogEl.value.getBoundingClientRect() const isInDialog = rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width if (!isInDialog) { close() } } } watch( () => props.modelValue, 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> <script lang="ts"> export default { compatConfig: { MODE: 3, }, } </script> <style lang="postcss" scoped> dialog::backdrop { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(3px); } </style>