【免费下载链接】plannotator
Annotate and review coding agent plans and code diffs visually, share with your team, send feedback to agents with one click.
本篇技术指南以 plannotator 仓库packages/ui/.migration/dropdown-menu.md迁移记录为骨架,完整还原 2026-07-07 这次组件引擎替换的全过程:为什么选@base-ui/react/menu作为新的菜单基元、Portal > Positioner > Popup三层结构如何重构Content与SubContent、消费端(OpenInAppButton、Agent 选择器)如何从 Radix 的asChild/onSelect迁移到 Base UI 的render/onClick,以及公开 API 上的破坏性变更与 HANDOFF 项。读者读完将掌握一套可复制的"Radix → Base UI"迁移方法论:既懂组件层改造,也懂消费端同步清扫与手工验收清单。
迁移背景与总体判定
packages/ui/HANDOFF.md明确记录了这次迁移的宏观背景:从0.23.0起,@plannotator/ui全面构建在Base UI(@base-ui/react@^1.6.0,当前 packages/ui/package.json 中为^1.7.0)之上,取代 Radix。这一举措与 shadcn/ui 在 2026 年 7 月将 Base UI 设为默认引擎的生态变化保持一致,并且是一次"整包级、零混合引擎"的刻意迁移——迁移完成后仓库内零@radix-ui/*依赖残留。
本次 dropdown-menu 的迁移判定(verdict)是:DropdownMenu → Base UI Menu,采用规范的Portal > Positioner > Popup结构重组,并在同一个提交内清扫 2 个仓库内消费端(OpenInAppButton与AnnotateAgentTerminalPanel),同时对公开 API 表面标记了若干差异项(deltas)。
迁移记录的收尾验证方式是全局残留扫描:
grep -n "radix-ui\|@radix-ui"对三个改动文件执行后均无匹配,确认@radix-ui/react-dropdown-menu已彻底移除。
组件层改造:dropdown-menu.tsx 的重写要点
迁移的核心文件是 packages/ui/components/ui/dropdown-menu.tsx,依赖从@radix-ui/react-dropdown-menu切换为@base-ui/react/menu(源码第 1 行import { Menu as MenuPrimitive } from "@base-ui/react/menu")。所有导出名称被完整保留,外部导入路径@plannotator/ui/components/ui/dropdown-menu无需变化,这为消费端迁移提供了第一层兼容保障。
Content/SubContent 的三层结构重组(FORWARD 规则)
Radix 时代的Content直接承载弹出层;Base UI 将其拆分为Portal(传送门)> Positioner(定位器)> Popup(弹出层)三层。源码中DropdownMenuContent的实现在第 26–52 行:
function DropdownMenuContent({ className, side, sideOffset = 4, align, alignOffset, ...props }: MenuPrimitive.Popup.Props & Pick<MenuPrimitive.Positioner.Props, "side" | "sideOffset" | "align" | "alignOffset">) { return ( <MenuPrimitive.Portal> <MenuPrimitive.Positioner className="isolate z-50 outline-none" side={side} sideOffset={sideOffset} align={align} alignOffset={alignOffset} > <MenuPrimitive.Popup >function DropdownMenuSubContent({ className, side = "right", sideOffset = 0, align = "start", alignOffset = -3, ...props })迁移记录明确指出:这些默认值是**承重(load-bearing)**的——Radix 的SubContent隐含了side="right"与start对齐行为,迁移到 Base UI 后这些隐含语义不复存在,必须以显式默认值补回,否则子菜单会出现在错误位置。alignOffset={-3}用于抵消子菜单触发器与弹出层之间的视觉缝隙。
部件重命名对照
Base UI 的部件命名更贴近语义,本次迁移涉及以下改名(全部在 packages/ui/components/ui/dropdown-menu.tsx 中有对应实现):
| Radix 名称 | Base UI 名称 | 说明 |
|---|---|---|
Label | GroupLabel | 语义化为"组标签",且必须位于Group内部 |
ItemIndicator | CheckboxItemIndicator/RadioItemIndicator | 按复选/单选拆分为两个指示器部件 |
Sub | SubmenuRoot | 子菜单根节点 |
SubTrigger | SubmenuTrigger | 子菜单触发器 |
动画与交互态的类名重写
Radix 时代依赖 tw-animate 的 keyframes 动画(data-[state=open]:animate-in+slide-in-from-*系列);Base UI 采用基于 transition 的起始/结束样式(starting/ending style)。迁移后的 Popup 共享样式(源码第 23–24 行):
origin-[var(--transform-origin)] transition-[opacity,scale] duration-150>赞【免费下载链接】plannotator
Annotate and review coding agent plans and code diffs visually, share with your team, send feedback to agents with one click.
相关推荐
plannotator review-editor:QAChecklist 复选框组件从 Radix 迁移到 Base UI 的 1:1 改造实录
plannotator review editor:QAChecklist 复选框组件从 Radix 迁移到 Base UI 的 1:1 改造实录 本文围绕 p
@plannotator/ui Badge 组件迁移实录:Radix `asChild` 到 Base UI `render` 的多态改造解析
@plannotator/ui Badge 组件迁移实录:Radix asChild 到 Base UI render 的多态改造解析 本文以 迁移报告 htt
plannotator 评审编辑器实践:把 DiffTypePicker 从 Radix DropdownMenu 迁移到 Base UI Menu
plannotator 评审编辑器实践:把 DiffTypePicker 从 Radix DropdownMenu 迁移到 Base UI Menu 本文以 p