1
0
forked from noxious/client

299 lines
6.5 KiB
Vue

<template>
<Teleport to="body">
<div class="modal-container" v-if="isModalOpenRef" :style="{ top: y + 'px', left: x + 'px', width: width + 'px', height: height + 'px' }">
<div class="modal-header" @mousedown="startDrag">
<slot name="modalHeader" />
<div class="buttons">
<!-- <button><img alt="resize" draggable="false" src="/assets/icons/modalFullscreen.svg" /></button>-->
<button @click="close" v-if="closable"><img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" /></button>
</div>
</div>
<div class="modal-body">
<slot name="modalBody" />
<img v-if="isResizable" src="/assets/icons/resize-icon.svg" alt="resize" class="resize" @mousedown="startResize" />
</div>
<div v-if="$slots.modalFooter" class="modal-footer">
<slot name="modalFooter" />
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { defineEmits, onMounted, onUnmounted, ref, watch } from 'vue'
const properties = 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
}
})
watch(() => properties.isModalOpen, (value) => {
isModalOpenRef.value = value
}
)
const isModalOpenRef = ref(properties.isModalOpen)
const emit = defineEmits(['modal:close', 'character:create'])
function close() {
emit('modal:close')
}
// (re)size logics
const width = ref(properties.modalWidth)
const height = ref(properties.modalHeight)
let minWidth = ref(200)
let minHeight = ref(100)
let isResizing = ref(false)
let startWidth = 0
let startHeight = 0
const startResize = (event: MouseEvent) => {
isResizing.value = true
startWidth = width.value - event.clientX
startHeight = height.value - event.clientY
event.preventDefault()
}
const resizeModal = (event: MouseEvent) => {
if (!isResizing.value) return
let newWidth = startWidth + event.clientX
let newHeight = startHeight + event.clientY
width.value = newWidth > minWidth.value ? newWidth : minWidth.value
height.value = newHeight > minHeight.value ? newHeight : minHeight.value
}
const stopResize = () => {
isResizing.value = false
}
// make modal draggable
let startX = 0
let startY = 0
let initialX = 0
let initialY = 0
const x = ref(0)
const y = ref(0)
const isDragging = ref(false)
// set modal position center of the screen
onMounted(() => {
x.value = window.innerWidth / 2 - 150
y.value = window.innerHeight / 2 - 100
})
const startDrag = (event: MouseEvent) => {
isDragging.value = true
startX = event.clientX
startY = event.clientY
initialX = x.value
initialY = y.value
event.preventDefault()
}
const 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
}
const stopDrag = () => {
isDragging.value = false
}
watch(() => properties.modalWidth, (value) => {
width.value = value
}
)
watch(() => properties.modalHeight, (value) => {
height.value = value
}
)
onMounted(() => {
addEventListener('mousemove', drag)
addEventListener('mouseup', stopDrag)
addEventListener('mousemove', resizeModal)
addEventListener('mouseup', stopResize)
addEventListener('resize', handleResize)
})
onUnmounted(() => {
removeEventListener('mousemove', drag)
removeEventListener('mouseup', stopDrag)
removeEventListener('mousemove', resizeModal)
removeEventListener('mouseup', stopResize)
removeEventListener('resize', handleResize)
})
// Make sure modal doesn't go off screen
watch(() => x.value, (value) => {
if (value < 0) { x.value = 0 } else if (value + width.value > window.innerWidth) { x.value = window.innerWidth - width.value }
}
)
watch(() => y.value, (value) => {
if (value < 0) { y.value = 0 } else if (value + height.value > window.innerHeight) { y.value = window.innerHeight - height.value }
}
)
// also on window resize function
function handleResize() {
if (x.value + width.value > window.innerWidth) {
x.value = window.innerWidth - width.value
}
if (y.value + height.value > window.innerHeight) {
y.value = window.innerHeight - height.value
}
}
</script>
<style lang="scss">
@import '@/assets/scss/main';
.modal-container {
position: fixed;
top: 0;
left: 0;
max-width: 1000px;
background-color: rgba($dark-gray, 0.8);
border: 2px solid $dark-cyan;
z-index: 999;
display: flex;
flex-direction: column;
border-radius: 10px;
backdrop-filter: blur(5px);
box-shadow: 0 0 10px rgba($black, 0.5);
.modal-header {
cursor: move;
padding: 0 20px;
min-height: 50px;
display: flex;
justify-content: space-between;
gap: 30px;
align-items: center;
border-bottom: 1px solid $dark-cyan;
.modal-title {
margin: 0;
font-weight: 400;
}
.buttons {
display: flex;
gap: 10px;
button {
width: 20px;
height: 20px;
margin: 0;
padding: 0;
position: relative;
img {
width: 100%;
height: 100%;
}
&:hover {
transform: rotate(180deg);
transition: ease-in-out transform 0.3s;
}
}
}
}
.modal-body {
max-height: 80vh;
overflow: auto;
padding: 15px;
flex-grow: 1;
.resize {
filter: invert(60%);
position: absolute;
bottom: 0;
right: 0;
width: 20px;
height: 20px;
cursor: nwse-resize;
}
.submit {
display: inline-block;
button {
margin-right: 20px;
}
}
button {
padding: 6px 15px;
min-width: 100px;
}
.modal-form {
display: inline;
.form-fields {
display: flex;
flex-direction: column;
margin-bottom: 20px;
label {
margin-bottom: 10px;
font-family: "Poppins";
}
input {
max-width: 250px;
font-family: "Poppins";
border: 1px solid $cyan;
border-radius: 5px;
background-color: rgba($white, 0.8);
padding: 8px 10px;
}
}
}
}
.modal-footer {
padding: 0 20px;
min-height: 50px;
display: flex;
justify-content: end;
gap: 30px;
align-items: center;
border-top: 1px solid $dark-cyan;
button {
padding: 6px 15px;
min-width: 100px;
border-radius: 5px;
border-width: 1px;
}
}
}
</style>