介绍
弹出式的气泡菜单。
代码演示
基础使用
弹窗会显示默认插槽内容。
点击 reference 插槽内容时会显示弹出框,点击弹出框外部时会隐藏弹出框。
vue
<template>
<s-popover>
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<div class="p-3">内容</div>
</s-popover>
</template>
<script setup lang="ts"></script>受控
可使用 v-model:visible 控制弹出框显隐,可通过 outside-closable 取消外部点击关闭。
vue
<template>
<s-popover v-model:visible="visible" :outside-closable="false">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<div class="p-4">
<div class="font-bold text-lg">标题</div>
<div class="mt-1">这是内容这是内容</div>
<div class="flex gap-2 mt-3 justify-end">
<s-button size="xs" variant="outlined" @click="visible = false">取消</s-button>
<s-button size="xs" variant="solid" @click="visible = false">确定</s-button>
</div>
</div>
</s-popover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const visible = ref(false)
</script>vue
<template>
<s-popover v-model:visible="visible" :outside-closable="false">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<div class="p-4">
<div class="font-bold text-lg">标题</div>
<div class="mt-1">这是内容这是内容</div>
<div class="flex gap-2 mt-3 justify-end">
<s-button size="xs" variant="outlined" @click="visible = false">取消</s-button>
<s-button size="xs" variant="solid" @click="visible = false">确定</s-button>
</div>
</div>
</s-popover>
</template>
<script setup lang="js">
import { ref } from 'vue'
const visible = ref(false)
</script>菜单内容
可结合 Menu 组件实现弹出式菜单选择。
vue
<template>
<s-popover v-model:visible="visible">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
</template>
<script setup lang="ts">
import { toast, type MenuItemProps } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const onSelect = (props: MenuItemProps) => {
visible.value = false
toast(props.label!)
}
</script>vue
<template>
<s-popover v-model:visible="visible">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
</template>
<script setup lang="js">
import { toast } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const onSelect = (props) => {
visible.value = false
toast(props.label)
}
</script>暗黑模式
使用 theme 属性可设置暗黑模式。
vue
<template>
<s-popover v-model:visible="visible" theme="dark">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<s-menu theme="dark" @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
</template>
<script setup lang="ts">
import { toast, type MenuItemProps } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const onSelect = (props: MenuItemProps) => {
visible.value = false
toast(props.label!)
}
</script>vue
<template>
<s-popover v-model:visible="visible" theme="dark">
<template #reference>
<s-button>显示气泡弹出框</s-button>
</template>
<s-menu theme="dark" @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
</template>
<script setup lang="js">
import { toast } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const onSelect = (props) => {
visible.value = false
toast(props.label)
}
</script>弹出位置
气泡弹出框会尽量在视窗中匹配各个位置以便可以完整展示,同时不会完全脱离 reference 元素,默认底部居中展示。
vue
<template>
<div style="margin: 0 60px">
<s-grid :columns="3" clickable>
<s-grid-item text="top-start" @click="show($event, 'top-start')" />
<s-grid-item text="top" @click="show($event, 'top')" />
<s-grid-item text="top-end" @click="show($event, 'top-end')" />
</s-grid>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between">
<s-grid :columns="1" clickable style="width: 60px">
<s-grid-item text="left-start" @click="show($event, 'left-start')" />
<s-grid-item text="left" @click="show($event, 'left')" />
<s-grid-item text="left-end" @click="show($event, 'left-end')" />
</s-grid>
<s-popover v-model:visible="visible" :position="position">
<template #reference>
<div class="flex justify-center items-center size-16 m-auto bg-(--s-color-warning)">
box
</div>
</template>
<div class="p-3">
<div class="font-bold text-lg">标题</div>
<div class="mt-1">这是内容这是内容</div>
<div class="mt-1">这是内容这是内容</div>
</div>
</s-popover>
<s-grid :columns="1" clickable style="width: 60px">
<s-grid-item text="right-start" @click="show($event, 'right-start')" />
<s-grid-item text="right" @click="show($event, 'right')" />
<s-grid-item text="right-end" @click="show($event, 'right-end')" />
</s-grid>
</div>
<div style="margin: 0 60px">
<s-grid :columns="3" clickable>
<s-grid-item text="bottom-start" @click="show($event, 'bottom-start')" />
<s-grid-item text="bottom" @click="show($event, 'bottom')" />
<s-grid-item text="bottom-end" @click="show($event, 'bottom-end')" />
</s-grid>
</div>
</template>
<script setup lang="ts">
import { type PopoverPosition } from 'sard'
import { ref } from 'vue'
const position = ref<PopoverPosition>('bottom')
const visible = ref(false)
const show = (event: MouseEvent, pos: PopoverPosition) => {
event.stopPropagation()
position.value = pos
visible.value = true
}
</script>vue
<template>
<div style="margin: 0 60px">
<s-grid :columns="3" clickable>
<s-grid-item text="top-start" @click="show($event, 'top-start')" />
<s-grid-item text="top" @click="show($event, 'top')" />
<s-grid-item text="top-end" @click="show($event, 'top-end')" />
</s-grid>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between">
<s-grid :columns="1" clickable style="width: 60px">
<s-grid-item text="left-start" @click="show($event, 'left-start')" />
<s-grid-item text="left" @click="show($event, 'left')" />
<s-grid-item text="left-end" @click="show($event, 'left-end')" />
</s-grid>
<s-popover v-model:visible="visible" :position="position">
<template #reference>
<div class="flex justify-center items-center size-16 m-auto bg-(--s-color-warning)">
box
</div>
</template>
<div class="p-3">
<div class="font-bold text-lg">标题</div>
<div class="mt-1">这是内容这是内容</div>
<div class="mt-1">这是内容这是内容</div>
</div>
</s-popover>
<s-grid :columns="1" clickable style="width: 60px">
<s-grid-item text="right-start" @click="show($event, 'right-start')" />
<s-grid-item text="right" @click="show($event, 'right')" />
<s-grid-item text="right-end" @click="show($event, 'right-end')" />
</s-grid>
</div>
<div style="margin: 0 60px">
<s-grid :columns="3" clickable>
<s-grid-item text="bottom-start" @click="show($event, 'bottom-start')" />
<s-grid-item text="bottom" @click="show($event, 'bottom')" />
<s-grid-item text="bottom-end" @click="show($event, 'bottom-end')" />
</s-grid>
</div>
</template>
<script setup lang="js">
import 'sard'
import { ref } from 'vue'
const position = ref('bottom')
const visible = ref(false)
const show = (event, pos) => {
event.stopPropagation()
position.value = pos
visible.value = true
}
</script>自定义 reference
如果因 DOM 结构限制,无法在 Popover 组件的 reference 插槽放置元素时, 可以通过 reference 属性接收 DOM 元素。
vue
<template>
<s-popover v-model:visible="visible" position="right" :reference="referenceRef">
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
<div
ref="reference"
class="flex justify-center items-center size-25 bg-(--s-color-warning)"
@click="visible = true"
>
box
</div>
</template>
<script setup lang="ts">
import { toast, type MenuItemProps } from 'sard'
import { ref, useTemplateRef } from 'vue'
const visible = ref(false)
const onSelect = (props: MenuItemProps) => {
visible.value = false
toast(props.label!)
}
const referenceRef = useTemplateRef('reference')
</script>vue
<template>
<s-popover v-model:visible="visible" position="right" :reference="referenceRef">
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
<div
ref="reference"
class="flex justify-center items-center size-25 bg-(--s-color-warning)"
@click="visible = true"
>
box
</div>
</template>
<script setup lang="js">
import { toast } from 'sard'
import { ref, useTemplateRef } from 'vue'
const visible = ref(false)
const onSelect = (props) => {
visible.value = false
toast(props.label)
}
const referenceRef = useTemplateRef('reference')
</script>手动定义位置
reference 属性除了接收 DOM 元素,还可以接受 { getBoundingClientRect: () => DOMRect } 类型对象,通过构建 DOMRect 对象,可以将弹出框放置到任意位置。
vue
<template>
<s-popover v-model:visible="visible" :position="position" :reference="reference">
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
<s-list card>
<s-list-item arrow hover title="屏幕中下" @click="onBottomCenter" />
<s-list-item arrow hover title="屏幕右下" @click="onBottomEnd" />
<s-list-item arrow hover title="屏幕中上" @click="onTopCenter" />
</s-list>
</template>
<script setup lang="ts">
import { toast, type MenuItemProps, type PopoverPosition, type PopperTarget } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const position = ref<PopoverPosition>('bottom')
let domRect = new DOMRect()
const reference = ref<PopperTarget>({
getBoundingClientRect() {
return domRect
},
})
const onSelect = (props: MenuItemProps) => {
visible.value = false
toast(props.label!)
}
const onShow = (event: MouseEvent, pos: PopoverPosition, rect: DOMRect) => {
event.stopPropagation()
position.value = pos
domRect = rect
visible.value = true
}
const onBottomCenter = (event: MouseEvent) => {
onShow(event, 'bottom', new DOMRect(0, window.innerHeight - 10, window.innerWidth, 10))
}
const onBottomEnd = (event: MouseEvent) => {
onShow(event, 'bottom-end', new DOMRect(window.innerWidth - 40, window.innerHeight - 10, 10, 10))
}
const onTopCenter = (event: MouseEvent) => {
onShow(event, 'top', new DOMRect(0, 0, window.innerWidth, 10))
}
</script>vue
<template>
<s-popover v-model:visible="visible" :position="position" :reference="reference">
<s-menu @select="onSelect">
<s-menu-item :value="1" label="选项1" />
<s-menu-item :value="2" label="选项2" />
<s-menu-item :value="3" label="选项3" />
</s-menu>
</s-popover>
<s-list card>
<s-list-item arrow hover title="屏幕中下" @click="onBottomCenter" />
<s-list-item arrow hover title="屏幕右下" @click="onBottomEnd" />
<s-list-item arrow hover title="屏幕中上" @click="onTopCenter" />
</s-list>
</template>
<script setup lang="js">
import { toast } from 'sard'
import { ref } from 'vue'
const visible = ref(false)
const position = ref('bottom')
let domRect = new DOMRect()
const reference = ref({
getBoundingClientRect() {
return domRect
},
})
const onSelect = (props) => {
visible.value = false
toast(props.label)
}
const onShow = (event, pos, rect) => {
event.stopPropagation()
position.value = pos
domRect = rect
visible.value = true
}
const onBottomCenter = (event) => {
onShow(event, 'bottom', new DOMRect(0, window.innerHeight - 10, window.innerWidth, 10))
}
const onBottomEnd = (event) => {
onShow(event, 'bottom-end', new DOMRect(window.innerWidth - 40, window.innerHeight - 10, 10, 10))
}
const onTopCenter = (event) => {
onShow(event, 'top', new DOMRect(0, 0, window.innerWidth, 10))
}
</script>API
PopoverProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| visible | 是否显示气泡弹出框 | boolean | - |
| position | 弹出位置 | PopoverPosition | 'bottom' |
| theme | 主题风格 | 'dark' | 'light' | 'light' |
| refGap | 气泡弹出框与reference元素的间距,单位 px | number | 10 |
| viewport-gap | 气泡弹出框距与视窗的间距,单位 px | number | 10 |
| reference | 触发弹出框的 HTML 元素,或者类似 PopperTarget 的结构类型对象 | PopperTarget | Element | null | - |
| outside-closable | 点击弹出框外部时是否关闭弹出框 | boolean | true |
PopperTarget
ts
export interface PopperTarget {
getBoundingClientRect: () => DOMRect
}PopoverSlots
| 插槽 | 描述 | 属性 |
|---|---|---|
| default | 自定义弹出框内容 | - |
| reference | 触发弹出框的 HTML 元素,只接受单个根元素 | - |
PopoverEmits
| 事件 | 描述 | 类型 |
|---|---|---|
| update:visible | 弹出框显隐时触发 | (visible: boolean) => void |
PopoverPosition
tsx
type PopoverPosition =
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'主题定制
样式变量
| CSS 变量 | 值 |
|---|---|
--s-popover-bg | var(--s-bg-color-elevated) |
--s-popover-box-shadow | var(--s-box-shadow-secondary) |
--s-popover-min-width | 80px |
--s-popover-border-radius | var(--s-border-radius) |
--s-popover-bg-dark | var(--s-gray-800) |
--s-popover-color-dark | var(--s-white) |
--s-popover-arrow-size | 10px |