Ark UI 组件示例创建实战:从 API 分析到跨框架 Storybook 与文档同步的完整工作流

原创2026-10-12 10:22:331,410 阅读
文章标签:前端UI组件设计系统

Ark UI 组件示例创建实战:从 API 分析到跨框架 Storybook 与文档同步的完整工作流

Ark UI 是一套基于 Zag.js 状态机构建的无样式(headless)、可访问组件库,同时覆盖 React、Solid、Svelte 与 Vue 四个框架。本指南以仓库内的 component-example-creator 智能体规范(.claude/agents/component-example-creator.md)为骨架,完整讲解为组件编写示例的标准流程:如何分析组件 API、设计示例集、在四个框架中平行实现、更新 Storybook stories、同步官网文档,以及如何做质量验证。读完本文,你将掌握一套可复制的、让组件示例“一次编写、四框架齐出、Storybook 与文档自动同步”的工程化方法。

角色定位:组件示例创建者的三项核心职责

在 Ark UI 的开发体系中,为组件产出示例并非简单的“写个 demo”,而是一套与仓库工程结构深度绑定的协作规范。根据组件示例创建者的职责定义,其核心工作有三块:

  1. 创建组件示例:开发实用、贴近真实场景的示例,覆盖组件的功能特性、变体与使用场景,且必须横跨全部四个受支持框架(React、Solid、Svelte、Vue)。
  2. 更新 Storybook Stories:维护并增强 *.stories.tsx / *.stories.ts / *.stories.vue 文件,保证示例组织有序、可访问,并体现最佳实践。
  3. 同步文档:让组件文档中的代码示例、stories 与文档保持三方一致。

触发这类工作的典型场景包括:用户为 Checkbox 请求不同状态的示例、为 Select 新增 clearable 属性后需要配套示例、Avatar 的 stories 缺少新的 size 变体等。本质上是"功能实现 → 示例演示 → 文档沉淀"链条中的示例环节。

创建示例的 7 步实现工作流

规范中有一条硬性约束:每个示例必须走完下述所有步骤,跳过任何一步都视为示例不完整。这也是把"示例"从临时 demo 升级为仓库一等公民的关键。

第 1 步:分析组件 API

先阅读组件的 TypeScript 类型、props 接口和现有实现,摸清所有可用特性与变体。以仓库中的 Checkbox 为例,其根组件基于 use-checkbox.ts 的 useCheckbox 钩子,通过 @zag-js/checkbox 的 machine 与 connect 串联状态机,props 会与 Field、CheckboxGroup 的上下文自动合并(mergeProps(checkboxGroup?.getItemProps(...) ?? {}, ownProps))。这意味着 API 分析不能只看组件表面,还要理解它依附的上下文:disabled、readOnly、invalid、required 等布尔属性会从 useFieldContext 自动透传。

第 2 步:审查现有示例

检查当前 stories 与文档,找出缺口、过时模式或缺失的使用场景。每个组件目录下有独立的 examples/ 文件夹,可直接对比四框架的文件清单是否对齐。例如 React Checkbox 目录 的 examples/ 下有 basic、controlled、default-checked、disabled、group、indeterminate、root-provider、with-field、with-form 等 17 个示例文件,Vue 与 Svelte 侧也有同名等价文件——审查示例就是在核对这类"跨框架示例平价"(仓库用 COMPONENT_EXAMPLES_DIFF.md 跟踪差异)。

第 3 步:设计示例集

一套合格的示例集应覆盖六类场景:

  • 基本用法(Basic):最简单的可用实现;
  • 常见变体(Variants):尺寸、颜色、状态;
  • 交互状态(Interactive):hover、focus、disabled、error;
  • 高级模式(Advanced):组合、受控与非受控;
  • 可访问性(Accessibility):键盘导航、ARIA 属性;
  • 边界情况与错误处理(Edge cases)。

第 4 步:跨框架实现

为所有受支持框架创建示例,保持 API 平价(API parity),并遵循 framework_patterns.md 中记录的框架专属模式(详见下文"跨框架实现要点")。

第 5 步:更新 Stories 文件(强制步骤)

这一步是强制的——不更新 stories 文件,示例就不会出现在 Storybook 中。四个框架的更新方式各不相同,规范给出了精确的落点:

  • React:packages/react/src/components/{component}/{component}.stories.tsx,按字母序追加一行导出;
  • Solid:packages/solid/src/components/{component}/{component}.stories.tsx,同样按字母序追加导出;
  • Vue:packages/vue/src/components/{component}/{component}.stories.ts,需同时添加 import 与 render 条目;
  • Svelte:packages/svelte/src/lib/components/{component}/{component}.stories.ts,需同时添加 import 与带 Component 包装的导出。

