跳至内容

弹出框

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

<template>
  <PopoverRoot>
    <PopoverTrigger
      class="rounded-full w-[35px] h-[35px] inline-flex items-center justify-center text-grass11 bg-white shadow-[0_2px_10px] shadow-blackA7 hover:bg-green3 focus:shadow-[0_0_0_2px] focus:shadow-black cursor-default outline-none"
      aria-label="Update dimensions"
    >
      <Icon icon="radix-icons:mixer-horizontal" />
    </PopoverTrigger>
    <PopoverPortal>
      <PopoverContent
        side="bottom"
        :side-offset="5"
        class="rounded p-5 w-[260px] bg-white shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2)] focus:shadow-[0_10px_38px_-10px_hsla(206,22%,7%,.35),0_10px_20px_-15px_hsla(206,22%,7%,.2),0_0_0_2px_theme(colors.green7)] 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-[15px] leading-[19px] font-semibold mb-2.5">
            Dimensions
          </p>
          <fieldset class="flex gap-5 items-center">
            <label
              class="text-[13px] text-grass11 w-[75px]"
              for="width"
            > Width </label>
            <input
              id="width"
              class="w-full inline-flex items-center justify-center flex-1 rounded px-2.5 text-[13px] 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-[13px] text-grass11 w-[75px]"
              for="maxWidth"
            > Max. width </label>
            <input
              id="maxWidth"
              class="w-full inline-flex items-center justify-center flex-1 rounded px-2.5 text-[13px] 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-[13px] text-grass11 w-[75px]"
              for="height"
            > Height </label>
            <input
              id="height"
              class="w-full inline-flex items-center justify-center flex-1 rounded px-2.5 text-[13px] 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-[13px] text-grass11 w-[75px]"
              for="maxHeight"
            > Max. height </label>
            <input
              id="maxHeight"
              class="w-full inline-flex items-center justify-center flex-1 rounded px-2.5 text-[13px] 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-[5px] right-[5px] 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" />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>

功能

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

安装

从命令行安装组件。

sh
$ npm add radix-vue

剖析

导入所有部分并将其拼凑在一起。

vue
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'radix-vue'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger />
    <PopoverAnchor />
    <PopoverPortal>
      <PopoverContent>
        <PopoverClose />
        <PopoverArrow />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>

API 参考

包含弹出框的所有部分。

道具默认类型
defaultOpen
false
boolean

弹出框在最初呈现时的打开状态。当您不需要控制其打开状态时使用。

modal
false
boolean

弹出框的模态性。当设置为 true 时,外部元素的交互将被禁用,并且只有弹出框内容对屏幕阅读器可见。

open
boolean

弹出框的受控打开状态。

发射有效载荷
update:open
[value: boolean]

弹出框的打开状态发生变化时调用的事件处理程序。

插槽 (默认)有效载荷
open
boolean

当前打开状态

触发器

切换弹出框的按钮。默认情况下,PopoverContent 将相对于触发器定位。

道具默认类型
as
'button'
AsTag | Component

此组件应呈现为的元素或组件。可以被asChild覆盖

asChild
boolean

更改为子元素传递的默认渲染元素,合并它们的 props 和行为。

阅读我们的 组合 指南以了解更多详细信息。

数据属性价值
[data-state]"open" | "closed"

锚点

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

道具默认类型
as
'div'
AsTag | Component

此组件应呈现为的元素或组件。可以被asChild覆盖

asChild
boolean

更改为子元素传递的默认渲染元素,合并它们的 props 和行为。

阅读我们的 组合 指南以了解更多详细信息。

元素
可测量

门户

使用时,将内容部分门户到body中。

道具默认类型
disabled
boolean

禁用传送并内联渲染组件

reference

forceMount
boolean

用于在需要更多控制时强制挂载。当使用 Vue 动画库控制动画时很有用。

to
string | HTMLElement

Vue 原生传送组件道具:to

reference

内容

弹出框打开时弹出的组件。

道具默认类型
align
'start' | 'center' | 'end'

相对于触发器的首选对齐方式。发生碰撞时可能会改变。

alignOffset
number

startend对齐选项偏移的像素数。

arrowPadding
number

箭头和内容边缘之间的填充。如果您的内容有圆角,这将防止它溢出角落。

as
'div'
AsTag | Component

此组件应呈现为的元素或组件。可以被asChild覆盖

asChild
boolean

更改为子元素传递的默认渲染元素,合并它们的 props 和行为。

阅读我们的 组合 指南以了解更多详细信息。

avoidCollisions
boolean

当为true时,覆盖侧面和对齐首选项以防止与边界边缘发生碰撞。

collisionBoundary
Element | (Element | null)[] | null

用作碰撞边界的元素。默认情况下,这是视窗,但您可以提供其他元素以将其包含在此检查中。

collisionPadding
number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>

从边界边缘到应该发生碰撞检测的位置的像素距离。接受一个数字(所有边都相同),或一个部分填充对象,例如:{ top: 20, left: 20 }。

disableOutsidePointerEvents
boolean

当为true时,将禁用DismissableLayer外部元素上的悬停/焦点/单击交互。用户需要在外部元素上点击两次才能与它们交互:一次关闭DismissableLayer,另一次触发元素。

forceMount
boolean

用于在需要更多控制时强制挂载。当使用 Vue 动画库控制动画时很有用。

hideWhenDetached
boolean

当触发器完全被遮挡时是否隐藏内容。

prioritizePosition
boolean

强制内容定位在视窗内。

可能会与引用元素重叠,这可能不是想要的。

side
'top' | 'right' | 'bottom' | 'left'

打开时渲染到触发器的首选侧面。当发生碰撞并且启用 avoidCollisions 时将被反转。

sideOffset
number

