mcpField
C
cc-figma-component

cc-figma-component

github.com/nvillapiano设计与 UI3369 次安装31 周前更新
代码仓库

关于 cc-figma-component

cc-figma-component 是一项根据组件契约生成 Figma 组件集的技能。它推导组件级令牌并绑定 Figma 变量,同时遵循契约定义的变体与结构。

主要功能

  1. 01读取组件契约
  2. 02推导组件级令牌
  3. 03将属性绑定到 Semantic 变量
  4. 04按变体轴与状态构建组件集
  5. 05应用契约定义的组件结构

适用场景

  1. 01根据契约生成 Figma 组件
  2. 02按属性和状态创建变体集
  3. 03确保组件视觉属性使用 Semantic 变量

MCP 相关

该技能通过 Figma 的 `use_figma` 调用,在 Figma 文件中检查和创建页面、组件及变量绑定。

安装

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

$npx skills add https://github.com/nvillapiano/component-contracts-figma --skill cc-figma-component

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

详细介绍

cc-figma-component — 组件契约组件技能

根据组件契约生成 Figma 组件集。此技能会读取契约、推导组件范围的(Tier 3)令牌,并在 Figma 中构建完全绑定变量的组件集。

前置条件 — 请先加载以下技能:

  • figma-use — 插件 API 语法规则(每次调用 use_figma 前都必须加载)
  • figma-generate-library — 设计系统工作流和组件创建模式
  • 必须已运行 cc-figma-tokens — 目标 Figma 文件中必须存在 Primitives 和 Semantic 集合

在此技能中调用 use_figma 时,始终传入 skillNames: "cc-figma-component"。


全局规则

帧尺寸默认设为 HUG

此技能创建的每个帧都必须在创建后立即将两个尺寸属性设为 'HUG',无一例外。

node.layoutSizingHorizontal = 'HUG';
node.layoutSizingVertical   = 'HUG';

这适用于:基础组件帧、每个变体副本、Generation Notes 帧,以及运行期间创建的任何其他帧。切勿依赖 Figma 的默认设置——它会将新帧设为 FIXED,尺寸为 100px,导致组件无论内边距如何都呈纵向比例。仅当契约中明确将插槽子项的 figma.sizing 设为 "fixed" 时,才可偏离 HUG。在这种情况下,直接使用契约中的 figma.width 和 figma.height 值:

if (child.figma?.sizing === 'fixed') {
  node.layoutSizingHorizontal = 'FIXED';
  node.layoutSizingVertical   = 'FIXED';
  node.resize(child.figma.width, child.figma.height);
}

这是唯一合理的 HUG 默认设置例外。


1. 配置

首先,从项目根目录读取 .component-contracts。任何回复中都不得输出 FIGMA_ACCESS_TOKEN。

FIGMA_ACCESS_TOKEN=...       # never output this value
FIGMA_FILE_KEY=...           # use for all use_figma calls
TOKENS_DIR=...               # root directory of token files
CONTRACTS_DIR=...            # root directory of contract files

2. 契约结构

组件契约是位于 {CONTRACTS_DIR}/{id}.contract.json 的 JSON 文件。规划之前,请完整读取该文件。

此技能使用的关键部分:

tokens — 定义组件的 Tier 3 令牌绑定,按类别组织:

{
  "tokens": {
    "color": {
      "background.primary": "brand.500",
      "text.primary": "neutral.0",
      "border.secondary": "brand.500"
    },
    "spacing": {
      "padding.x.sm": "space.sm",
      "padding.y.sm": "space.xs"
    },
    "typography": {
      "label.size": "textSize.sm",
      "label.weight": "weight.semibold"
    },
    "border": {
      "radius": "radius.md",
      "width": "border.width.sm"
    },
    "motion": {
      "transition": "duration.fast"
    }
  }
}

props — 定义组件的变体轴(例如 variant、size、state)

a11y — 无障碍要求(ARIA 角色、键盘行为)

