HoverCard 悬浮卡片
指针停留后出现的信息卡:预览一个对象,不打断当前操作。
用法
与 Tooltip 的分界在于卡片本体可交互:指针停在卡片上不收起,其中的链接与按钮都可点击
组件结构
加粗的是必需部件。
data-scope="hover-card":root · trigger · positioner · content · title · description · arrow
示例
延时
openDelay 默认 700ms,closeDelay 默认 300ms:收起等待正是留给指针从触发器移动到卡片上的通行时间
受控
传入 open 后由宿主决定;悬停与 Escape 都只发意图,最终是否写回由外部按钮共用的同一份状态决定
尺寸
三档改变卡片的内边距与字号,不写 size 即默认档;把指针停在触发器上查看差别
朝向与间距
placement 是请求值,空间不足时定位引擎会自动翻面;offset 调整卡片与触发器的距离
禁用
disabled 只关闭卡片本身,触发器照常可点击、可聚焦,也照常不进入展开等待
设计指引
何时使用
- 链接或头像的预览:用户资料、文档摘要、商品简介。
- 信息属于顺带查看的内容,不需要专门点击。
何时不用
特性
openDelay与closeDelay成对:进入需要停留、离开有宽限,指针斜向移到卡片上不会误收。- 开合状态可受控。
- 内容与 Popover 共用 M2 磨砂面:单层背景模糊、柔和顶光和浮层阴影,正文保持不透明。箭头只复用底色与边界,不叠加模糊;减少透明、高对比与强制色偏好由材质令牌统一响应。
--xh-hover-card-backdrop可覆盖模糊配方;打印时整块预览收起。
组合
最佳实践
- 打开延时设为几百毫秒,否则指针扫过一段文字会弹出一串卡片。
- 卡片内的信息在其他位置也应有正式入口。
反模式
- 卡片内放操作按钮:指针移动过去的途中可能已经关闭。
- 延时为 0。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-hover-card> |
| Vue 组件 | XhHoverCardArrow XhHoverCardContent XhHoverCardDescription XhHoverCardPositioner XhHoverCardRoot XhHoverCardTitle XhHoverCardTrigger |
| 组合式函数 | useHoverCard |
| 状态机 | hoverCardMachine |
| 皮肤 | @xihan-ui/styles/hover-card.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
open | boolean | ||
defaultOpen | boolean | ||
placement | Placement | 请求的浮层朝向,默认 bottom;空间不足时由定位引擎避让。 | |
offset | number | 浮层与锚点的间距(px)。 | |
openDelay | number | 悬停进入到展开的等待毫秒,默认 700。 | |
closeDelay | number | 指针离开 trigger 或 content 到收起的等待毫秒,默认 300。 | |
dir | Direction | 文字方向,仅在显式提供时写到根节点上。 | |
disabled | boolean | 只关闭卡片本身,不影响 trigger 元素自身的可用性。 | |
size | Size | 尺寸:sm / md / lg,决定卡片的内边距档位。 | |
onOpenChange | (details: HoverCardOpenChangeDetails) => void | open 变化意图回调。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
open-change | HoverCardOpenChangeDetails | open 状态变化;detail 为 { open: boolean } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhHoverCardRoot | default | HoverCardRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhHoverCardPositioner | container | () => Element | null | 浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。 | |
XhHoverCardRoot | children | SlotChildren<HoverCardRootSlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
root | 'open' | 'closed' |
trigger | 'open' | 'closed' |
positioner | 'open' | 'closed' |
content | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:closed · opening · visible · visible.open · visible.closing
事件:POINTER.ENTER · POINTER.LEAVE · FOCUS · BLUR · ESCAPE · OPEN · CLOSE · after.openDelay · after.closeDelay · CONTROLLED.OPEN · CONTROLLED.CLOSE
判据:isOpenControlled · isDisabled · isFocusHeld
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
open | boolean | |
setOpen | (next: boolean) => void | |
getRootProps | () => T['element'] | |
getTriggerProps | () => T['button'] | |
getPositionerProps | () => T['element'] | |
getContentProps | () => T['element'] | |
getTitleProps | () => T['element'] | |
getDescriptionProps | () => T['element'] | |
getArrowProps | () => T['element'] |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Tab / Shift+Tab | not disabled | 焦点进入 trigger 立即展开、离开卡片即收起,都不走延时 |
Escape | 浮层可见(含收起等待期) | 立即收起,不等 closeDelay |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
trigger | aria-controls | content 部件的 id |
trigger | aria-expanded | 'true' | 'false' |
trigger | aria-haspopup | 'dialog' |
content | aria-describedby | description 部件的 id | undefined |
content | aria-hidden | !open || undefined |
content | aria-labelledby | title 部件的 id | trigger 部件的 id |
content | aria-modal | 'false' |
content | role | 'dialog' |
arrow | aria-hidden | 'true' |
样式参考
皮肤
@xihan-ui/styles/hover-card.css 使用 [data-scope="hover-card"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-state | 'open' | 'closed' |
trigger | data-disabled | ''(条件成立时才出现) |
trigger | data-state | 'open' | 'closed' |
positioner | data-hidden | ''(条件成立时才出现) |
positioner | data-placement | 定位引擎算出的实际落位 |
positioner | data-positioned | ''(条件成立时才出现) |
positioner | data-state | 'open' | 'closed' |
content | data-placement | 定位引擎算出的实际落位 |
content | data-size | props.size |
content | data-state | 'open' | 'closed' |
content | data-xh-material | 'frosted' |
arrow | data-placement | 定位引擎算出的实际落位 |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-hover-card-arrow-size | arrow | --xh-_overlay-arrow-size | default | --xh-overlay-arrow-size | hover-card 的 arrow 部件 --xh-_overlay-arrow-size 覆盖槽。 |
--xh-hover-card-backdrop | content | -webkit-backdrop-filterbackdrop-filter | xh-material=frosted | --xh-_material-backdrop | hover-card 的 content 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。 |
--xh-hover-card-bg | arrowcontent | background | defaultnot([data-xh-action-control])xh-material=frosted | --xh-_material-bg--xh-material-frosted-bg | hover-card 的 arrow、content 部件 background 覆盖槽。 |
--xh-hover-card-border | arrowcontent | border | defaultnot([data-xh-action-control])xh-material=frosted | --xh-_material-border--xh-material-frosted-border | hover-card 的 arrow、content 部件 border 覆盖槽。 |
--xh-hover-card-description-fg | description | color | default | --xh-fg-muted | hover-card 的 description 部件 color 覆盖槽。 |
--xh-hover-card-description-font-size | description | font-size | default | --xh-text-secondary-size | hover-card 的 description 部件 font-size 覆盖槽。 |
--xh-hover-card-fg | content | color | not([data-xh-action-control])xh-material=frosted | --xh-_material-fg | hover-card 的 content 部件 color 覆盖槽。 |
--xh-hover-card-gap | content | gap | default | --xh-space-2 | hover-card 的 content 部件 gap 覆盖槽。 |
--xh-hover-card-layer | positioner | z-index | default | --xh-_layer | hover-card 的 positioner 部件 z-index 覆盖槽。 |
--xh-hover-card-max-h | content | max-block-size | default | --xh-overlay-max-h | hover-card 的 content 部件 max-block-size 覆盖槽。 |
--xh-hover-card-max-w | content | max-inline-size | default | --xh-_hover-card-max-w | hover-card 的 content 部件 max-inline-size 覆盖槽。 |
--xh-hover-card-px | content | padding-inline | default | --xh-_hover-card-pad | hover-card 的 content 部件 padding-inline 覆盖槽。 |
--xh-hover-card-py | content | padding-block | default | --xh-_hover-card-pad | hover-card 的 content 部件 padding-block 覆盖槽。 |
--xh-hover-card-radius | content | border-radius | default | --xh-shape-overlay | hover-card 的 content 部件 border-radius 覆盖槽。 |
--xh-hover-card-shadow | content | box-shadow | not([data-xh-action-control])xh-material=frosted | --xh-_material-shadow | hover-card 的 content 部件 box-shadow 覆盖槽。 |
--xh-hover-card-title-fg | title | color | default | --xh-fg-default | hover-card 的 title 部件 color 覆盖槽。 |
--xh-hover-card-title-font-size | title | font-size | default | --xh-text-label-size | hover-card 的 title 部件 font-size 覆盖槽。 |
--xh-hover-card-title-font-weight | title | font-weight | default | --xh-font-weight-semibold | hover-card 的 title 部件 font-weight 覆盖槽。 |
--xh-hover-card-trigger-gap | trigger | gap | default | --xh-control-gap-sm | hover-card 的 trigger 部件 gap 覆盖槽。 |
动效
动效角色:出现(锚定面板)(见动效规范)。
共享关键帧 xh-overlay-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立。时长与缓动读动效令牌,改令牌即改全局节奏。
皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
