跳到主要内容

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>,
)

尺寸规范

属性Token
最小高度40px
水平内边距Spacing_88px
垂直内边距Spacing_88px
图标-文字间距Spacing_88px
文字-计数间距Spacing_44px
圆角Radius_55px
字号Font-Size/Body14px
行高Line-Height/Body22px
属性Token
最小高度40px
左内边距Spacing_88px
右内边距Spacing_44px
子项间距Spacing_44px

颜色 Token

状态背景文字颜色
默认Labels/Secondary#3d3d3d
悬停Grays/Gray-1#ebebebLabels/Secondary#3d3d3d
选中Grays/Gray-1#ebebebLabels/Secondary#3d3d3d
分组标题Labels/Tertiary#757575
计数Labels/Tertiary#757575

Props

属性类型默认值说明
childrenReactNode菜单项和分组(组合式 API)
itemsMenuItemEntry[]声明式菜单项配置
headerReactNode可选的标题
valuestring受控选中值
defaultValuestring默认选中值
onValueChange(value: string) => void选中值变化回调
classNamestring自定义 className
字段类型必填说明
valuestring唯一标识,用于选中匹配
labelReactNode菜单项文本
iconReactNode前缀图标
countReactNode尾部计数文本
actionReactNodehover 时操作元素
childrenMenuItemEntry[]子菜单项;非空时该条目渲染为可折叠父项(不参与选中)
defaultOpenbooleanfalsechildren 非空时的初始展开状态(非受控)
keyKeyReact 列表 key(默认使用 value
classNamestring自定义 className
属性类型默认值说明
labelReactNode菜单项文本(必填)
valuestring唯一标识,配合 Menu value 使用
iconReactNode前缀图标
countReactNode尾部计数文本
selectedboolean显式选中态(优先于 context 匹配)
actionReactNodehover 时显示的操作元素
onClick(e) => void点击回调
classNamestring自定义 className
属性类型默认值说明
titleReactNode分组标题(必填)
itemsMenuItemEntry[]声明式子菜单项配置
childrenReactNode子菜单项(组合式 API)
actionReactNode标题栏操作按钮
openboolean受控展开状态
defaultOpenbooleantrue默认展开状态
onOpenChange(open: boolean) => void展开状态变化回调
classNamestring自定义 className