|
<template> |
|
<div class="popover" :class="{ 'center': center }" ref="triggerRef"> |
|
<div class="popover-content" :style="contentStyle" ref="contentRef"> |
|
<slot name="content" v-if="contentVisible"></slot> |
|
</div> |
|
<slot></slot> |
|
</div> |
|
</template> |
|
|
|
<script lang="ts" setup> |
|
import { type CSSProperties, onMounted, onUnmounted, ref, watch, computed } from 'vue' |
|
import tippy, { type Instance, type Placement } from 'tippy.js' |
|
|
|
import 'tippy.js/animations/scale.css' |
|
|
|
const props = withDefaults(defineProps<{ |
|
value?: boolean |
|
trigger?: 'click' | 'mouseenter' | 'manual' |
|
placement?: Placement |
|
appendTo?: HTMLElement | 'parent' |
|
contentStyle?: CSSProperties |
|
center?: boolean |
|
offset?: number |
|
}>(), { |
|
value: false, |
|
trigger: 'click', |
|
placement: 'bottom', |
|
center: false, |
|
offset: 8, |
|
}) |
|
|
|
const emit = defineEmits<{ |
|
(event: 'update:value', payload: boolean): void |
|
(event: 'show'): void |
|
(event: 'hide'): void |
|
}>() |
|
|
|
const instance = ref<Instance>() |
|
const triggerRef = ref<HTMLElement>() |
|
const contentRef = ref<HTMLElement>() |
|
const contentVisible = ref(false) |
|
|
|
const contentStyle = computed(() => { |
|
return props.contentStyle || {} |
|
}) |
|
|
|
watch(() => props.value, () => { |
|
if (!instance.value) return |
|
if (props.value) instance.value.show() |
|
else instance.value.hide() |
|
}) |
|
|
|
onUnmounted(() => { |
|
if (instance.value) instance.value.destroy() |
|
}) |
|
|
|
onMounted(() => { |
|
instance.value = tippy(triggerRef.value!, { |
|
content: contentRef.value!, |
|
allowHTML: true, |
|
trigger: props.trigger, |
|
placement: props.placement, |
|
interactive: true, |
|
appendTo: props.appendTo || document.body, |
|
maxWidth: 'none', |
|
offset: [0, props.offset], |
|
duration: 200, |
|
animation: 'scale', |
|
theme: 'popover', |
|
onShow() { |
|
contentVisible.value = true |
|
}, |
|
onShown() { |
|
if (!props.value) { |
|
emit('update:value', true) |
|
emit('show') |
|
} |
|
}, |
|
onHidden() { |
|
if (props.value) { |
|
emit('update:value', false) |
|
emit('hide') |
|
} |
|
contentVisible.value = false |
|
}, |
|
}) |
|
}) |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.popover.center { |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
.popover-content { |
|
background-color: #fff; |
|
padding: 10px; |
|
border: 1px solid $borderColor; |
|
box-shadow: $boxShadow; |
|
border-radius: $borderRadius; |
|
font-size: 13px; |
|
} |
|
</style> |
|
|
|
<style lang="scss"> |
|
.tippy-box[data-theme~='popover'] { |
|
border: 0; |
|
outline: 0; |
|
} |
|
</style> |