183 lines
4.8 KiB
Vue

<template>
<Teleport to="body">
<div v-if="isModalOpenRef" class="fixed bg-opacity-80 bg-gray-300 border-solid border-2 border-cyan-200 z-50 flex flex-col rounded-lg backdrop-blur-sm shadow-lg" :style="modalStyle">
<div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-cyan-200">
<slot name="modalHeader" />
<div class="flex gap-2.5">
<button @click="close" v-if="closable" class="w-5 h-5 m-0 p-0 relative hover:rotate-180 transition-transform duration-300 ease-in-out">
<img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" class="w-full h-full" />
</button>
</div>
</div>
<div class="overflow-auto flex-grow">
<slot name="modalBody" />
<img v-if="isResizable" src="/assets/icons/resize-icon.svg" alt="resize" class="absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize filter invert-[60%]" @mousedown="startResize" />
</div>
<div v-if="$slots.modalFooter" class="px-5 min-h-[50px] flex justify-end gap-7.5 items-center border-solid border-t border-cyan-200">
<slot name="modalFooter" />
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { defineEmits, onMounted, onUnmounted, ref, watch, computed } from 'vue'
const props = defineProps({
isModalOpen: {
type: Boolean,
default: false
},
closable: {
type: Boolean,
default: true
},
isResizable: {
type: Boolean,
default: true
},
modalWidth: {
type: Number,
default: 500
},
modalHeight: {
type: Number,
default: 280
}
})
const isModalOpenRef = ref(props.isModalOpen)
const emit = defineEmits(['modal:close', 'character:create'])
const width = ref(props.modalWidth)
const height = ref(props.modalHeight)
const x = ref(0)
const y = ref(0)
const minWidth = ref(200)
const minHeight = ref(100)
const isResizing = ref(false)
const isDragging = ref(false)
let startX = 0
let startY = 0
let initialX = 0
let initialY = 0
let startWidth = 0
let startHeight = 0
const modalStyle = computed(() => ({
top: `${y.value}px`,
left: `${x.value}px`,
width: `${width.value}px`,
height: `${height.value}px`,
maxWidth: '100vw',
maxHeight: '100vh'
}))
function close() {
emit('modal:close')
}
function startResize(event: MouseEvent) {
isResizing.value = true
startWidth = width.value - event.clientX
startHeight = height.value - event.clientY
event.preventDefault()
}
function resizeModal(event: MouseEvent) {
if (!isResizing.value) return
const newWidth = Math.min(startWidth + event.clientX, window.innerWidth)
const newHeight = Math.min(startHeight + event.clientY, window.innerHeight)
width.value = Math.max(newWidth, minWidth.value)
height.value = Math.max(newHeight, minHeight.value)
adjustPosition()
}
function stopResize() {
isResizing.value = false
}
function startDrag(event: MouseEvent) {
isDragging.value = true
startX = event.clientX
startY = event.clientY
initialX = x.value
initialY = y.value
event.preventDefault()
}
function drag(event: MouseEvent) {
if (!isDragging.value) return
const dx = event.clientX - startX
const dy = event.clientY - startY
x.value = initialX + dx
y.value = initialY + dy
adjustPosition()
}
function stopDrag() {
isDragging.value = false
}
function adjustPosition() {
x.value = Math.max(0, Math.min(x.value, window.innerWidth - width.value))
y.value = Math.max(0, Math.min(y.value, window.innerHeight - height.value))
}
function handleResize() {
width.value = Math.min(width.value, window.innerWidth)
height.value = Math.min(height.value, window.innerHeight)
adjustPosition()
}
function initializePosition() {
width.value = Math.min(props.modalWidth, window.innerWidth)
height.value = Math.min(props.modalHeight, window.innerHeight)
x.value = (window.innerWidth - width.value) / 2
y.value = (window.innerHeight - height.value) / 2
}
watch(
() => props.isModalOpen,
(value) => {
isModalOpenRef.value = value
if (value) {
initializePosition()
}
}
)
watch(
() => props.modalWidth,
(value) => {
width.value = Math.min(value, window.innerWidth)
}
)
watch(
() => props.modalHeight,
(value) => {
height.value = Math.min(value, window.innerHeight)
}
)
onMounted(() => {
window.addEventListener('mousemove', drag)
window.addEventListener('mouseup', stopDrag)
window.addEventListener('mousemove', resizeModal)
window.addEventListener('mouseup', stopResize)
window.addEventListener('resize', handleResize)
initializePosition()
})
onUnmounted(() => {
window.removeEventListener('mousemove', drag)
window.removeEventListener('mouseup', stopDrag)
window.removeEventListener('mousemove', resizeModal)
window.removeEventListener('mouseup', stopResize)
window.removeEventListener('resize', handleResize)
})
</script>