mcpField
S
sb-stories

sb-stories

github.com/strongeron开发3981 次安装4 周前更新
代码仓库

关于 sb-stories

sb-stories 是一项指导 Storybook 组件与页面故事编写的技能,面向维护组件目录的开发者。它强调依据真实使用情况和状态编写故事,并通过验证脚本检查结果。

主要功能

  1. 01按实际状态编写组件故事
  2. 02依据组件使用数据排序变体
  3. 03导入真实页面并模拟数据
  4. 04规范 Controls、覆盖层与交互
  5. 05运行脚本验证故事

适用场景

  1. 01为现有组件补充 Storybook 状态故事
  2. 02用真实页面和不同数据状态构建页面故事
  3. 03批量编写并验证组件故事

MCP 相关

根据项目是否配置 MCP,选择相应的编写参考;WITH_MCP 流程由 MCP 注入 CSF3 约定。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/strongeron/storybook-workbench --skill sb-stories

安装后在 Claude Code 中输入/sb-stories确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CONTEXT.md
agents/
openai.yaml
references/
anti-patterns.md
composition-patterns.md
directory-structure.md
extraction-workflow.md
factory-patterns.md
storybook-surface.md
test-wiring.md
validate-workflow.md
with-mcp.md
without-mcp.md
scripts/
check-story-ready.sh
discover-runtime.py
extract-prop-shapes.sh
extract-states.sh
page-patterns.py
scaffold-factory.sh
scaffold-page-story.py
validate-stories.sh
templates/
controlled-component-story.tsx

详细介绍

sb-stories——单个组件及其真实状态

默认的 Build 模式。组件位于 src/components/ 中;你需要为其编写可见状态的 story。

开始编写前(先问问自己)

  • 它真的在使用吗? 检查 .storybook/project-inventory.json 中的 components.real[]。如果它在 dead[] 中,就移除它——不要编写故事。如果它属于 vendor(shadcn ui/),则降低优先级。
  • 哪些状态会显著改变行为或外观? 避免笛卡尔积组合。references/anti-patterns.md 中列出了每种基础组件的最低状态表(Button 8 个、Input 8 个、Modal 5 个、Form 6 个)。请读取 .storybook/component-states.json,不要猜测——如果文件不存在,请先生成: scripts/extract-states.sh。(更完整的提取上下文见:references/extraction-workflow.md。) 要在同一个画布上渲染所有这些状态,请使用 sb-wrappers 中的 StateGrid 包装器(variants × states → StateMatrix)——不要手动搭建网格。这些是组件阶段的包装器;数据包装器(ProjectInventory / DesignSystemHealth / AppFlowGraph …)应在各自步骤完成后再处理。
  • 这个应用实际提供了哪些状态? 读取 .storybook/component-usage.json(由 sb-inventory 的 extract-component-usage.sh 生成——如果文件不存在,请生成)。使用 props.<prop>.values 优先展示真实调用处传入的变体;对于 declaredButUnused 中的内容(例如从未使用的 variant=danger),仍需为完整性编写对应状态,但要添加 ['usage:unused'] 标签,并注明“此应用中未使用”——不要用生产环境从未渲染的状态充斥目录。
  • 无需手动编写每个组件的文档——它们会统一组合生成。 每个组件的 autodocs 页面都会自动获得一个**“此应用中的真实用法”**区域:sb-setup 只需将 UsageSection 区块接入 preview.ts 的 docs.page 一次,它就会为每个组件渲染一张“使用位置”图,展示组件出现在哪些页面、嵌套在什么内容中、渲染了什么、使用了哪些令牌——这些信息来自用法图谱(component-pages.json)。因此,添加故事时,不要手动添加用法区块;只需运行 refresh-usage.sh,让图谱保持最新并自动填充该区域。若要交互式探索整个图谱(点击任意令牌 / 组件 / 页面,即可查看其所有使用位置),请搭建 sb-wrappers 中的 UsageExplorer 包装器。详情及 docs.page 组合方式见 sb-inventory(§“autodocs 中的真实用法”)和 sb-setup(文档页面组合)。
  • 要用 Factory 吗? 如果 3 个或更多故事共享同一种数据形状,就要用。读取 .storybook/prop-shapes.json(其中标记了候选项的 liveUsages)——如果文件不存在,请先运行 scripts/extract-prop-shapes.sh。 然后运行 scripts/scaffold-factory.sh <Type> <import-path>,并补全确定性存根(references/factory-patterns.md);否则直接内联 args。
  • 标题分类体系? 按照 storySort.order 匹配;如果项目没有设置,请根据 references/directory-structure.md 选择一种。
  • play 是否确有必要? 仅当存在交互、异步数据、portal、CSS 驱动的状态或无障碍需求时才编写;绝不要只写一个 toBeVisible()(反模式 34)。此外,项目必须恰好有一个 CssCheck(反模式 33):一个断言真实 getComputedStyle 令牌值的故事,这是共享预览已加载应用 CSS 的唯一证明。二者均源自 npx storybook ai setup 的提示;validate-stories.sh 的检查 13 和项目计数校验会强制执行。
  • “playground” / 展示故事由 Controls 驱动,而不是通过点击 / 悬停操作。 如果评审者问:“这个 playground 故事是什么——我可以点击或悬停吗?”答案是:playground 就是带有 Controls 面板的默认故事——你在 Controls 标签页中更改 args(变体、尺寸、状态),画布就会重新渲染。这不是画布交互;悬停 / 焦点 / 激活状态位于 StateGrid 交互矩阵(addon-pseudo-states)中,而脚本化的点击→断言流程则位于 play 函数中。因此,请明确说明故事使用哪一种方式:Controls = 尝试属性 · 伪状态 = 悬停 / 焦点列 · play = 真实交互。
  • Controls 面板已正确配置吗? 组件故事必须提供可用的 Controls 面板——它既是评审者的属性试验区,也为 autodocs 的 ArgTypes 表提供支持。react-vite 默认使用的 react-docgen 不会将 TS 联合类型推断为下拉选项,因此要为每个枚举 / 联合类型属性声明 argTypes(control: 'select'|'inline-radio' + options),使用 table.category 分组,隐藏逃生阀 / 不可序列化属性(className、refs、icon / callback / data 属性),并在仅用于渲染的展示故事中禁用控件(parameters: { controls: { disable: true } })。完整模式及 docgen 注意事项见:references/without-mcp.md §13。(面板本身必须可见——sb-setup 会写入包含 showPanel: true 的 manager.ts;sb-audit 的 audit-controls.sh 会标记任何缺少此配置的组件故事。)

