Toolbar 工具栏

组合按钮、链接和切换组,以统一的键盘顺序操作。

'use client'

import { useState } from 'react'
import { Bold, Italic, Underline, Undo2, Redo2 } from 'lucide-react'
import {
  Toolbar,
  ToolbarButton,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarSeparator,
} from '@hina-ui/react'

export default function Demo() {
  const [formats, setFormats] = useState(['bold'])

  return (
    <Toolbar label="文本工具" size="sm">
      <ToolbarButton label="撤销">
        <Undo2 />
      </ToolbarButton>
      <ToolbarButton label="重做" disabled>
        <Redo2 />
      </ToolbarButton>
      <ToolbarSeparator />
      <ToolbarToggleGroup value={formats} onValueChange={setFormats} type="multiple" label="格式">
        <ToolbarToggleItem value="bold" label="加粗">
          <Bold />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="italic" label="斜体">
          <Italic />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="underline" label="下划线">
          <Underline />
        </ToolbarToggleItem>
      </ToolbarToggleGroup>
    </Toolbar>
  )
}
tsx

用法

Toolbar 提供工具栏语义、尺寸和方向键导航。ToolbarButton 执行动作,ToolbarLink 渲染链接,ToolbarSeparator 分隔控件;控件复用 Button 的视觉样式。

import {
  Toolbar,
  ToolbarButton,
  ToolbarLink,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarSeparator,
} from '@hina-ui/react'
ts

使用 label 或 aria-labelledby 命名工具栏。按钮的 children 放文字,icon 和 trailing 放前后内容。仅图标按钮设置 label,图标放 children;它提供方形尺寸、可访问名称,并在 TooltipProvider 内显示提示,与 IconButton 一致。

尚未执行

'use client'

import { useState } from 'react'
import { Copy, Download, RotateCcw } from 'lucide-react'
import { Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [action, setAction] = useState('尚未执行')

  return (
    <Stack align="start" gap="sm">
      <Toolbar label="操作" size="sm">
        <ToolbarButton onClick={() => setAction('复制')} icon={<Copy />}>
          复制
        </ToolbarButton>
        <ToolbarButton onClick={() => setAction('下载')} icon={<Download />}>
          下载
        </ToolbarButton>
        <ToolbarButton onClick={() => setAction('重置')} icon={<RotateCcw />}>
          重置
        </ToolbarButton>
        <ToolbarSeparator />
        <ToolbarLink href="#api">API</ToolbarLink>
      </Toolbar>
      <Text size="sm" tone="muted" aria-live="polite">
        {action}
      </Text>
    </Stack>
  )
}
tsx

示例

单选与多选

ToolbarToggleGroup 默认 type="single",value / onValueChange 为字符串或 undefined;再次点击选中项会清空。设置 type="multiple" 时绑定字符串数组,各项独立切换。defaultValue 设置非受控初始值。

每个 ToolbarToggleItem 的 value 在组内唯一。方向键只移动焦点,点击、Enter 或 Space 才切换选中状态。

格式:bold · 对齐:start

'use client'

import { useState } from 'react'
import { Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight } from 'lucide-react'
import {
  Toolbar,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarSeparator,
  Stack,
  Text,
} from '@hina-ui/react'

export default function Demo() {
  const [formats, setFormats] = useState(['bold'])
  const [alignment, setAlignment] = useState<string | undefined>('start')

  return (
    <Stack align="start" gap="sm">
      <Toolbar label="切换组" size="sm">
        <ToolbarToggleGroup value={formats} onValueChange={setFormats} type="multiple" label="格式">
          <ToolbarToggleItem value="bold" label="加粗">
            <Bold />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="italic" label="斜体">
            <Italic />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="underline" label="下划线">
            <Underline />
          </ToolbarToggleItem>
        </ToolbarToggleGroup>
        <ToolbarSeparator />
        <ToolbarToggleGroup value={alignment} onValueChange={setAlignment} label="对齐">
          <ToolbarToggleItem value="start" label="起始对齐">
            <AlignLeft />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="center" label="居中">
            <AlignCenter />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="end" label="末尾对齐">
            <AlignRight />
          </ToolbarToggleItem>
        </ToolbarToggleGroup>
      </Toolbar>
      <Text size="sm" tone="muted">
        格式:{formats.join(', ') || '无'} · 对齐:{alignment ?? '无'}
      </Text>
    </Stack>
  )
}
tsx

纵向

orientation="vertical" 改为纵向排列和上下方向键导航。分隔线自动转向;图标提示的位置由控件的 side 设置。

'use client'

import { useState } from 'react'
import { MousePointer2, Hand, Type, Plus } from 'lucide-react'
import {
  Toolbar,
  ToolbarButton,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarSeparator,
} from '@hina-ui/react'

