Astryx NavIcon 组件契约解析:圆形导航图标的职责边界、主题化目标与兼容性设计
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
导读
NavIcon 是 Astryx 设计系统中一个极简但职责明确的组件:它把调用方传入的图标内容放进一个圆形、accent 背景的容器中,用于 TopNav、PageNav 等导航头部作为视觉锚点。本文基于 NavIcon.spec.md 这份组件契约文档,结合 NavIcon.tsx、NavIcon.test.tsx 与 NavIcon.doc.mjs 的源码实现,完整解读其职责边界、行为与布局契约(FR1/FR2)、主题化解剖(theming anatomy)、navicon废弃别名机制,以及对应的验证体系。读完本文,你将能准确判断 NavIcon 的"拥有/不拥有"边界、理解nav-icon与navicon双主题目标的来龙去脉,并学会用其验证映射在仓库中做回归检查。
组件定位:一个纯展示型的圆形图标容器
Intent:契约文档记录了什么
NavIcon 的职责非常收敛:渲染调用方提供的图标内容(caller-supplied icon content),并把它放进一个圆形导航容器中。这份位于packages/core/src/NavIcon/NavIcon.spec.md的契约文档当前为draft状态,其目的并非引入新行为,而是:
记录当前的消费者解剖结构(consumer anatomy)与主题化归属(theming ownership),不改变运行时行为或目标兼容性。
也就是说,这是一份"事实记录型"契约——它把组件当前已经存在的结构、样式目标与兼容约束用可验证的条款固化下来,供主题作者、维护者和自动化工具共同引用。
从源码看,NavIcon 的实现极简,完整的渲染逻辑只有一层:
export function NavIcon({ icon, xstyle, className, style, ref, ...props }: NavIconProps) { return ( <span ref={ref} {...mergeProps( themeProps('nav-icon', undefined, { legacyNames: ['navicon'], }), stylex.props(styles.base, xstyle), className, style, )} {...props}> {icon} </span> ); }见 NavIcon.tsx。根元素是一个<span>,唯一的必填 prop 是icon: ReactNode,类型定义NavIconProps extends BaseProps<HTMLSpanElement>说明它继承基础的 HTML 属性透传能力(data-testid、style等),并由index.ts统一对外导出组件与类型(见 index.ts)。
圆形容器的样式实现
styles.base定义了"圆形容器"的全部视觉特征(见 NavIcon.tsx):
| 样式属性 | 取值 | 作用 |
|---|---|---|
display | flex | 让图标内容水平垂直居中 |
align-items/justify-content | center | 内容居中 |
border-radius | 50% | 圆形外观 |
background-color | --color-accent | accent 主题色背景 |
color | --color-on-accent | 前景对比色 |
flex-shrink | 0 | 在导航头部布局中不被压缩 |
width/height | --size-element-md | 固定的中号元素尺寸 |
这些值全部来自主题 token(colorVars、sizeVars),意味着容器的圆、色、尺寸都由主题驱动,而不是硬编码——这正是后文"主题化解剖"契约能成立的前提。
职责边界:拥有什么,不拥有什么
契约文档用明确的"Owns / Does not own"划分了 NavIcon 的边界:
拥有(Owns)
- 圆形容器本身及其当前的
nav-icon主题化目标(theming target)。
不拥有(Does not own / non-goals)
- 通过
icon传入的图标图形或组件——属于调用方(caller); - 交互语义——NavIcon 是纯展示容器(display-only container),不是按钮。
这一边界的实际含义:任何人替换图标内容都不构成对 NavIcon 解剖结构的修改。契约中的"Allowed variation"一节明确指出:"调用方提供的图标图形与实现可以自由变化,而不成为 NavIcon 拥有的解剖结构";"Representative states"则强调:对任何调用方图标,文档化的解剖结构都是相同的。
对应到使用文档 NavIcon.doc.mjs 的 bestPractices,这条边界也被翻译成了三条使用建议:
- ✅ 在导航头部使用 NavIcon,为所在区块提供可识别的视觉锚点;
- ✅ 传入
Icon或尺寸相近的图标组件以保证比例协调; - ❌ 不要为交互目的使用 NavIcon——它是展示容器,不是按钮。
兼容性与迁移:一份"纯文档"级别的变更
契约文档给出了明确的兼容性声明:
- Released default preserved:
yes - 兼容性类别:仅追加文档(additive documentation only);运行时、DOM、样式、目标(targets)、别名(aliases)与公共 API 均保持不变
- 受控/非受控行为:不适用
- 迁移决策:无
这意味着这份 draft 契约不引入任何面向消费者的迁移动作,消费者迁移说明应归属于消费者文档与 release notes,而不是组件契约本身。
从源码可以验证这一承诺:组件没有新增 props、没有改动 DOM 结构、也没有调整样式目标。navicon这个历史遗留的"连写"别名被显式保留,仅作为兼容元数据存在:
themeProps('nav-icon', undefined, { // `navicon` ran the compound name together; keep it emitted so // existing themes continue to work. legacyNames: ['navicon'], });行为与布局契约:FR1 / FR2 两个候选不变量
契约文档将当前行为固化为一组可验证的"候选不变量"(candidate invariants):
| ID | 候选不变量 | 依据 | 草稿评审状态 |
|---|---|---|---|
| FR1 | 当前渲染包含一个圆形容器与调用方提供的图标内容 | 当前源码、文档与测试 | 已验证为当前行为;未决定新行为 |
| FR2 | 容器携带nav-icon;废弃的navicon别名保留为兼容元数据 | 当前源码、文档与测试 | 已验证为当前行为;目标不变 |
在转换与优先级规则(Transformation and precedence order)上,契约明确"不引入新的排序规则";在性能与资源(Performance and resources)上同样"不引入新的性能或资源规则"。这两条的意义在于收缩变更面:FR1/FR2 之外的一切行为,都不在本契约的管辖范围内。
源码层面的 FR1 验证
FR1 的圆形容器 + 图标内容,可以在实现中找到直接对应:
- 圆形容器:
styles.base中的borderRadius: '50%'与--color-accent背景; - 图标内容:
{icon}被渲染在<span>根元素内部,且icon是NavIconProps中required: true的必填项(见 NavIcon.doc.mjs 的 props 表)。
源码层面的 FR2 验证
FR2 的双主题目标由themeProps工具函数实现。查看 themeProps.ts 的实现:
export function themeProps( component: string, props?: ClassProps, options?: ThemePropsOptions, ): ThemeProps { const className = buildClassName(component, props); const legacy = options?.legacyNames?.map(name => stableClassName(name)) ?? []; return { className: legacy.length > 0 ? [className, ...legacy].join(' ') : className, ...themeDataAttributes(props), }; }调用themeProps('nav-icon', undefined, {legacyNames: ['navicon']})会同时产出astryx-nav-icon(规范目标)与astryx-navicon(遗留别名)两个类名。astryx-前缀来自集中式命名模块 naming.ts,其中NAMESPACE = 'astryx',确保整个系统的命名空间只有一个来源。
测试侧同样验证了这一点(见 NavIcon.test.tsx):
describe('NavIcon theme target names', () => { it('renders the deprecated class beside the current one', () => { render(<NavIcon icon={<span>Icon</span>}>{ "Container": {"target": "nav-icon"}, "Icon": {"inherits": "nav-icon"} }含义拆解:
- Container是主题目标
nav-icon的直接持有者——主题通过.astryx-nav-icon选择器修改圆形容器的外观; - Icon不拥有独立目标,而是
inherits: "nav-icon",即继承容器的主题化行为——这再次呼应"图标归调用方所有"的边界:NavIcon 不为图标声明独立的样式钩子。
这份解剖结构在 NavIcon.doc.mjs 的anatomy数组中也有对应定义:
const anatomy = [ { name: 'Container', required: true, description: 'Circular painted container for the supplied icon.', }, { name: 'Icon', required: true, description: 'Caller-supplied visual content rendered inside the container.', }, ];同时,文档的主题目标注册表明确给出了两个类的废弃关系:
theming: { targets: [ {className: 'astryx-nav-icon'}, // Retained beside the canonical names for backwards compatibility. // New themes use the canonical targets above. {className: 'astryx-navicon', deprecatedFor: 'nav-icon'}, ], },deprecatedFor: 'nav-icon'是给发现与诊断工具用的替换指引:新主题一律使用规范目标nav-icon,navicon仅用于让既有主题继续工作。这与 themeProps.ts 中ThemePropsOptions.legacyNames的文档约定一致——"主题目标是公共 API,重命名会静默破坏所有引用它的主题;除非有明确的退役决策,否则保持别名继续输出"。
设计关系与层级角色
契约文档的设计关系表将两个解剖部位映射到设计需求与契约条款:
| 解剖部位或状态 | 设计需求 | 表征权威 | 层级角色 | 组件契约 |
|---|---|---|---|---|
| Container | 呈现当前圆形涂色表面 | 当前源码与公共文档 | 支撑(Supporting) | FR1、FR2 |
| Icon | 呈现调用方提供的视觉内容 | 调用方提供的内容 | 依赖上下文(Context-dependent) | FR1 |
要点:
- Container 的"表征权威"是当前源码与公共文档——它以事实为准,不引入新的设计权威;
- Icon 的"表征权威"是调用方提供的内容——设计上不承诺任何具体图形;
- Container 承担支撑性层级角色,同时支撑 FR1(存在性)与 FR2(主题目标);Icon 仅参与 FR1。
设计关系层面,契约还引用了两个共享拥有者(shared owners)而非复述其规则:component-theming-surface(组件主题化表面)与 public-component-api(公共组件 API)。NavIcon 的主题行为服从这两个架构文档定义的全局规则,这解释了为什么契约本体只需要记录nav-icon一个目标即可。
验证映射:如何证明契约不变量成立
契约文档的验证映射表给出了每个契约条款的验证手段、代表状态、失败预期与审计位置:
| 契约 | 验证方式 | 代表状态 | 变更或失败预期 | 审计章节 |
|---|---|---|---|---|
| FR1 | NavIcon.test.tsx内容套件 | 调用方提供的图标 | 移除容器或所提供内容会破坏现有的渲染与 ref 断言 | audit:NavIcon/anatomy |
| FR2 | NavIcon.test.tsx目标名套件 | 当前与废弃目标类 | 移除任一输出的兼容类会破坏现有目标断言 | audit:NavIcon/theming |
| 主题化解剖映射 | scripts/check-knowledge.mjs | 规范解剖与当前目标 | 缺失、多余、带前缀、过期或由别名支撑的映射会导致仓库校验失败 | audit:NavIcon/theming |
这三行映射分别对应三条验证通道:
内容套件(FR1):NavIcon.test.tsx 中的三个测试——渲染图标内容、正确转发 ref(断言收到
HTMLSpanElement)、透传data-testid。任何一个断言失败都意味着解剖结构被破坏。目标名套件(FR2):
renders the deprecated class beside the current one测试同时断言astryx-nav-icon与astryx-navicon,任何一类缺失都会失败。仓库级校验:主题化解剖映射(Container →
nav-icon,Icon → inheritsnav-icon)由 scripts/check-knowledge.mjs 做全仓库校验——"缺失、多余、带前缀、过期或由别名支撑的映射"都会让校验失败。这意味着主题解剖 JSON 不是文档摆设,而是被持续机器验证的知识契约。
实战使用:从模板块到导航头部集成
基本用法
NavIcon 的典型消费方式是把一个图标组件作为icon传入。CLI 模板块给出了开箱即用的示例(见 NavIconShowcase.tsx):
import {NavIcon} from '@astryxdesign/core/NavIcon'; import {Icon} from '@astryxdesign/core/Icon'; import {HStack} from '@astryxdesign/core/Layout'; export default function NavIconShowcase() { return ( <HStack gap={4} vAlign="center"> <NavIcon icon={<Icon icon="search" />} /> <NavIcon icon={<Icon icon="calendar" />} /> <NavIcon icon={<Icon icon="wrench" />} /> </HStack> ); }从源码 JSDoc(见 NavIcon.tsx)可以看到两个正式推荐的使用场景:
<TopNavHeading heading="Dashboard" logo={<NavIcon icon={<HomeIcon style={{width: 16, height: 16}} />} />} /> <PageNavHeader icon={<NavIcon icon={<HomeIcon style={{width: 16, height: 16}} />} />} heading="My App" />与导航头部组件的集成
NavIcon 是 TopNavHeading.tsx 中logoprop 的推荐取值之一——该 prop 的文档描述为"标题文本前显示的标志元素,可以是图片、NavIcon 或任意 ReactNode"(见 TopNavHeading.doc.mjs)。这意味着 NavIcon 的输出(一个带astryx-nav-icon类的圆形<span>)可以安全地嵌套进导航头部布局,flexShrink: 0保证了它在大标题或超长导航项前不会被压缩变形。
关键注意事项
icon是必填prop(required: true);- 组件支持
xstyle(StyleX 扩展样式)、className、style与任意 HTML span 属性透传,ref转发到根元素; - 不要把交互语义(onClick、键盘操作等)寄托在 NavIcon 上——它是纯展示容器,交互应放在外层按钮或链接中;
- 若你的主题仍在使用
astryx-navicon,它可以继续工作,但新主题请迁移到astryx-nav-icon。
边界与约束:契约不做什么
契约文档最后用 "Content boundary" 明确了自身的克制范围:
- 本文件不重复消费者 prop 表、示例、实现步骤或系统规则,而是链接到它们的拥有者(NavIcon.doc.mjs、架构文档等);
- 决策日志(Decision log)为空:本契约只记录当前事实,不引入组件局部的设计决策;
- 开放问题(Open questions)为空。
这种"记录事实、不制造决策"的姿态正是 Astryx 知识契约体系的设计取向:组件契约(.spec.md)与使用文档(.doc.mjs)分工明确,前者管行为不变量与验证,后者管 props、示例与最佳实践,两者再通过scripts/check-knowledge.mjs这样的校验脚本在仓库层面保持同步。对主题作者与维护者而言,这意味着:只要astryx-nav-icon目标与 FR1/FR2 不变量不被破坏,NavIcon 的变更就始终落在契约允许的范围内。
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考