mcpField
P
penpot-component-factory

penpot-component-factory

github.com/penpot设计与 UI108141 次安装3 周前更新
代码仓库

关于 penpot-component-factory

面向 Penpot 的技能,用于构建设计系统组件及完整的令牌化变体集。它引导设计师和组件工程师分阶段创建与验证,并提示变体分组可能损坏文件。

主要功能

  1. 01构建弹性布局组件画板
  2. 02从组件基础生成变体
  3. 03将属性绑定到设计令牌
  4. 04验证变体完整性与视觉效果
  5. 05以独立组件作为安全后备方案

适用场景

  1. 01为按钮创建必需的交互状态
  2. 02构建尺寸与层级变体矩阵
  3. 03分组前检查组件输出

MCP 相关

使用 execute_code、export_shape 和 shapeStructure 等 Penpot MCP 工具创建、检查与验证组件。

安装

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

$npx skills add https://github.com/penpot/penpot-ai-kit --skill penpot-component-factory

安装后在 Claude Code 中输入/penpot-component-factory确认已加载。

包含的文件

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

references/
01-variant-axes.md
02-flex-grid-layout.md
03-variant-api.md
04-instance-swap-detach.md
05-anti-rationalization.md
06-error-recovery.md
scripts/
buildComponentBase.js
createVariantGroup.js
createVariants.js
switchVariantDemo.js
validateComponent.js

详细介绍

penpot-component-factory — 完整的令牌化变体

1. 标题 + 工作原理

penpot-component-factory 用于构建组件及其完整的变体矩阵;所有修改都通过 execute_code 执行;使用 export_shape 进行视觉验证;使用 penpotUtils.shapeStructure 读取结构(完整工具列表:shared/penpot-mcp-tool-reference.md)。 它会将基础组件构建为采用弹性布局的画板(所有值都通过 penpot-foundations 令牌化),沿多个轴生成变体,将它们组合成变体容器 (在 ≥ 2.17 版本中使用 penpotUtils.createVariantContainer;低层级备用方案为 penpot.createVariantFromComponents),并验证完整性。

2. 最重要的一条规则

每个交互式组件都必须包含所有必需状态,并且每个值都必须是令牌。 不得硬编码填充或间距;除非设计系统明确另有规定,否则不得缺少 Hover/Pressed/Focus/Disabled 状态。一次构建一个变体;组合前先创建检查点。

3. Penpot MCP 工具参考

完整工具列表:shared/penpot-mcp-tool-reference.md。关键调用:

调用用途
penpot.createBoard() + addFlexLayout()基础组件容器
penpot.library.local.createComponent(shapes)将基础形状转换为组件
shape.clone()从基础形状派生变体
penpotUtils.createVariantContainer([{ shape, properties }])首选(≥ 2.17):一次调用创建容器、轴和值 — 需要通过重复文件门控
penpot.createVariantFromComponents(mainInstances)缺少辅助函数时使用的低层级备用方案
instance.switchVariant(pos, value)展示/切换变体(pos = variants.properties 中的索引)
export_shape矩阵的视觉检查点

4. Plugin API 基础知识

