'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>
)
}
用法
Toolbar 提供工具栏语义、尺寸和方向键导航。ToolbarButton 执行动作,ToolbarLink 渲染链接,ToolbarSeparator 分隔控件;控件复用 Button 的视觉样式。
import {
Toolbar,
ToolbarButton,
ToolbarLink,
ToolbarToggleGroup,
ToolbarToggleItem,
ToolbarSeparator,
} from '@hina-ui/react'
使用 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>
)
}
示例
单选与多选
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>
)
}
纵向
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>
)
}
外观
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>
)
}
尺寸
工具栏的 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>
)
}
禁用
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>
)
}
组合控件
将 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>
)
}
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>
)
}
行为与无障碍
工具栏以 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 | — | 分隔线样式 |
分隔线始终垂直于工具栏的排列方向,不参与键盘导航。