183 lines
4.8 KiB
Vue
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>
|