注意事项编号参见 shared/plugin-api-gotchas.md。

  • 将基础组件构建为画板(使用 createBoard,而非 createFrame),并通过 addFlexLayout() 添加弹性布局;设置 dir、间距、内边距、horizontalSizing/verticalSizing。
  • #4 弹性/网格布局会覆盖子项的 x/y 坐标 — 按追加顺序排列子项;使用 layoutChild 设置每个子项的尺寸/边距。
  • clone() 会复制形状的所有属性 — 这是构建变体矩阵的基础。
  • #9 变体 API — 优先在重复文件门控下使用 penpotUtils.createVariantContainer([{ shape: mainInstance, properties: { Size: "Medium", State: "Default" } }])(≥ 2.17);仅在备用情况下使用低层级的 penpot.createVariantFromComponents(mainInstances) + renameProperty/addProperty/setVariantProperty 路径。使用 instance.switchVariant(pos, value) 切换 — pos 是 variants.properties 中的索引,不是轴名称。Board.combineAsVariants(ids) 虽由 2.17 版本的 penpot_api_info 列出,但尚未验证 — 请勿使用。
  • #6 在修改实例内部结构前先分离实例 — 但绝不要对变体实例执行此操作(参见 #12)。
  • #12 变体修改会损坏文件 — 这是本技能的关键故障模式。 setVariantProperty(pos, value) 会更新变体属性,但不会更新变体根节点内部的 :variant-name,因此文件无法通过后端引用完整性验证:此后所有涉及组件的修改都会被拒绝,并返回插件无法显示的 HTTP 400 错误 — 调用会挂起约 30 秒,会话会终止,尚未刷新的修改会回滚。相同的损坏也会由 addVariant() + 重命名、对变体执行 comp.instance() + detach()、对已分离的变体实例执行 createComponent,以及对变体画板执行 shape.remove() 引发;需要手动恢复。读取变体和创建特定变体的实例是安全的。安全策略: 将每个状态构建为独立画板,并逐个调用 createComponent([board]),命名为 Component / State;第 3 阶段(§6)说明了重复文件、验证保存和备用方案的流程。
  • 使用 penpot_api_info('VariantContainer') / penpot_api_info('Variants') 验证不熟悉的签名。

5. Token 感知型简报规范

  • 上下文 — 当前启用的设计系统/Token 集;组件用途。
  • 目标 — 单个组件及其变体维度。
  • 输入 — 基础布局、Token 集(penpot-foundations)、必需维度。
  • 约束 — 所有值均使用 Token;包含所有必需状态;命名格式为 Property=Value。
  • 验收标准 — 矩阵完整;不存在硬编码值;每个状态均清晰易读且符合 AA 对比度要求;名称遵循约定。

以资深组件工程师的身份行动。

6. 强制工作流

**视觉自检(强制):**在每个展示视觉成果的 ✋ 检查点之前, 执行 shared/visual-self-review.md 中的导出 → 查看 → 修复循环——导出刚构建的单元,按照检查清单自行检查图像,修复可见缺陷(最多 2 轮),并展示同一份导出,同时说明任何仍存在的缺陷。

阶段 0 — 探索。 high_level_overview;读取 Token(tokenOverview())和现有组件。根据 references/01-variant-axes.md 确定维度。✋ 检查点:确认维度矩阵。

阶段 1 — 基础组件。 使用弹性布局构建基础 Board,并应用 Token(scripts/buildComponentBase.js),然后执行 createComponent。✋ 检查点:检查基础组件(export_shape)。

阶段 2 — 将变体创建为组件。 对矩阵中的每个单元格,克隆基础组件,根据状态重新应用 Token,然后对其执行 createComponent——createVariantFromComponents 需要每个变体各自对应一个组件。使用 scripts/createVariants.js(收集每个单元格主实例的 ID)。✋ 检查点:检查各单元格。

阶段 3 — 分组、命名维度并整理。 ⚠️ 高风险阶段——先阅读 shared/plugin-api-gotchas.md 中的 #12。 在实时会话中,变体组件变更(setVariantProperty、重命名维度、分离/移除变体)曾导致文件损坏,并使之后的所有保存操作卡住(后端会拒绝每次保存,并返回未显示的 HTTP 400 错误)。开始此阶段前:✋ 检查点——请用户复制文件(或确认接受在临时文件上承担风险)。然后只应用一项变体变更,确认文件仍能保存(之后的只读调用必须成功并持久化),再继续。如果任何调用卡住约 30 秒,立即停止并改用下方的回退方案。

