FloatButton 浮动按钮
用于在视口边缘提供持续可见的操作入口。
用法
展开一组悬浮操作
组件结构
加粗的是必需部件。
data-scope="float-button":root · trigger · list
示例
悬停展开
指针进入时展开,键盘与触控仍可点击
变体
设置浮动按钮的表面
尺寸
使用小、中、大三档尺寸
设计指引
何时使用
- 长页面中的常用主操作。
- 移动端或窄屏中的紧凑操作组。
何时不用
特性
- 支持四个视口角与安全区偏移。
- 支持点击或悬停展开;键盘与触控始终使用点击。
- Escape、层外点击和再次触发均可收起。
- 收起后动作项退出 Tab 序列。
- 触发器走 Action Control floating 档:默认 48px 圆形、图标 24px,按下缩放并换底;默认(outline)使用磨砂浮动表面,solid / subtle / ghost 使用对应语义表面。
- 原生按钮动作项自动继承触发器的尺寸与外观。
- 应用设为
data-material="liquid"时,默认(outline)的触发器与原生按钮动作项换成液态面并结成一组:展开时动作从触发器里分离,收起时融回后再隐藏;彼此靠近的部分边缘相连。按住触发器时液面随手指形变。减弱动效下不分离、不形变。
组合
最佳实践
- 为每个图标按钮提供可访问名称。
- 将操作数量控制在 2 至 5 个。
- 使用
offset避开系统手势区。
反模式
- 不要承载高风险的破坏性操作。
- 不要遮挡主要内容或固定导航。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-float-button> |
| Vue 组件 | XhFloatButtonList XhFloatButtonRoot XhFloatButtonTrigger |
| 组合式函数 | useFloatButton |
| 状态机 | floatButtonMachine |
| 皮肤 | @xihan-ui/styles/float-button.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
defaultOpen | boolean | ||
dir | Direction | 文字方向,只作用于排版;作者未提供时不写入。 | |
disabled | boolean | ||
expandTrigger | FloatButtonExpandTrigger | 展开方式,默认 click。 | |
offset | number | 距两条边的距离(px),默认 24。 | |
onOpenChange | (details: CollapsibleOpenChangeDetails) => void | open 变化意图;受控时是唯一出口,非受控时随内部转移一并通知。 | |
open | boolean | ||
placement | FloatButtonPlacement | 固定在哪一角,默认 bottom-end。 | |
size | Size | 尺寸:sm / md / lg,默认与 lg 同档:悬浮按钮需要易于触达,起始即比行内按钮大一档。 | |
tone | Tone | 颜色:brand / neutral / success / warning / danger / info。 | |
translations | Partial<FloatButtonTranslations> | ||
variant | ActionVariant | 变体:solid / subtle / outline / ghost,默认 outline(缺省中性,描边 + 磨砂面;solid 才品牌实心)。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
open-change | CollapsibleOpenChangeDetails | 展开状态变化;detail 为 { open: boolean } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhFloatButtonRoot | default | FloatButtonRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhFloatButtonRoot | children | SlotChildren<FloatButtonRootSlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
root | 'open' | 'closed' |
trigger | 'open' | 'closed' |
list | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:open · closed
事件:OPEN · CLOSE · TOGGLE · DISABLE · CONTROLLED.OPEN · CONTROLLED.CLOSE · PRESS.START · PRESS.END
判据:isDisabled · isOpenControlled · canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
open | boolean | 展开的动作组当前是否显示。 |
setOpen | (next: boolean) => void | |
getRootProps | () => T['element'] | |
getTriggerProps | () => T['button'] | |
getListProps | () => T['element'] |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | focus in trigger, not disabled | 展开 / 收起 list;悬停展开时这条路照样在,触摸与键盘都靠它 |
Enter / Space | held in trigger, not disabled | 按住期间投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下 |
Escape | open,无论焦点是否仍在整组内 | 只收起当前 LayerRegistry 的栈顶层;更晚打开的 Drawer / Popover 先处理自己的 Escape |
Tab / Shift+Tab | open | 走进展开的那一组;收起时 list 带 hidden,里面的按钮一并退出 Tab 序列 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
trigger | aria-controls | list 部件的 id |
trigger | aria-expanded | 'true' | 'false' |
trigger | aria-label | props.translations?.trigger |
list | aria-labelledby | trigger 部件的 id |
list | role | 'group' |
样式参考
皮肤
@xihan-ui/styles/float-button.css 使用 [data-scope="float-button"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-placement | props.placement |
root | data-size | props.size |
root | data-state | 'open' | 'closed' |
root | data-tone | props.tone |
root | data-variant | props.variant |
trigger | data-disabled | ''(条件成立时才出现) |
trigger | data-pressed | ''(条件成立时才出现) |
trigger | data-state | 'open' | 'closed' |
trigger | data-xh-action-control | '' |
trigger | data-xh-action-display | 'always' |
trigger | data-xh-action-profile | 'floating' |
trigger | data-xh-action-size | props.size |
trigger | data-xh-action-variant | props.variant |
trigger | data-xh-ink-surface | ''(条件成立时才出现) |
trigger | data-xh-liquid | '' |
trigger | data-xh-material | 'frosted' | undefined |
list | data-placement | props.placement |
list | data-state | 'open' | 'closed' |
list | data-xh-liquid | '' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-float-button-bg | listroottrigger | --xh-ink-surfacebackground-color | @media (forced-colors: none)defaultdisabledfocus-visiblematerial=liquidnot([data-scope])variant=outlinewhere([data-material='liquid'])xh-ink-surfacexh-liquidxh-liquid-goo | --xh-_action-variant-bg-disabled--xh-_action-variant-bg-focus-visible--xh-_action-variant-bg-rest--xh-_float-button-bg--xh-_material-bg--xh-_material-bg-focustransparent | float-button 的 list、root、trigger 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-float-button-bg-active | listroottrigger | background-color | @media (forced-colors: none)activedisabledis(:active, [data-pressed])loadingmaterial=liquidnot(:disabled)not([data-disabled])not([data-loading])not([data-scope])pressedvariant=outlinewhere([data-material='liquid'])xh-liquidxh-liquid-goo | --xh-_action-variant-bg-pressed--xh-_float-button-bg-active--xh-_material-bg-pressed--xh-material-liquid-fg | float-button 的 list、root、trigger 部件 background-color 覆盖槽。 |
--xh-float-button-bg-hover | listroottrigger | background-color | @media (forced-colors: none)@media (forced-colors: none) and (hover: hover)@media (hover: hover)disabledhoverloadingmaterial=liquidnot(:disabled)not([data-disabled])not([data-loading])not([data-scope])variant=outlinewhere([data-material='liquid'])xh-liquidxh-liquid-goo | --xh-_action-variant-bg-hover--xh-_float-button-bg-hover--xh-_material-bg-hover--xh-material-liquid-fg | float-button 的 list、root、trigger 部件 background-color 覆盖槽。 |
--xh-float-button-border | listroottrigger | borderborder-color | @media (forced-colors: none)defaultdisabledfocus-visiblematerial=liquidnot([data-scope])variant=outlinewhere([data-material='liquid'])xh-liquidxh-liquid-goo | --xh-_action-variant-border-disabled--xh-_action-variant-border-focus-visible--xh-_action-variant-border-rest--xh-_float-button-border--xh-_material-bordertransparent | float-button 的 list、root、trigger 部件 border、border-color 覆盖槽。 |
--xh-float-button-border-hover | listroottrigger | border-color | @media (forced-colors: none)@media (forced-colors: none) and (hover: hover)@media (hover: hover)disabledhoveris(:active, [data-pressed])loadingmaterial=liquidnot(:disabled)not([data-disabled])not([data-loading])not([data-scope])pressedvariant=outlinewhere([data-material='liquid'])xh-liquidxh-liquid-goo | --xh-_action-variant-border-hover--xh-_action-variant-border-pressed--xh-_float-button-border-hover--xh-_material-bordertransparent | float-button 的 list、root、trigger 部件 border-color 覆盖槽。 |
--xh-float-button-fg | listroottrigger | color | @media (forced-colors: none)defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingmaterial=liquidnot([data-disabled])not([data-loading])not([data-scope])pressedvariant=outlinewhere([data-material='liquid'])xh-liquid-goo | --xh-_action-variant-fg-focus-visible--xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed--xh-_action-variant-fg-rest--xh-_float-button-fg--xh-_material-fg--xh-material-liquid-fg | float-button 的 list、root、trigger 部件 color 覆盖槽。 |
--xh-float-button-gap | listroot | gap | default | --xh-space-2 | float-button 的 list、root 部件 gap 覆盖槽。 |
--xh-float-button-icon-size | roottrigger | --xh-icon-size | default | --xh-_action-profile-glyph-size--xh-_float-button-glyph-size | float-button 的 root、trigger 部件 --xh-icon-size 覆盖槽。 |
--xh-float-button-layer | root | z-index | default | --xh-_layer | float-button 的 root 部件 z-index 覆盖槽。 |
--xh-float-button-radius | listtrigger | border-radius | default | --xh-_action-profile-radius--xh-shape-circle | float-button 的 list、trigger 部件 border-radius 覆盖槽。 |
--xh-float-button-shadow | *listroottrigger | --xh-_liquid-goo-shadowbox-shadow | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])not([data-scope])pressedvariant=outlinexh-liquid-goo-layer | --xh-_float-button-shadow--xh-_material-shadow--xh-material-liquid-shadownone | float-button 的 *、list、root、trigger 部件 --xh-_liquid-goo-shadow、box-shadow 覆盖槽。 |
--xh-float-button-size | listtrigger | block-sizeinline-size | defaultxh-action-profile=floating | --xh-_action-profile-visual-size--xh-_float-button-size | float-button 的 list、trigger 部件 block-size、inline-size 覆盖槽。 |
动效
动效角色:按压 · 状态 · 出现(锚定面板) · 出现(无锚定弹出)(见动效规范)。
共享关键帧 xh-pop-in · xh-pop-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;background-color · border-color · scale 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
响应式
皮肤另按输入能力分档:hover: hover:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
