felicity-lims/webapp/components/ui/FelModal.vue

171 lines
4.5 KiB
Vue
Raw Normal View History

2024-11-17 20:33:54 +08:00
<template>
<Transition
appear
name="modal"
@after-leave="$emit('after-leave')"
>
<div
v-if="true"
class="modal-mask fixed inset-0 z-50 overflow-y-auto bg-black bg-opacity-50 backdrop-blur-sm"
@click="handleOutsideClick"
role="dialog"
aria-modal="true"
>
<div
class="min-h-screen px-4 flex items-center justify-center"
>
<Transition
name="modal-inner"
appear
>
<div
:class="[
'modal-container bg-white rounded-lg shadow-xl transform',
'max-h-[90vh] overflow-y-auto',
contentWidth ? contentWidth : 'w-full max-w-2xl'
]"
@click.stop
ref="modalRef"
>
<div class="p-6">
<!-- Header -->
<div class="flex items-center justify-between mb-4">
<div class="flex-1">
<slot name="header">
<h3 class="text-lg font-semibold text-gray-900">
Default Header
</h3>
</slot>
</div>
<button
@click="$emit('close')"
class="inline-flex items-center justify-center w-8 h-8 ml-4 text-gray-400
transition-colors duration-200 rounded-full hover:text-red-500
hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500"
aria-label="Close modal"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Divider -->
<div class="h-px bg-gray-200 my-4"></div>
<!-- Body -->
<div class="modal-body">
<slot name="body">
Default body content
</slot>
</div>
<!-- Divider -->
<div class="h-px bg-gray-200 my-4"></div>
<!-- Footer -->
<div class="modal-footer">
<slot name="footer">
<div class="flex justify-end space-x-3">
<button
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border
border-gray-300 rounded-md shadow-sm hover:bg-gray-50
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
@click="$emit('close')"
>
Cancel
</button>
</div>
</slot>
</div>
</div>
</div>
</Transition>
</div>
</div>
</Transition>
</template>
2023-11-10 14:05:15 +08:00
<script lang="ts">
2024-11-17 20:33:54 +08:00
import { defineComponent, onMounted, onUnmounted, ref } from 'vue'
2023-11-10 14:05:15 +08:00
export default defineComponent({
2024-11-17 20:33:54 +08:00
name: 'enhanced-modal',
2023-11-10 14:05:15 +08:00
props: {
contentWidth: String,
},
2024-11-17 20:33:54 +08:00
emits: ['close', 'after-leave'],
2023-11-10 14:05:15 +08:00
setup(props, { emit }) {
2024-11-17 20:33:54 +08:00
const modalRef = ref<HTMLElement | null>(null)
const handleOutsideClick = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (!modalRef.value?.contains(target)) {
emit('close')
}
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
emit('close')
}
}
// Prevent body scroll when modal is open
const lockScroll = () => {
document.body.style.overflow = 'hidden'
}
const unlockScroll = () => {
document.body.style.overflow = ''
}
2023-11-10 14:05:15 +08:00
onMounted(() => {
2024-11-17 20:33:54 +08:00
document.addEventListener('keydown', handleEscape)
lockScroll()
})
onUnmounted(() => {
document.removeEventListener('keydown', handleEscape)
unlockScroll()
})
return {
modalRef,
handleOutsideClick
}
}
})
2023-11-10 14:05:15 +08:00
</script>
<style lang="postcss" scoped>
.modal-mask {
2024-11-17 20:33:54 +08:00
transition: opacity 0.3s ease;
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
.modal-enter-from,
.modal-leave-to {
opacity: 0;
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
.modal-inner-enter-active,
.modal-inner-leave-active {
transition: all 0.3s ease-out;
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
.modal-inner-enter-from {
2023-11-10 14:05:15 +08:00
opacity: 0;
2024-11-17 20:33:54 +08:00
transform: scale(0.9);
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
.modal-inner-leave-to {
2023-11-10 14:05:15 +08:00
opacity: 0;
2024-11-17 20:33:54 +08:00
transform: scale(0.95);
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
.modal-inner-enter-to,
.modal-inner-leave-from {
opacity: 1;
transform: scale(1);
2023-11-10 14:05:15 +08:00
}
2024-11-17 20:33:54 +08:00
</style>