编写源文件(互斥——只加载一个)

grep -q '@storybook/addon-mcp' package.json && test -f .mcp.json && echo WITH_MCP || echo WITHOUT_MCP
  • WITH_MCP → references/with-mcp.md(MCP 会注入 CSF3 约定;你只需专注于判断)。 Storybook 的工具名称会随次要版本变化;当前名称见 references/storybook-surface.md。
  • WITHOUT_MCP → references/without-mcp.md(13 个验证缺口 + 4 个关键 SB10 模式)。
  • 受控组件(Switch/Toggle/Checkbox/Tabs/Accordion/Select)应从 templates/controlled-component-story.tsx 开始——AI 容易弄错的是 useArgs 同步。

文件放在哪里(先询问——不要把文件散落在仓库各处)。 从 .storybook/audit/status.md 读取 storiesLocation(唯一规则位于 CONTEXT.md §STORIES LOCATION)。

  • 如果未设置(例如,仓库已有 Storybook,因此 sb-setup 从未询问),先停下并询问用户,再编写任何 story——绝不要猜测,也不要悄悄把文件放在同一目录。使用 AskUserQuestion (Claude)/ request_user_input(Codex);如果没有可用的阻塞式工具,则使用编号列表:

    应该把 stories 保存在哪里?(其他所有内容都已位于 .storybook/ 下。)

    1. .storybook/stories/ (推荐——集中在一处、彼此隔离;不改动 src/,只需删除一个文件夹即可移除)
    2. 与组件放在一起 src/**/<Name>.stories.tsx (适用于长期维护的项目)
    3. 自定义文件夹 (由你指定——仍集中在一处) 然后在 .storybook/audit/status.md 中将其记录为 storiesLocation: <isolated|colocated|PATH>,确保 main.ts 的 stories 包含该路径,然后继续。推荐选项 1。
  • isolated(或 .storybook/stories/) → 在 .storybook/stories/ 下按目录树镜像编写 (.storybook/stories/components/CourseCard.stories.tsx),通过 @/ 别名导入组件。 开发服务器不会监视 .storybook/stories/:在那里新增、移动或删除 story 文件后,该文件会一直不在索引中(或在索引中保持过时状态),且每个 MCP 工具都会报告 “No story found”。在那里更改文件后,需重启 Storybook(已在 10.6.0 上验证;src/ 中的 stories 会实时更新)。
  • colocated → src/components/<X>/<X>.stories.tsx。自定义路径 → 每个 story 都写入该路径。