platforms — 目标平台(web、ios、android)


3. Tier 3 令牌推导

组件范围的(Tier 3)令牌由契约的 tokens 部分推导得出,不会单独存储为文件。请在运行时根据以下规则推导:

命名约定
--ds-{componentId}-{role}-{qualifier}

其中:

  • componentId — 契约的 id 字段(例如 button、accordion)
  • role — 与 Figma 对齐的角色名称(参见下方的角色转换表)
  • qualifier — 根据契约值中的 Semantic 别名名称推导得出
角色转换表
契约类别 + 键前缀CSS/Figma 角色
color.background.*fill
color.text.*text
color.border.*border
color.focus.*focus
spacing.*spacing
typography.*typography
border.radiusborder(作为 border-radius)
border.widthborder(作为 border-width)
motion.*transition
限定符推导

限定符取自契约中的 Semantic 别名值,并移除其量级后缀:

"background.primary": "brand.500"   →  qualifier = "brand"   →  --ds-button-fill-brand
"background.primary.hover": "brand.600"  →  qualifier = "brand-hover"
"text.primary": "neutral.0"         →  qualifier = "neutral-0"

状态后缀(hover、active、focus、disabled)追加在限定符之后:

--ds-button-fill-brand-hover
--ds-button-fill-brand-active
Semantic 变量查找

每个 Tier 3 令牌都必须绑定到 Figma 中对应的 Semantic 变量。请按名称查找变量:

contract value "brand.500"  →  Semantic variable named "brand/500"
contract value "space.sm"   →  Semantic variable named "space/sm"
contract value "radius.md"  →  Semantic variable named "radius/md"

如果未找到 Semantic 变量,请停止操作,并在继续之前向用户报告缺失的变量。


4. 组件集架构

主变体属性(例如 variant)的每个值都对应一个单独的组件集,而不是合并为一个组件集。次级轴(Size、State)则成为每个组件集中的变体属性。

命名约定
{ComponentName}/{VariantValue}   →   Button/Primary, Accordion/Multiple
  • 组件名称:首字母大写的标题格式(Button、Accordion)
  • 变体值:首字母大写的标题格式(Primary、Secondary)
  • 分隔符:/
每个组件集中的变体轴

除主要变体外,所有轴都会成为组件集中的属性:

Button/Primary:
  Size=Small,  State=Default
  Size=Small,  State=Hover
  Size=Small,  State=Focus
  Size=Small,  State=Active
  Size=Small,  State=Disabled
  Size=Small,  State=Loading
  Size=Medium, State=Default
  ... (18 total variants per set)
属性命名
  • 属性名称:采用标题式大小写,与契约中的属性名称一致(Size、State)
  • 属性值:采用标题式大小写(Small、Medium、Large、Default、Hover、Focus、Active、Disabled、Loading)
标准状态值

始终从契约的 states 数组读取状态,不要使用硬编码列表。将每个值转换为标题式大小写,作为 Figma 属性值(例如,"default" → "Default")。不同组件的状态集合各不相同:Button 有 6 个状态,包括 Loading;Accordion 有 5 个状态,不包括 Loading。


5. 执行模式

默认(快速): 按顺序运行所有阶段,中途不停止。先呈现 Phase 0 计划并等待批准,然后不间断地执行 Phase 1–7。这是所有运行的默认模式。

调试模式: 每个阶段结束后停止,并等待明确批准后再继续。用于诊断问题或验证中间输出。提示中包含“debug mode”即可启用。仅在调试模式下保留脚本;完成后请手动清理。

脚本位置: 所有工作文件都必须写入 scripts/ 子目录,切勿写入仓库根目录。根目录下的点文件(.mcp-args-phase3.json、.phase2-code-raw.js 等)不会由 rimraf 步骤清理,并会在仓库中不断累积。

注意: 将 scripts/ 添加到 .gitignore,这些是生成的工作文件,不是源文件。


