
OpenMetadata Radio 组件完全指南react-aria RadioGroup 驱动的单选项表单控件实现与使用【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata本文是 OpenMetadata UI 设计规范体系中Radio单选框组件的权威技术指南完整覆盖openmetadata/ui-core-components包中RadioGroup、RadioButton、RadioButtonBase的适用场景、结构解剖、Tailwind 设计 Token、Props/API、交互状态与测试验证。读完本文你将掌握如何在 OpenMetadata 前端项目中正确使用 Radio 组件实现互斥单选表单并理解其基于 react-aria 的受控/非受控模型与border 画在::after上的底层视觉规范。组件定位与适用场景Radio 组件属于 OpenMetadata UI 规范中的Base / form基础表单类别状态为Stable稳定对应组件为openmetadata/ui-core-components包中的RadioGroupRadioButton以及底层的RadioButtonBase。官方规格文档位于 openmetadata-ui/src/main/resources/ui/specs/untitled/radio.md源码位于 openmetadata-ui-core-components/src/main/resources/ui/src/components/base/radio-buttons。Use when适用场景当用户需要从一组互斥的小选项通常 2–5 个可见选项中恰好选择一个时使用。所有RadioButton必须放在RadioGroup内部渲染由RadioGroup统一持有选中值并通过 React Context 向所有按钮下发size。Dont use when不适用场景允许多选 → 应使用 Checkbox二选一的开/关布尔设置 → 应使用 Toggle选项列表过长 → 应使用 Select。这一用什么、不用什么的判定逻辑在源码中同样体现在 Checkbox 的规格文档中Checkbox 明确标注选项为一组互斥单选时应使用RadioGroup两个组件互为对照共同构成 OpenMetadata 表单选择控件的完整决策树。组件架构三层结构与 Context 传递从源码结构看Radio 组件由三层构成职责逐层分离RadioGroup最外层容器持有选中值与size通过RadioGroupContext.Provider向下传递RadioButton单个选项的标签包装层基于 react-aria 的AriaRadio负责渲染 label 与 hint 文本RadioButtonBase纯视觉层只负责绘制圆圈外观表面、边框、内点、聚焦环、禁用态不关心交互逻辑。核心实现位于 radio-buttons.tsx其 Context 定义如下export interface RadioGroupContextType { size?: sm | md; } const RadioGroupContext createContextRadioGroupContextType | null(null);RadioButton在渲染时读取该 Context 并覆盖自身的size默认值const context useContext(RadioGroupContext); size context?.size ?? size;这意味着你永远不需要在单个RadioButton上手动重复设置尺寸只要在RadioGroup上声明一次size整组按钮会通过 Context 自动对齐——这是该组件区别于普通样式组件的关键设计。从interface RadioGroupProps extends RadioGroupContextType, AriaRadioGroupProps可以看出size同时出现在RadioGroup的 props 与 Context 类型中类型层面也保证了这一点。底层交互能力选中态管理、键盘导航、焦点管理、aria-checked等无障碍语义全部委托给 react-aria 的AriaRadioGroup/AriaRadioOpenMetadata 侧只做视觉定制与文案包装。Anatomy组件结构解剖官方规格文档给出如下结构示意( ) Option A ← RadioButton: circle ::after border inner dot when selected (•) Option B Hint ← selected shows the brand-filled dot组件由四个部分构成Part说明groupRadioGroup容器垂直flex列布局tw:flex tw:flex-col tw:gap-4circleRadioButtonBase绘制的圆圈表面 ::after边框 内部选中圆点label选项主文本位于圆圈右侧hint标签下方的次级说明文本可选对应到源码 radio-buttons.tsxRadioButton的 DOM 结构是react-aria 的AriaRadio渲染为label内部依次放置RadioButtonBase圆圈和一个tw:inline-flex tw:flex-col的文本包装层文本层内label用p、hint用span渲染。特别值得注意的是hint 的span上绑定了onClick{(event) event.stopPropagation()}防止点击提示文本时误触发放选项的选中行为——这是一个容易被忽略但很实用的交互细节。设计 Token 与视觉实现Radio 组件的全部视觉呈现由tw:前缀的 Tailwind utility 类完成规格文档给出了完整的 Token 映射表Parttw:utilityCircle surfacetw:bg-primarytw:rounded-fullCircle borderborderAfter→tw:after:outline-primarySelectedtw:bg-brand-solidtw:after:outline-brand-solidInner dottw:bg-fg-whitetw:size-1.5md 尺寸下tw:size-2Focus ringtw:outline-2 tw:outline-offset-2 tw:outline-focus-ringDisabledtw:border-disabledtw:bg-disabled_subtledot 用tw:bg-fg-disabled_subtleGroup layouttw:flex tw:flex-col tw:gap-4Label / hinttw:text-secondary/tw:text-tertiary对应源码 RadioButtonBasediv className{cx( // Border on ::after — the elements own outline is reserved for the focus ring below. tw:relative tw:flex tw:size-4 tw:min-h-4 tw:min-w-4 tw:cursor-pointer tw:appearance-none tw:items-center tw:justify-center tw:rounded-full tw:bg-primary, ${borderAfter} tw:after:outline-primary, size md tw:size-5 tw:min-h-5 tw:min-w-5, isSelected !isDisabled tw:bg-brand-solid tw:after:outline-brand-solid, isDisabled tw:cursor-not-allowed tw:border-disabled tw:bg-disabled_subtle, isFocusVisible tw:outline-2 tw:outline-offset-2 tw:outline-focus-ring, className )} div className{cx( tw:size-1.5 tw:rounded-full tw:bg-fg-white tw:opacity-0 tw:transition-inherit-all, size md tw:size-2, isDisabled tw:bg-fg-disabled_subtle, isSelected tw:opacity-100 )} / /div这段实现有两个值得深挖的设计决策1. 边框画在::after上绝不使用tw:ring-*。borderAfter是定义在 tailwindClasses.ts 中的复合工具类export const borderAfter tw:after:pointer-events-none tw:after:absolute tw:after:inset-0 tw:after:rounded-[inherit] tw:after:outline-1 tw:after:-outline-offset-1;它把边框绘制为一个绝对定位、继承圆角tw:after:rounded-[inherit]、outline-1-outline-offset-1的::after伪元素。规格文档特别强调Border is drawn on::after(viaborderAfter), nevertw:ring-*依据是 colors.md §2.3.1Tailwind 的ring-*实际编译为box-shadow而WebKit 不对 box-shadow 做像素对齐pixel-snap在 Safari 缩放时会出现边框变细甚至消失的问题。因此项目规则明确可见边缘禁止使用tw:ring-*占用布局空间用tw:border-*不占布局空间用tw:outline-*。圆圈自身的 outline 被保留给焦点环使用边框则完全交给::after。2. 选中圆点用透明度控制而非条件挂载。内部白色圆点始终渲染默认tw:opacity-0选中时切换为tw:opacity-100并带有tw:transition-inherit-all过渡——这让选中/取消选中有平滑的动画效果同时避免状态切换时 DOM 重建。Props / API 详解RadioGroupPropType / valuesPurposesizesm|md默认sm通过 Context 应用到组内每个按钮value/defaultValuestring当前选中值受控 / 非受控两种模式onChange(value: string) void选择变更回调isDisabled/isRequired/isInvalidbooleanreact-aria组级状态orientationhorizontal|vertical布局方向childrenReactNode组内的RadioButton列表需要说明的是规格文档中onChange回调签名是(value: string) void。从源码看RadioGroup直接透传 react-aria 的AriaRadioGroupProps而 react-aria 的onChange原生签名是(value: T) void——本项目约定泛型T为 string因此实际使用中 onChange 拿到的是被选中按钮的value字符串可直接用于状态更新或表单提交。RadioButtonPropType / valuesPurposevaluestring必填该选项被选中时对外抛出的值labelReactNode圆圈右侧的主文本hintReactNode主文本下方的次级说明文字sizesm|md会被组级 Context 的 size 覆盖isDisabledboolean仅禁用当前单个选项源码中RadioButtonProps extends AriaRadioProps且额外声明了size、label、hint与ref其中ref类型为RefHTMLLabelElement说明单个选项最终渲染为原生label元素点击整行含文本均可触发选中。sm与md两种尺寸的完整样式差异如下来自源码sizes映射维度smmd圆圈尺寸tw:size-416pxtw:size-520px圆圈与文本间距tw:gap-2tw:gap-3内点尺寸tw:size-1.5tw:size-2文本层级间距无tw:gap-0.5label / hint 字号tw:text-smtw:text-md圆圈与文本对齐tw:mt-0.5有文本时同左交互状态States规格文档定义了四种核心状态StateTreatmentDefault空圆圈tw:bg-primarytw:after:outline-primarycursor-pointerFocustw:outline-2 tw:outline-offset-2 tw:outline-focus-ring焦点环画在元素自身 outline 上Selectedtw:bg-brand-solidtw:after:outline-brand-solid内部白色圆点可见Disabledtw:border-disabledtw:bg-disabled_subtlecursor-not-allowed内点tw:bg-fg-disabled_subtle源码中状态的组合逻辑值得注意选中态类名带!isDisabled条件isSelected !isDisabled tw:bg-brand-solid ...而禁用态类名是独立追加的——当选中但禁用同时成立时视觉上以禁用态为准内点会使用tw:bg-fg-disabled_subtle灰化处理避免出现高亮但不可用的矛盾视觉。焦点环仅在键盘导航等场景下由 react-aria 的isFocusVisible驱动显示鼠标点击不会出现多余的外圈。实战代码示例基础用法受控/非受控规格文档给出的最小示例实现了可见性范围选择public / privateimport { RadioButton, RadioGroup } from openmetadata/ui-core-components; RadioGroup aria-label{t(label.visibility)} sizemd onChange{setScope} RadioButton label{t(label.public)} valuepublic / RadioButton label{t(label.private)} valueprivate / /RadioGroup;其中t(...)是 OpenMetadata 的 i18n 翻译函数aria-label用于为无可见标题的组提供无障碍名称。如果不传onChange而传defaultValue则进入非受控模式由组件内部维护选中值。Storybook 场景四件套仓库中的 RadioButtons.stories.tsx 提供了四个官方 Story 场景可直接作为开发参照1. Default基础单选—— 非受控模式 默认值RadioGroup labelSelect an option defaultValueoption1 RadioButton labelOption 1 valueoption1 / RadioButton labelOption 2 valueoption2 / RadioButton labelOption 3 valueoption3 / /RadioGroup2. Sizes尺寸对比—— 并排渲染两组sm/md验证 Context 尺寸下发RadioGroup defaultValuesm1 labelSmall sizesm {/* Option A / B / C */} /RadioGroup RadioGroup defaultValuemd1 labelMedium sizemd {/* Option A / B / C */} /RadioGroup3. WithHints带提示文本—— 适合套餐/定价类场景RadioGroup defaultValuepro labelPricing plan RadioButton hintUp to 5 users, 10 GB storage labelBasic valuebasic / RadioButton hintUp to 50 users, 100 GB storage labelPro valuepro / RadioButton hintUnlimited users, unlimited storage labelEnterprise valueenterprise / /RadioGroup4. WithDisabled单选项禁用RadioGroup defaultValueavailable labelOptions RadioButton labelAvailable valueavailable / RadioButton isDisabled labelDisabled option valuedisabled / RadioButton labelAnother option valueanother / /RadioGroup测试验证组件测试位于 radio-buttons.stories.test.tsx使用 Testing Library Vitest覆盖了以下关键契约Story 导出面约束RadioButtons故事模块只允许导出Default、Sizes、WithDisabled、WithHints四个命名 Story且明确断言不得存在已移除的StandaloneButtons独立按钮Story——这从测试层面强制了RadioButton必须放在RadioGroup内使用的组件契约默认 Story 渲染渲染后能查到 Option 1/2/3 文本Sizes Story页面中应存在恰好 2 个radiogroup角色getAllByRole(radiogroup)长度为 2且每组都有完整的 A/B/C 选项WithHintshint 文本如 Up to 5 users, 10 GB storage正常渲染WithDisabledgetByLabelText(Disabled option)断言被禁用toBeDisabled()。这套测试一方面守护了无障碍语义radiogrouprole、label 关联另一方面把禁用态必须真正禁用和禁止独立使用单选按钮固化为回归防线。交叉引用与延伸阅读Radio 组件与同属 Base / form 系列的其他选择控件共同构成完整体系官方规格文档给出如下交叉引用兄弟组件Checkbox多选· Toggle开关· Input文本输入样式基础Tailwind 规范 · Tailwind 工具类参考 · colors.md含 §2.3.1 边框规范如需在真实业务中查看 RadioGroup 的消费方式可以参考 tree-select-node.tsx 等应用层组件中的实际调用模式。掌握本文所述的结构、Token 与 API 约定后你便可以在 OpenMetadata 前端中稳定地使用 Radio 组件构建一致、可访问、符合设计规范的单选表单。【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考