--- name: native-component-doc description: 为 @skyroc/native-ui 组件编写文档。当用户说出 native 组件名(如 Button、Cell、ActionSheet)并要求写/补文档时触发。自动定位组件源码、playground demo、已有文档,按 native-ui-docs 的规范生成或更新 MDX。 argument-hint: 组件名,如 Button、Cell、ActionSheet --- # Native UI 组件文档工作流 为 `@skyroc/native-ui`(React Native + Uniwind)编写文档的专用工作流。 > 这不是 web-ui 那套。native 的 `` 会把 demo 源码整份渲染出来、右栏还有整页手机预览, > 因此**不写内联代码块**、**demo 一律具名导出**、**每个 demo 都要在汇总页里串一遍**。 > 与 `component-doc`(web-ui 用)的差异见文末「与 web-ui 文档规范的差异」。 ## 文件定位规则 给定组件名 `$COMPONENT`(如 `ActionSheet`),转 kebab-case `$slug`(如 `action-sheet`): | 用途 | 路径 | | ------------------- | -------------------------------------------------------------- | | 组件源码 | `packages/native/ui/src/components/$slug/` | | Playground 单点 demo | `apps/native-ui-playground/src/demos/$slug/*.tsx` | | Playground 汇总页 | `apps/native-ui-playground/src/demos/$slug/index.tsx` | | Playground 路由页 | `apps/native-ui-playground/app/components/$slug.tsx` | | 文档 MDX | `docs/native-ui-docs/content/docs/components/($group)/$slug.mdx` | **文档基础设施**(了解即可,不必每次都读): | 文件 | 作用 | | ----------------------------------------------- | -------------------------------------------------------------------- | | `docs/native-ui-docs/components/mdx.tsx` | MDX 组件注册,只有 `Demo` / `PropsTable` / `TypeTable` / `UnionType` | | `docs/native-ui-docs/components/demo/index.tsx` | ``:读 demo 源码 + 渲染预览 + 「在 playground 打开」 | | `docs/native-ui-docs/components/demo/demo-preview.tsx` | 动态 import demos 目录,**按模块名取具名导出** | | `docs/native-ui-docs/components/props-table.tsx` | `` API 属性表 | | `docs/native-ui-docs/components/type-table.tsx` | `` + `` 类型区 | | `docs/native-ui-docs/components/type-anchor.tsx` | PascalCase 类型 → 锚点链接,`BUILTIN_TYPE_NAMES` 白名单 | | `docs/native-ui-docs/components/type-registry.ts`| 跨页面类型链接注册表 | | `docs/native-ui-docs/lib/playground-demo.ts` | 文档页 slug → playground 整页路由,决定是否分栏 | ## 侧边栏分组 `content/docs/components/` 下按 fumadocs 路由组分组,路由组不进 URL(`(general)/button.mdx` → `/docs/components/button`)。 写完文档后**必须**把 `$slug` 加进对应分组的 `meta.json` 的 `pages` 里(meta.json 已预置全部规划中的 slug,通常只需确认位置正确)。 | 目录 | 标题 | 组件 | | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `(general)` | 通用 | button, floating-button, text, text-ellipsis, divider, image | | `(layout)` | 布局容器 | space, grid, cell, collapse | | `(input)` | 输入 | form, field, input, password-input, search, checkbox, radio, switch, slider, stepper, rate, signature, picker, picker-group, date-picker, time-picker, calendar, number-keyboard, tree-select | | `(data-display)` | 数据展示 | avatar, badge, tag, count-down, rolling-text, swipe-cell | | `(navigation)` | 导航 | navbar, tabs, sidebar, anchor-nav, index-bar, back-top, pagination, dropdown-menu | | `(overlay)` | 弹层覆盖 | popup, dialog, sheet, action-sheet, share-sheet | | `(feedback)` | 反馈 | toast, notify | `meta.json` 里列了但文件还不存在的 slug 会被 fumadocs 静默跳过(`resolveFolderItem` 找不到 node 直接 return),所以预置清单不会报错。 `meta.json` 里**不要写 `"collapsible": false`**。fumadocs-ui 16.14.4 的 `SidebarFolderTrigger` 在 `collapsible: false` 分支直接渲染成 `jsx("div", { ...props })`,而上层 (`layouts/docs/slots/sidebar.js:220`)传下来的 `className` 是个 `(state) => string` 函数 —— 函数原样落到 DOM 上,React 报 `Invalid value for prop \`className\` on
tag`,分组标题也因此丢掉全部样式类。 `collapsible` 走默认的 `true` 时,trigger 渲染成 Base UI 的 `Collapsible.Trigger`,函数 className 会被 `useRenderElement` 正常解析。`defaultOpen: true` 已经能让分组默认展开,不需要 `collapsible: false`。 ## 执行流程 ### Phase 1:读源码与 demo,做一致性校验 1. 读 `packages/native/ui/src/components/$slug/` 全部文件 - `index.ts`:实际导出了哪些组件、哪些类型 - `types.ts`:public props 与类型别名 - `*-variants.ts`:`slots` / `variants` / `compoundVariants` / `defaultVariants` —— **表格里的每一行数值都从这里抄,不要凭印象写** - 主组件 `*.tsx`:props 实际怎么解构、默认值在哪、有没有 `hitSlop` / `accessibilityState` / `TextClassContext` / Portal 之类的 RN 特有行为 2. 读 `apps/native-ui-playground/src/demos/$slug/` 全部 demo + `index.tsx` 汇总页 - 汇总页的 `
` 就是**天然的章节大纲**,文档章节直接对齐它 3. 已有文档就读一遍,判断是补全还是重写 4. 参考范例:`docs/native-ui-docs/content/docs/components/(general)/button.mdx`(当前唯一的完整样板) #### 实现一致性校验(必须执行) 文档不是 API 想象稿,从**源码 / 类型 / variants / demo** 四者交叉验证: - `types.ts` 声明的 props,主组件是否真的解构并使用 - 默认值实际在哪里设置(`defaultVariants` vs 组件内 `= false`),两处不一致时以运行时为准 - `classNames` 的每个 slot 是否真的接到了对应节点上 - `index.ts` 是否真的导出了你要写进文档的类型 - RN 特有:`hitSlop`、`accessibilityRole` / `accessibilityState`、`Pressable` 透传、`TextClassContext` 继承范围、Portal / Modal 挂载点 发现类型/API 承诺与实现不一致时:先明确指出这是实现问题;能改就改实现或类型,不要在文档里绕过去;不能改就在最终说明里列为风险,并且**不在文档中承诺未实现的能力**。不允许靠降低文档表述来掩盖实现 bug。 ### Phase 2:写文档 #### MDX 结构 ```mdx --- title: $COMPONENT description: 一句话描述组件用途 --- 概述段落:组件做什么、基于什么 RN 原语封装、有什么与众不同的行为。 \`\`\`tsx import { $COMPONENT } from '@skyroc/native-ui'; \`\`\` ## 基础用法 ## 何时使用 - 使用场景 / 移动端取舍 - 与相似组件的区分(如 Popup vs Sheet vs ActionSheet) ## 功能章节(variant / color / size / shape / ...) 说明文字 +(枚举类的)表格 补充说明:容易踩的坑、和 web 端行为不同的地方 ## 无障碍 role / accessibilityState / 热区 等 RN 专属说明(有就写) ## API ### $COMPONENT ## 类型 \`\`\`ts import type { ... } from '@skyroc/native-ui'; \`\`\` ``` #### Demo 引用规则(native 专属) - 格式:``,**没有 `modules/` 这一层**(那是 web-ui 的) - **不要在 `` 后面贴内联代码块**。`` 已经把整份 demo 源码渲染在预览下方了,内联代码是重复噪音 - **覆盖率要求:`src/demos/$slug/index.tsx` 里串的每个子 demo,文档必须有且仅有一处 `` 引用**。章节顺序也尽量对齐汇总页 - demo 缺失时**必须补 demo**,不能因为没 demo 就只写文字 写完用这条命令核对覆盖率(无输出即一一对应): ```bash slug=button; group='(general)' diff \ <(ls apps/native-ui-playground/src/demos/$slug | grep -v '^index' | sed 's/\.tsx$//' | sort) \ <(grep -o "@playground/$slug/[A-Za-z0-9]*" "docs/native-ui-docs/content/docs/components/$group/$slug.mdx" | sed 's|.*/||' | sort -u) ``` #### 新建 Playground Demo 的规范 1. 路径:`apps/native-ui-playground/src/demos/$slug/$DemoName.tsx`,`$DemoName` 以组件名开头(`ButtonLoading`、`CellSize`) 2. **必须具名导出,且导出名 === 文件名** —— `demo-preview.tsx` 是按模块名去取 `mod[exportName]` 的,写成 `export default` 或改名都会拿不到组件 3. 模板: ```tsx import { Button } from '@skyroc/native-ui'; import { View } from 'react-native'; const ButtonLoading = () => { return ( {/* ... */} ); }; export { ButtonLoading }; ``` 4. 约束: - 不写 `'use client'`(RN,不是 Next 客户端组件) - 从 `@skyroc/native-ui` 导入组件,不从内部路径导入 - 容器统一 `View` + uniwind 类名,带上 `bg-background p-4`,保证在文档预览的手机框里边距一致 - 每个 demo 只聚焦一个功能点,自包含,不依赖同目录 `shared.tsx` - 需要文字就用 `@skyroc/native-ui` 的 `Text`,才能继承 `TextClassContext` 5. **同步汇总页**:在 `src/demos/$slug/index.tsx` 里 import 并加一个 `
`。汇总页只负责串场,不要把示例代码写回去 6. **确认整页路由存在**:`apps/native-ui-playground/app/components/$slug.tsx`。它是文档右栏分栏预览的来源(`resolvePlaygroundPage` 按最后一段 slug 找同名文件),缺了文档就退回单栏 #### PropsTable 书写规则 - 短字面量联合**直接内联**到 `type` 字段,不要另起 PascalCase 类型名,也不要写 `` - 例:`"'solid' | 'tonal' | 'outline' | 'ghost'"`、`"'sm' | 'md' | 'lg' | 'icon'"`、`"'horizontal' | 'vertical'"` - 对象类型、slot 配置、子组件 Props、外部大型类型才用 PascalCase 引用 - `default` 用字符串写:`"'md'"`、`'false'` - `required: true` 仅必填时加 - 表格开头或结尾说明透传关系:如「除下表外,`Button` 透传 `Pressable` 的全部属性」 - `ref` 也写进表里(RN 的 ref 常用于 `measure` / 滚动定位) #### 类型完整性规则(严格遵循) **PropsTable / TypeTable 中出现的每一个非内置 PascalCase 类型名,都必须在当前页 `## 类型` 区域有定义,或在 `type-registry.ts` 注册跨页链接。** 写完逐项核对: 1. 收集所有 `` 的 `type`、所有 `` 的 `fields[].type` 里的 PascalCase 词 2. 排除 `type-anchor.tsx` 的 `BUILTIN_TYPE_NAMES`(含 RN 原语:`PressableProps` / `ViewStyle` / `TextStyle` / `StyleProp` / `GestureResponderEvent` / `AccessibilityRole` / `View` / `Ref` 等) 3. 剩下的每一个都要满足:当前页 `` 定义 ∨ 当前页 `` 定义 ∨ `type-registry.ts` 注册 4. 遗漏的如果只是短联合 → 改成内联,不补 `` 5. 用到了 PascalCase 但它是 React / RN / TS 内置或三方类型 → 加进 `BUILTIN_TYPE_NAMES`,否则会生成错误的本页锚点 6. 跨页引用格式:`TypeName: '/docs/components/target-slug#anchor'`(注意 `docsRoute = '/docs'`,别漏 `/docs` 前缀) #### 类型区写法 - 先给一段 `import type { ... } from '@skyroc/native-ui'` 代码块,列出本组件对外导出的类型 - ``:联合类型(由 variants 推导出来的 `XxxVariant` / `XxxSize` / `XxxSlots` 等),一句话说明它控制什么 - ``:对象类型(`SlotClassNames`、子组件 Props、Option / ItemData 之类) - 顺序建议:`` 在前(跟 Props 表顺序对应),`` 在后 ### Phase 3:收尾核对 1. 覆盖率命令跑一遍,`` 与 demos 目录一一对应 2. 分组 `meta.json` 的 `pages` 含 `$slug` 3. 类型完整性清单逐条过 4. 表格里的数值(高度、字号、间距、圆角)与 `*-variants.ts` 逐条对得上 5. 最终回复里区分:本次改了哪些文件、发现但未处理的实现问题、未验证的部分及原因 ## 工作区边界 - 默认只动:目标 `$slug.mdx`、缺失的 playground demo 及其汇总页、分组 `meta.json` - 不回滚、不格式化、不整理与本组件无关的用户改动 - 组件源码有未提交改动时,基于当前工作区实现写文档 - 只有实现问题导致文档无法正确描述组件时,才改组件源码或类型,且改之前先说明问题 ## 与 web-ui 文档规范的差异 | 维度 | web-ui (`component-doc`) | native-ui(本 skill) | | ------------ | -------------------------------------------- | -------------------------------------------------- | | Demo 路径 | `@playground/$slug/modules/Name` | `@playground/$slug/Name`(无 `modules`) | | Demo 导出 | `export default` | **具名导出,名字 === 文件名** | | 内联代码块 | 推荐,跟在 `` 后 | **不写**,`` 已渲染完整源码 | | Demo 运行时 | react-live 沙箱 + `scope.ts` | Turbopack 动态 import 真实 RN 组件,`ssr: false` | | 整页预览 | 无 | 右栏手机框渲染 `app/components/$slug.tsx` 整页 | | `'use client'` | 需要 | 不需要 | | 内置类型白名单 | DOM / React 为主 | 额外含 RN 原语(`PressableProps` / `ViewStyle` 等) |