Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<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"
@keydown.esc="close"
>
<header class="tw-flex tw-justify-between tw-p-4 flex-none">
<div>
<slot name="header" />
</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 props = withDefaults(
defineProps<{
modelValue: boolean
isModal?: boolean
}>(),
{
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()
}
}
}
onClickOutside(dialogEl, 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>