CatPtain's picture
Upload 339 files
89ce340 verified
raw
history blame
3.06 kB
<template>
<Teleport to="body">
<Transition name="modal-fade">
<div class="modal" ref="modalRef" v-show="visible" tabindex="-1" @keyup.esc="onEsc()">
<div class="mask" @click="onClickMask()"></div>
<Transition name="modal-zoom"
@afterLeave="contentVisible = false"
@before-enter="contentVisible = true"
>
<div class="modal-content" v-show="visible" :style="contentStyle">
<span class="close-btn" v-if="closeButton" @click="close()"><IconClose /></span>
<slot v-if="contentVisible"></slot>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>
<script lang="ts" setup>
import { computed, nextTick, ref, watch, type CSSProperties } from 'vue'
import { icons } from '@/plugins/icon'
const { IconClose } = icons
const props = withDefaults(defineProps<{
visible: boolean
width?: number
closeButton?: boolean
closeOnClickMask?: boolean
closeOnEsc?: boolean
contentStyle?: CSSProperties
}>(), {
width: 480,
closeButton: false,
closeOnClickMask: true,
closeOnEsc: true,
})
const modalRef = ref<HTMLDivElement>()
const emit = defineEmits<{
(event: 'update:visible', payload: boolean): void
(event: 'closed'): void
}>()
const contentVisible = ref(false)
const contentStyle = computed(() => {
return {
width: props.width + 'px',
...(props.contentStyle || {})
}
})
watch(() => props.visible, () => {
if (props.visible) {
nextTick(() => modalRef.value!.focus())
}
})
const close = () => {
emit('update:visible', false)
emit('closed')
}
const onEsc = () => {
if (props.visible && props.closeOnEsc) close()
}
const onClickMask = () => {
if (props.closeOnClickMask) close()
}
</script>
<style lang="scss" scoped>
.modal, .mask {
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 5000;
}
.modal {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
outline: 0;
border: 0;
}
.mask {
position: absolute;
background: rgba(0, 0, 0, .25);
}
.modal-content {
z-index: 5001;
padding: 20px;
background: #fff;
border-radius: $borderRadius;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
position: relative;
}
.close-btn {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 16px;
right: 16px;
cursor: pointer;
}
.modal-fade-enter-active {
animation: modal-fade-enter .25s both ease-in;
}
.modal-fade-leave-active {
animation: modal-fade-leave .25s both ease-out;
}
.modal-zoom-enter-active {
animation: modal-zoom-enter .25s both cubic-bezier(.4, 0, 0, 1.5);
}
.modal-zoom-leave-active {
animation: modal-zoom-leave .25s both;
}
@keyframes modal-fade-enter {
from {
opacity: 0;
}
}
@keyframes modal-fade-leave {
to {
opacity: 0;
}
}
@keyframes modal-zoom-enter {
from {
transform: scale3d(.3, .3, .3);
}
}
@keyframes modal-zoom-leave {
to {
transform: scale3d(.3, .3, .3);
}
}
</style>