export default function Demo() {
  const [tool, setTool] = useState<string | undefined>('pointer')

  return (
    <Toolbar label="纵向工具" orientation="vertical" size="sm">
      <ToolbarToggleGroup value={tool} onValueChange={setTool} label="工具">
        <ToolbarToggleItem value="pointer" label="选择" side="right">
          <MousePointer2 />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="hand" label="移动" side="right">
          <Hand />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="text" label="文字" side="right">
          <Type />
        </ToolbarToggleItem>
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarButton label="添加" side="right">
        <Plus />
      </ToolbarButton>
    </Toolbar>
  )
}
tsx

外观

primary 带边框和表面背景,secondary 使用凹陷背景,bare 去掉背景、边框和内边距。

primary

secondary

bare

import { Copy, Scissors, ClipboardPaste } from 'lucide-react'
import { Toolbar, ToolbarButton, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack align="start">
      {(['primary', 'secondary', 'bare'] as const).map(variant => (
        <Stack key={variant} align="start" gap="xs">
          <Text size="sm" tone="muted">
            {variant}
          </Text>
          <Toolbar variant={variant} label={variant} size="sm">
            <ToolbarButton label="剪切">
              <Scissors />
            </ToolbarButton>
            <ToolbarButton label="复制">
              <Copy />
            </ToolbarButton>
            <ToolbarButton label="粘贴">
              <ClipboardPaste />
            </ToolbarButton>
          </Toolbar>
        </Stack>
      ))}
    </Stack>
  )
}
tsx

尺寸

工具栏的 size 由按钮、链接和切换项继承,也可以在单个控件上覆盖。间距与控件高度跟随密度 token。

sm

md

lg

import { Copy, Scissors, ClipboardPaste } from 'lucide-react'
import { Toolbar, ToolbarButton, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack align="start">
      {(['sm', 'md', 'lg'] as const).map(size => (
        <Stack key={size} align="start" gap="xs">
          <Text size="sm" tone="muted">
            {size}
          </Text>
          <Toolbar size={size} label={size}>
            <ToolbarButton label="剪切">
              <Scissors />
            </ToolbarButton>
            <ToolbarButton label="复制">
              <Copy />
            </ToolbarButton>
            <ToolbarButton label="粘贴">
              <ClipboardPaste />
            </ToolbarButton>
          </Toolbar>
        </Stack>
      ))}
    </Stack>
  )
}
tsx

禁用

Toolbar.disabled 禁用全部控件,ToolbarToggleGroup.disabled 禁用组内切换项,单项的 disabled 只影响自身。禁用项会退出方向键导航;loading 也会暂停该项的操作。

'use client'

import { useState } from 'react'
import { Bold, Italic, Undo2, Redo2 } from 'lucide-react'
import {
  Toolbar,
  ToolbarButton,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarSeparator,
  Stack,
  FormField,
  Switch,
} from '@hina-ui/react'

export default function Demo() {
  const [disabled, setDisabled] = useState(false)
  const [groupDisabled, setGroupDisabled] = useState(false)
  const [formats, setFormats] = useState(['bold'])

  return (
    <Stack align="start">
      <FormField label="禁用全部" orientation="horizontal">
        <Switch checked={disabled} onCheckedChange={setDisabled} />
      </FormField>
      <FormField label="禁用切换组" orientation="horizontal">
        <Switch checked={groupDisabled} onCheckedChange={setGroupDisabled} />
      </FormField>
      <Toolbar label="禁用状态" disabled={disabled} size="sm">
        <ToolbarButton label="撤销">
          <Undo2 />
        </ToolbarButton>
        <ToolbarButton label="重做" disabled>
          <Redo2 />
        </ToolbarButton>
        <ToolbarSeparator />
        <ToolbarToggleGroup
          value={formats}
          onValueChange={setFormats}
          type="multiple"
          label="格式"
          disabled={groupDisabled}
        >
          <ToolbarToggleItem value="bold" label="加粗">
            <Bold />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="italic" label="斜体">
            <Italic />
          </ToolbarToggleItem>
        </ToolbarToggleGroup>
      </Toolbar>
    </Stack>
  )
}
tsx

组合控件

将 ToolbarButton 作为 DropdownMenu 或 Popover 的触发器传入 children,可保留浮层与工具栏各自的键盘行为。

asChild 把行为和属性合并到唯一子控件,不额外嵌套按钮。示例复用 Toggle。自定义子控件需要把属性和事件转发到实际可聚焦元素;禁用、加载状态应设置在 ToolbarButton 上,使其同步退出方向键导航。

尚未执行

'use client'

import { useState } from 'react'
import { Bold, Copy, Ellipsis, Trash2 } from 'lucide-react'
import {
  Toolbar,
  ToolbarButton,
  ToolbarSeparator,
  Toggle,
  DropdownMenu,
  DropdownMenuItem,
  Stack,
  Text,
} from '@hina-ui/react'

