# Menu

导航菜单组件，支持垂直/水平布局、多级子菜单（分组、折叠、弹出）及下拉菜单容器。

## 适用场景

- 侧边导航菜单（垂直布局）
- 顶部水平导航
- 下拉菜单（配合 MenuPopper）
- 多级嵌套菜单、多选菜单

## Props

### Menu Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| size | `"md" \| "lg"` | `"md"` | 否 | 尺寸 |
| layout | `MenuLayout` | `"vertical"` | 否 | 布局方式 |
| selectedIds | `MenuItemId[]` | `-` | 否 | 选中的 item id 列表（受控） |
| expandedIds | `MenuItemId[]` | `-` | 否 | 展开的 submenu id 列表（受控） |
| multiple | `boolean` | `false` | 否 | 是否为多选 |
| multipleItemType | `MultipleItemType` | `"normal"` | 否 | 多选选项类型 |
| submenuType | `"group" \| "folder" \| "popper"` | `"folder"` | 否 | submenu type 默认值，统一设置子孙 Submenu 的类型 |
| submenuPopperTriggerType | `"hover" \| "click"` | `"hover"` | 否 | submenu popper 触发方式默认值 |
| folderToggleSpot | `"auto" \| "toggle"` | `"auto"` | 否 | folder 类型展开/收起的操作热区 |
| reverseColor | `boolean` | `false` | 否 | 使用反色，适用于深色背景 |
| onSelect | `(id: MenuItemId, selectedIds: MenuItemId[], currentSelectedIds: MenuItemId[]) => void` | `-` | 否 | 点选 item 的回调 |
| onDeselect | `(id: MenuItemId, selectedIds: MenuItemId[], currentSelectedIds: MenuItemId[]) => void` | `-` | 否 | 反选 item 的回调 |
| onChangeSelectedIds | `(selectedIds: MenuItemId[]) => void` | `-` | 否 | selectedIds 变化时的回调（select/deselect 均触发） |
| onExpand | `(id: MenuItemId, expandedIds: MenuItemId[], currentExpandedIds: MenuItemId[]) => void` | `-` | 否 | 子菜单展开时的回调 |
| onCollapse | `(id: MenuItemId, expandedIds: MenuItemId[], currentExpandedIds: MenuItemId[]) => void` | `-` | 否 | 子菜单折叠时的回调 |
| onChangeExpandedIds | `(expandedIds: MenuItemId[]) => void` | `-` | 否 | expandedIds 变化时的回调（expand/collapse 均触发） |

### MenuItem Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| id | `MenuItemId` | `-` | 否 | 唯一标识 |
| disabled | `boolean` | `false` | 否 | 是否禁用 |
| type | `"normal" \| "checkbox"` | `"normal"` | 否 | 类型 |
| selected | `boolean` | `-` | 否 | 是否选中控制值；不控制则以 context.selectedIds 判断 |
| icon | `SvgFC` | `-` | 否 | 前缀 icon，仅在第一层 Menu 下有效 |
| href | `string` | `-` | 否 | 链接跳转地址，设置后使用 `<a>` 元素 |
| target | `string` | `-` | 否 | 链接跳转的 target |
| onClick | `(e: MouseEvent<Element, MouseEvent>) => void` | `-` | 否 | 点击回调 |
| indeterminate | `boolean` | `-` | 否 | 是否部分选中 |
| prefix | `ReactNode` | `-` | 否 | 前缀内容 |
| suffix | `ReactNode` | `-` | 否 | 后缀内容 |
| suffixArrow | `boolean \| SvgFC` | `-` | 否 | 后缀箭头 |
| suffixLoading | `boolean \| SvgFC` | `-` | 否 | 后缀 loading，用于懒加载时体现 loading 状态 |