变体流程:scripts/createVariantGroup.js 受门控——除非 storage.cf.fileDuplicatedConfirmed === true(仅在用户复制文件并确认保存正常后,才在之前的调用中设置该值),否则会返回 { halted: true }。之后优先使用 penpotUtils.createVariantContainer(records.map(r => ({ shape: main, properties: r.properties }))) (路径 "helper",≥ 2.17:一次调用即可设置维度和取值);若不可用,则回退到 penpot.createVariantFromComponents

  • renameProperty/addProperty/setVariantProperty(路径 "legacy",在脚本中原样保留)。无论哪种方式,都会清除容器填充(#11),为容器设置弹性布局(否则变体会堆叠在同一位置:container.flex || container.addFlexLayout();行、间距、内边距、wrap),并返回 { containerId, path, properties, components, next }。**保存验证:**调用完成后,在进行任何进一步变更之前,在 Penpot UI 中进行一项无关紧要的更改,并确认其已持久保存——卡住约 30 秒意味着文件已损坏;丢弃副本并改用下方的回退方案。✋ 检查点。

**回退方案(始终安全):**完全跳过变体容器。将阶段 2 中各单元格的组件保留为独立组件,命名为 Component / Axis=Value, …(例如 Button / Size=Medium, State=Hover),每次只创建一个组件,调用之间短暂停顿,并将组件排列成带标签的网格。Penpot 会按 / 前缀将它们分组,之后用户可在 UI 中将其转换为真正的变体。

阶段 4 — 验证。 运行 scripts/validateComponent.js——确认所有必需状态均已包含、所有值均已使用 Token、命名正确。可选运行 scripts/switchVariantDemo.js 演示切换。报告结果。

7. 关键规则

  1. 所有交互组件都必须包含 Default/Hover/Pressed/Focus/Disabled,除非系统另有规定。
  2. 不得使用硬编码值——绑定到语义令牌(或组件令牌)。
  3. 变体属性/值名称格式:Property=Value,采用 PascalCase(Size=Medium、State=Hover)。
  4. 使用 flex Board 构建;除非设置 layoutChild.absolute,否则绝不对子元素使用绝对定位。
  5. 操作须幂等:不要重复创建已有的变体。
  6. 仅在必要时分离,并报告分离情况——绝不分离变体实例(注意事项 #12)。
  7. 分组后,为变体容器设置 flex 布局——否则变体会叠放在同一位置。
  8. 变体修改存在已知的文件损坏风险(注意事项 #12):在第 3 阶段前复制文件,首次修改后验证保存结果;如果出现卡顿,则回退为独立的 Component / State 组件。

8. 领域架构

标准轴:

  • Size = Small | Medium | Large
  • Hierarchy = Primary | Secondary | Tertiary
  • State = Default | Hover | Pressed | Focus | Disabled
  • Icon = None | Left | Right

所需矩阵是这些轴的笛卡尔积——将其控制在系统允许的最小范围内(不要生成设计语言中未使用的组合)。每个变体都是克隆的基础组件,并绑定特定状态的令牌。

9. 模式与策略

默认使用审查模式。变体/组件重构和 detach() 绝不自动应用(shared/modes-and-policies.md)。

10. 状态管理

RUN_ID 下的记录键:phase、baseComponentId、created:[{variant:'Size=Medium,State=Hover', id}]。截断后,使用组件读取结果重新推导。

11. 用户检查点

阶段后展示的产物要询问的内容
0拟定的轴矩阵批准这些轴?
1基础组件 export_shape批准基础组件?
2变体网格 export_shape批准这些变体?
3变体容器批准合并?

12. 命名约定

shared/naming-conventions.md:组件使用 PascalCase(Button);变体使用 Property=Value;内部图层采用语义名称(label、icon、button)。

13. 反合理化对照表

借口错误原因应对措施(停止)
“我稍后再添加悬停/禁用状态。”组件状态不完整,上线后会出现问题。所有必需状态都是验收门槛;现在就构建它们。
“这个状态用普通矩形就行。”硬编码形状会偏离系统规范。克隆基础组件并绑定状态令牌。
“我直接硬编码悬停颜色。”这会破坏主题和治理规范。使用/提议语义令牌(color.action.primary.hover.bg)。
“把图标设为绝对定位,这样更简单。”这与 flex 冲突,也会破坏尺寸调整。使用 flex 顺序 + layoutChild;仅在有充分理由时,才使用 layoutChild.absolute 设置绝对定位。
“我假设变体创建方法/参数就可以。”变体 API 很容易用错。优先使用 penpotUtils.createVariantContainer;仅在缺少该辅助函数时,才使用底层的 createVariantFromComponents;通过 penpot_api_info('Variants') 验证。绝不使用 combineAsVariants(未经验证)。
“变体修改成功过一次——我这次就一次调用批量处理剩下的。”一次出错的 setVariantProperty 就会悄无声息地损坏文件;之后每次保存都会卡住(注意事项 #12)。一次修改 → 验证保存结果已持久化 → 继续。任何操作卡顿约 30 秒时,都应停止并回退为独立组件。

14. 辅助代码片段

// Base as a flex Board (Phase 1) — gaps/fill/padding bound to tokens (padding binds on ≥ 2.17; mirror + exception on 2.16.x, gotchas #8)
const board = penpot.createBoard();
board.name = "Button";
const flex = board.addFlexLayout();
flex.dir = "row";
flex.horizontalSizing = "auto"; flex.verticalSizing = "auto";

// gap binds to a token; padding binds on Penpot ≥ 2.17 — try applyToken per side, fall back to the RESOLVED value + a padding-mirrors-token exception
const gapTok = penpotUtils.findTokenByName("spacing.inset.sm");   // or propose it at the checkpoint
if (gapTok) board.applyToken(gapTok, ["columnGap"]);
const padY = penpotUtils.findTokenByName("spacing.8");
const padX = penpotUtils.findTokenByName("spacing.16");
penpot.currentPage.root.appendChild(board);                                     // append before binding layout props
const exceptions = [];
for (const [tok, sides, fallback] of [[padY, ["paddingTop", "paddingBottom"], 8], [padX, ["paddingLeft", "paddingRight"], 16]]) {
  for (const side of sides) {
    try { if (tok) board.applyToken(tok, [side]); else throw new Error("token missing"); }
    catch { flex[side.replace("padding", "").toLowerCase() + "Padding"] = tok ? Number(tok.resolvedValue) : fallback; exceptions.push({ kind: "padding-mirrors-token", token: tok && tok.name, side }); }
  }
}

// FILL POLICY: a button is a SURFACE — bind its bg token (never a literal, never the default white)
board.fills = [];
const bgTok = penpotUtils.findTokenByName("color.action.primary.bg");
if (bgTok) board.applyToken(bgTok, ["fill"]);

const label = penpot.createText("Button");
board.appendChild(label);
const comp = penpot.library.local.createComponent([board]);
return { component: comp.name, id: comp.mainInstance().id, exceptions };

15. 参考资源

  • penpot_api_info('Board')、penpot_api_info('VariantContainer')、penpot_api_info('Variants')、penpot_api_info('LibraryComponent')。

16. 支持文件

references/:01-variant-axes.md、02-flex-grid-layout.md、03-variant-api.md、04-instance-swap-detach.md、05-anti-rationalization.md、06-error-recovery.md。 scripts/:buildComponentBase.js、createVariants.js(每个单元格一个组件)、createVariantGroup.js(有门槛限制;helper ≥ 2.17 或使用旧版路径)、switchVariantDemo.js、validateComponent.js。

规范路径。 在原生安装中,shared/… 和 policies/… 会解析到此软件包内(由安装程序供应);在 Claude Code 插件安装中,它们位于插件根目录,即 ${CLAUDE_PLUGIN_ROOT}/shared/…,距此文件向上两级目录。

评论

支持 Markdown