export default function Demo() {
  const [bold, setBold] = useState(false)
  const [action, setAction] = useState('尚未执行')

  return (
    <Stack align="start" gap="sm">
      <Toolbar label="组合控件" size="sm">
        <ToolbarButton asChild>
          <Toggle
            value={bold}
            onValueChange={setBold}
            label="加粗"
            size="sm"
            renderIcon={() => <Bold />}
          />
        </ToolbarButton>
        <ToolbarButton label="复制" onClick={() => setAction('复制')}>
          <Copy />
        </ToolbarButton>
        <ToolbarSeparator />
        <DropdownMenu
          label="更多操作"
          content={
            <>
              <DropdownMenuItem onSelect={() => setAction('复制')} icon={<Copy />}>
                复制
              </DropdownMenuItem>
              <DropdownMenuItem tone="danger" onSelect={() => setAction('删除')} icon={<Trash2 />}>
                删除
              </DropdownMenuItem>
            </>
          }
        >
          <ToolbarButton label="更多">
            <Ellipsis />
          </ToolbarButton>
        </DropdownMenu>
      </Toolbar>
      <Text size="sm" tone="muted" aria-live="polite">
        {action}
      </Text>
    </Stack>
  )
}
tsx

RTL

dir="rtl" 同时调整排列和左右方向键。未传入时继承 ConfigProvider 的方向,或最近祖先的 dir。

import { Toolbar, ToolbarButton, ToolbarSeparator, ToolbarLink } from '@hina-ui/react'

export default function Demo() {
  return (
    <Toolbar label="RTL 工具栏" dir="rtl" size="sm">
      <ToolbarButton>第一项</ToolbarButton>
      <ToolbarButton>第二项</ToolbarButton>
      <ToolbarSeparator />
      <ToolbarLink href="#api">API</ToolbarLink>
    </Toolbar>
  )
}
tsx

行为与无障碍

工具栏以 role="toolbar" 呈现,切换组使用 role="group",切换项通过 aria-pressed 表达状态。默认按钮为 type="button",不会提交所在表单。

按键行为
Tab / Shift + Tab进入或离开工具栏,重新进入时恢复最近的焦点项
← / →横向工具栏中移动焦点,RTL 时反转
↑ / ↓纵向工具栏中移动焦点
Home / End移至首个 / 最后一个可用项
Enter / Space执行按钮或链接,切换选中项

loop 默认开启,允许从末项回到首项。横向控件可自然换行,方向键仍按 DOM 顺序移动,不使用二维网格导航。

需要独立使用同轴方向键的控件,例如输入框或 Slider,应保留自身的键盘操作;不要通过 ToolbarButton 将它们加入这组方向键导航。

API

Toolbar

Prop
类型
默认值
说明
label
string
—
可访问名称,也可传 aria-labelledby
orientation
'horizontal' | 'vertical'
'horizontal'
排列及导航方向
dir
'ltr' | 'rtl'
继承
阅读方向
loop
boolean
true
首尾循环导航
disabled
boolean
false
禁用全部控件
size
'sm' | 'md' | 'lg'
'md'
控件尺寸
variant
'primary' | 'secondary' | 'bare'
'primary'
容器外观
className
string
—
根节点样式

children 放置工具栏部件。根节点接收原生属性。

ToolbarButton / ToolbarLink / ToolbarToggleItem

Prop
类型
默认值
说明
label
string
—
仅图标控件的名称与提示内容
tooltip
boolean
true
有 label 与 TooltipProvider 时显示提示
side
'top' | 'right' | 'bottom' | 'left'
'top'
提示方向
size
'sm' | 'md' | 'lg'
继承
控件尺寸
variant
'solid' | 'soft' | 'outline' | 'ghost' | 'link'
'ghost'
Button 外观
tone
'accent' | 'neutral' | 'danger'
'neutral'
表现色
disabled
boolean
false
禁用该项
loading
boolean
false
加载状态,同时禁用
ripple
boolean
true
按压波纹
as
string | Component
按钮为 'button',链接为 'a'
底层元素
asChild
boolean
false
合并到唯一子控件
className
string
—
控件样式

ToolbarLink 额外接收 href、target、rel。ToolbarToggleItem 额外要求 value: string,且必须放入 ToolbarToggleGroup。children、icon、trailing 与 Button 相同,原生属性和事件转发到控件元素。

ToolbarToggleGroup

Prop
类型
默认值
说明
value / onValueChange
string | string[] | undefined
—
当前选择
defaultValue
string | string[]
—
非受控初始值
type
'single' | 'multiple'
'single'
单选或多选
label
string
—
组名称
disabled
boolean
false
禁用组内切换项
className
string
—
组容器样式

children 放置切换项,onValueChange 在选中状态改变时触发。组内控件仍属于外层 Toolbar 的同一条焦点序列。

ToolbarSeparator

Prop
类型
默认值
说明
decorative
boolean
true
是否为纯装饰分隔线
className
string
—
分隔线样式

分隔线始终垂直于工具栏的排列方向,不参与键盘导航。