1
0
mirror of https://github.com/elk-zone/elk synced 2024-12-04 17:58:07 +09:00
elk/components/modal/ModalDialog.vue

154 lines
3.4 KiB
Vue
Raw Normal View History

2022-11-23 12:48:01 +09:00
<script setup lang='ts'>
2022-11-27 10:52:46 +09:00
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
2022-11-30 12:27:19 +09:00
type DialogType = 'top' | 'right' | 'bottom' | 'left' | 'dialog'
2022-11-27 10:52:46 +09:00
const {
type = 'dialog',
2022-11-30 12:27:19 +09:00
closeButton = false,
2022-11-27 10:52:46 +09:00
} = defineProps<{
type?: DialogType
2022-11-30 12:27:19 +09:00
closeButton?: boolean
2022-11-27 10:52:46 +09:00
}>()
2022-11-23 12:48:01 +09:00
const { modelValue } = defineModel<{
modelValue: boolean
2022-11-27 12:13:39 +09:00
closeButton?: boolean
2022-11-23 12:48:01 +09:00
}>()
2022-11-24 17:04:53 +09:00
2022-11-27 12:13:39 +09:00
let isVisible = $ref(modelValue.value)
let isOut = $ref(!modelValue.value)
2022-11-27 10:52:46 +09:00
const positionClass = computed(() => {
switch (type) {
case 'dialog':
return 'border rounded top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2'
case 'bottom':
return 'bottom-0 left-0 right-0 border-t'
case 'top':
return 'top-0 left-0 right-0 border-b'
case 'left':
return 'bottom-0 left-0 top-0 border-r'
case 'right':
return 'bottom-0 top-0 right-0 border-l'
default:
return ''
}
})
2022-11-27 12:13:39 +09:00
const transformClass = computed(() => {
if (isOut) {
switch (type) {
case 'dialog':
return 'op0'
case 'bottom':
return 'translate-y-[100%]'
case 'top':
return 'translate-y-[100%]'
case 'left':
return 'translate-x-[-100%]'
case 'right':
return 'translate-x-[100%]'
default:
return ''
}
2022-11-27 10:52:46 +09:00
}
})
const target = ref<HTMLElement | null>(null)
const { activate, deactivate } = useFocusTrap(target)
2022-11-27 12:13:39 +09:00
function close() {
modelValue.value = false
}
2022-11-27 10:52:46 +09:00
watchEffect(() => {
if (modelValue)
activate()
else
deactivate()
})
2022-11-27 12:13:39 +09:00
useEventListener('keydown', (e: KeyboardEvent) => {
if (!modelValue.value)
return
if (e.key === 'Escape') {
close()
e.preventDefault()
}
2022-11-24 17:04:53 +09:00
})
2022-11-27 12:13:39 +09:00
let unsubscribe: () => void
2022-11-27 12:13:39 +09:00
watch(modelValue, async (v) => {
if (v) {
isOut = true
isVisible = true
setTimeout(() => {
isOut = false
}, 10)
unsubscribe = useRouter().beforeEach(() => {
unsubscribe()
close()
})
2022-11-27 12:13:39 +09:00
}
else {
unsubscribe?.()
2022-11-27 12:13:39 +09:00
isOut = true
}
})
function onTransitionEnd() {
if (!modelValue.value)
isVisible = false
}
2022-11-23 12:48:01 +09:00
</script>
<template>
<SafeTeleport to="#teleport-end">
2022-11-23 12:48:01 +09:00
<div
v-if="isVisible"
class="scrollbar-hide"
fixed top-0 bottom-0 left-0 right-0 z-10 overscroll-none overflow-y-scroll
:class="modelValue ? '' : 'pointer-events-none'"
2022-11-23 12:48:01 +09:00
>
<!-- The style `scrollbar-hide overscroll-none overflow-y-scroll` and `h="[calc(100%+0.5px)]"` is used to implement scroll locking, -->
<!-- corresponding to issue: #106, so please don't remove it. -->
<div
bg-base bottom-0 left-0 right-0 top-0 absolute transition-opacity duration-500 ease-out
h="[calc(100%+0.5px)]"
:class="isOut ? 'opacity-0' : 'opacity-85'"
@click="close"
/>
<div
ref="target"
bg-base border-base absolute transition-all duration-200 ease-out transform
:class="`${positionClass} ${transformClass}`"
@transitionend="onTransitionEnd"
>
<slot />
</div>
2022-11-30 12:27:19 +09:00
<button
v-if="closeButton"
btn-action-icon bg="black/20" aria-label="Close"
hover:bg="black/40" dark:bg="white/10" dark:hover:bg="white/20"
absolute top-0 right-0 m1
@click="close"
>
2022-11-30 05:37:45 +09:00
<div i-ri:close-fill text-white />
</button>
2022-11-23 12:48:01 +09:00
</div>
</SafeTeleport>
2022-11-23 12:48:01 +09:00
</template>
2022-11-27 17:02:09 +09:00
<style socped>
.scrollbar-hide {
scrollbar-width: none;
}
2022-11-30 05:37:45 +09:00
2022-11-27 17:02:09 +09:00
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>