Toolbar 工具栏
用于组织一组相关的操作控件。
用法
集中常用编辑操作
组件结构
加粗的是必需部件。
data-scope="toolbar":root · group · item · separator · overflow-trigger
示例
分组
将相关操作收在一起
垂直布局
按纵向排列工具
附着工具面
为悬浮工具条提供完整表面
收进更多菜单
放不下的操作按次序收进行尾的更多按钮
设计指引
何时使用
- 文本编辑、表格操作或画布工具。
- 相关控件需要统一的方向键导航。
何时不用
- 只有少量独立操作时直接使用按钮。
- 操作必须连接成一个整体时使用按钮组。
特性
- 默认
ghost形态只组织控件,不绘制工具条外框。 outline形态提供带内距、描边与背景的附着式工具面;subtle为淡底。group将相关操作连接成连续分段,并以低对比度分隔线区分。- 独立条目是接入 Action Control text 档的无描边工具按钮,组内条目使用中性操作面;
aria-pressed表示选中状态,选中项是实心主色配面上反白(与 Toggle、切换按钮组的选中段同一块面),悬停与按压走主色的深一档、再深一档。 - 条目只放图标时声明
iconOnly(Web Components 在条目上写data-icon-only):条目按方钮画,宽等于高,三种尺寸都一样。 - 支持水平、垂直、分组、分隔线与整体禁用。
- 方向键在条目间移动,禁用项会被跳过。
- 放了
overflow-trigger时不折行:宽度不够时,放不下的条目按次序收进行尾的「更多」按钮,点开是一张菜单,选中其中一项即触发该条目自己的点击;宽度够时「更多」按钮收起。容器变宽变窄、条目增减或改写后自动重新计算。
组合
- 使用
group收紧相关操作。 - 使用
separator区分操作组。 - 使用
overflow-trigger放在root的最后,承接放不下的条目。菜单里的文字取条目的aria-label(没有时取条目文字),写了aria-pressed的开关条目在菜单里显示为勾选项,条目上写的data-tone(如破坏性操作的danger)随条目带进菜单项,工具条上的分组与分隔线在菜单里画成分隔线。itemIcons按条目的value给图标记录,收起的条目带着它进菜单,排在文字之前;工具条上那颗按钮里放不放图标归作者。不写内容时由皮肤画一枚横排三点,可及名缺省为More,用translations.overflowTrigger换成本地文案。 - Web Components 只写一颗空的
<button data-xh-part="overflow-trigger">,菜单的浮层与条目由元素自己建。
最佳实践
- 仅图标条目声明
iconOnly,并且必须提供aria-label:它也是条目收进菜单后显示的文字。 - 使用
aria-pressed表示可切换工具的当前状态。 - 常用的操作排在前面:收纳从尾部开始,排在最后的最先收进菜单。
反模式
- 不要在工具栏中放置文本输入控件。
- 不要将整页所有操作放入同一工具栏。
- 不要在放不下时让工具条横向滚动:用
overflow-trigger收进菜单。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-toolbar> |
| Vue 组件 | XhToolbarGroup XhToolbarItem XhToolbarOverflowTrigger XhToolbarRoot XhToolbarSeparator |
| 组合式函数 | useToolbar |
| 状态机 | toolbarMachine |
| 皮肤 | @xihan-ui/styles/toolbar.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
orientation | Orientation | 主轴,默认 horizontal。它决定 root 的 aria-orientation、方向键接管哪一对键 (另一轴原样放行给页面),以及分隔线的朝向(恒与主轴垂直)。 | |
dir | Direction | 文字方向,默认 ltr;只改写水平主轴上左右方向键的语义。 | |
loop | boolean | 方向键到达末尾是否回绕,默认 true。 | |
disabled | boolean | 整条禁用:条目全部为 aria-disabled,方向键不再接管。 | |
variant | ControlVariant | 形态:ghost 只组织控件不画面(默认),outline 为附着式工具面,subtle 为淡底。默认 ghost。 | |
size | Size | 尺寸:sm / md / lg,同时调整排布与默认条目尺寸。 | |
translations | Partial<ToolbarTranslations> | 读屏文案:「更多」钮的可及名,缺省 More。 | |
itemIcons | Readonly<Record<string, IconRecord>> | 条目的图标,键是条目的 value:收起进「更多」菜单的条目带着它,排在文字之前。条目自己(工具条上的那颗按钮)里放不放图标归作者。 |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhToolbarRoot | default | ToolbarRootSlotProps |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhToolbarItem | value | string | 是 | |
XhToolbarItem | disabled | boolean | ||
XhToolbarItem | iconOnly | boolean | 只放图标、没有看得见的文字:按方钮画,宽等于高;名字由 aria-label 给。 | |
XhToolbarItem | as | ElementType | 条目渲染为哪个标签,默认 button;不自动补 type="button",表单内需自行声明。 | |
XhToolbarRoot | children | SlotChildren<ToolbarRootSlotProps> |
状态
以下名称仅用于内部状态机。
状态:idle
事件:ITEM.FOCUS · OVERFLOW_TRIGGER.FOCUS · TOOLBAR.BLUR · PRESS.START · PRESS.END · OVERFLOW.SELECT
判据:canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
focusedValue | string | null | 焦点锚点;焦点不在工具条内、或停在「更多」钮上时为 null。 |
orientation | Orientation | 生效的主轴。 |
separatorOrientation | Orientation | 分隔线的朝向:恒与主轴垂直(横向工具条中的分隔线是竖线)。 |
disabled | boolean | |
overflowItems | readonly ToolbarOverflowItem[] | 收进「更多」菜单的条目,文档序;全部放得下时为空数组。 |
getRootProps | () => T['element'] | |
getGroupProps | () => T['element'] | |
getItemProps | (props: ToolbarItemProps) => T['element'] | |
getSeparatorProps | () => T['element'] | |
getOverflowTriggerProps | () => T['button'] | 行尾的「更多」钮,也是「更多」菜单的触发器:适配器按 asChild 的规则把菜单的开合接线合进来,解剖标记归工具条,工具条的处理器先跑。 |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Tab / Shift+Tab | roving tabindex(恒开) | 整条只占一个 Tab 位:焦点落到锚点条目,无锚点时先落容器再由它转投给第一个可停留条目 |
ArrowRight / ArrowDown | 焦点在条内且未整条禁用;横排收 ArrowRight、竖排收 ArrowDown | 焦点移到下一个可停留条目(禁用项与收进菜单的条目跳过、放不下时「更多」钮排在最后、尽头按 loop 回绕);dir=rtl 时水平主轴改由 ArrowLeft 承担 |
ArrowLeft / ArrowUp | 焦点在条内且未整条禁用;横排收 ArrowLeft、竖排收 ArrowUp | 焦点移到上一个可停留条目(禁用项与收进菜单的条目跳过、尽头按 loop 回绕);dir=rtl 时水平主轴改由 ArrowRight 承担 |
Home | 焦点在条内且未整条禁用 | 焦点移到首个可停留条目 |
End | 焦点在条内且未整条禁用 | 焦点移到末个可停留条目;放不下时是「更多」钮 |
Enter / Space | held on item, 整条未禁用且条目未禁用 | 按住期间该条目投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或转禁用撤下。激活语义仍归条目自身(原生 button 的 click) |
Enter / Space / ArrowDown / ArrowUp | 焦点在「更多」钮上;竖排工具条里上下键仍归工具条走位,只收 Enter 与 Space | 展开「更多」菜单:Enter / Space / ArrowDown 落到首项,ArrowUp 落到末项;菜单里选中一项即替收起的条目触发它自己的点击,菜单收起 |
Escape | 「更多」菜单展开 | 收起菜单,焦点回到「更多」钮 |
交叉轴的两个方向键 | 焦点在条内(横排按上下、竖排按左右) | 不归工具条管:原样放行给页面滚动与读屏,绝不 preventDefault;落在菜单触发器(含「更多」钮)上时由触发器展开菜单 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
root | aria-disabled | 'true' | 'false' |
root | aria-orientation | props.orientation |
root | role | 'toolbar' |
group | role | 'group' |
item | aria-disabled | 'true' | 'false' |
separator | aria-orientation | 'vertical' | 'horizontal' |
separator | role | 'separator' |
overflow-trigger | aria-disabled | 'true' | 'false' |
overflow-trigger | aria-label | resolveTranslations(TOOLBAR_EN_US, prop('translations… |
样式参考
皮肤
@xihan-ui/styles/toolbar.css 按 [data-scope="toolbar"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-toolbar 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-orientation | props.orientation |
root | data-size | props.size |
root | data-variant | props.variant |
group | data-disabled | ''(条件成立时才出现) |
group | data-orientation | props.orientation |
item | data-disabled | ''(条件成立时才出现) |
item | data-icon-only | ''(条件成立时才出现) |
item | data-pressed | ''(条件成立时才出现) |
item | data-xh-action-control | '' |
item | data-xh-action-display | 'always' |
item | data-xh-action-profile | 'icon' | 'text' |
item | data-xh-action-size | props.size |
item | data-xh-action-variant | 'ghost' |
separator | data-orientation | 'vertical' | 'horizontal' |
overflow-trigger | data-disabled | ''(条件成立时才出现) |
overflow-trigger | data-xh-action-control | '' |
overflow-trigger | data-xh-action-display | 'always' |
overflow-trigger | data-xh-action-profile | 'icon' |
overflow-trigger | data-xh-action-size | props.size |
overflow-trigger | data-xh-action-variant | 'ghost' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-toolbar-bg | root | background | default | --xh-_toolbar-root-bg | toolbar 的 root 部件 background 覆盖槽。 |
--xh-toolbar-bg-disabled | root | background | disabled | --xh-_toolbar-root-bg-disabled | toolbar 的 root 部件 background 覆盖槽。 |
--xh-toolbar-border | root | border | default | --xh-_toolbar-root-border | toolbar 的 root 部件 border 覆盖槽。 |
--xh-toolbar-fg | root | color | default | --xh-fg-default | toolbar 的 root 部件 color 覆盖槽。 |
--xh-toolbar-gap | root | gap | default | --xh-_toolbar-gap | toolbar 的 root 部件 gap 覆盖槽。 |
--xh-toolbar-group-gap | group | gap | default | --xh-space-0 | toolbar 的 group 部件 gap 覆盖槽。 |
--xh-toolbar-icon-size | root | --xh-icon-size | defaultsize=lgsize=sm | --xh-glyph-size-lg--xh-glyph-size-md--xh-glyph-size-sm | toolbar 的 root 部件 --xh-icon-size 覆盖槽。 |
--xh-toolbar-item-bg | groupitem | --xh-ink-surfacebackground-color | defaultnot([aria-pressed='true'])xh-ink-surface | --xh-_action-variant-bg-rest--xh-bg-subtle | toolbar 的 group、item 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-toolbar-item-bg-active | item | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | toolbar 的 item 部件 background-color 覆盖槽。 |
--xh-toolbar-item-bg-disabled | groupitem | --xh-ink-surfacebackground-color | disablednot([aria-pressed='true'])xh-ink-surface | --xh-_action-variant-bg-disabled--xh-bg-subtle | toolbar 的 group、item 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-toolbar-item-bg-hover | item | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | toolbar 的 item 部件 background-color 覆盖槽。 |
--xh-toolbar-item-bg-pressed | item | --xh-ink-surfacebackground-colorborderborder-color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressedxh-ink-surface | --xh-bg-brand | toolbar 的 item 部件 --xh-ink-surface、background-color、border、border-color 覆盖槽。 |
--xh-toolbar-item-bg-pressed-active | item | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-bg-brand-active | toolbar 的 item 部件 background-color 覆盖槽。 |
--xh-toolbar-item-bg-pressed-disabled | item | --xh-ink-surfacebackground-color | disabledxh-ink-surface | --xh-toolbar-item-bg-pressed | toolbar 的 item 部件 --xh-ink-surface、background-color 覆盖槽。 |
--xh-toolbar-item-bg-pressed-hover | item | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-bg-brand-hover | toolbar 的 item 部件 background-color 覆盖槽。 |
--xh-toolbar-item-fg | item | color | @media printdefaultdisabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-fg-hover--xh-_action-variant-fg-pressed--xh-_action-variant-fg-rest--xh-fg-default | toolbar 的 item 部件 color 覆盖槽。 |
--xh-toolbar-item-fg-pressed | item | color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-on-brand | toolbar 的 item 部件 color 覆盖槽。 |
--xh-toolbar-item-font-size | item | font-size | default | --xh-_action-profile-font-size | toolbar 的 item 部件 font-size 覆盖槽。 |
--xh-toolbar-item-font-weight | item | font-weight | default | --xh-text-label-weight | toolbar 的 item 部件 font-weight 覆盖槽。 |
--xh-toolbar-item-gap | item | gap | default | --xh-_action-profile-gap | toolbar 的 item 部件 gap 覆盖槽。 |
--xh-toolbar-item-h | item | block-sizeinline-sizemin-inline-size | defaulticon-onlyorientation=verticalxh-action-profile=icon | --xh-_action-profile-visual-size | toolbar 的 item 部件 block-size、inline-size、min-inline-size 覆盖槽。 |
--xh-toolbar-item-px | item | padding-inline | default | --xh-_action-profile-padding-inline | toolbar 的 item 部件 padding-inline 覆盖槽。 |
--xh-toolbar-item-radius | groupitem | border-end-end-radiusborder-end-start-radiusborder-radiusborder-start-end-radiusborder-start-start-radius | defaultfirst-of-typelast-of-typenot(:has(~ [data-scope='toolbar'][data-part='item']:not([hidden])not([hidden])orientation=horizontalorientation=vertical | --xh-_action-profile-radius--xh-shape-control | toolbar 的 group、item 部件 border-end-end-radius、border-end-start-radius、border-radius、border-start-end-radius、border-start-start-radius 覆盖槽。 |
--xh-toolbar-px | root | padding-inline | default | --xh-_toolbar-root-p | toolbar 的 root 部件 padding-inline 覆盖槽。 |
--xh-toolbar-py | root | padding-block | default | --xh-_toolbar-root-p | toolbar 的 root 部件 padding-block 覆盖槽。 |
--xh-toolbar-radius | root | border-radius | defaultvariant=outline | --xh-shape-surface | toolbar 的 root 部件 border-radius 覆盖槽。 |
--xh-toolbar-separator-color | groupseparator | background | default | --xh-border-default--xh-fg-default | toolbar 的 group、separator 部件 background 覆盖槽。 |
--xh-toolbar-separator-gap | separator | margin-blockmargin-inline | orientation=horizontalorientation=vertical | --xh-space-3 | toolbar 的 separator 部件 margin-block、margin-inline 覆盖槽。 |
--xh-toolbar-separator-inset | separator | margin-blockmargin-inline | orientation=horizontalorientation=vertical | --xh-space-0 | toolbar 的 separator 部件 margin-block、margin-inline 覆盖槽。 |
--xh-toolbar-separator-opacity | groupseparator | opacity | default | --xh-control-separator-opacity | toolbar 的 group、separator 部件 opacity 覆盖槽。 |
--xh-toolbar-separator-radius | separator | border-radius | default | --xh-shape-pill | toolbar 的 separator 部件 border-radius 覆盖槽。 |
--xh-toolbar-separator-thickness | groupseparator | block-sizeinline-sizemargin-blockmargin-inline | orientation=horizontalorientation=vertical | --xh-stroke-thin | toolbar 的 group、separator 部件 block-size、inline-size、margin-block、margin-inline 覆盖槽。 |
--xh-toolbar-shadow | root | box-shadow | variant=outline | none | toolbar 的 root 部件 box-shadow 覆盖槽。 |
动效
动效角色:按压 · 状态(见动效规范)。
background-color · color 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。
系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。