以仓库真实的 Checkbox 为例,React 与 Solid 的 stories 写法是单行导出(如 checkbox.stories.tsx 中的 export { Disabled } from './examples/disabled.tsx'),而 Svelte 侧(checkbox.stories.ts)则是先 import DisabledExample from './examples/disabled.svelte',再以 render: () => ({ Component: DisabledExample }) 的对象形式导出;Vue 侧(checkbox.stories.ts)同样采用 render: () => ({ components: { Component: XxxExample }, template: '<Component />' }) 的模式。

第 6 步:更新文档(如适用)

组件文档需要包含:与新示例一致的代码片段、具体的实现细节(哪些 props 挂在哪个子组件上、该调用哪些方法)、使用场景与收益、内置能力与组合能力的区分、双向状态同步说明、可访问性备注,以及相关组件的链接。

第 7 步:错误检查

规范特别注明:创建示例时不运行 typecheck 与 lint,保持流程连贯。这与"先产出、后统一验证"的节奏一致,最终的质量把关交给第 9 节的质量保证清单。

跨框架实现要点:四个框架各自该注意什么

在四框架平行实现时,framework_patterns.md 提供了详尽的差异对照,以下是最容易踩坑的几处。

受控状态管理的框架差异

以 Checkbox 的 controlled 示例为例,四框架的状态写法完全不同:

  • React:useState,onCheckedChange={(e) => setChecked(e.checked)},见 controlled.tsx;
  • Solid:createSignal,通过 value() 读取、setValue 更新;
  • Vue:ref + v-model:checked,见 controlled.vue;
  • Svelte:Svelte 5 runes,let checked = $state(false) + bind:checked,见 controlled.svelte。

图标导入约定

示例中若使用 Lucide 图标,必须遵守"带 Icon 后缀"的命名约定(XIcon、CheckIcon、ChevronDownIcon),并分别从 lucide-react、lucide-solid、lucide-vue-next、lucide-svelte 导入,不得用 as 重命名。

布尔属性的三态逻辑

Zag.js 的状态机依赖 undefined 来触发默认值。因此:

  • React/Solid 中写 disabled?: boolean(缺省即 undefined,正确),不要写 disabled?: boolean = false;
  • Vue 中使用 withDefaults(defineProps<Props>(), { disabled: undefined } satisfies BooleanDefaults<Props>);
  • Svelte 中写 let { disabled = $bindable(undefined) } = $props()。

emit 与回调的差异

Vue 走 emit 路线('update:checked' 用于 v-model、checkedChange 用于回调),其余框架统一用 onCheckedChange?: (details: ChangeDetails) => void 回调 props。

Solid 的 SSR 安全要求

在 Solid 组件中,必须使用 ark.<element> 工厂而非原生 HTML 元素(如 <ark.div> 而不是 <div>)。原因是原生元素会编译为 solid-js/web 的 template() / use() 调用,这些函数只存在于浏览器构建,在 Deno 等 SSR 环境会崩溃;ark 工厂内部使用 Dynamic,将元素创建推迟到渲染时,因此是 SSR 安全的。条件渲染也应使用 <Show> 组件而非布尔表达式。

asChild 模式:四个框架的四种形态

asChild 允许用自定义元素替换组件根元素而保留其功能与 props,四框架实现差异显著:

框架 Prop 名 类型 用法
React asChild boolean 布尔标志 + 包裹子元素
Solid asChild (props: PropsFn) => JSX.Element 渲染函数,需调用 itemProps() 展开 props
Vue as-child boolean 模板中 kebab-case
Svelte asChild Snippet<[PropsFn]> {#snippet asChild(itemProps)} 块

典型应用场景:把菜单项渲染成链接、自定义触发器、与路由库(Next.js Link、Vue Router、SvelteKit Link)组合。

示例结构模板:从 Basic 到 AdvancedComposition

规范给出每个示例应遵循的结构骨架:

// 清晰、描述性的命名
export const BasicUsage = () => (
  // 最小化、聚焦的实现
  // 每个示例只清晰演示一个概念
)

export const WithVariants = () => (
  // 并排展示不同变体,便于用户对比
)

export const InteractiveStates = () => (
  // 演示状态变化与组件行为
)

export const AdvancedComposition = () => (
  // 组合多特性的真实复杂场景
)

仓库中的实际命名遵循 kebab-case 语义:basic、controlled、disabled、default-checked、indeterminate、root-provider、with-field、with-form 等,与文档中 <Example id="..." /> 的 id 一一对应。

代码质量标准

  • TypeScript First:所有示例必须完整类型标注,禁止 any;
  • 无注释:遵循仓库"代码自文档化"的极简注释风格;
  • 绝对导入:一律从框架包导入(如 @ark-ui/react/checkbox、@ark-ui/solid/checkbox);
  • 可访问性:每个示例都须符合 ARIA 规范;
  • 一致性:四框架 API 保持一致;
  • 真实世界模式:示例要贴近真实用法,而非刻意构造的场景。

Root Provider 模式:示例中最关键的高级用法

root-provider 示例几乎是每个组件的标配,它演示的是外部状态管理:调用 useComponent(props) 拿到组件 API/store,再把它传给 Component.RootProvider,从而在任何位置直接访问状态与方法(如 .setChecked()、.checked)。

规范强调两种使用方式二选一、绝不混用:

  • 方式一(声明式):props 直接传给 Component.Root,组件自管内部状态,对状态与方法访问有限;
  • 方式二(编程式):useCheckbox() + Checkbox.RootProvider value={checkbox},获得完整的状态与方法访问能力,适合编程式控制与复杂交互。

仓库中 React 的 root-provider 示例 展示了完整形态:先 const checkbox = useCheckbox(),再以 checkbox.checked 控制按钮文案、checkbox.setChecked(true/false) 实现外部勾选/取消;Solid 侧(root-provider.tsx)由于响应式特性改为 checkbox() 函数调用;Vue 侧(root-provider.vue)用 :value="checkbox" 绑定并用 v-if / v-else 切换按钮。四框架演示了完全相同的交互,验证了 API 平价原则。

文档同步:让示例进入官网与 MDX

示例的最终归宿是文档网站。根据 documentation-patterns.md 与 component_patterns.md,组件文档位于 website/src/content/pages/components/,通过 <Example> 组件引用示例:

### Basic Usage

<Example id="basic" />

<!-- 跨组件引用 -->
<Example component="checkbox" id="with-field" />

<!-- 指定框架 -->
<Example id="controlled" framework="react" />

文档正文还需写清:具体实现细节(props 挂载位置、应调用的方法)、使用场景与收益、内置 vs 组合的边界、双向状态同步说明、可访问性备注,以及相关组件链接。一个规范性页面通常按"前置元数据 → <ComponentPreview> → Anatomy → Examples(由简到繁)→ Guides → API Reference(<ComponentTypes> / <ContextType>)→ Accessibility"组织。

质量保证:六项交付前检查

示例交付前必须通过以下 QA 清单:

  1. 所有示例无 TypeScript 编译错误;
  2. 示例在四个框架中均能正常工作;
  3. 用键盘导航实测可访问性(Tab、方向键、Enter/Space 激活);
  4. 确认 stories 在 Storybook 中正确渲染;
  5. 文档示例与 story 实现一致;
  6. 示例遵循既有代码库模式。

配合仓库测试实践(见 component_patterns.md 的 Testing Guidelines),可用 vitest-axe 做无障碍断言(expect(results).toHaveNoViolations()),用 fireEvent.keyDown 验证键盘导航与焦点管理,用 userEvent 验证受控/非受控行为。运行 bun run test 或 bun run typecheck(各包均支持 dev / build / test / typecheck / lint 命令,见 development.md)做整体验证。

何时寻求澄清与最终输出格式

遇到以下情况应主动向用户确认,而非自行猜测:组件 API 含糊或文档缺失、某个特性的预期用途不明、现有示例中存在冲突模式、可访问性要求不清、框架特定实现细节缺失。

最终交付物按固定结构输出:

  1. Summary:所创建示例的概览;
  2. Files Modified:变更文件清单及说明;
  3. Example Code:每个框架完整可运行的示例代码;
  4. Story Updates:*.stories.* 文件的改动;
  5. Documentation Updates:docs/markdown 文件的改动;
  6. Testing Notes:如何验证示例正确工作。

一句话收尾:这套流程的最终目标是让每个组件示例做到"生产级、组织良好、可读可引用",成为使用该组件最权威的参考——这正是 Ark UI 四框架一致体验得以长期维持的工程保障。

登录后查看全文
ark