跳转到内容

Popover 气泡卡片 ​

由点击触发、贴着触发器的一小块浮层,可以放任意内容与交互。

用法 ​

点击展开,Escape 或点击外部关闭;positioner 负责定位,content 才是浮层本体

组件结构 ​

加粗的是必需部件。

data-scope="popover":trigger · positioner · content · title · description · close-trigger · arrow

示例 ​

朝向与间距 ​

placement 是请求值,空间不足时定位引擎会自动翻面;offset 调整浮层与触发器的距离

受控 ​

传入 open 后由宿主决定;这里额外关闭点击外部关闭,只有按钮与 Escape 能收起

当前:收起

尺寸 ​

三档改变浮层的内边距与字号,不写 size 即默认档;逐个点开触发器查看差别

确认气泡 ​

标题、说明与两个按钮组成一次就地确认;两个按钮按下后都只是收起浮层

记录还在

长内容滚动 ​

浮层自身不限高,为内部容器设置上限并开启滚动,标题与关闭按钮就不随内容滚动

模态浮层 ​

modal 使焦点限制在浮层内:Tab 到末尾回绕,旁边的按钮此时无法获得焦点

当前分组:收件箱

事件 ​

open-change 带一份 { open },报告的是本次要进入的状态;非受控时内部开合也照常触发一次

最近:(还没动过)

浮层与触发器同宽 ​

测量触发器的实际宽度写进 content 的行内样式,同时解除最大宽度上限;触发器更换文案后宽度随之变化

书写方向 ​

start / end 是逻辑对齐不是左右:RTL 下 bottom-start 贴的是锚点右缘,块轴上的对齐不受影响

落在指针位置 ​

触发器缩为一个像素、按点击坐标固定放置,浮层就固定在刚点击的位置;再点一次更换落点

在这块区域里点一下

设计指引 ​

何时使用 ​

  • 补充信息或一小组操作,不需要为此打开对话框。
  • 内容中有可聚焦元素(按钮、输入框),这是它与文字提示的分界。

何时不用 ​

  • 只有一句纯文字说明时,使用文字提示。
  • 悬停即出、不需要点击时,使用悬浮卡片。
  • 内容是一列命令时,使用菜单。

特性 ​

  • placement 只是首选位置,空间不足时定位引擎自动翻面。
  • modal 可选:需要锁定下层时开启;展开期间可动态切换,模态档会锁定页面滚动并让背景失活。
  • 可以与触发器同宽,也可以落在指针位置。
  • end 等对齐是逻辑方向,跟随书写方向,不是物理左右。

默认内容面使用 M2 磨砂配方,背景模糊只发生在浮层本体,箭头复用底色和边界,不重复模糊。正文保持不透明。触发器与关闭按钮走 Action Control 家族配方:触发器为 text 档中性描边,关闭按钮为 icon 档 ghost 面,悬停与按下沿画布承载阶梯换底,Space / Enter 与触屏按住期间投影 data-pressed,与指针按下同一副按压面。说明文字为 13px 说明档。系统减少透明度、高对比与强制色时,原位切换为实体表面;打印时收起交互浮层。

组合 ​

最佳实践 ​

  • 打开后焦点进入浮层,Escape 关闭并归还焦点。
  • 模态浮层关闭时,滚动锁与背景失活保留到真实退场动画结束;退场内容自身立即退出焦点与交互树。
  • 内容控制在一屏内,需要滚动时应改用抽屉。

反模式 ​

  • 悬停触发却内含按钮:指针移动过去的途中就会关闭。
  • 气泡内再弹出气泡。

API 参考 ​

产物 ​

层值
自定义元素<xh-popover>
Vue 组件XhPopoverArrow XhPopoverCloseTrigger XhPopoverContent XhPopoverDescription XhPopoverPositioner XhPopoverRoot XhPopoverTitle XhPopoverTrigger
组合式函数usePopover
状态机popoverMachine
皮肤@xihan-ui/styles/popover.css

Props ​