6. 工作流

阶段 0 — 发现(只读)
  1. 读取 .component-contracts 配置
  2. 从 CONTRACTS_DIR 读取契约文件
  3. 检查 Figma 文件,确认是否已有 Semantic/Primitive 集合,以及是否已有同名组件
  4. 验证 Semantic 和 Primitive 集合是否存在;如果不存在,停止并告知用户先运行 cc-figma-tokens
  5. 从契约的 tokens 部分推导出完整的 Tier 3 token 列表
  6. 查找每个 Semantic 变量,在继续前确认它们都存在
  7. 根据契约的 props 部分构建完整的变体矩阵
  8. 向用户呈现完整计划:
    • 组件名称和变体数量
    • 包含 Semantic 别名的完整 Tier 3 token 列表
    • 缺失的 Semantic 变量
  9. 在调试模式下:等待用户明确批准后再继续
阶段 1 — 页面设置
  1. 为组件创建专用页面:{ComponentName}(例如 Button、Accordion)
  2. 使用 await figma.setCurrentPageAsync(page) 切换到该页面
  3. 返回页面 ID
阶段 2 — 基础组件

先构建一个基础组件(主要变体的 Default 状态):

  1. 创建一个启用了自动布局的水平 Frame
  2. 创建后立即使用以下两行确切代码将尺寸设置为 HUG——两行都必须设置,缺一不可:
    frame.layoutSizingHorizontal = 'HUG';
    frame.layoutSizingVertical   = 'HUG';
    
    Figma 默认将新 Frame 的尺寸设为 FIXED,高度为 100px。省略垂直方向那一行,无论填充值如何,都会生成纵向按钮。请在添加任何子节点或绑定前完成此操作。
  3. 将所有结构属性(填充、间距、圆角)绑定到语义变量
  4. 根据契约的 composition.slots 部分添加子节点。使用 childOrder 确定渲染顺序——不要假设顺序固定:
    • 读取 composition.slots.{slotName}.childOrder——此数组定义了添加到 Frame 中的子节点顺序
    • 对于 childOrder 中的每个子节点,读取其 figma 提示:
      • figma.textProp — 创建文本节点,并将其连接到该组件属性名称
      • figma.booleanProp — 创建图标占位 Frame,设置 visible: false,并将其连接到该 BOOLEAN 属性名称
      • figma.position: "trailing" — 无论其他子节点的顺序如何,此子节点都放在最后
      • figma.rotateOnExpand — 在生成说明中注明旋转无法绑定变量
      • figma.visibleWhen — 在生成说明中注明可见性由状态驱动
    • 如果未定义 composition.slots,则回退为:隐藏的图标占位 Frame,然后是标签文本节点
    • 图标占位 Frame 始终为固定尺寸(md 为 20×20),无填充,默认设置 visible: false
  5. 将所有视觉属性绑定到语义变量——不得使用硬编码值
  6. **添加完所有子节点后,立即执行创建后的尺寸检查。**这会修正压缩器可能漏掉 HUG 设置的 Frame:
    // Walk every frame in the component subtree and enforce HUG
    function enforceHug(node) {
      if (node.type === 'FRAME' || node.type === 'COMPONENT') {
        if (node.layoutSizingVertical === 'FIXED') {
          node.layoutSizingHorizontal = 'HUG';
          node.layoutSizingVertical   = 'HUG';
        }
      }
      if ('children' in node) node.children.forEach(enforceHug);
    }
    enforceHug(baseComponent);
    
  7. 在所有节点上递归应用 setExplicitVariableModeForCollection
  8. 验证:get_screenshot — 确认按钮宽大于高后再继续
  9. 在调试模式下:等待用户检查点
阶段 3 — 变体矩阵

