Lzh on GitHub

Popover

通过按钮触发,在门户中显示丰富的内容。
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger
      class="rounded-full w-[35px] h-[35px] inline-flex items-center justify-center text-grass11 bg-white shadow-sm border hover:bg-stone-50 cursor-default focus:shadow-[0_0_0_2px]  focus:shadow-black dark:focus:shadow-green8 focus:outline-none "
      aria-label="Update dimensions"
    >
      <Icon icon="radix-icons:mixer-horizontal" />
    </PopoverTrigger>
    <PopoverPortal>
      <PopoverContent
        side="bottom"
        :side-offset="5"
        class="rounded-lg p-5 w-[260px] bg-white shadow-sm border will-change-[transform,opacity] data-[state=open]:data-[side=top]:animate-slideDownAndFade data-[state=open]:data-[side=right]:animate-slideLeftAndFade data-[state=open]:data-[side=bottom]:animate-slideUpAndFade data-[state=open]:data-[side=left]:animate-slideRightAndFade"
      >
        <div class="flex flex-col gap-2.5">
          <p class="text-mauve12 text-sm leading-[19px] font-semibold mb-2.5">
            Dimensions
          </p>
          <fieldset class="flex gap-5 items-center">
            <label
              class="text-xs text-grass11 w-[75px]"
              for="width"
            > Width </label>
            <input
              id="width"
              class="w-full inline-flex bg-stone-50 items-center justify-center flex-1 rounded px-2.5 text-xs leading-none text-grass11 shadow-[0_0_0_1px] shadow-green7 h-[25px] focus:shadow-[0_0_0_2px] focus:shadow-green8 outline-none"
              defaultValue="100%"
            >
          </fieldset>
          <fieldset class="flex gap-5 items-center">
            <label
              class="text-xs text-grass11 w-[75px]"
              for="maxWidth"
            > Max. width </label>
            <input
              id="maxWidth"
              class="w-full inline-flex bg-stone-50 items-center justify-center flex-1 rounded px-2.5 text-xs leading-none text-grass11 shadow-[0_0_0_1px] shadow-green7 h-[25px] focus:shadow-[0_0_0_2px] focus:shadow-green8 outline-none"
              defaultValue="300px"
            >
          </fieldset>
          <fieldset class="flex gap-5 items-center">
            <label
              class="text-xs text-grass11 w-[75px]"
              for="height"
            > Height </label>
            <input
              id="height"
              class="w-full inline-flex bg-stone-50 items-center justify-center flex-1 rounded px-2.5 text-xs leading-none text-grass11 shadow-[0_0_0_1px] shadow-green7 h-[25px] focus:shadow-[0_0_0_2px] focus:shadow-green8 outline-none"
              defaultValue="25px"
            >
          </fieldset>
          <fieldset class="flex gap-5 items-center">
            <label
              class="text-xs text-grass11 w-[75px]"
              for="maxHeight"
            > Max. height </label>
            <input
              id="maxHeight"
              class="w-full inline-flex bg-stone-50 items-center justify-center flex-1 rounded px-2.5 text-xs leading-none text-grass11 shadow-[0_0_0_1px] shadow-green7 h-[25px] focus:shadow-[0_0_0_2px] focus:shadow-green8 outline-none"
              defaultValue="none"
            >
          </fieldset>
        </div>
        <PopoverClose
          class="rounded-full h-[25px] w-[25px] inline-flex items-center justify-center text-grass11 absolute top-[8px] right-[8px] hover:bg-green4 focus:shadow-[0_0_0_2px] focus:shadow-green7 outline-none cursor-default"
          aria-label="Close"
        >
          <Icon icon="radix-icons:cross-2" />
        </PopoverClose>
        <PopoverArrow class="fill-white stroke-gray-200" />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>

功能特点

  • 可控或不可控。
  • 自定义侧边、对齐方式、偏移量、碰撞处理。
  • 可选渲染一个指向箭头。
  • 焦点完全由管理和自定义。
  • 支持模态和非模态模式。
  • 关闭和分层行为高度可定制。

安装

从命令行安装组件。

$ 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 部分。

属性默认值类型描述
defaultOpenfalsebooleanPopover 首次渲染时的打开状态。当您不需要控制其打开状态时使用。
modalfalsebooleanPopover 的模态。当设置为 true 时,与外部元素的交互将被禁用,并且只有 Popover 内容对屏幕阅读器可见。
openbooleanPopover 的受控打开状态。

触发事件 (Emit)

事件Payload描述
update:open[value: boolean]Popover 打开状态更改时调用的事件处理程序。

默认插槽

插槽参数参数类型描述
openboolean当前打开状态
close(): void关闭 popover

Trigger

切换 Popover 的按钮。默认情况下,PopoverContent 会将自身定位在触发器旁边。

属性默认值类型描述
as'button'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildfalseboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。有关详细信息,请阅读我们的组合指南

数据属性

属性
[data-state]"open" | "closed"

Anchor

一个可选的元素,用于定位 PopoverContent。如果未使用此部分,内容将与 PopoverTrigger 并排定位。

属性默认值类型描述
as'div'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildfalseboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。有关详细信息,请阅读我们的组合指南
referenceReferenceElement用作定位参考(或锚点)的元素。如果未提供,将使用当前组件作为锚点。

Portal

使用时,将内容部分传送到 body 中。

