Popover
功能
- 可受控或非受控
- 自定义边、对齐、偏移、碰撞处理
- 可选显示指向箭头
- 完全管理并支持自定义的焦点控制
- 支持模态和非模态
- 高度可自定义的关闭和层级行为
安装
在命令行中安装组件。
$ npm add reka-ui结构
导入所有部件并进行组合。
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot>
<PopoverTrigger />
<PopoverAnchor />
<PopoverPortal>
<PopoverContent>
<PopoverClose />
<PopoverArrow />
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>API 参考
Root
包含 Popover 的所有部件。
| Prop | Default | Type |
|---|---|---|
defaultOpen | false | booleanThe open state of the popover when it is initially rendered. Use when you do not need to control its open state. |
modal | false | booleanThe modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers. |
open | booleanThe controlled open state of the popover. |
| Emit | Payload |
|---|---|
update:open | [value: boolean]Event handler called when the open state of the popover changes. |
| Slots (default) | Payload |
|---|---|
open | booleanCurrent open state |
close | (): voidClose the popover |
Trigger
用于切换 Popover 显示状态的按钮。默认情况下,PopoverContent 会相对于此触发器进行定位。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Anchor
用于作为 PopoverContent 定位基准的可选元素。如果未使用此部件,内容将相对于 PopoverTrigger 进行定位。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
reference | ReferenceElementThe reference (or anchor) element that is being referred to for positioning. If not provided will use the current component as anchor. |
Portal
使用此部件时,会将内容部分传送到 body 中。
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Content
Popover 打开时弹出的组件。
| Prop | Default | Type |
|---|---|---|
align | 'start' | 'center' | 'end'The preferred alignment against the trigger. May change when collisions occur. | |
alignFlip | booleanFlip alignment when colliding with boundary.
May only occur when | |
alignOffset | numberAn offset in pixels from the | |
arrowPadding | numberThe padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. | |
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
avoidCollisions | booleanWhen | |
collisionBoundary | Element | (Element | null)[] | nullThe element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. | |
collisionPadding | number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. | |
disableOutsidePointerEvents | booleanWhen | |
disableUpdateOnLayoutShift | booleanWhether to disable the update position for the content when the layout shifted. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
hideWhenDetached | booleanWhether to hide the content when the trigger becomes fully occluded. | |
positionStrategy | 'fixed' | 'absolute'The type of CSS position property to use. | |
prioritizePosition | booleanForce content to be position within the viewport. Might overlap the reference element, which may not be desired. | |
reference | ReferenceElementThe custom element or virtual element that will be set as the reference to position the floating element. If provided, it will replace the default anchor element. | |
side | 'top' | 'right' | 'bottom' | 'left'The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. | |
sideFlip | booleanFlip to the opposite side when colliding with boundary. | |
sideOffset | numberThe distance in pixels from the trigger. | |
sticky | 'partial' | 'always'The sticky behavior on the align axis. | |
updatePositionStrategy | 'always' | 'optimized'Strategy to update the position of the floating element on every animation frame. |
| Emit | Payload |
|---|---|
closeAutoFocus | [event: Event]Event handler called when auto-focusing on close. Can be prevented. |
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent]Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
openAutoFocus | [event: Event]Event handler called when auto-focusing on open. Can be prevented. |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS Variable | Description |
|---|---|
--reka-popover-content-transform-origin | 根据内容和箭头的位置/偏移计算出的 transform-origin |
--reka-popover-content-available-width | 触发器和边界边缘之间的剩余宽度 |
--reka-popover-content-available-height | 触发器和边界边缘之间的剩余高度 |
--reka-popover-trigger-width | 触发器的宽度 |
--reka-popover-trigger-height | 触发器的高度 |
Arrow
可选的箭头元素,随 Popover 一起显示。可用于在视觉上连接锚点和 PopoverContent。必须在 PopoverContent 内部渲染。
| Prop | Default | Type |
|---|---|---|
as | 'svg' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
height | 5 | numberThe height of the arrow in pixels. |
rounded | booleanWhen | |
width | 10 | numberThe width of the arrow in pixels. |
Close
用于关闭已打开 Popover 的按钮。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
示例
限制内容尺寸
您可能需要限制内容的宽度以匹配触发器的宽度。您可能还希望限制其高度,使其不超过视口。
我们提供了多个 CSS 自定义属性,例如 --reka-popover-trigger-width 和 --reka-popover-content-available-height 来支持此需求。使用它们来限制内容的尺寸。
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot>
<PopoverTrigger>…</PopoverTrigger>
<PopoverPortal>
<PopoverContent
class="PopoverContent"
:side-offset="5"
>
…
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>/* styles.css */
.PopoverContent {
width: var(--reka-popover-trigger-width);
max-height: var(--reka-popover-content-available-height);
}感知原点的动画
我们提供了一个 CSS 自定义属性 --reka-popover-content-transform-origin。使用它可以根据 side、sideOffset、align、alignOffset 以及任何碰撞情况,从计算出的原点对内容进行动画处理。
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot>
<PopoverTrigger>…</PopoverTrigger>
<PopoverPortal>
<PopoverContent class="PopoverContent">
…
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>/* styles.css */
.PopoverContent {
transform-origin: var(--reka-popover-content-transform-origin);
animation: scaleIn 0.5s ease-out;
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}感知碰撞的动画
我们提供了 data-side 和 data-align 属性。它们的值会在运行时根据碰撞情况发生变化。使用它们来创建感知碰撞和方向的动画。
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot>
<PopoverTrigger>…</PopoverTrigger>
<PopoverPortal>
<PopoverContent class="PopoverContent">
…
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>/* styles.css */
.PopoverContent {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.PopoverContent[data-side="top"] {
animation-name: slideUp;
}
.PopoverContent[data-side="bottom"] {
animation-name: slideDown;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}使用自定义锚点
如果您不希望使用触发器作为锚点,可以将内容锚定到另一个元素。
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot>
<PopoverAnchor as-child>
<div class="Row">
行作为锚点 <PopoverTrigger>触发器</PopoverTrigger>
</div>
</PopoverAnchor>
<PopoverPortal>
<PopoverContent>…</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>/* styles.css */
.Row {
background-color: gainsboro;
padding: 20px;
}使用插槽 Prop 关闭
或者,您可以使用 PopoverRoot 的插槽 Prop 提供的 close 方法,以编程方式关闭 Popover。
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>
<template>
<PopoverRoot v-slot="{ close }">
<PopoverTrigger>打开</PopoverTrigger>
<PopoverAnchor />
<PopoverPortal>
<PopoverContent>
<button type="submit" @click="close">
提交
</button>
<PopoverArrow />
</PopoverContent>
</PopoverPortal>
</PopoverRoot>
</template>可访问性
键盘交互
| Key | Description |
|---|---|
Space | 打开/关闭 Popover。 |
Enter | 打开/关闭 Popover。 |
Tab | 将焦点移动到下一个可聚焦元素 |
Shift + Tab | 将焦点移动到上一个可聚焦元素 |
Esc | 关闭 Popover 并将焦点移动到 PopoverTrigger。 |
自定义 API
通过将原始部件抽象到您自己的组件中来创建您自己的 API。
抽象箭头并设置默认配置
此示例抽象了 PopoverArrow 部件并设置了默认的 sideOffset 配置。
用法
<script setup lang="ts">
import { Popover, PopoverContent, PopoverTrigger } from './your-popover'
</script>
<template>
<Popover>
<PopoverTrigger>Popover trigger</PopoverTrigger>
<PopoverContent>Popover content</PopoverContent>
</Popover>
</template>实现
// your-popover.ts
export { default as PopoverContent } from 'PopoverContent.vue'
export { PopoverRoot as Popover, PopoverTrigger } from 'reka-ui'<!-- PopoverContent.vue -->
<script setup lang="ts">
import type { PopoverContentEmits, PopoverContentProps } from 'reka-ui'
import { PopoverContent, PopoverPortal, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<PopoverContentProps>()
const emits = defineEmits<PopoverContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<PopoverPortal>
<PopoverContent v-bind="{ ...forwarded, ...$attrs }">
<slot />
</PopoverContent>
</PopoverPortal>
</template>