与触发器之间的像素距离。

sticky
'partial' | 'always'

对齐轴上的粘性行为。partial 会将内容保留在边界中,只要触发器至少部分地在边界中,而 "always" 会无论如何都将内容保留在边界中。

trapFocus
boolean

是否应该将焦点困在MenuContent

updatePositionStrategy
'always' | 'optimized'

在每一帧动画中更新浮动元素位置的策略。

发射有效载荷
closeAutoFocus
[event: Event]

关闭时自动聚焦时调用的事件处理程序。可以阻止。

escapeKeyDown
[event: KeyboardEvent]

按下 Esc 键时调用的事件处理程序。可以阻止。

focusOutside
[event: FocusOutsideEvent]

焦点移出DismissableLayer时调用的事件处理程序。可以阻止。

interactOutside
[event: PointerDownOutsideEvent | FocusOutsideEvent]

DismissableLayer外部发生交互时调用的事件处理程序。具体来说,当pointerdown事件发生在外部或焦点移出它时。可以阻止。

openAutoFocus
[event: Event]

打开时自动聚焦时调用的事件处理程序。可以阻止。

pointerDownOutside
[event: PointerDownOutsideEvent]

DismissableLayer外部发生pointerdown事件时调用的事件处理程序。可以阻止。

数据属性价值
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS 变量描述
--radix-popover-content-transform-origin
从内容和箭头位置/偏移计算的transform-origin
--radix-popover-content-available-width
触发器和边界边缘之间的剩余宽度
--radix-popover-content-available-height
触发器和边界边缘之间的剩余高度
--radix-popover-trigger-width
触发器的宽度
--radix-popover-trigger-height
触发器的高度

箭头

一个可选的箭头元素,用于与弹出框一起渲染。这可用于帮助视觉上将锚点与PopoverContent连接起来。必须渲染在PopoverContent内部。

道具默认类型
as
'svg'
AsTag | Component

此组件应呈现为的元素或组件。可以被asChild覆盖

asChild
boolean

更改为子元素传递的默认渲染元素,合并它们的 props 和行为。

阅读我们的 组合 指南以了解更多详细信息。

height
5
number

箭头的像素高度。

width
10
number

箭头的像素宽度。

关闭

关闭打开弹出框的按钮。

道具默认类型
as
'button'
AsTag | Component

此组件应呈现为的元素或组件。可以被asChild覆盖

asChild
boolean

更改为子元素传递的默认渲染元素,合并它们的 props 和行为。

阅读我们的 组合 指南以了解更多详细信息。

示例

约束内容大小

您可能希望约束内容的宽度,使其与触发器宽度匹配。您可能还想约束其高度,使其不超过视窗。

我们公开了几个 CSS 自定义属性,例如--radix-popover-trigger-width--radix-popover-content-available-height来支持这一点。使用它们来约束内容尺寸。

vue
// index.vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'radix-vue'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger></PopoverTrigger>
    <PopoverPortal>
      <PopoverContent
        class="PopoverContent"
        :side-offset="5"
      >

      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
css
/* styles.css */
.PopoverContent {
  width: var(--radix-popover-trigger-width);
  max-height: var(--radix-popover-content-available-height);
}

支持原点的动画

我们提供了一个 CSS 自定义属性 --radix-popover-content-transform-origin。使用它可以根据 sidesideOffsetalignalignOffset 和任何冲突来从内容的计算原点进行动画。

vue
// index.vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'radix-vue'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger></PopoverTrigger>
    <PopoverPortal>
      <PopoverContent class="PopoverContent">

      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
css
/* styles.css */
.PopoverContent {
  transform-origin: var(--radix-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 属性。它们的 value 会在运行时改变以反映碰撞。使用它们可以创建支持碰撞和方向的动画。

vue
// index.vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'radix-vue'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger></PopoverTrigger>
    <PopoverPortal>
      <PopoverContent class="PopoverContent">

      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
css
/* 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);
  }
}

使用自定义锚点

如果你不想使用触发器作为锚点,可以将内容锚定到另一个元素。

vue
// index.vue
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'radix-vue'
</script>

<template>
  <PopoverRoot>
    <PopoverAnchor as-child>
      <div class="Row">
        Row as anchor <PopoverTrigger>Trigger</PopoverTrigger>
      </div>
    </PopoverAnchor>

    <PopoverPortal>
      <PopoverContent></PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
css
/* styles.css */
.Row {
  background-color: gainsboro;
  padding: 20px;
}

无障碍性

遵循 Dialog WAI-ARIA 设计模式.

键盘交互

描述
空格
打开/关闭弹出框。
回车
打开/关闭弹出框。
Tab
将焦点移动到下一个可聚焦元素
Shift + Tab
将焦点移动到上一个可聚焦元素
Esc
关闭弹出框并将焦点移动到 PopoverTrigger

自定义 API

通过将基本部分抽象到您自己的组件中来创建您自己的 API。

抽象箭头并设置默认配置

此示例抽象了 PopoverArrow 部分并设置了默认 sideOffset 配置。

用法

vue
<script setup lang="ts">
import { Popover, PopoverContent, PopoverTrigger } from './your-popover'
</script>

<template>
  <Popover>
    <PopoverTrigger>Popover trigger</PopoverTrigger>
    <PopoverContent>Popover content</PopoverContent>
  </Popover>
</template>

实现

ts
// your-popover.ts
export { default as PopoverContent } from 'PopoverContent.vue'

export { PopoverRoot as Popover, PopoverTrigger } from 'radix-vue'
vue
<!-- PopoverContent.vue -->
<script setup lang="ts">
import { PopoverContent, type PopoverContentEmits, type PopoverContentProps, PopoverPortal, useForwardPropsEmits, } from 'radix-vue'

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>