属性类型必填说明
openboolean
defaultOpenboolean
placementPlacement
dirDirection文字方向,默认 ltr。只改写浮层在行内轴上 start 与 end 的落点。
offsetnumber
modalboolean模态浮层陷入焦点;默认 false(非模态,Tab 可离开)。
closeOnEscapeboolean
closeOnInteractOutsideboolean
translationsPartial<PopoverTranslations>
sizeSize尺寸:sm / md / lg,决定面板的内边距档位。
onOpenChange(details: PopoverOpenChangeDetails) => voidopen 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
open-changePopoverOpenChangeDetailsopen 状态变化;detail 为 { open: boolean }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhPopoverRootdefaultPopoverRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhPopoverPositionercontainer() => Element | null浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。
XhPopoverRootchildrenSlotChildren<PopoverRootSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
trigger'open' | 'closed'
positioner'open' | 'closed'
content'open' | 'closed'

以下名称仅用于内部状态机。

状态:open · closed

事件:OPEN · TOGGLE · CLOSE · CONTROLLED.OPEN · CONTROLLED.CLOSE · PRESS.START · PRESS.END

判据:isOpenControlled

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
openboolean
setOpen(next: boolean) => void
getTriggerProps() => T['button']
getPositionerProps() => T['element']
getContentProps() => T['element']
getTitleProps() => T['element']
getDescriptionProps() => T['element']
getCloseTriggerProps() => T['button']
getArrowProps() => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Enter / Spacefocus in trigger切换开合,展开时把焦点移入 content
Escapeopen关闭并把焦点还给 trigger
Tabopen 且 modal在 content 内向后循环焦点
Shift+Tabopen 且 modal在 content 内向前循环焦点
Enter / Spaceheld in trigger / close-trigger按住期间该按钮投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或浮层收起撤下

ARIA ​

以下属性由 connect 生成。

部件属性值
triggeraria-controlscontent 部件的 id
triggeraria-expanded'true' | 'false'
triggeraria-haspopup'dialog'
contentaria-describedbydescription 部件的 id
contentaria-hidden!open || undefined
contentaria-labelledbytitle 部件的 id
contentaria-modal'true' | 'false'
contentrole'dialog'
close-triggeraria-labelprops.translations.close
arrowaria-hidden'true'

样式参考 ​

皮肤 ​

