feat: Support custom rendering
🤔 This is a ...
- [x] 🆕 New feature
- [ ] 🐞 Bug fix
- [ ] 📝 Site / documentation improvement
- [ ] 📽️ Demo improvement
- [ ] 💄 Component style improvement
- [ ] 🤖 TypeScript definition improvement
- [ ] 📦 Bundle size optimization
- [ ] ⚡️ Performance optimization
- [ ] ⭐️ Feature enhancement
- [ ] 🌐 Internationalization
- [ ] 🛠 Refactoring
- [ ] 🎨 Code style optimization
- [ ] ✅ Test Case
- [ ] 🔀 Branch merge
- [ ] ⏩ Workflow
- [ ] ⌨️ Accessibility improvement
- [ ] ❓ Other (about what?)
🔗 Related Issues
💡 Background and Solution
- The specific problem to be addressed.
- List the final API implementation and usage if needed.
- If there are UI/interaction changes, consider providing screenshots or GIFs.
📝 Change Log
- Read Keep a Changelog! Track your changes, like a cat tracks a laser pointer.
- Describe the impact of the changes on developers, not the solution approach.
- Reference: https://ant.design/changelog
| Language | Changelog |
|---|---|
| 🇺🇸 English | Support custom rendering |
| 🇨🇳 Chinese | Support custom rendering |
Summary by CodeRabbit
- 新功能
- 增加 Menu 的 itemRender 属性,支持自定义菜单项、子菜单、分组与分割线渲染;示例新增演示项并展示将菜单项包装为外链及添加尾部分割线的用法。
- 文档
- 在 API 文档中补充 itemRender 的说明、签名与默认行为。
- 测试
- 新增测试覆盖自定义渲染场景,验证菜单项被正确包装为外链。
- 重构/样式
- 若干内部渲染路径与格式调整,外显行为保持一致。
Walkthrough
为 Menu 引入可自定义的 itemRender 渲染回调,并将其类型与上下文贯穿到 Menu、SubMenu、MenuItem、MenuItemGroup、Divider、类型定义、示例与测试中;示例与测试新增用例将 item 包裹为外链。
Changes
| Cohort / File(s) | Summary |
|---|---|
类型与上下文src/interface.ts, src/context/MenuContext.tsx |
新增导出类型 ItemRenderType,在 ItemSharedProps 与 MenuContextProps 中加入可选 itemRender。 |
Menu:入口与转发src/Menu.tsx |
在 MenuProps 中新增 itemRender?: ItemRenderType,从 props 解构并通过 MenuContextProvider 转发;调整部分 useMemo 依赖。 |
菜单项渲染:Itemsrc/MenuItem.tsx |
从 props 与上下文合并 itemRender,在渲染前调用 mergedItemRender(renderNode, { item: { type: 'item', ... }, keys, eventOpt }) 并用返回值替换渲染节点;过滤传入子组件的辅助 props。 |
菜单组渲染:Groupsrc/MenuItemGroup.tsx |
接收并合并 prop/context 的 itemRender 与 eventOpt,在非 measure 路径对 childList 调用 itemRender(childList, { item: { type: 'group', ... }, keys })。 |
子菜单渲染:SubMenusrc/SubMenu/index.tsx |
支持从 props 接收 itemRender 与 eventOpt,在构建 childList 后通过 itemRender 转换 childList 节点并使用返回值。 |
分隔线:Dividersrc/Divider.tsx |
接收 prop/context itemRender 并合并;构建 renderNode 后可通过 itemRender(renderNode, { item: { type: 'divider', ... }, keys, eventOpt }) 转换;组件签名改为接收单一 props 参数。 |
工具与格式化src/utils/nodeUtil.tsx, src/utils/commonUtil.ts |
向合并组件传递额外 eventOpt,并对部分额外内容渲染做小幅格式/单行化调整,功能保持一致。 |
示例与文档docs/examples/items.tsx, README.md |
示例新增 itemRender 用例(对 type==='item' 的节点包裹外链)并新增示例项;README 增加 itemRender API 文档。 |
测试tests/MenuItem.spec.tsx |
新增测试验证 itemRender 将 item originNode 包裹为外链并检查 href(文件中出现重复测试插入)。 |
Sequence Diagram(s)
sequenceDiagram
autonumber
actor Dev as 开发者
participant Menu as Menu
participant Context as MenuContext
participant Component as 子组件(Item/Group/SubMenu/Divider)
participant itemRender as itemRender 回调
participant Renderer as 渲染管线
Dev->>Menu: 提供 items 与 itemRender
Menu->>Context: 将 itemRender 注入上下文
Renderer->>Component: 构建 originNode / childList
alt itemRender 存在(prop 或 context)
Component->>itemRender: itemRender(originNode/childList, { item, keys, eventOpt })
itemRender-->>Component: customNode
Component-->>Renderer: 返回 customNode
else 无 itemRender
Component-->>Renderer: 返回 originNode
end
Renderer-->>Menu: 完整节点树
Menu-->>Dev: 渲染完成
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
注意可能需要额外关注:
-
src/MenuItem.tsx、src/MenuItemGroup.tsx、src/SubMenu/index.tsx、src/Divider.tsx中构造传入itemRender的元数据(type、eventOpt、keys)是否一致且类型安全。 -
src/Menu.tsx中 useMemo 依赖调整对重渲染或性能的影响。 -
tests/MenuItem.spec.tsx中重复测试应去重并确认断言覆盖正确。
Possibly related PRs
- react-component/menu#740 — 修改
src/utils/nodeUtil.tsx中合并项构造逻辑,与本次对同一文件和合并传参的改动高度相关。
Suggested reviewers
- zombieJ
Poem
小兔叼来一行 render,原生节点换新装,
外层轻裹成链接,菜单点滴更自如。
回调穿针又缝线,子树悄然换模样,
浅笑藏在 props 里,点击生风向。 🐰✨
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 标题简洁明了地反映了PR的主要目标:支持自定义渲染。虽然未涵盖所有细节,但准确总结了核心功能改动。 |
| Linked Issues check | ✅ Passed | PR完整实现了Issue #54289的所有核心要求:实现了itemRender API、支持包裹Menu.Item节点、支持与Link/Upload等组件集成、解决sticky布局样式问题。 |
| Out of Scope Changes check | ✅ Passed | 所有改动均与itemRender功能的实现直接相关,包括类型定义、组件改造、上下文传递、文档更新和测试补充,无超出范围的变更。 |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
📜 Recent review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between b2981dd97b7ea2438e53aed80732a9e5e76f7048 and 478dcf8f0002c2b62cd204c1355887452ac841f2.
📒 Files selected for processing (3)
-
src/MenuItem.tsx(5 hunks) -
src/MenuItemGroup.tsx(1 hunks) -
src/SubMenu/index.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/MenuItemGroup.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-23T06:01:34.975Z
Learnt from: codewizardTM
Repo: react-component/menu PR: 818
File: src/Menu.tsx:415-431
Timestamp: 2025-10-23T06:01:34.975Z
Learning: In src/Menu.tsx focus logic, defaultFocusKey should only use keys from focusableElements/key2element, not from childList.find(), because childList items might not be rendered in DOM (e.g., in overflow menu) which would cause focus to fail.
Applied to files:
-
src/SubMenu/index.tsx -
src/MenuItem.tsx
📚 Learning: 2025-10-23T07:19:07.493Z
Learnt from: codewizardTM
Repo: react-component/menu PR: 818
File: src/Menu.tsx:415-431
Timestamp: 2025-10-23T07:19:07.493Z
Learning: In src/Menu.tsx focus logic, when validating candidate focus keys (selected/active/default), must check against focusableElements (not just key2element) to exclude disabled items. Use focusableKeys = new Set(focusableElements.map(el => element2key.get(el))) and validate with focusableKeys.has(k), otherwise disabled selected items will cause focus to fail without fallback.
Applied to files:
-
src/MenuItem.tsx
🧬 Code graph analysis (2)
src/SubMenu/index.tsx (1)
src/interface.ts (1)
ItemType(79-79)
src/MenuItem.tsx (1)
src/context/MenuContext.tsx (1)
MenuContext(69-69)
🔇 Additional comments (1)
src/MenuItem.tsx (1)
92-94: itemRender 的双源合并逻辑正确从 props 和 context 获取
itemRender并通过propItemRender || contextItemRender合并的模式是合理的,允许在 Menu 级别统一配置或在单个 MenuItem 上覆盖。Also applies to: 117-120
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
Codecov Report
:x: Patch coverage is 84.00000% with 4 lines in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 99.19%. Comparing base (3ba3f98) to head (364df90).
| Files with missing lines | Patch % | Lines |
|---|---|---|
| src/MenuItemGroup.tsx | 66.66% | 1 Missing and 1 partial :warning: |
| src/Divider.tsx | 88.88% | 1 Missing :warning: |
| src/SubMenu/index.tsx | 80.00% | 1 Missing :warning: |
Additional details and impacted files
@@ Coverage Diff @@
## master #792 +/- ##
==========================================
- Coverage 99.72% 99.19% -0.54%
==========================================
Files 26 26
Lines 727 743 +16
Branches 199 209 +10
==========================================
+ Hits 725 737 +12
- Misses 2 5 +3
- Partials 0 1 +1
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
No dependency changes detected. Learn more about Socket for GitHub.
👍 No dependency changes detected in pull request