### Submenu Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| style | `CSSProperties` | `-` | 否 | 自定义 style |
| id | `MenuItemId` | `-` | 否 | 唯一标识 |
| type | `"group" \| "folder" \| "popper"` | `-` | 否 | 子菜单类型 |
| selected | `boolean` | `-` | 否 | 是否选中控制值；不控制则以 context.selectedIds 判断 |
| expanded | `boolean` | `-` | 否 | 是否展开控制值；不控制则以 context.expandedIds 判断 |
| itemContent | `ReactNode` | `-` | 否 | 菜单项元素 |
| popperTriggerType | `"hover" \| "click"` | `-` | 否 | popper menu 触发类型 |
| popperPlacement | `Placement` | `-` | 否 | popper menu 位置 |
| onClick | `(e: MouseEvent<Element, MouseEvent>) => void` | `-` | 否 | 点击回调 |
| selectable | `boolean` | `-` | 否 | 是否可以选择（点击是否触发 MenuItem select 行为） |
| disablePortal | `boolean` | `-` | 否 | 禁用 menu popper 的 popup portal |
| portalContainer | `HTMLElement \| (() => HTMLElement)` | `-` | 否 | 指定 menu popper 的 popup portal |
| icon | `SvgFC` | `-` | 否 | 前缀 icon，仅在第一层 Menu 下有效 |
| prefix | `ReactNode` | `-` | 否 | 前缀内容 |
| suffix | `ReactNode` | `-` | 否 | 后缀内容 |

### MenuPopper Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| size | `"md" \| "lg" \| "sm" \| "xs" \| "xl"` | `-` | 否 | 尺寸 |
| type | `"hover" \| "click"` | `"click"` | 否 | 触发方式 |
| disabled | `boolean` | `false` | 否 | 是否禁用 |
| placement | `Placement` | `"bottom-start"` | 否 | popper 位置 |
| targetNode | `ReactNode` | `-` | 否 | popper 的 target ReactNode |
| loading | `boolean` | `-` | 否 | 是否处于 loading 状态 |
| emptyHint | `ReactNode` | `-` | 否 | 无内容提示 |
| visible | `boolean` | `-` | 否 | popper 是否显示控制值 |
| target | `(() => HTMLElement \| null \| void) \| HTMLElement \| null` | `-` | 否 | 指定 popper 目标元素 |
| defaultVisible | `boolean` | `-` | 否 | popper 是否显示默认值 |
| onChange | `(visible: boolean, e?: Event \| SyntheticEvent<Element, Event>) => void` | `-` | 否 | visible 变化回调 |
| beforeChange | `(visible: boolean) => boolean \| void \| Promise<unknown>` | `-` | 否 | visible 变化前的回调，返回 false 或 reject 则不进行变化 |
| mouseEnterDelay | `number` | `-` | 否 | mouseEnter 延迟触发，单位：毫秒 |
| mouseLeaveDelay | `number` | `-` | 否 | mouseLeave 延迟触发，单位：毫秒 |
| focusDelay | `number` | `-` | 否 | focus 延迟触发，单位：毫秒 |
| blurDelay | `number` | `-` | 否 | blur 延迟触发，单位：毫秒 |
| shouldHideParent | `boolean` | `-` | 否 | 是否隐藏时同时隐藏 parent |
| shouldHideOnMousedownDocument | `boolean \| ((e: Event) => boolean)` | `-` | 否 | click 类型下点击外部是否关闭，false 可禁用此行为 |
| disablePortal | `boolean` | `-` | 否 | 禁用 popup portal，维持 children in parent DOM 层级 |
| portalContainer | `HTMLElement \| (() => HTMLElement)` | `-` | 否 | 指定 popup portal 挂载的容器，默认为 document.body |
| matchMinWidthToTarget | `boolean` | `true` | 否 | popperWrap 最小宽度匹配到 target 宽度 |
| matchWidthToTarget | `boolean` | `-` | 否 | popperWrap 宽度匹配到 target 宽度 |
| refPopperWrap | `Ref<HTMLDivElement>` | `-` | 否 | popperWrap 的 element ref |
| withArrow | `boolean` | `-` | 否 | 是否带箭头 |
| modifiers | `Partial<Modifier<any, any>>[]` | `-` | 否 | popper modifier 设置，透传给底层 Popper |