为主要变体属性的每个值创建单独的组件集:

  1. 为每种 Size × State 组合复制基础组件
  2. 对每个副本,在应用任何令牌覆盖前,使用以下两行确切代码将尺寸重置为 HUG:
    duplicate.layoutSizingHorizontal = 'HUG';
    duplicate.layoutSizingVertical   = 'HUG';
    
    复制操作可能会保留原组件的尺寸模式,但必须明确地对每个副本重新设置,不能例外。
  3. 按状态应用特定于变体的令牌覆盖(例如,悬停时更改填充颜色)
  4. 为每个组件命名:Size=Small, State=Default 等(标题大小写,Property=Value)
  5. 调用 combineAsVariants 前,对所有变体 Frame 执行 HUG 强制检查。这是安全措施,用于捕捉 Agent 遗漏的 Frame:
    variants.forEach(v => {
      v.layoutSizingHorizontal = 'HUG';
      v.layoutSizingVertical   = 'HUG';
    });
    
    完成此检查后,任何变体的高度仍为 100px,都表示存在绑定问题,而非尺寸问题。
  6. 调用 combineAsVariants 创建组件集
  7. 将组件集重命名为 {ComponentName}/{VariantValue}(例如 Button/Primary)
  8. 调用 combineAsVariants 后立即:手动以网格布局排列变体,并调整至实际边界:
    const COLS = 6; // one column per state
    const GAP = 40;
    const PADDING = 40; // padding inside component set on all sides
    
    variants.forEach((v, i) => {
      v.x = PADDING + (i % COLS) * (v.width + GAP);
      v.y = PADDING + Math.floor(i / COLS) * (v.height + GAP);
    });
    
    // Resize to actual content bounds — never guess, always calculate
    const maxRight = Math.max(...variants.map(v => v.x + v.width));
    const maxBottom = Math.max(...variants.map(v => v.y + v.height));
    componentSet.resize(maxRight + PADDING, maxBottom + PADDING);
    
  9. 对每个变体值(Primary、Secondary、Ghost、Destructive)重复此操作
  10. 验证:get_screenshot — 确认所有尺寸的按钮都宽大于高
  11. 在调试模式下:等待用户检查点
阶段 4 — 变量绑定检查

对每个变体节点:

  1. 将填充属性绑定到正确的语义颜色变量
  2. 将描边属性绑定到正确的语义颜色变量
  3. 将填充和间距绑定到正确的语义间距变量
  4. 将圆角半径绑定到正确的语义圆角变量
  5. 绑定后,对每个节点及其所有子节点递归调用 setExplicitVariableModeForCollection
  6. 验证:get_screenshot — 确认颜色正确解析(没有淡显或未解析的变量)
阶段 5 — 组件属性
  1. 定义与契约中 props 部分匹配的组件属性:
    • TEXT label — 链接到所有变体(包括 Loading)中 Label 文本节点的 characters。切勿替换为类似 "Saving" 的硬编码字符串——标签文本始终由 label 属性值驱动。
    • BOOLEAN iconStart — 通过 componentPropertyReferences 链接到 Icon 占位符的 visible 属性。默认值为 false(隐藏)。这是切换图标插槽可见性的正确方式。
    • BOOLEAN iconOnly — 在组件集中定义。不自动链接到布局(参见已知限制)。
    • BOOLEAN fullWidth — 在组件集中定义。不自动链接到布局(参见已知限制)。
  2. 不要将 disabled 或 loading 添加为 BOOLEAN 属性——它们仅作为变体状态处理。
  3. 除非文件中存在实际的图标组件,否则不要为图标添加 INSTANCE_SWAP。
  4. 验证:确认属性面板与契约中的属性匹配。
阶段 6 — 画布注释

最终验证前,在组件页面上创建一个 ⚠️ Generation Notes 框架,记录 Agent 的所有假设、标记的问题或未能解决的事项。这样,任何打开文件的人都能了解输出内容。

