弹出框

锚定在按钮上的可访问弹出层。

安装

pnpm dlx shadcn@latest add @lumi-ui/popover

基本用法

import {
  Popover,
  PopoverContent,
  PopoverDescription,
  PopoverTitle,
  PopoverTrigger,
} from "@/registry/ui/popover";
 
export function PopoverDemo() {
  return (
    <Popover>
      <PopoverTrigger>打开</PopoverTrigger>
      <PopoverContent>
        <PopoverTitle>弹出框标题</PopoverTitle>
        <PopoverDescription>弹出框描述</PopoverDescription>
      </PopoverContent>
    </Popover>
  );
}

组件结构

<Popover>   
  <PopoverTrigger />
  <PopoverContent>     
    <PopoverTitle />     
    <PopoverDescription />     
  </PopoverContent> 
</Popover>

功能特性

带箭头

与触发器等宽

实用示例

悬停打开

分离的触发器

import { createPopoverHandle } from "@/components/ui/popover";
 
const demoPopover = createPopoverHandle();
 
<PopoverTrigger handle={demoPopover}>
  触发器
</PopoverTrigger>
 
<Popover handle={demoPopover}>
  ...
</Popover>

多个触发器

单个弹出框可以由多个触发元素打开。你可以通过让多个分离的触发器共享同一个 handle 来实现,或者在单个 <Popover> 中放置多个 <PopoverTrigger> 组件。

同一弹出框中的多个触发器
<Popover>
  <PopoverTrigger>触发器 1</PopoverTrigger>
  <PopoverTrigger>触发器 2</PopoverTrigger>
  ...
</Popover>
多个分离的触发器
const demoPopover = createPopoverHandle();
 
<PopoverTrigger handle={demoPopover}>
  触发器 1
</PopoverTrigger>
 
<PopoverTrigger handle={demoPopover}>
  触发器 2
</PopoverTrigger>
 
<Popover handle={demoPopover}>
  ...
</Popover>

弹出框可以根据是哪个触发器打开它来渲染不同的内容。具体做法是向 <PopoverTrigger> 传递一个载荷(payload),并在 <Popover> 中使用 function-as-a-child 模式。通过为 createPopoverHandle() 函数提供类型参数,载荷可以获得严格的类型约束:

const demoPopover = createPopoverHandle<{ text: string }>();
 
<PopoverTrigger handle={demoPopover} payload={{ text: '触发器 1' }}>
  触发器 1
</PopoverTrigger>
 
<PopoverTrigger handle={demoPopover} payload={{ text: '触发器 2' }}>
  触发器 2
</PopoverTrigger>
 
<Popover handle={demoPopover}>
  {({ payload }) => (
    <PopoverPortal>
      <PopoverPositioner>
        <PopoverPopup>
          <PopoverArrow />
          <PopoverTitle>弹出框</PopoverTitle>
          {payload !== undefined && (
            <PopoverDescription>
              此弹出框由 {payload.text} 打开
            </PopoverDescription>
          )}
        </PopoverPopup>
      </PopoverPositioner>
    </PopoverPortal>
  )}
</Popover>

受控模式与多个触发器

你可以通过 <Popover> 上的 openonOpenChange 属性从外部控制弹出框的打开状态,从而根据应用状态管理其可见性。当使用多个触发器时,你需要通过 <Popover> 上的 triggerId 属性和每个 <PopoverTrigger> 上的 id 属性来管理当前哪个触发器处于激活状态。

需要注意的是,并没有单独的 onTriggerIdChange 属性。相反,onOpenChange 回调会接收一个额外参数 eventDetails,其中包含了发起本次状态变更的触发器元素。

弹出框动画

你可以在弹出框于不同触发元素之间切换时为其添加动画效果,包括位置、尺寸和内容的过渡动画。

位置与尺寸

要为弹出框的位置添加动画,请对 Positioner 的 left、right、top 和 bottom 属性应用 CSS 过渡。要为尺寸添加动画,请对 Popup 的 width 和 height 应用过渡。

内容

弹出框还支持内容过渡。当不同触发器在同一个弹出框中显示不同内容时,这个特性会非常有用。

要启用内容动画,请将内容包裹在 <PopoverViewport> 部分中。这个部分提供了实现方向感知动画所需的能力。它会渲染一个带有 data-activation-direction 属性(left、right、up 或 down)的 div,用于指示新触发器相对于上一个触发器的位置。

<PopoverViewport> 内部,内容会进一步被包裹在带有 data 属性的 div 中,以便于添加样式:

  • data-current:当前可见的内容,可能是没有过渡时的当前内容,也可能是过渡时进入的内容。
  • data-previous:过渡过程中即将退出的内容。

你可以利用这些属性为进入和退出动画设置样式。