Menu
- 组件说明:侧边导航菜单,用于文件管理和分类导航。由
Menu(容器)、MenuItem(导航项)和MenuGroup(可折叠分组)组成。支持声明式items配置和组合式 API 两种用法。 - 尺寸基线:MenuItem 最小高度 40px,内边距 8px,字号 14px/20px,图标 20×20px;MenuGroup 标题栏最小高度 40px。
- 实现约定:纯 design 层组件 —
Menu渲染为<nav>语义标签,MenuGroup基于 Radix Collapsible 实现展开收起动画。选中状态通过value/defaultValue/onValueChange管理。 - Figma 规范
基础用法
通过 items 声明式配置菜单项,选中状态由 defaultValue 管理。
结果
Loading...
实时编辑器
render( <div style={{ width: 220 }}> <Menu defaultValue="files" items={[ { value: 'files', label: '全部文件', count: '(572)' }, { value: 'unfiled', label: '未归类', count: '(203)' }, { value: 'trash', label: '回收站', count: '(82)' }, ]} /> </div>, )
组合式 API
直接使用 MenuItem 子组件,灵活性更高。每个 item 的 value 属性配合 Menu 的 defaultValue 实现自动选中。
结果
Loading...
实时编辑器
render( <div style={{ width: 220 }}> <Menu header="文件" defaultValue="files"> <MenuItem value="files" label="全部文件" count="(572)" /> <MenuItem value="unfiled" label="未归类" count="(203)" /> <MenuItem value="trash" label="回收站" count="(82)" /> </Menu> </div>, )
带图标
通过 icon 属性为每个菜单项添加前缀图标。
结果
Loading...
实时编辑器
const SearchIcon = () => ( <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="9" cy="9" r="5.5" stroke="currentColor" strokeWidth="1.2" /> <path d="M13 13L16 16" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" /> </svg> ) const HomeIcon = () => ( <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M3 10L10 4L17 10M5 9V16H8V12H12V16H15V9" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" /> </svg> ) render( <div style={{ width: 220 }}> <Menu defaultValue="home"> <MenuItem value="search" icon={<SearchIcon />} label="搜索" /> <MenuItem value="home" icon={<HomeIcon />} label="首页" /> </Menu> </div>, )
受控选中
使用 value + onValueChange 实现完全受控的选中状态。
结果
Loading...
实时编辑器
const ControlledExample = () => { const [value, setValue] = useState('files') return ( <div style={{ width: 220 }}> <Menu value={value} onValueChange={setValue}> <MenuItem value="files" label="全部文件" count="(572)" /> <MenuItem value="unfiled" label="未归类" count="(203)" /> <MenuItem value="trash" label="回收站" count="(82)" /> </Menu> <p style={{ fontSize: 12, color: '#757575', marginTop: 8, paddingLeft: 8 }}> 当前选中: {value} </p> </div> ) } render(<ControlledExample />)
可折叠分组
MenuGroup 支持 items 声明式 API,带旋转箭头动画和展开收起过渡效果。
结果
Loading...
实时编辑器
render( <div style={{ width: 220 }}> <Menu defaultValue="files"> <MenuItem value="files" label="全部文件" count="(572)" /> <MenuGroup title="文件夹" items={[ { value: 'meetings', label: '会议', count: '(199)' }, { value: 'memos', label: '语音备忘录', count: '(156)' }, { value: 'reading', label: '阅读', count: '(48)' }, ]} /> <MenuGroup title="标签" defaultOpen={false} items={[ { value: 'minutes', label: '会议纪要', count: '(24)' }, { value: 'selling', label: '销售计划', count: '(59)' }, ]} /> </Menu> </div>, )
分组操作按钮
通过 action 属性在分组标题栏渲染操作按钮(如"新建"按钮)。
结果
Loading...
实时编辑器
render( <div style={{ width: 220 }}> <Menu> <MenuGroup title="文件夹" action={ <button type="button" style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, display: 'flex', alignItems: 'center' }}> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M10.499 9.50049H15.333V10.5005H10.5V15.3335H9.5V10.4995H4.66602V9.49951H9.49902V4.6665H10.499V9.50049Z" fill="currentColor" /> </svg> </button> } items={[ { value: 'meetings', label: '会议', count: '(199)' }, { value: 'memos', label: '语音备忘录', count: '(156)' }, ]} /> </Menu> </div>, )
多级菜单
MenuItemEntry.children 非空时,对应菜单项渲染为可折叠父项(任意深度递归)。父项点击仅切换展开/收起、不参与选中,叶子项才会匹配 defaultValue / value。
结果
Loading...
实时编辑器
render( <div style={{ width: 220 }}> <Menu defaultValue="projects/alpha" items={[ { value: 'home', label: '首页' }, { value: 'projects', label: '项目', defaultOpen: true, children: [ { value: 'projects/active', label: '进行中', defaultOpen: true, children: [ { value: 'projects/alpha', label: 'Alpha', count: '(3)' }, { value: 'projects/beta', label: 'Beta' }, ], }, { value: 'projects/archive', label: '归档' }, ], }, { value: 'tags', label: '标签', children: [ { value: 'tags/important', label: '重要' }, { value: 'tags/personal', label: '个人' }, ], }, ]} /> </div>, )
尺寸规范
MenuItem
| 属性 | Token | 值 |
|---|---|---|
| 最小高度 | — | 40px |
| 水平内边距 | Spacing_8 | 8px |
| 垂直内边距 | Spacing_8 | 8px |
| 图标-文字间距 | Spacing_8 | 8px |
| 文字-计数间距 | Spacing_4 | 4px |
| 圆角 | Radius_5 | 5px |
| 字号 | Font-Size/Body | 14px |
| 行高 | Line-Height/Body | 22px |
MenuGroup 标题栏
| 属性 | Token | 值 |
|---|---|---|
| 最小高度 | — | 40px |
| 左内边距 | Spacing_8 | 8px |
| 右内边距 | Spacing_4 | 4px |
| 子项间距 | Spacing_4 | 4px |
颜色 Token
| 状态 | 背景 | 文字颜色 |
|---|---|---|
| 默认 | — | Labels/Secondary#3d3d3d |
| 悬停 | Grays/Gray-1#ebebeb | Labels/Secondary#3d3d3d |
| 选中 | Grays/Gray-1#ebebeb | Labels/Secondary#3d3d3d |
| 分组标题 | — | Labels/Tertiary#757575 |
| 计数 | — | Labels/Tertiary#757575 |
Props
Menu
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children | ReactNode | — | 菜单项和分组(组合式 API) |
| items | MenuItemEntry[] | — | 声明式菜单项配置 |
| header | ReactNode | — | 可选的标题 |
| value | string | — | 受控选中值 |
| defaultValue | string | — | 默认选中值 |
| onValueChange | (value: string) => void | — | 选中值变化回调 |
| className | string | — | 自定义 className |
MenuItemEntry
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| value | string | ✓ | 唯一标识,用于选中匹配 |
| label | ReactNode | ✓ | 菜单项文本 |
| icon | ReactNode | — | 前缀图标 |
| count | ReactNode | — | 尾部计数文本 |
| action | ReactNode | — | hover 时操作元素 |
| children | MenuItemEntry[] | — | 子菜单项;非空时该条目渲染为可折叠父项(不参与选中) |
| defaultOpen | boolean | false | children 非空时的初始展开状态(非受控) |
| key | Key | — | React 列表 key(默认使用 value) |
| className | string | — | 自定义 className |
MenuItem
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| label | ReactNode | — | 菜单项文本(必填) |
| value | string | — | 唯一标识,配合 Menu value 使用 |
| icon | ReactNode | — | 前缀图标 |
| count | ReactNode | — | 尾部计数文本 |
| selected | boolean | — | 显式选中态(优先于 context 匹配) |
| action | ReactNode | — | hover 时显示的操作元素 |
| onClick | (e) => void | — | 点击回调 |
| className | string | — | 自定义 className |
MenuGroup
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| title | ReactNode | — | 分组标题(必填) |
| items | MenuItemEntry[] | — | 声明式子菜单项配置 |
| children | ReactNode | — | 子菜单项(组合式 API) |
| action | ReactNode | — | 标题栏操作按钮 |
| open | boolean | — | 受控展开状态 |
| defaultOpen | boolean | true | 默认展开状态 |
| onOpenChange | (open: boolean) => void | — | 展开状态变化回调 |
| className | string | — | 自定义 className |