Popover.vue 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <template>
  2. <div class="popover" :class="{ 'center': center }" ref="triggerRef">
  3. <div class="popover-content" :style="contentStyle" ref="contentRef">
  4. <slot name="content" v-if="contentVisible"></slot>
  5. </div>
  6. <slot></slot>
  7. </div>
  8. </template>
  9. <script lang="ts" setup>
  10. import { type CSSProperties, onMounted, onUnmounted, ref, watch, computed, useTemplateRef } from 'vue'
  11. import tippy, { type Instance, type Placement } from 'tippy.js'
  12. import 'tippy.js/animations/scale.css'
  13. const props = withDefaults(defineProps<{
  14. value?: boolean
  15. trigger?: 'click' | 'mouseenter' | 'manual'
  16. placement?: Placement
  17. appendTo?: HTMLElement | 'parent'
  18. contentStyle?: CSSProperties
  19. center?: boolean
  20. offset?: number
  21. }>(), {
  22. value: false,
  23. trigger: 'click',
  24. placement: 'bottom',
  25. center: false,
  26. offset: 8,
  27. })
  28. const emit = defineEmits<{
  29. (event: 'update:value', payload: boolean): void
  30. (event: 'show'): void
  31. (event: 'hide'): void
  32. }>()
  33. const instance = ref<Instance>()
  34. const contentVisible = ref(false)
  35. const triggerRef = useTemplateRef<HTMLElement>('triggerRef')
  36. const contentRef = useTemplateRef<HTMLElement>('contentRef')
  37. const contentStyle = computed(() => {
  38. return props.contentStyle || {}
  39. })
  40. watch(() => props.value, () => {
  41. if (!instance.value) return
  42. if (props.value) instance.value.show()
  43. else instance.value.hide()
  44. })
  45. onUnmounted(() => {
  46. if (instance.value) instance.value.destroy()
  47. })
  48. onMounted(() => {
  49. instance.value = tippy(triggerRef.value!, {
  50. content: contentRef.value!,
  51. allowHTML: true,
  52. trigger: props.trigger,
  53. placement: props.placement,
  54. interactive: true,
  55. appendTo: props.appendTo || document.body,
  56. maxWidth: 'none',
  57. offset: [0, props.offset],
  58. duration: 200,
  59. animation: 'scale',
  60. theme: 'popover',
  61. onShow() {
  62. contentVisible.value = true
  63. },
  64. onShown() {
  65. if (!props.value) {
  66. emit('update:value', true)
  67. emit('show')
  68. }
  69. },
  70. onHidden() {
  71. if (props.value) {
  72. emit('update:value', false)
  73. emit('hide')
  74. }
  75. contentVisible.value = false
  76. },
  77. })
  78. })
  79. </script>
  80. <style lang="scss" scoped>
  81. .popover.center {
  82. display: flex;
  83. justify-content: center;
  84. align-items: center;
  85. }
  86. .popover-content {
  87. background-color: #fff;
  88. padding: 10px;
  89. // border: 1px solid $borderColor;
  90. box-shadow: $boxShadow;
  91. border-radius: 10px;
  92. // border-radius: $borderRadius;
  93. font-size: 13px;
  94. }
  95. </style>
  96. <style lang="scss">
  97. .tippy-box[data-theme~='popover'] {
  98. border: 0;
  99. outline: 0;
  100. }
  101. </style>