跳到正文
册页

浮层

Popover - 气泡卡片

锚在控件上的面板,里面装可以交互的内容。

用法

什么时候用它

任何带链接、字段或按钮的东西。tooltip 描述而不能被进入——把控件放进 tooltip,它就再也够不到了。
TSX
import { Popover } from '@misoto22/folio'

说明

Radix Popover 的根节点、触发器、锚点和关闭按钮,带类型的原样透传。

结构

Popover anatomy
部件说明
触发器必填PopoverTrigger,一个透传。写 asChild 就能保住你自己那个控件;面板被 Escape 关掉时,Radix 把焦点送回这里。
面板必填锚定的那个 dialog:宽度写死 18rem,内边距 1rem,离触发器 8px,名字由 label 给。打开时焦点会移进去,但不会被困在里面。
锚点PopoverAnchor,用在面板该对着的不是打开它的那个控件时——溢出按钮所作用的那一行,或者正文里的一段选区。
关闭PopoverClose,以及 showClose 在顶部末端角上渲染的那个 32px 的 X。和 Dialog 不一样,它默认是关的。

实践建议

推荐

  • label 要说明面板里装的是什么,而不是把触发器再念一遍:它是在进入时播报的,所以「Filter options」告诉读者他落到了哪里,而把按钮上的字重复一遍,什么新消息都没有。
  • 视觉上的锚点不是触发器时,用 PopoverAnchor——比如一个作用在选中行上的工具栏按钮——否则面板跟着按钮走,会离它正在编辑的那个东西越来越远。
  • 关掉面板的那个控件要用 PopoverClose 包住,别自己翻 state,这样关闭走的是 Radix,焦点回到触发器,而不是回到文档最开头。
  • 面板里放表单时,把 showClose 打开。它默认是关的,而一个非模态、唯一出路是点到别处的面板,让一次进行中的编辑没有任何一个明确的收尾动作。

避免

  • 它不是模态的——Radix 的 modal 默认是 false,这里没有任何东西改它,所以既没有焦点陷阱也没有滚动锁定:从里面最后一个控件继续 Tab,焦点就走进页面,Radix 把这读成焦点跑到了外面,于是在任务做到一半时关掉面板。
  • 一串动作该放进 DropdownMenu。popover 里的内容就是普通的 Tab 停靠点,十个动作就是十个停靠点,还没有首字母跳转;而菜单是一个停靠点,方向键和按字母跳都在它里面。
  • 面板停在 --z-dropdown,解析出来是 220——高过 Dialog 的 210,好让从对话框里打开的 popover 够得到。反过来说,一个由页面打开、又被你自己的 state 一直按着开的 popover,会盖在随后到来的模态上面;Radix 会在外部交互时把它关掉,而一个无视这一点的受控 open,是唯一能看到这一幕的写法。

示例

a filter panel

一块里面装着控件的面板,而这正是它和 tooltip 之间的全部分别:往提示里放一个字段或一个按钮,那东西就够不着了。label 为这块面板命名,并会在进入时被播报,所以让它说清面板里装了什么,而不是把触发器的话重复一遍。showClose 默认是关的,这里打开是因为面板里装着一份表单——一块唯一出口是「点到别处」的非模态面板,会让一次进行到一半的编辑没有一个明确的收尾。它是真的不模态:这里没有焦点陷阱,所以从最后一个控件继续按 Tab,焦点会走进页面里,而 Radix 会把这读成「焦点跑到外面去了」并关掉面板。

anchored elsewhere

这块面板是对着它所编辑的那一行定位的,不是对着打开它的那个工具栏按钮。PopoverAnchor 就是干这个的:没有它,面板会跟着触发器走,从正在被修改的那个东西旁边飘开,而在一张长表格上,那意味着一个悬在屏幕顶端的编辑器,对着靠近底部的一行。触发器仍然是触发器——按 Escape 焦点还是回到它那儿——只是几何位置挪了。

Selected row

codex/photo-cache62s

panel or menu

两块带锚点的面板并排放着,对着区分它们的那一个问题:里面的内容是你要填的,还是你要挑的?一个 popover 的内容是普通的 tab 停靠点,所以四个动作放在一个里面,就是四个停靠点,既没有首字母跳转、也没有方向键——而一个菜单是单个停靠点,两样都有。反过来,菜单装不下一个滑块或一个字段,因为 menuitem 不是一个输入值的地方。两者都 portal 到同一层,也都在 Escape 时关回它们的触发器;两者都不该放进 Dialog 里,那会盖在它们上面。

组成部分

在调用处组合,而不是通过 props 配置——所以这个组件没有预料到的布局,依然表达得出来。

PopoverContent

锚在某个控件上的面板,里面装着读者可以交互的内容。

和 Tooltip 的分界线不是视觉上的,是行为上的:tooltip 是描述,进不去;popover 装的是你能 Tab 进去的东西。任何带链接、字段或按钮的内容都属于 popover,把它放进 tooltip 会让它够不着——焦点刚要移进去,tooltip 就关了。

和 DropdownMenu 的分界:菜单是一串带菜单语义、用方向键导航的动作。popover 是自由格式的,里面的内容像页面其他地方一样用 Tab 走。

PopoverContent props
属性类型默认值说明
label必填string给面板起名字,供辅助技术使用。必填——popover 是一个 dialog。
showClosebooleanfalse显示右上角的关闭控件。

同时接受 ComponentProps<typeof PopoverPrimitive.Content> 里的全部属性,它们会直接透传给底层元素,不再逐条列出。

再导出

Popover = PopoverPrimitive.Root

Radix Popover 的根节点、触发器、锚点和关闭按钮,带类型的原样透传。

PopoverTrigger = PopoverPrimitive.Trigger
PopoverAnchor = PopoverPrimitive.Anchor
PopoverClose = PopoverPrimitive.Close

键盘操作

Popover keyboard interactions
按键作用
EnterSpace打开它。
Tab像页面其他地方一样用 Tab 走过里面的内容。
Escape关闭它,并把焦点还给触发器。

无障碍

  • 在有边界的框里——设备预览、内嵌控制台——用 `<OverlayContainer container={el}>` 包住这棵子树。面板会渲染进那个元素,按它的边界翻转,而不是按视口;框上设的 `dir` 和 `data-density` 也就跟着生效了。
  • label 必填:popover 是一个 dialog,没有名字的 dialog 什么都没播报。
  • 里面的内容像页面其他部分一样用 Tab 走,不像菜单那样用方向键。