'use client'
import { useState } from 'react'
import {
Kbd,
Menubar,
MenubarCheckboxItem,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarSub,
Stack,
Text,
} from '@hina-ui/react'
export default function Demo() {
const [last, setLast] = useState('')
const [wrap, setWrap] = useState(true)
const [numbers, setNumbers] = useState(false)
const [theme, setTheme] = useState('system')
return (
<Stack gap="sm" align="start">
<Menubar label="编辑器菜单">
<MenubarMenu label="文件">
<MenubarItem trailing={<Kbd>Ctrl N</Kbd>} onSelect={() => setLast('新建')}>
新建
</MenubarItem>
<MenubarItem trailing={<Kbd>Ctrl O</Kbd>} onSelect={() => setLast('打开')}>
打开
</MenubarItem>
<MenubarSub label="最近打开">
<MenubarItem onSelect={() => setLast('第一章.md')}>第一章.md</MenubarItem>
<MenubarItem onSelect={() => setLast('大纲.md')}>大纲.md</MenubarItem>
</MenubarSub>
<MenubarSeparator />
<MenubarItem trailing={<Kbd>Ctrl S</Kbd>} onSelect={() => setLast('保存')}>
保存
</MenubarItem>
</MenubarMenu>
<MenubarMenu label="编辑">
<MenubarItem trailing={<Kbd>Ctrl Z</Kbd>} onSelect={() => setLast('撤销')}>
撤销
</MenubarItem>
<MenubarItem trailing={<Kbd>Ctrl Y</Kbd>} onSelect={() => setLast('重做')}>
重做
</MenubarItem>
<MenubarSeparator />
<MenubarItem trailing={<Kbd>Ctrl F</Kbd>} onSelect={() => setLast('查找')}>
查找
</MenubarItem>
</MenubarMenu>
<MenubarMenu label="视图">
<MenubarCheckboxItem checked={wrap} onCheckedChange={setWrap}>
自动换行
</MenubarCheckboxItem>
<MenubarCheckboxItem checked={numbers} onCheckedChange={setNumbers}>
显示行号
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarRadioGroup value={theme} onValueChange={setTheme}>
<MenubarRadioItem value="light">浅色</MenubarRadioItem>
<MenubarRadioItem value="dark">深色</MenubarRadioItem>
<MenubarRadioItem value="system">跟随系统</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarMenu>
</Menubar>
{last && (
<Text tone="muted" size="sm">
选择了:{last}
</Text>
)}
</Stack>
)
}
tsx
用法
import { Menubar, MenubarMenu, MenubarItem } from '@hina-ui/react'
ts
Menubar 是一条横向的栏,里面放若干个 MenubarMenu,每个菜单的 label 是栏上的触发器文字,children 是菜单里的条目。点击触发器展开菜单,展开期间把指针移到另一个触发器上就切换过去;条目、多选项、单选项、分组、标题、分隔线与子菜单都与 DropdownMenu 同一套,只是名字换成 Menubar 开头。
import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/react'
export default function Demo() {
return (
<Menubar label="示例菜单">
<MenubarMenu label="文件">
<MenubarItem>新建</MenubarItem>
<MenubarItem>打开</MenubarItem>
<MenubarItem>保存</MenubarItem>
</MenubarMenu>
<MenubarMenu label="编辑">
<MenubarItem>撤销</MenubarItem>
<MenubarItem>重做</MenubarItem>
</MenubarMenu>
<MenubarMenu label="帮助">
<MenubarItem>文档</MenubarItem>
<MenubarItem>关于</MenubarItem>
</MenubarMenu>
</Menubar>
)
}
tsx
示例
条目
条目的 icon 属性用于前置图标,trailing 属性放快捷键这类尾部内容,删除这类不可撤销的操作设置 tone="danger";MenubarLabel 是不可选中的标题,MenubarSeparator 画一条分隔线。
import { Copy, FilePlus, Trash2 } from 'lucide-react'
import {
Kbd,
Menubar,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarSeparator,
} from '@hina-ui/react'
export default function Demo() {
return (
<Menubar label="条目示例">
<MenubarMenu label="文件">
<MenubarLabel>当前文档</MenubarLabel>
<MenubarItem icon={<FilePlus />} trailing={<Kbd>Ctrl N</Kbd>}>
新建
</MenubarItem>
<MenubarItem icon={<Copy />}>复制一份</MenubarItem>
<MenubarSeparator />
<MenubarItem tone="danger" icon={<Trash2 />}>
删除
</MenubarItem>
</MenubarMenu>
</Menubar>
)
}
tsx
多选项与单选项
MenubarCheckboxItem 是可以同时选中多项的开关,切换后菜单保持展开;一组互斥的选项用 MenubarRadioGroup 包裹。
自动换行开,行号关,字号中
'use client'
import { useState } from 'react'
import {
Menubar,
MenubarCheckboxItem,
MenubarLabel,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
Stack,
Text,
} from '@hina-ui/react'
const names: Record<string, string> = { sm: '小', md: '中', lg: '大' }
export default function Demo() {
const [wrap, setWrap] = useState(true)
const [numbers, setNumbers] = useState(false)
const [size, setSize] = useState('md')
return (
<Stack gap="sm" align="start">
<Menubar label="视图设置">
<MenubarMenu label="视图">
<MenubarCheckboxItem checked={wrap} onCheckedChange={setWrap}>
自动换行
</MenubarCheckboxItem>
<MenubarCheckboxItem checked={numbers} onCheckedChange={setNumbers}>
显示行号
</MenubarCheckboxItem>
<MenubarSeparator />
<MenubarLabel>字号</MenubarLabel>
<MenubarRadioGroup value={size} onValueChange={setSize}>
<MenubarRadioItem value="sm">小</MenubarRadioItem>
<MenubarRadioItem value="md">中</MenubarRadioItem>
<MenubarRadioItem value="lg">大</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarMenu>
</Menubar>
<Text tone="muted" size="sm">
{`自动换行${wrap ? '开' : '关'},行号${numbers ? '开' : '关'},字号${names[size]}`}
</Text>
</Stack>
)
}
tsx
受控
value / onValueChange 绑定当前展开的菜单的 value,没有展开时是空字符串;可以从外部展开某个菜单。
当前展开:无
'use client'
import { useState } from 'react'
import { Button, Inline, Menubar, MenubarItem, MenubarMenu, Stack, Text } from '@hina-ui/react'
export default function Demo() {
const [open, setOpen] = useState('')
return (
<Stack gap="md" align="start">
<Inline gap="sm">
<Button variant="outline" tone="neutral" onClick={() => setOpen('file')}>
展开「文件」
</Button>
<Button variant="outline" tone="neutral" onClick={() => setOpen('help')}>
展开「帮助」
</Button>
</Inline>
<Menubar value={open} onValueChange={setOpen} label="受控菜单">
<MenubarMenu label="文件" value="file">
<MenubarItem>新建</MenubarItem>
<MenubarItem>打开</MenubarItem>
</MenubarMenu>
<MenubarMenu label="帮助" value="help">
<MenubarItem>文档</MenubarItem>
<MenubarItem>关于</MenubarItem>
</MenubarMenu>
</Menubar>
<Text tone="muted" size="sm">
当前展开:{open || '无'}
</Text>
</Stack>
)
}
tsx
不可用的菜单
MenubarMenu 的 disabled 让触发器不可用,条目上的 disabled 只让那一条不可选。
import { Menubar, MenubarItem, MenubarMenu } from '@hina-ui/react'
export default function Demo() {
return (
<Menubar label="不可用示例">
<MenubarMenu label="文件">
<MenubarItem>新建</MenubarItem>
<MenubarItem disabled>另存为</MenubarItem>
</MenubarMenu>
<MenubarMenu label="调试" disabled>
<MenubarItem>开始调试</MenubarItem>
</MenubarMenu>
</Menubar>
)
}
tsx
行为
- 点击触发器展开菜单,再次点击收起;展开期间指针移到另一个触发器即切换到那个菜单。
- 左右方向键在触发器之间移动,展开期间左右键切换菜单,上下键在条目间移动,回车或者空格选中,Esc 收起并把焦点还给触发器。
- 菜单打开期间页面照常可以滚动与交互,指针才能在触发器之间移动;选中条目或者点击外部都会收起。
无障碍
- 根是
role="menubar",用label给它一个名称;触发器是带aria-haspopup的menuitem,展开的面板是role="menu"。 - 触发器构成一组漫游焦点,Tab 只进出一次,方向键在其中移动。
API
Menubar
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | — | 菜单栏的无障碍名称 |
loop | boolean | true | 方向键到达两端时是否绕回 |
value | string | — | 当前展开的菜单的 value,可受控 |
className | string | — | 追加至根元素的类名 |
条目与其他子件
MenubarItem、MenubarCheckboxItem、MenubarRadioGroup、MenubarRadioItem、MenubarGroup、MenubarLabel、MenubarSeparator 与 MenubarSub 的属性与回调与 DropdownMenu 的同名子件完全一致,见 DropdownMenu。