Kasir/resources/js/components/Modal.vue
2025-08-28 22:45:11 +07:00

96 lines
1.9 KiB
Vue

<template>
<Teleport to="body">
<Transition name="modal">
<div
v-if="active"
class="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"
@click="handleOverlayClick"
>
<div
class="bg-white rounded-lg shadow-2xl max-h-[90vh] overflow-y-auto relative"
:class="sizeClass"
@click.stop
>
<slot></slot>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { computed, watch, onBeforeUnmount } from 'vue'
const props = defineProps({
active: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'md',
validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', 'full'].includes(value)
},
clickOutside: {
type: [Boolean, String],
default: true
}
})
const emit = defineEmits(['close'])
const sizeClass = computed(() => {
const sizes = {
xs: 'w-full max-w-xs',
sm: 'w-full max-w-sm',
md: 'w-full max-w-md',
lg: 'w-full max-w-lg',
xl: 'w-full max-w-xl',
'2xl': 'w-full max-w-2xl',
'3xl': 'w-full max-w-3xl',
'4xl': 'w-full max-w-4xl',
full: 'w-[95vw] h-[95vh] max-w-none max-h-none'
}
return sizes[props.size] || sizes.md
})
const handleOverlayClick = () => {
if (clickOutside.value) {
emit('close')
}
}
watch(() => props.active, (newVal) => {
if (newVal) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
})
onBeforeUnmount(() => {
document.body.style.overflow = ''
})
</script>
<style>
.modal-enter-active,
.modal-leave-active {
transition: all 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .bg-white,
.modal-leave-to .bg-white {
transform: scale(0.95) translateY(-20px);
}
.modal-enter-active .bg-white,
.modal-leave-active .bg-white {
transition: transform 0.3s ease;
}
</style>