@xihan-ui/styles/popover.css 使用 [data-scope="popover"][data-part="trigger"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
triggerdata-pressed''(条件成立时才出现)
triggerdata-state'open' | 'closed'
triggerdata-xh-action-control''
triggerdata-xh-action-display'always'
triggerdata-xh-action-profile'text'
triggerdata-xh-action-size'md'
triggerdata-xh-action-variant'outline'
positionerdata-hidden''(条件成立时才出现)
positionerdata-placement定位引擎算出的实际落位
positionerdata-positioned''(条件成立时才出现)
positionerdata-state'open' | 'closed'
contentdata-placement定位引擎算出的实际落位
contentdata-sizeprops.size
contentdata-state'open' | 'closed'
contentdata-xh-material'frosted'
close-triggerdata-pressed''(条件成立时才出现)
close-triggerdata-xh-action-control''
close-triggerdata-xh-action-display'always'
close-triggerdata-xh-action-profile'icon'
close-triggerdata-xh-action-size'sm'
close-triggerdata-xh-action-variant'ghost'
arrowdata-placement定位引擎算出的实际落位

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-popover-arrow-sizearrow--xh-_overlay-arrow-sizedefault--xh-overlay-arrow-sizepopover 的 arrow 部件 --xh-_overlay-arrow-size 覆盖槽。
--xh-popover-backdropcontent-webkit-backdrop-filter
backdrop-filter
xh-material=frosted--xh-_material-backdroppopover 的 content 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。
--xh-popover-bgarrow
content
backgrounddefault
not([data-xh-action-control])
xh-material=frosted
--xh-_material-bg
--xh-material-frosted-bg
popover 的 arrow、content 部件 background 覆盖槽。
--xh-popover-borderarrow
content
borderdefault
not([data-xh-action-control])
xh-material=frosted
--xh-_material-border
--xh-material-frosted-border
popover 的 arrow、content 部件 border 覆盖槽。
--xh-popover-close-bg-activeclose-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedpopover 的 close-trigger 部件 background-color 覆盖槽。
--xh-popover-close-bg-focusclose-triggerbackground-colorfocus-visible--xh-_action-variant-bg-focus-visiblepopover 的 close-trigger 部件 background-color 覆盖槽。
--xh-popover-close-bg-hoverclose-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverpopover 的 close-trigger 部件 background-color 覆盖槽。
--xh-popover-close-fgclose-triggercolordefault--xh-material-frosted-fg-mutedpopover 的 close-trigger 部件 color 覆盖槽。
--xh-popover-close-fg-focusclose-triggercolorfocus-visible--xh-popover-close-fg-hoverpopover 的 close-trigger 部件 color 覆盖槽。
--xh-popover-close-fg-hoverclose-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-focus-visible
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
popover 的 close-trigger 部件 color 覆盖槽。
--xh-popover-close-radiusclose-triggerborder-radiusdefault--xh-shape-controlpopover 的 close-trigger 部件 border-radius 覆盖槽。
--xh-popover-close-sizeclose-trigger
content
title
block-size
inline-size
padding-inline-end
default
has([data-scope='popover'][data-part='close-trigger'])
xh-action-profile=icon
--xh-_action-profile-visual-size
--xh-control-h-sm
popover 的 close-trigger、content、title 部件 block-size、inline-size、padding-inline-end 覆盖槽。
--xh-popover-description-fgdescriptioncolordefault--xh-fg-mutedpopover 的 description 部件 color 覆盖槽。
--xh-popover-description-font-sizedescriptionfont-sizedefault--xh-text-secondary-sizepopover 的 description 部件 font-size 覆盖槽。
--xh-popover-fgcontentcolornot([data-xh-action-control])
xh-material=frosted
--xh-_material-fgpopover 的 content 部件 color 覆盖槽。
--xh-popover-gapcontentgapdefault--xh-space-2popover 的 content 部件 gap 覆盖槽。
--xh-popover-icon-sizeclose-trigger
content
trigger
--xh-icon-sizedefault--xh-_action-profile-glyph-size
--xh-glyph-size-md
popover 的 close-trigger、content、trigger 部件 --xh-icon-size 覆盖槽。
--xh-popover-layerpositionerz-indexdefault--xh-_layerpopover 的 positioner 部件 z-index 覆盖槽。
--xh-popover-max-hcontentmax-block-sizedefault--xh-overlay-max-hpopover 的 content 部件 max-block-size 覆盖槽。
--xh-popover-max-wcontentmax-inline-sizedefault--xh-_popover-max-wpopover 的 content 部件 max-inline-size 覆盖槽。
--xh-popover-pxcontentpadding-inlinedefault--xh-_popover-padpopover 的 content 部件 padding-inline 覆盖槽。
--xh-popover-pycontentpadding-blockdefault--xh-_popover-padpopover 的 content 部件 padding-block 覆盖槽。
--xh-popover-radiuscontentborder-radiusdefault--xh-shape-overlaypopover 的 content 部件 border-radius 覆盖槽。
--xh-popover-shadowcontentbox-shadownot([data-xh-action-control])
xh-material=frosted
--xh-_material-shadowpopover 的 content 部件 box-shadow 覆盖槽。
--xh-popover-title-fgtitlecolordefault--xh-material-frosted-fgpopover 的 title 部件 color 覆盖槽。
--xh-popover-title-font-sizetitlefont-sizedefault--xh-text-label-sizepopover 的 title 部件 font-size 覆盖槽。
--xh-popover-title-font-weighttitlefont-weightdefault--xh-font-weight-semiboldpopover 的 title 部件 font-weight 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 出现(锚定面板)(见动效规范)。

共享关键帧 xh-overlay-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License