### MenuItemSelectAll Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| type | `"normal" \| "checkbox"` | `-` | 否 | 类型 |
| icon | `SvgFC` | `-` | 否 | 前缀 icon，仅在第一层 Menu 下有效 |
| prefix | `ReactNode` | `-` | 否 | 前缀内容 |
| suffix | `ReactNode` | `-` | 否 | 后缀内容 |
| onClick | `(e: MouseEvent<Element, MouseEvent>) => void` | `-` | 否 | 点击回调 |
| indeterminate | `boolean` | `-` | 否 | 是否部分选中 |
| suffixArrow | `boolean \| SvgFC` | `-` | 否 | 后缀箭头 |
| suffixLoading | `boolean \| SvgFC` | `-` | 否 | 后缀 loading |
| disabled | `boolean` | `-` | 否 | 是否禁用 |
| selected | `boolean` | `-` | 否 | 是否选中控制值 |
| href | `string` | `-` | 否 | 链接跳转地址 |
| target | `string` | `-` | 否 | 链接跳转的 target |

### LocaleMenu Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| all_selected | `LocaleText` | `-` | 是 | "全部"文案 |

## 典型用法

### 垂直导航菜单

> Submenu `type` 支持 `folder`（折叠，默认）、`group`（分组）、`popper`（弹出）

```tsx
const [selectedIds, setSelectedIds] = useState<MenuItemId[]>([])
const [expandedIds, setExpandedIds] = useState<MenuItemId[]>([])

<Menu
    selectedIds={selectedIds}
    onChangeSelectedIds={setSelectedIds}
    expandedIds={expandedIds}
    onChangeExpandedIds={setExpandedIds}
>
    <MenuItem id={'item_1'}>一级选项甲</MenuItem>
    <Submenu id={'group_2'} type={'group'} itemContent={'一级分组'} icon={SvgSearch}>
        <MenuItem id={'item_21'}>二级选项甲</MenuItem>
        <MenuItem id={'item_22'}>二级选项乙</MenuItem>
    </Submenu>
    <Submenu id={'sub_1'} type={'folder'} itemContent={'一级折叠'}>
        <MenuItem id={'item_11'}>二级选项戊</MenuItem>
        <MenuItem id={'item_12'}>二级选项己</MenuItem>
    </Submenu>
    <Submenu id={'popper_3'} type={'popper'} itemContent={'一级弹出'} icon={SvgSearch}>
        <MenuItem id={'item_33'}>二级选项庚</MenuItem>
        <MenuItem id={'item_34'}>二级选项辛</MenuItem>
    </Submenu>
    <MenuItem id={'item_4'} disabled>禁用的一级选项</MenuItem>
</Menu>
```

### 水平导航菜单

```tsx
<Menu layout={'horizontal'} size={'lg'} reverseColor>
    <MenuItem id={'item_1'}>option 1</MenuItem>
    <MenuItem icon={SvgViewTile} id={'item_2'}>option 2</MenuItem>
    <Submenu id={'menu_sub'} type={'popper'} itemContent={'subMenu'}>
        <MenuItem id={'item_3'}>option 3</MenuItem>
        <MenuItem id={'item_4'}>option 4</MenuItem>
    </Submenu>
</Menu>
```

### MenuPopper 下拉菜单

```tsx
<MenuPopper withArrow targetNode={<Button>DropDown</Button>}>
    <Menu>
        <MenuItem id={'item_1'}>option 1</MenuItem>
        <MenuItem id={'item_2'}>option 2</MenuItem>
        <MenuItem id={'item_3'}>option 3</MenuItem>
    </Menu>
</MenuPopper>
```

## 注意事项

- 同一个菜单中不应混用 `popper` 和 `folder` 类型的 Submenu，使用 `Menu.submenuType` 统一设置
- `folderToggleSpot` 为 `"auto"` 时：若 Submenu 无操作（非 selectable、无 onClick、itemContent 非 `<a>`），整个 item 热区均可展开/收起；否则仅箭头区域可操作；`"toggle"` 强制仅箭头触发展开/收起
- `Submenu`/`MenuItem` 的 `id` 作为 selectedIds、expandedIds 判断的唯一标识，不提供时内部使用随机 id
- 导航菜单中不存在 disabled 导航项，不可用项应直接隐藏
- 每层级导航数量建议不超过 7 个，文字不超过 6 个中文字符，深度最大 3 层
- 如需简单的下拉菜单，优先考虑使用 DropMenu 组件