属性默认值类型描述
deferfalseboolean延迟解决 Teleport 目标,直到应用程序的其他部分挂载(需要 Vue 3.5.0+)。 参考
disabledfalseboolean禁用 teleport 并在行内渲染组件。 参考
forceMountfalseboolean当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。
tostring | HTMLElementVue 原生 teleport 组件 prop :to参考

Content

当 Popover 打开时弹出的组件。

使用 Presence 组件构建 - 支持任何动画技术,同时保持对存在(presence)触发事件的访问。
属性默认值类型描述
align'start' | 'center' | 'end'针对触发器的首选对齐方式。碰撞时可能会改变。
alignFlipboolean在与边界发生碰撞时翻转对齐方式。仅当 prioritizePositiontrue 时才可能发生。
alignOffset0numberstartend 对齐选项的像素偏移量。
arrowPadding0number箭头与内容边缘之间的填充。如果您的内容有 border-radius,这将防止其溢出角落。
as'div'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildfalseboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。有关详细信息,请阅读我们的组合指南
avoidCollisionsfalseboolean当为 true 时,覆盖侧边和对齐首选项以防止与边界边缘发生碰撞。
collisionBoundarynullElement | (Element | null)[] | null用作碰撞边界的元素。默认情况下是视口,但您可以提供其他元素以包含在此检查中。
collisionPadding0number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>距离边界边缘发生碰撞检测的像素距离。接受数字(所有侧面相同)或部分填充对象,例如:{ top: 20, left: 20 }
disableOutsidePointerEventsfalseboolean当为 true 时,DismissableLayer 外部的悬停/焦点/点击交互将被禁用。用户需要两次点击外部元素才能与其交互:一次关闭 DismissableLayer,再次触发元素。
disableUpdateOnLayoutShiftfalseboolean是否在布局发生变化时禁用内容的更新位置。
forceMountfalseboolean当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。
hideWhenDetachedfalseboolean当触发器完全被遮挡时是否隐藏内容。
positionStrategy'absolute''fixed' | 'absolute'要使用的 CSS position 属性类型。
prioritizePositionfalseboolean强制内容定位在视口内。可能会与参考元素重叠,这可能不希望发生。
referenceReferenceElement将作为参考来定位浮动元素的自定义元素或虚拟元素。如果提供,它将替换默认的锚点元素。
side'top' | 'right' | 'bottom' | 'left'打开时,内容相对于触发器的首选侧边渲染。当发生碰撞且 avoidCollisions 启用时,将反转。
sideOffset0number距触发器的像素距离。
sticky'partial''partial' | 'always'对齐轴上的粘性行为。partial 将使内容保持在边界内,只要触发器至少部分在边界内,而 always 将始终使内容保持在边界内。
updatePositionStrategy'optimized''always' | 'optimized'在每个动画帧上更新浮动元素位置的策略。

触发事件 (Emit)

事件Payload描述
closeAutoFocus[event: Event]关闭时自动聚焦时调用的事件处理程序。可以阻止。
escapeKeyDown[event: KeyboardEvent]Escape 键按下时调用的事件处理程序。可以阻止。
focusOutside[event: FocusOutsideEvent]焦点移出 DismissableLayer 时调用的事件处理程序。可以阻止。
interactOutside[event: PointerDownOutsideEvent | FocusOutsideEvent]DismissableLayer 外部发生交互时调用的事件处理程序。具体来说,当 pointerdown 事件发生在外部或焦点移出时。可以阻止。
openAutoFocus[event: Event]打开时自动聚焦时调用的事件处理程序。可以阻止。
pointerDownOutside[event: PointerDownOutsideEvent]pointerdown 事件发生在 DismissableLayer 外部时调用的事件处理程序。可以阻止。

数据属性

属性
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"

CSS 变量

变量描述
--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 内部渲染。

属性默认值类型描述
as'svg'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildfalseboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。有关详细信息,请阅读我们的组合指南
height5number箭头的像素高度。
roundedfalseboolean当为 true 时,渲染圆角版本的箭头。不适用于 as/asChild
width10number箭头的像素宽度。

Close

关闭打开的 Popover 的按钮。

属性默认值类型描述
as'button'AsTag | Component此组件应渲染为的元素或组件。可以通过 asChild 覆盖。
asChildfalseboolean将默认渲染的元素更改为作为子元素传递的元素,合并它们的 props 和行为。有关详细信息,请阅读我们的组合指南

示例

约束内容大小

您可能希望约束内容的宽度以使其与触发器宽度匹配。您可能还希望约束其高度以不超过视口。 我们暴露了几个 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。使用它可以根据 sidesideOffsetalignalignOffset 以及任何碰撞情况,从计算出的原点对内容进行动画处理。

<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-sidedata-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">
        Row as anchor <PopoverTrigger>Trigger</PopoverTrigger>
      </div>
    </PopoverAnchor>
    <PopoverPortal>
      <PopoverContent></PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
styles.css
.Row {
  background-color: gainsboro;
  padding: 20px;
}

使用插槽 props 关闭

或者,您可以使用 PopoverRoot 插槽属性提供的 close 方法以编程方式关闭弹出框。

<script setup>
import { PopoverAnchor, PopoverArrow, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
</script>

<template>
  <PopoverRoot v-slot="{ close }">
    <PopoverTrigger>Open</PopoverTrigger>
    <PopoverAnchor />
    <PopoverPortal>
      <PopoverContent>
        <button type="submit" @click="close">
          Submit
        </button>
        <PopoverArrow />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>

可访问性

遵循 Dialog WAI-ARIA 设计模式

键盘交互

按键描述
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
// 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>