该框架应放置在所有组件集下方,并与其清晰分隔。使用浅黄色填充(#FFFBEB),自动布局方向设为垂直,并明确设置尺寸:

notesFrame.layoutSizingHorizontal = 'HUG';
notesFrame.layoutSizingVertical   = 'HUG';

这样可确保框架扩展以容纳所有注释文本,而不会在固定高度下发生裁切。添加一个标题文本节点,后接各条单独的注释。

如适用,务必为以下每种情况添加注释:

条件要写入的注释
focus/default 与某个变体填充色相同"Focus ring color conflict: focus/default may be invisible on [variant] fill. Define a focus/onBrand Semantic token and re-run."
Loading 状态没有 spinner 组件"State=Loading: No spinner component found. Label text preserved. Add a spinner instance and wire to iconStart, or create a dedicated loading indicator."
任意令牌绑定到 Primitives 而非 Semantic"Token gap: [token name] has no Semantic alias. Bound to Primitive directly. Add a Semantic alias to align with the token architecture."
fullWidth 或 iconOnly 未连接到布局"fullWidth / iconOnly are defined as component properties but do not drive layout automatically. See known constraints."
契约中存在 iconEnd 属性"iconEnd is not modeled in Figma — no trailing icon slot created. Add a second icon placeholder frame and wire to an iconEnd BOOLEAN if needed."
契约中的任意属性未在 Figma 中建模"[prop name] from contract not modeled in Figma: [reason]."
生成过程中作出了任何假设明确记录该假设。

如果没有注释,也要创建该框架,并添加文本 "No issues — all tokens resolved, all props wired."


阶段 7 — 最终验证
  1. get_metadata — 确认结构:变体数量、属性定义、层级结构。
  2. get_screenshot — 进行视觉检查:所有尺寸下按钮的宽度均大于高度、文本未被裁切、元素没有重叠、间距正确、变体没有被裁切。
  3. 返回结构化摘要:
{
  "component": "Button",
  "componentSets": [
    { "name": "Button/Primary", "variantCount": 18, "id": "..." },
    { "name": "Button/Secondary", "variantCount": 18, "id": "..." },
    { "name": "Button/Ghost", "variantCount": 18, "id": "..." },
    { "name": "Button/Destructive", "variantCount": 18, "id": "..." }
  ],
  "propertiesDefined": ["Size", "State", "label", "iconStart", "iconOnly", "fullWidth"],
  "annotations": ["list of notes written to canvas"],
  "tier3Tokens": [...],
  "pageId": "..."
}
  1. 清理——每次运行结束时都必须执行此操作,无论是否成功。调试模式是唯一例外。

    这些脚本是一次性的。 不要建议保留、复用或重新生成它们。不要指导用户运行 terser 或重写 invoke 文件。每次运行都会从头生成一套新文件。将它们视为构建缓存,而非源文件。

    删除 scripts/ 目录以及写入仓库根目录的任何工作文件:

    npx rimraf scripts/
    rm -f .mcp-*.json .mcp-*.txt .*-phase*.js .*-phase*.json .*-args*.json .*-code*.js .*-code*.json .tmp-*.js
    

    在调试模式下,保留所有文件以供检查。


7. 变量绑定规则

  • 组件只能绑定到语义变量,绝不能直接绑定到原语
  • 每个有对应令牌的视觉属性都 MUST 绑定变量,不得使用硬编码值
  • 执行任何变量绑定操作后,都 MUST 对每个节点及其所有子节点递归调用 setExplicitVariableModeForCollection
  • 在文本节点上使用 setBoundVariable 前,必须先加载字体:await figma.loadFontAsync({family, style})
  • cornerRadius 不支持直接使用 setBoundVariable,请改用单个圆角属性:topLeftRadius、topRightRadius、bottomLeftRadius、bottomRightRadius
  • 始终使用 setSharedPluginData / getSharedPluginData,绝不要使用 setPluginData / getPluginData。非共享版本会使用调试 UUID 作为插件标识符,而该标识符会在每次工具执行时发生变化,导致存储的数据永久无法检索,并使文件越来越臃肿。始终使用 component_contracts 命名空间:
    // CORRECT
    node.setSharedPluginData('component_contracts', 'dsb_key', 'component/button/base');
    node.getSharedPluginData('component_contracts', 'dsb_key');
    
    // WRONG — UUID changes per execution, data becomes orphaned
    node.setPluginData('dsb_key', 'component/button/base');
    

8. 已知限制

  • Frame 尺寸默认固定为 100px — Figma 会将新建 Frame 的尺寸设为 FIXED,高度为 100px。必须在每个 Frame、每个变体副本和 Generation Notes Frame 上显式设置这两行:

    node.layoutSizingHorizontal = 'HUG';
    node.layoutSizingVertical   = 'HUG';
    

    只设置水平尺寸而省略垂直尺寸,是按钮纵横比错误最常见的原因。没有简写方式——始终需要设置这两行。

  • 字间距 — 无法绑定到变量(em 单位与 Figma FLOAT 不兼容)。请根据 token 的原始值换算出固定像素值并设置。

  • combineAsVariants 不会自动布局——变体会堆叠在 (0,0) 位置。之后应立即手动进行网格布局。始终使用 Math.max,根据所有变体的位置、尺寸和内边距,将组件集调整为实际内容边界。切勿猜测调整后的尺寸。

  • 必须在 parent.appendChild(child) 之后设置 layoutSizingHorizontal/Vertical = 'FILL'——提前设置会抛出错误。

  • Ghost 模式 — 每次绑定操作后,都要在节点及其所有子节点上递归调用 setExplicitVariableModeForCollection。跳过这一步会导致变量显示为未解析。

  • 变体数量上限 — 如果每个组件集的 Size × State 大于 30 个变体,请在创建前告知用户。可考虑拆分为多个子组件。

  • 焦点环颜色冲突 — 此技能不会对焦点环颜色做出判断。读取 contract 的 color.focus.* token,并将其中定义的 Semantic 变量用于所有变体。如果 focus/default 与某个变体的填充色相同,请在 Generation Notes Frame 中标明,并建议用户定义 focus/onBrand Semantic token。切勿自行替换为 neutral/0 或其他颜色。

  • fullWidth 和 iconOnly 无法自动控制布局 — Figma Plugin API 的 componentPropertyReferences 仅支持 visible、characters 和 mainComponent。它不支持将布尔值关联到 layoutSizingHorizontal,也不支持在仅图标布局和完整布局之间切换。这些属性是为规格说明和 Code Connect 定义的。请向使用者说明此限制——布局变更需要手动创建变体或后续使用插件处理。

  • Figma 插件沙箱——未定义 atob — 插件运行时没有原生的 base64 解码功能。插件脚本中不要使用 atob 或 btoa。如果脚本超过 20k 字符限制而必须分块,请使用 base64 polyfill,或将逻辑拆分成多个连续的 use_figma 调用。

  • 旋转值始终使用角度,绝不使用弧度 — Figma Plugin API 的 node.rotation 属性接受角度。始终直接传入 contract 中的角度值(例如 -90)。切勿将角度转换为弧度(-Math.PI/2)。传入弧度会导致 Figma 界面显示类似 -1.57° 的值,看起来不正确,尽管视觉效果是正确的。contract 的 expandedRotation 和 defaultRotation 字段始终以角度表示。

  • Figma 插件沙箱——eval 已被屏蔽 — 需要动态执行代码时,请使用 new Function(code)() 作为解决方法。这是在 Figma 环境中组装并运行分块插件脚本的唯一可靠模式。

  • 图标插槽可见性 — 图标占位 Frame 默认必须隐藏(visible: false),并通过 componentPropertyReferences 关联到类型为 visible 的 BOOLEAN 组件属性(iconStart)。切勿让图标插槽始终可见,否则所有不使用图标的变体都会出现空框。

评论

支持 Markdown