无论用户选择哪种方式,所有 stories 都必须放在同一个位置——绝不要混用。

标题(即 Storybook 内的路径,与文件路径分开):遵循 .storybook/preview.ts 中的 storySort.order;否则使用 Components/<Domain>/<Name>。Overview / Spec / hub story 是一个入口——通过 storySort.order 将其固定在根目录顶部(根级 hub 排在最前;每个功能组内的 Overview/Spec 在子排序中排在最前,并以 '*' 收尾),绝不要让它按字母顺序排到其所概述内容的中间。参见 references/directory-structure.md → “Overview / spec / hub”。

页面 — 真实页面捕获(模式 A)

对于一个页面(pages/ · app/ · routes/ · views/ 下的路由/视图),不要重新创建它。 先运行 scripts/page-patterns.py <root>——它会逐页报告 importable、component、 layout、dataHook 和作为模拟信号的 dataType、sections(渲染顺序,不含 ui/), gridHint,以及 sharedSections[]。然后根据 importable 选择模式:

  • importable: true(页面有一个默认导出的组件)→ 原样导入真实页面,只模拟其数据层。 使用 scripts/scaffold-page-story.py <root> <page-suffix> 搭建:它会生成一个导入真实页面并连接检测到的 Provider (Inertia usePage / router / store)的 Pages/<Name> story,其 props 由基于 dataType 的工厂函数提供初始数据 (scripts/scaffold-factory.sh <dataType> <import-path>,参见 references/factory-patterns.md)。 针对每种实质不同的数据状态(空 / 已填充 / 错误)各添加一个 story——使用不同的工厂函数输入,绝不使用不同的标记。 绝不要重写页面的 JSX(反模式 27):布局、列和组件都属于真实页面,而不是你。
  • importable: false(内联组合 / 没有单一组件)→ 回退到页面组合 (references/composition-patterns.md 模式 4):使用真实的 sections 进行组合—— 仍由工厂函数提供数据,仍使用真实组件。

sharedSections[](至少由 2 个页面渲染的 section)是可复用的页面模式组件——为每个 section 单独创建一个 Components/* story,让页面组合这些组件,而不是重复实现。

autodocs 中的覆盖层(Dialog / Modal / Sheet / Drawer / Popover)

打开的覆盖层会将 position:fixed inset-0 覆盖层传送到 document.body。如果像 Primary 区块那样在 autodocs 页面中内联渲染,覆盖层会覆盖整个 Docs 页面——标题、“真实用法”部分和 Controls 都会消失在空白遮罩层后面。因此,对于任何带有 autodocs 的覆盖层组件,都应将 story 限定在其自身的 iframe 中:

parameters: {
  layout: 'fullscreen',
  docs: { story: { inline: false, height: '640px' } },  // portal stays inside the frame; Docs prose stays readable
}

story 视图(一个 story、完整画布)不受影响——这只针对 Docs 页面。不要对非覆盖层组件使用它(内联渲染更轻量)。

页面自身的 dataHook 只是其数据;预览还必须提供页面所运行于其中的 Provider 树 + 根 CSS。 从 .storybook/runtime.json 读取这些内容(如果文件不存在,则运行 scripts/discover-runtime.py . --out .storybook/runtime.json)—— 包括 providers[]、rootCss、portals[]、network.needsMsw——它们由 sb-setup 在共享预览中一次性设置,因此页面 story 通常无需重新连接它们。如果脚本已经写入 .storybook/*.json,绝不要再通过 shell 扫描推导这些内容——直接引用相应字段。

批量处理(多个组件)

每个组件编写一个 story(每个 story 只涵盖其真实状态),然后使用 scripts/validate-stories.sh 逐一检查。在 Claude Code 中,可以用 Agent 工具并行编写组件来加快批量处理,但工作内容相同——无需专用子 Agent。

完成前的检查

SKILL=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
CORE=${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT}}
"$CORE/scripts/validate-stories.sh" path/to/Foo.stories.tsx
# --strict adds tsc + eslint · --diff lints only changed stories
# For the "give me confidence" verdict (setup readiness preflight + conformance in one),
# run "$SKILL/scripts/check-story-ready.sh" path/to/Foo.stories.tsx instead — CONFIDENT when discovery JSONs are present.

任何 FAIL 都会以非零状态退出——继续之前先修复。如果你编写了 play,还要派发判断子 Agent(参见 references/validate-workflow.md);bash 无法验证 play 是否有意义。要将 stories 配置为 Agent 可运行的 CLI 检查(无头 vitest + a11y),请参见 references/test-wiring.md。

评论

支持 Markdown