@fprad0/skill-master-mcp 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +43 -9
- package/VERSION.md +3 -3
- package/bin/lib/client-config.mjs +268 -0
- package/bin/lib/menu-core.mjs +678 -33
- package/bin/skill-master-bootstrap-global.mjs +15 -1
- package/bin/skill-master-doctor.mjs +181 -0
- package/bin/skill-master-install-global-skills.mjs +30 -10
- package/bin/skill-master-menu.mjs +184 -36
- package/bin/skill-master-register-clients.mjs +43 -99
- package/dist/index.js +30 -5
- package/dist/index.js.map +1 -1
- package/docs/operations/GUIA_MULTI_COMPUTADOR.md +255 -0
- package/docs/operations/GUIA_NPM_PUBLICO.md +147 -0
- package/docs/skill-candidates/v0.0.10/cli-creator/LICENSE.txt +201 -0
- package/docs/skill-candidates/v0.0.10/cli-creator/SKILL.md +160 -0
- package/docs/skill-candidates/v0.0.10/cli-creator/agents/openai.yaml +4 -0
- package/docs/skill-candidates/v0.0.10/cli-creator/references/agent-cli-patterns.md +154 -0
- package/docs/skill-candidates/v0.0.10/developer-workstation-ops/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.10/figma/LICENSE.txt +2 -0
- package/docs/skill-candidates/v0.0.10/figma/SKILL.md +42 -0
- package/docs/skill-candidates/v0.0.10/figma/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma/references/figma-mcp-config.md +35 -0
- package/docs/skill-candidates/v0.0.10/figma/references/figma-tools-and-prompts.md +34 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/LICENSE.TXT +2 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/SKILL.md +349 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/references/mapping-checklist.md +7 -0
- package/docs/skill-candidates/v0.0.10/figma-code-connect-components/scripts/normalize_node_id.py +25 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/LICENSE.TXT +2 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/SKILL.md +537 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/references/rule-template.md +15 -0
- package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/scripts/check_agents_md.sh +9 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/LICENSE.TXT +2 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/SKILL.md +341 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-design/maintainers.yml +1 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/LICENSE.TXT +2 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/SKILL.md +314 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/maintainers.yml +3 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/code-connect-setup.md +260 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/component-creation.md +1014 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/discovery-phase.md +518 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/documentation-creation.md +834 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/error-recovery.md +540 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/naming-conventions.md +527 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/references/token-creation.md +962 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/bindVariablesToComponent.js +110 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/cleanupOrphans.js +127 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createComponentWithVariants.js +148 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createDocumentationPage.js +139 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createSemanticTokens.js +108 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createVariableCollection.js +49 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/inspectFileStructure.js +121 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/rehydrateState.js +92 -0
- package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/validateCreation.js +83 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/LICENSE.txt +2 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/SKILL.md +258 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-use/LICENSE.TXT +2 -0
- package/docs/skill-candidates/v0.0.10/figma-use/SKILL.md +233 -0
- package/docs/skill-candidates/v0.0.10/figma-use/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/figma-use/assets/figma-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/figma-use/assets/figma.png +0 -0
- package/docs/skill-candidates/v0.0.10/figma-use/assets/icon.svg +28 -0
- package/docs/skill-candidates/v0.0.10/figma-use/maintainers.yml +1 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/api-reference.md +301 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/common-patterns.md +512 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/component-patterns.md +488 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/effect-style-patterns.md +123 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/gotchas.md +599 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/maintainers.yml +12 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-patterns.md +513 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.d.ts +11293 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.index.md +441 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/text-style-patterns.md +203 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/validation-and-recovery.md +109 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/variable-patterns.md +354 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/maintainers.yml +9 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--creating.md +17 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--using.md +17 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components.md +50 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-effect-styles.md +52 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-text-styles.md +90 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--creating.md +13 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--using.md +13 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables.md +64 -0
- package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds.md +41 -0
- package/docs/skill-candidates/v0.0.10/frontend-design/LICENSE.txt +177 -0
- package/docs/skill-candidates/v0.0.10/frontend-design/SKILL.md +55 -0
- package/docs/skill-candidates/v0.0.10/frontend-ui-ux-systems/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.10/github/SKILL.md +74 -0
- package/docs/skill-candidates/v0.0.10/github/agents/openai.yaml +6 -0
- package/docs/skill-candidates/v0.0.10/github/assets/github-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/github/assets/github.png +0 -0
- package/docs/skill-candidates/v0.0.10/image-graphic-design-rendering/SKILL.md +28 -0
- package/docs/skill-candidates/v0.0.10/language-quality-pt-en-fr-it-ru/SKILL.md +28 -0
- package/docs/skill-candidates/v0.0.10/math-physics-reasoning/SKILL.md +28 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/LICENSE.txt +202 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/SKILL.md +236 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/reference/evaluation.md +602 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/reference/node_mcp_server.md +970 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/reference/python_mcp_server.md +719 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/connections.py +151 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/evaluation.py +373 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/requirements.txt +2 -0
- package/docs/skill-candidates/v0.0.10/mcp-client-readiness/SKILL.md +31 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/LICENSE.txt +201 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/SKILL.md +161 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/agents/openai.yaml +14 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/assets/openai-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/assets/openai.png +0 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/references/latest-model.md +37 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/references/prompting-guide.md +244 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/references/upgrade-guide.md +181 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/scripts/fetch-codex-manual.mjs +598 -0
- package/docs/skill-candidates/v0.0.10/openai-docs/scripts/resolve-latest-model-info.js +147 -0
- package/docs/skill-candidates/v0.0.10/playwright/LICENSE.txt +201 -0
- package/docs/skill-candidates/v0.0.10/playwright/NOTICE.txt +14 -0
- package/docs/skill-candidates/v0.0.10/playwright/SKILL.md +147 -0
- package/docs/skill-candidates/v0.0.10/playwright/agents/openai.yaml +6 -0
- package/docs/skill-candidates/v0.0.10/playwright/assets/playwright-small.svg +3 -0
- package/docs/skill-candidates/v0.0.10/playwright/assets/playwright.png +0 -0
- package/docs/skill-candidates/v0.0.10/playwright/references/cli.md +116 -0
- package/docs/skill-candidates/v0.0.10/playwright/references/workflows.md +95 -0
- package/docs/skill-candidates/v0.0.10/playwright/scripts/playwright_cli.sh +25 -0
- package/docs/skill-candidates/v0.0.10/polyglot-backend-engineering/SKILL.md +32 -0
- package/docs/skill-candidates/v0.0.10/screenshot/LICENSE.txt +201 -0
- package/docs/skill-candidates/v0.0.10/screenshot/SKILL.md +267 -0
- package/docs/skill-candidates/v0.0.10/screenshot/agents/openai.yaml +6 -0
- package/docs/skill-candidates/v0.0.10/screenshot/assets/screenshot-small.svg +5 -0
- package/docs/skill-candidates/v0.0.10/screenshot/assets/screenshot.png +0 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/ensure_macos_permissions.sh +54 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_display_info.swift +22 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_permissions.swift +40 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_window_info.swift +126 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.ps1 +163 -0
- package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.py +585 -0
- package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/SKILL.md +62 -0
- package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/agents/openai.yaml +4 -0
- package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/activation-policy.md +77 -0
- package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/human-approval-policy.md +83 -0
- package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/persona-dev-senior-master.md +46 -0
- package/docs/skill-candidates/v0.0.10/terminal-menu-operations/SKILL.md +30 -0
- package/docs/skill-candidates/v0.0.10/terminal-pixel-art-tui/SKILL.md +43 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/LICENSE.txt +202 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/SKILL.md +96 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/examples/console_logging.py +35 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/examples/element_discovery.py +40 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/examples/static_html_automation.py +33 -0
- package/docs/skill-candidates/v0.0.10/webapp-testing/scripts/with_server.py +106 -0
- package/docs/skill-candidates/v0.0.10/winui-app/LICENSE.txt +202 -0
- package/docs/skill-candidates/v0.0.10/winui-app/SKILL.md +94 -0
- package/docs/skill-candidates/v0.0.10/winui-app/agents/openai.yaml +5 -0
- package/docs/skill-candidates/v0.0.10/winui-app/assets/winui.png +0 -0
- package/docs/skill-candidates/v0.0.10/winui-app/config.yaml +50 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/_sections.md +96 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/accessibility-input-and-localization.md +51 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/build-run-and-launch-verification.md +72 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/community-toolkit-controls-and-helpers.md +57 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/controls-layout-and-adaptive-ui.md +84 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-environment-audit-and-remediation.md +82 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-setup-and-project-selection.md +67 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-template-first-recovery.md +62 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-winui-app-structure.md +62 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/motion-animations-and-polish.md +45 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/performance-diagnostics-and-responsiveness.md +46 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/sample-source-map.md +37 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/shell-navigation-and-windowing.md +67 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/styling-theming-materials-and-icons.md +71 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/testing-debugging-and-review-checklists.md +77 -0
- package/docs/skill-candidates/v0.0.10/winui-app/references/windows-app-sdk-lifecycle-notifications-and-deployment.md +52 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/SKILL.md +399 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/common-patterns.md +331 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/complete-examples.md +872 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/component-patterns.md +502 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/data-fetching.md +767 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/file-organization.md +502 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/loading-and-error-states.md +501 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/performance.md +406 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/routing-guide.md +364 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/styling-guide.md +428 -0
- package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/typescript-standards.md +418 -0
- package/docs/skill-candidates/v0.0.11/git-version-control-ops/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/go-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/java-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/javascript-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/json-contract-design/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/multi-client-mcp-ops/SKILL.md +36 -0
- package/docs/skill-candidates/v0.0.11/nextjs/SKILL.md +745 -0
- package/docs/skill-candidates/v0.0.11/nextjs/agents/openai.yaml +3 -0
- package/docs/skill-candidates/v0.0.11/nextjs/references/app-router-files.md +94 -0
- package/docs/skill-candidates/v0.0.11/python-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/ruby-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/SKILL.md +209 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/architecture_patterns.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/development_workflows.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/references/tech_stack_guide.md +103 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
- package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/project_scaffolder.py +114 -0
- package/docs/skill-candidates/v0.0.11/shadcn/SKILL.md +573 -0
- package/docs/skill-candidates/v0.0.11/shadcn/agents/openai.yaml +3 -0
- package/docs/skill-candidates/v0.0.11/sql-postgresql-engineering/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/terminal-shell-ops/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/SKILL.md +429 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/tsconfig-strict.json +92 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/references/utility-types.ts +335 -0
- package/docs/skill-candidates/v0.0.11/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/docs/skill-candidates/v0.0.11/ui-component-primitives/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/web-mobile-design-systems/SKILL.md +34 -0
- package/docs/skill-candidates/v0.0.11/windows-linux-platform-ops/SKILL.md +34 -0
- package/manifests/channels/beta.json +7 -7
- package/manifests/channels/stable.json +8 -8
- package/network/unapproved-skill-candidates.json +34 -1
- package/package.json +7 -2
- package/scripts/verify-menu-actions.mjs +115 -0
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
> Part of the [figma-generate-library skill](../SKILL.md).
|
|
2
|
+
|
|
3
|
+
# Component Creation Reference
|
|
4
|
+
|
|
5
|
+
Complete guide for Phase 3: building components with variant matrices, variable bindings, component properties, and documentation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Component Architecture
|
|
10
|
+
|
|
11
|
+
### Dependency Ordering: Atoms Before Molecules
|
|
12
|
+
|
|
13
|
+
Always build in dependency order. A molecule that contains an atom instance cannot exist until the atom is published. Suggested ordering:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Tier 0 (atoms): Icon, Avatar, Badge, Spinner
|
|
17
|
+
Tier 1 (molecules): Button, Checkbox, Toggle, Input, Select
|
|
18
|
+
Tier 2 (organisms): Card, Dialog, Menu, Navigation, Form
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If a component embeds an instance of another component, the embedded component must be created first. Build your dependency graph during Phase 0 and encode the creation order in the plan.
|
|
22
|
+
|
|
23
|
+
### Building Blocks Sub-Components (M3 Pattern)
|
|
24
|
+
|
|
25
|
+
For complex components with independent sub-element state machines, extract the sub-element into its own component set prefixed with `Building Blocks/` (public) or `.Building Blocks/` (hidden from assets panel). The dot-prefix is a Figma convention for suppressing a component from the public assets panel.
|
|
26
|
+
|
|
27
|
+
**When to use Building Blocks:**
|
|
28
|
+
- The sub-element has its own variant axes (state, selection) that would cause combinatorial explosion in the parent
|
|
29
|
+
- The sub-element repeats (nav items, table cells, calendar cells, segmented button segments)
|
|
30
|
+
- The sub-element has different variant axes than the parent
|
|
31
|
+
|
|
32
|
+
**Example (M3 Segmented Button):**
|
|
33
|
+
```
|
|
34
|
+
Building Blocks/Segmented button/Button segment (start) [27 variants: Config × State × Selected]
|
|
35
|
+
Building Blocks/Segmented button/Button segment (middle) [27 variants]
|
|
36
|
+
Building Blocks/Segmented button/Button segment (end) [27 variants]
|
|
37
|
+
|
|
38
|
+
Segmented button [16 variants: Segments=2-5 × Density=0/-1/-2/-3]
|
|
39
|
+
Each variant contains instances of the appropriate Building Block segment components.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The parent manages composition and configuration; the Building Block manages its own interaction states.
|
|
43
|
+
|
|
44
|
+
### Private Components (`__` Prefix)
|
|
45
|
+
|
|
46
|
+
Use the `__` prefix for internal helper components that should not appear in the team library (Shop Minis pattern). Use `_` for documentation-only components (UI3 pattern).
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
__asset // private icon/asset holder
|
|
50
|
+
_Label/Direction // documentation annotation helper
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Creating the Component Page
|
|
56
|
+
|
|
57
|
+
Each component lives on its own dedicated page (one page per component is the default). The page contains: a documentation frame at top-left and the component set positioned to its right or below.
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
(async () => {
|
|
61
|
+
try {
|
|
62
|
+
// Create or find the component page
|
|
63
|
+
let page = figma.root.children.find(p => p.name === 'Button');
|
|
64
|
+
if (!page) {
|
|
65
|
+
page = figma.createPage();
|
|
66
|
+
page.name = 'Button';
|
|
67
|
+
}
|
|
68
|
+
await figma.setCurrentPageAsync(page);
|
|
69
|
+
|
|
70
|
+
// Documentation frame — positioned at (40, 40)
|
|
71
|
+
const docFrame = figma.createFrame();
|
|
72
|
+
docFrame.name = 'Button / Documentation';
|
|
73
|
+
docFrame.x = 40;
|
|
74
|
+
docFrame.y = 40;
|
|
75
|
+
docFrame.resize(600, 400);
|
|
76
|
+
docFrame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
|
|
77
|
+
docFrame.layoutMode = 'VERTICAL';
|
|
78
|
+
docFrame.primaryAxisSizingMode = 'AUTO';
|
|
79
|
+
docFrame.counterAxisSizingMode = 'FIXED';
|
|
80
|
+
docFrame.paddingTop = 40;
|
|
81
|
+
docFrame.paddingBottom = 40;
|
|
82
|
+
docFrame.paddingLeft = 40;
|
|
83
|
+
docFrame.paddingRight = 40;
|
|
84
|
+
docFrame.itemSpacing = 16;
|
|
85
|
+
|
|
86
|
+
// Title text node
|
|
87
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' });
|
|
88
|
+
const title = figma.createText();
|
|
89
|
+
title.fontName = { family: 'Inter', style: 'Bold' };
|
|
90
|
+
title.fontSize = 32;
|
|
91
|
+
title.characters = 'Button';
|
|
92
|
+
docFrame.appendChild(title);
|
|
93
|
+
|
|
94
|
+
// Description text node
|
|
95
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
|
|
96
|
+
const desc = figma.createText();
|
|
97
|
+
desc.fontName = { family: 'Inter', style: 'Regular' };
|
|
98
|
+
desc.fontSize = 14;
|
|
99
|
+
desc.characters = 'Buttons allow users to take actions and make choices with a single tap.';
|
|
100
|
+
docFrame.appendChild(desc);
|
|
101
|
+
|
|
102
|
+
// Tag docFrame with pluginData for idempotency
|
|
103
|
+
docFrame.setPluginData('dsb_run_id', RUN_ID);
|
|
104
|
+
docFrame.setPluginData('dsb_key', 'doc/button');
|
|
105
|
+
|
|
106
|
+
figma.closePlugin(JSON.stringify({ docFrameId: docFrame.id, pageId: page.id }));
|
|
107
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
108
|
+
})();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 3. Base Component: Auto-Layout, Child Nodes, Variable Bindings
|
|
114
|
+
|
|
115
|
+
The base component is the template from which all variants are cloned. It must have:
|
|
116
|
+
1. Auto-layout (not manual positioning)
|
|
117
|
+
2. All child nodes present
|
|
118
|
+
3. ALL visual properties bound to variables (no hardcoded values)
|
|
119
|
+
|
|
120
|
+
### Complete Button Base Component Example
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
(async () => {
|
|
124
|
+
try {
|
|
125
|
+
const RUN_ID = 'ds-build-2024-001'; // replace with your actual run ID
|
|
126
|
+
await figma.setCurrentPageAsync(
|
|
127
|
+
figma.root.children.find(p => p.name === 'Button')
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Rehydrate variables from IDs stored in state ledger
|
|
131
|
+
const bgVar = await figma.variables.getVariableByIdAsync('VAR_ID_color_bg_primary');
|
|
132
|
+
const textVar = await figma.variables.getVariableByIdAsync('VAR_ID_color_text_on_primary');
|
|
133
|
+
const paddingVar = await figma.variables.getVariableByIdAsync('VAR_ID_spacing_md');
|
|
134
|
+
const radiusVar = await figma.variables.getVariableByIdAsync('VAR_ID_radius_md');
|
|
135
|
+
const gapVar = await figma.variables.getVariableByIdAsync('VAR_ID_spacing_sm');
|
|
136
|
+
|
|
137
|
+
// --- Base component frame ---
|
|
138
|
+
const comp = figma.createComponent();
|
|
139
|
+
comp.name = 'Size=Medium, Style=Primary, State=Default';
|
|
140
|
+
comp.layoutMode = 'HORIZONTAL';
|
|
141
|
+
comp.primaryAxisSizingMode = 'AUTO';
|
|
142
|
+
comp.counterAxisSizingMode = 'AUTO';
|
|
143
|
+
comp.counterAxisAlignItems = 'CENTER';
|
|
144
|
+
comp.primaryAxisAlignItems = 'CENTER';
|
|
145
|
+
|
|
146
|
+
// Padding — bound to spacing variables
|
|
147
|
+
comp.setBoundVariable('paddingTop', paddingVar);
|
|
148
|
+
comp.setBoundVariable('paddingBottom', paddingVar);
|
|
149
|
+
comp.setBoundVariable('paddingLeft', paddingVar);
|
|
150
|
+
comp.setBoundVariable('paddingRight', paddingVar);
|
|
151
|
+
comp.setBoundVariable('itemSpacing', gapVar);
|
|
152
|
+
|
|
153
|
+
// Corner radius — bound to radius variable
|
|
154
|
+
comp.setBoundVariable('topLeftRadius', radiusVar);
|
|
155
|
+
comp.setBoundVariable('topRightRadius', radiusVar);
|
|
156
|
+
comp.setBoundVariable('bottomLeftRadius', radiusVar);
|
|
157
|
+
comp.setBoundVariable('bottomRightRadius', radiusVar);
|
|
158
|
+
|
|
159
|
+
// Background fill — bound to color variable
|
|
160
|
+
const bgPaint = figma.variables.setBoundVariableForPaint(
|
|
161
|
+
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } },
|
|
162
|
+
'color',
|
|
163
|
+
bgVar
|
|
164
|
+
);
|
|
165
|
+
comp.fills = [bgPaint];
|
|
166
|
+
|
|
167
|
+
// --- Label text node ---
|
|
168
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
|
|
169
|
+
const label = figma.createText();
|
|
170
|
+
label.name = 'label';
|
|
171
|
+
label.fontName = { family: 'Inter', style: 'Medium' };
|
|
172
|
+
label.fontSize = 14;
|
|
173
|
+
label.characters = 'Button';
|
|
174
|
+
label.layoutSizingHorizontal = 'HUG';
|
|
175
|
+
label.layoutSizingVertical = 'HUG';
|
|
176
|
+
|
|
177
|
+
// Text fill — bound to color variable
|
|
178
|
+
const textPaint = figma.variables.setBoundVariableForPaint(
|
|
179
|
+
{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } },
|
|
180
|
+
'color',
|
|
181
|
+
textVar
|
|
182
|
+
);
|
|
183
|
+
label.fills = [textPaint];
|
|
184
|
+
comp.appendChild(label);
|
|
185
|
+
|
|
186
|
+
// --- Icon placeholder (Rectangle for now — will be INSTANCE_SWAP) ---
|
|
187
|
+
const iconBox = figma.createFrame();
|
|
188
|
+
iconBox.name = 'icon';
|
|
189
|
+
iconBox.resize(16, 16);
|
|
190
|
+
iconBox.fills = [];
|
|
191
|
+
iconBox.layoutSizingHorizontal = 'FIXED';
|
|
192
|
+
iconBox.layoutSizingVertical = 'FIXED';
|
|
193
|
+
comp.appendChild(iconBox);
|
|
194
|
+
|
|
195
|
+
// Tag for idempotency
|
|
196
|
+
comp.setPluginData('dsb_run_id', RUN_ID);
|
|
197
|
+
comp.setPluginData('dsb_phase', 'phase3');
|
|
198
|
+
comp.setPluginData('dsb_key', 'component/button/base');
|
|
199
|
+
|
|
200
|
+
figma.closePlugin(JSON.stringify({ baseCompId: comp.id }));
|
|
201
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
202
|
+
})();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**ALL of these must be variable-bound (never hardcoded):**
|
|
206
|
+
|
|
207
|
+
| Property | Variable type | API method |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| Fill color | COLOR | `setBoundVariableForPaint(..., 'color', var)` |
|
|
210
|
+
| Stroke color | COLOR | `setBoundVariableForPaint(..., 'color', var)` |
|
|
211
|
+
| Text fill | COLOR | `setBoundVariableForPaint(..., 'color', var)` |
|
|
212
|
+
| Padding (all 4 sides) | FLOAT | `comp.setBoundVariable('paddingTop', var)` |
|
|
213
|
+
| Gap / itemSpacing | FLOAT | `comp.setBoundVariable('itemSpacing', var)` |
|
|
214
|
+
| Corner radius (all 4) | FLOAT | `comp.setBoundVariable('topLeftRadius', var)` etc. |
|
|
215
|
+
| Stroke weight | FLOAT | `comp.setBoundVariable('strokeWeight', var)` |
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 4. Variant Matrix
|
|
220
|
+
|
|
221
|
+
### Defining Axes
|
|
222
|
+
|
|
223
|
+
For each component, identify its variant axes before writing any code. Standard axes:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Button:
|
|
227
|
+
Size → [Small, Medium, Large]
|
|
228
|
+
Style → [Primary, Secondary, Outline, Ghost]
|
|
229
|
+
State → [Default, Hover, Focused, Pressed, Disabled]
|
|
230
|
+
Total = 3 × 4 × 5 = 60 combinations — exceeds 30 limit → split by Style
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### The 30-Combination Cap and Split Strategy
|
|
234
|
+
|
|
235
|
+
When the product of all variant axes exceeds 30 combinations, split the matrix. Options:
|
|
236
|
+
|
|
237
|
+
1. **Split by a primary axis**: Create separate component sets, one per Style (Primary Button, Secondary Button, etc.)
|
|
238
|
+
2. **Use INSTANCE_SWAP**: Remove a visual axis (like Icon) from the variant matrix entirely and expose it as an INSTANCE_SWAP property instead
|
|
239
|
+
3. **Use Building Blocks**: Extract sub-elements with their own state axes into Building Block component sets
|
|
240
|
+
|
|
241
|
+
For Button with Size × State = 15 combinations, add Style as a variant axis only if Style ≤ 2 options (15 × 2 = 30). For more Styles, split.
|
|
242
|
+
|
|
243
|
+
### Creating All Variants with use_figma
|
|
244
|
+
|
|
245
|
+
Build each variant by cloning the base component and adjusting the variable bindings that differ per variant. Pass in the base component ID from the previous call's state.
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
(async () => {
|
|
249
|
+
try {
|
|
250
|
+
const RUN_ID = 'ds-build-2024-001';
|
|
251
|
+
const BASE_COMP_ID = 'BASE_ID_FROM_STATE'; // from state ledger
|
|
252
|
+
|
|
253
|
+
await figma.setCurrentPageAsync(
|
|
254
|
+
figma.root.children.find(p => p.name === 'Button')
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const base = await figma.getNodeByIdAsync(BASE_COMP_ID);
|
|
258
|
+
|
|
259
|
+
// Variable IDs from state ledger
|
|
260
|
+
const vars = {
|
|
261
|
+
// Primary style
|
|
262
|
+
bg_primary: await figma.variables.getVariableByIdAsync('VAR_ID_color_bg_primary'),
|
|
263
|
+
text_primary: await figma.variables.getVariableByIdAsync('VAR_ID_color_text_on_primary'),
|
|
264
|
+
// Secondary style
|
|
265
|
+
bg_secondary: await figma.variables.getVariableByIdAsync('VAR_ID_color_bg_secondary'),
|
|
266
|
+
text_secondary: await figma.variables.getVariableByIdAsync('VAR_ID_color_text_secondary'),
|
|
267
|
+
// Disabled
|
|
268
|
+
bg_disabled: await figma.variables.getVariableByIdAsync('VAR_ID_color_bg_disabled'),
|
|
269
|
+
text_disabled: await figma.variables.getVariableByIdAsync('VAR_ID_color_text_disabled'),
|
|
270
|
+
// Sizes
|
|
271
|
+
padding_sm: await figma.variables.getVariableByIdAsync('VAR_ID_spacing_sm'),
|
|
272
|
+
padding_md: await figma.variables.getVariableByIdAsync('VAR_ID_spacing_md'),
|
|
273
|
+
padding_lg: await figma.variables.getVariableByIdAsync('VAR_ID_spacing_lg'),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const axes = {
|
|
277
|
+
Size: ['Small', 'Medium', 'Large'],
|
|
278
|
+
Style: ['Primary', 'Secondary'],
|
|
279
|
+
State: ['Default', 'Hover', 'Disabled'],
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const paddingBySize = { Small: vars.padding_sm, Medium: vars.padding_md, Large: vars.padding_lg };
|
|
283
|
+
|
|
284
|
+
const components = [];
|
|
285
|
+
|
|
286
|
+
for (const size of axes.Size) {
|
|
287
|
+
for (const style of axes.Style) {
|
|
288
|
+
for (const state of axes.State) {
|
|
289
|
+
const clone = base.clone();
|
|
290
|
+
clone.name = `Size=${size}, Style=${style}, State=${state}`;
|
|
291
|
+
|
|
292
|
+
// Bind padding by size
|
|
293
|
+
clone.setBoundVariable('paddingTop', paddingBySize[size]);
|
|
294
|
+
clone.setBoundVariable('paddingBottom', paddingBySize[size]);
|
|
295
|
+
clone.setBoundVariable('paddingLeft', paddingBySize[size]);
|
|
296
|
+
clone.setBoundVariable('paddingRight', paddingBySize[size]);
|
|
297
|
+
|
|
298
|
+
// Bind fill by style + state
|
|
299
|
+
const isDisabled = state === 'Disabled';
|
|
300
|
+
const bgVar = isDisabled ? vars.bg_disabled : (style === 'Primary' ? vars.bg_primary : vars.bg_secondary);
|
|
301
|
+
const txtVar = isDisabled ? vars.text_disabled : (style === 'Primary' ? vars.text_primary : vars.text_secondary);
|
|
302
|
+
|
|
303
|
+
const bgPaint = figma.variables.setBoundVariableForPaint(
|
|
304
|
+
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', bgVar
|
|
305
|
+
);
|
|
306
|
+
clone.fills = [bgPaint];
|
|
307
|
+
|
|
308
|
+
const labelNode = clone.findOne(n => n.name === 'label');
|
|
309
|
+
const textPaint = figma.variables.setBoundVariableForPaint(
|
|
310
|
+
{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }, 'color', txtVar
|
|
311
|
+
);
|
|
312
|
+
labelNode.fills = [textPaint];
|
|
313
|
+
|
|
314
|
+
clone.setPluginData('dsb_run_id', RUN_ID);
|
|
315
|
+
clone.setPluginData('dsb_key', `component/button/variant/${size}/${style}/${state}`);
|
|
316
|
+
|
|
317
|
+
components.push(clone);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
figma.closePlugin(JSON.stringify({ variantIds: components.map(c => c.id) }));
|
|
323
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
324
|
+
})();
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 5. `combineAsVariants` + Grid Layout
|
|
330
|
+
|
|
331
|
+
After all variant components exist, combine them into a ComponentSet and position them in a grid. This MUST be a separate `use_figma` call — you must pass in all variant IDs from the previous call's return value.
|
|
332
|
+
|
|
333
|
+
### Grid Design Conventions
|
|
334
|
+
|
|
335
|
+
Professional design systems lay out variants in a readable grid where:
|
|
336
|
+
- **Columns** = the property users interact with most (typically **State**: Default, Hover, Focused, Pressed, Disabled)
|
|
337
|
+
- **Rows** = structural axes grouped together (typically **Size × Style**, where Size varies fastest)
|
|
338
|
+
- **Gap** = 16–40px between variants (20px is a safe default; match existing file if one exists)
|
|
339
|
+
- **Padding** = 40px around the grid inside the ComponentSet frame
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
Visual structure:
|
|
343
|
+
Default Hover Focused Pressed Disabled
|
|
344
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
345
|
+
│ Small/Primary [comp] [comp] [comp] [comp] [comp] │
|
|
346
|
+
│ Small/Secondary [comp] [comp] [comp] [comp] [comp] │
|
|
347
|
+
│ Medium/Primary [comp] [comp] [comp] [comp] [comp] │
|
|
348
|
+
│ Medium/Secondary[comp] [comp] [comp] [comp] [comp] │
|
|
349
|
+
│ Large/Primary [comp] [comp] [comp] [comp] [comp] │
|
|
350
|
+
│ Large/Secondary [comp] [comp] [comp] [comp] [comp] │
|
|
351
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Why State on columns?** State is the axis designers scan horizontally to verify interaction consistency. Size/Style define the "identity" of each row. This matches how professional design systems (M3, Polaris, Simple DS) organize their grids.
|
|
355
|
+
|
|
356
|
+
### Adding Row/Column Header Labels
|
|
357
|
+
|
|
358
|
+
After laying out the grid, add text labels OUTSIDE the ComponentSet to help navigation. These are siblings of the ComponentSet on the page — not children of it:
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// Add column headers above the component set
|
|
362
|
+
const colLabels = ['Default', 'Hover', 'Focused', 'Pressed', 'Disabled'];
|
|
363
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
|
|
364
|
+
for (let i = 0; i < colLabels.length; i++) {
|
|
365
|
+
const label = figma.createText();
|
|
366
|
+
label.fontName = { family: 'Inter', style: 'Medium' };
|
|
367
|
+
label.characters = colLabels[i];
|
|
368
|
+
label.fontSize = 11;
|
|
369
|
+
label.fills = [{ type: 'SOLID', color: { r: 0.5, g: 0.5, b: 0.5 } }];
|
|
370
|
+
label.x = cs.x + padding + i * (childWidth + gap);
|
|
371
|
+
label.y = cs.y - 20;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Add row headers to the left of the component set
|
|
375
|
+
const rowLabels = ['Small / Primary', 'Small / Secondary', 'Med / Primary', ...];
|
|
376
|
+
for (let i = 0; i < rowLabels.length; i++) {
|
|
377
|
+
const label = figma.createText();
|
|
378
|
+
label.fontName = { family: 'Inter', style: 'Medium' };
|
|
379
|
+
label.characters = rowLabels[i];
|
|
380
|
+
label.fontSize = 11;
|
|
381
|
+
label.fills = [{ type: 'SOLID', color: { r: 0.5, g: 0.5, b: 0.5 } }];
|
|
382
|
+
label.x = cs.x - 120;
|
|
383
|
+
label.y = cs.y + padding + i * (childHeight + gap) + childHeight / 2 - 6;
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Note:** These labels are documentation aids, not part of the component itself. They help designers navigate the variant grid.
|
|
388
|
+
|
|
389
|
+
### Grid layout code
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
(async () => {
|
|
393
|
+
try {
|
|
394
|
+
const VARIANT_IDS = ['ID1', 'ID2', '...']; // from state ledger
|
|
395
|
+
const PAGE_ID = 'PAGE_ID'; // from state ledger
|
|
396
|
+
|
|
397
|
+
await figma.setCurrentPageAsync(await figma.getNodeByIdAsync(PAGE_ID));
|
|
398
|
+
|
|
399
|
+
// Collect component nodes
|
|
400
|
+
const components = await Promise.all(
|
|
401
|
+
VARIANT_IDS.map(id => figma.getNodeByIdAsync(id))
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Combine as variants
|
|
405
|
+
const cs = figma.combineAsVariants(components, figma.currentPage);
|
|
406
|
+
cs.name = 'Button';
|
|
407
|
+
|
|
408
|
+
// Grid layout: position each variant based on its property values
|
|
409
|
+
// Determine column axis (State) and row axes (Size × Style)
|
|
410
|
+
const axes = {
|
|
411
|
+
Size: ['Small', 'Medium', 'Large'],
|
|
412
|
+
Style: ['Primary', 'Secondary'],
|
|
413
|
+
State: ['Default', 'Hover', 'Disabled'],
|
|
414
|
+
};
|
|
415
|
+
const COL_AXIS = 'State'; // columns
|
|
416
|
+
const ROW_AXES = ['Size', 'Style']; // rows (Size changes fastest)
|
|
417
|
+
|
|
418
|
+
const gap = 16;
|
|
419
|
+
const padding = 40;
|
|
420
|
+
|
|
421
|
+
// Measure child dimensions (all should be same height within Size tier)
|
|
422
|
+
// Use the first child as reference for column width
|
|
423
|
+
const childWidth = 120; // approximate; refine after first screenshot
|
|
424
|
+
const childHeight = 40;
|
|
425
|
+
|
|
426
|
+
cs.children.forEach(child => {
|
|
427
|
+
const props = {};
|
|
428
|
+
child.name.split(', ').forEach(part => {
|
|
429
|
+
const [k, v] = part.split('=');
|
|
430
|
+
props[k] = v;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const colIdx = axes[COL_AXIS].indexOf(props[COL_AXIS]);
|
|
434
|
+
// Row = Size index * number of styles + Style index
|
|
435
|
+
const rowIdx = axes.Size.indexOf(props.Size) * axes.Style.length
|
|
436
|
+
+ axes.Style.indexOf(props.Style);
|
|
437
|
+
|
|
438
|
+
child.x = padding + colIdx * (childWidth + gap);
|
|
439
|
+
child.y = padding + rowIdx * (childHeight + gap);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Resize component set to fit all children + padding
|
|
443
|
+
let maxX = 0, maxY = 0;
|
|
444
|
+
for (const child of cs.children) {
|
|
445
|
+
maxX = Math.max(maxX, child.x + child.width);
|
|
446
|
+
maxY = Math.max(maxY, child.y + child.height);
|
|
447
|
+
}
|
|
448
|
+
cs.resizeWithoutConstraints(maxX + padding, maxY + padding);
|
|
449
|
+
|
|
450
|
+
// Style the component set frame
|
|
451
|
+
cs.fills = [{ type: 'SOLID', color: { r: 0.95, g: 0.95, b: 0.98 } }];
|
|
452
|
+
cs.cornerRadius = 8;
|
|
453
|
+
|
|
454
|
+
// Position component set on page (to the right of doc frame)
|
|
455
|
+
cs.x = 680;
|
|
456
|
+
cs.y = 40;
|
|
457
|
+
|
|
458
|
+
cs.setPluginData('dsb_run_id', 'ds-build-2024-001');
|
|
459
|
+
cs.setPluginData('dsb_key', 'componentset/button');
|
|
460
|
+
|
|
461
|
+
figma.closePlugin(JSON.stringify({ componentSetId: cs.id }));
|
|
462
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
463
|
+
})();
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Critical rules for combineAsVariants:**
|
|
467
|
+
- `components` must be a non-empty array containing ONLY `ComponentNode` objects (not frames, not groups)
|
|
468
|
+
- After combining, children are placed at (0,0) and overlap — you MUST manually position them
|
|
469
|
+
- `resizeWithoutConstraints` is required after positioning to make the component set frame fit its contents
|
|
470
|
+
- There is no `figma.createComponentSet()` — you cannot create an empty component set
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 6. Component Properties
|
|
475
|
+
|
|
476
|
+
Add TEXT, BOOLEAN, and INSTANCE_SWAP properties to the ComponentSet (not to individual variants). The return value of `addComponentProperty` is the actual property key (it gets a `#id:id` suffix appended) — save this key and use it immediately when setting `componentPropertyReferences`.
|
|
477
|
+
|
|
478
|
+
### TEXT Properties
|
|
479
|
+
|
|
480
|
+
Expose editable text in instances:
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
// On the ComponentSetNode (cs):
|
|
484
|
+
const labelKey = cs.addComponentProperty('Label', 'TEXT', 'Button');
|
|
485
|
+
// labelKey is now something like "Label#0:1"
|
|
486
|
+
|
|
487
|
+
// Wire to the label child in each variant:
|
|
488
|
+
for (const child of cs.children) {
|
|
489
|
+
const labelNode = child.findOne(n => n.name === 'label');
|
|
490
|
+
if (labelNode) {
|
|
491
|
+
labelNode.componentPropertyReferences = { characters: labelKey };
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### BOOLEAN Properties
|
|
497
|
+
|
|
498
|
+
Toggle child node visibility:
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
const showIconKey = cs.addComponentProperty('Show Icon', 'BOOLEAN', true);
|
|
502
|
+
|
|
503
|
+
for (const child of cs.children) {
|
|
504
|
+
const iconNode = child.findOne(n => n.name === 'icon');
|
|
505
|
+
if (iconNode) {
|
|
506
|
+
iconNode.componentPropertyReferences = { visible: showIconKey };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### INSTANCE_SWAP Properties
|
|
512
|
+
|
|
513
|
+
Allow swapping a nested component instance (e.g., swap the icon):
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
// defaultIconCompId is the ID of the default icon component (from state ledger)
|
|
517
|
+
const iconKey = cs.addComponentProperty('Icon', 'INSTANCE_SWAP', DEFAULT_ICON_COMP_ID);
|
|
518
|
+
|
|
519
|
+
for (const child of cs.children) {
|
|
520
|
+
const iconSlot = child.findOne(n => n.name === 'icon');
|
|
521
|
+
if (iconSlot && iconSlot.type === 'INSTANCE') {
|
|
522
|
+
iconSlot.componentPropertyReferences = { mainComponent: iconKey };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**Use INSTANCE_SWAP instead of creating a variant per icon.** Never add "Icon=ChevronRight, Icon=ChevronLeft, ..." as VARIANT axes — that causes combinatorial explosion. One INSTANCE_SWAP property covers all icons.
|
|
528
|
+
|
|
529
|
+
### Creating Icon Components for INSTANCE_SWAP
|
|
530
|
+
|
|
531
|
+
INSTANCE_SWAP needs a real Component ID as its default value. Before wiring INSTANCE_SWAP, you need at least one icon component. Here's how to create icons from SVG:
|
|
532
|
+
|
|
533
|
+
```javascript
|
|
534
|
+
(async () => {
|
|
535
|
+
try {
|
|
536
|
+
// Create a simple icon component from SVG
|
|
537
|
+
const svgNode = figma.createNodeFromSvg(
|
|
538
|
+
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' +
|
|
539
|
+
'<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
540
|
+
'</svg>'
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// Wrap in a component
|
|
544
|
+
const iconComp = figma.createComponent();
|
|
545
|
+
iconComp.name = 'Icon/ChevronRight';
|
|
546
|
+
iconComp.resize(24, 24);
|
|
547
|
+
iconComp.clipsContent = true;
|
|
548
|
+
|
|
549
|
+
// Move SVG children into the component
|
|
550
|
+
for (const child of [...svgNode.children]) {
|
|
551
|
+
iconComp.appendChild(child);
|
|
552
|
+
}
|
|
553
|
+
svgNode.remove();
|
|
554
|
+
|
|
555
|
+
// Bind the icon fill to a color variable (so it respects themes)
|
|
556
|
+
// Find vector children and bind their fills
|
|
557
|
+
iconComp.findAll(n => n.type === 'VECTOR').forEach(vec => {
|
|
558
|
+
// For stroke-based icons:
|
|
559
|
+
if (vec.strokes.length > 0) {
|
|
560
|
+
const strokePaint = figma.variables.setBoundVariableForPaint(
|
|
561
|
+
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', iconColorVar
|
|
562
|
+
);
|
|
563
|
+
vec.strokes = [strokePaint];
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
iconComp.setPluginData('dsb_run_id', RUN_ID);
|
|
568
|
+
iconComp.setPluginData('dsb_key', 'icon/chevron-right');
|
|
569
|
+
|
|
570
|
+
figma.closePlugin(JSON.stringify({ iconCompId: iconComp.id }));
|
|
571
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
572
|
+
})();
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
**Then use the returned `iconCompId` as the default value for INSTANCE_SWAP:**
|
|
576
|
+
```javascript
|
|
577
|
+
const iconKey = cs.addComponentProperty('Icon', 'INSTANCE_SWAP', ICON_COMP_ID);
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Constraining swap options with `preferredValues`:**
|
|
581
|
+
After adding the INSTANCE_SWAP property, you can optionally limit which components appear in the swap picker:
|
|
582
|
+
```javascript
|
|
583
|
+
// Get the property definitions to find the exact key
|
|
584
|
+
const props = cs.componentPropertyDefinitions;
|
|
585
|
+
const iconPropKey = Object.keys(props).find(k => k.startsWith('Icon'));
|
|
586
|
+
|
|
587
|
+
// Set preferred values (array of component keys or instance IDs)
|
|
588
|
+
cs.editComponentProperty(iconPropKey, {
|
|
589
|
+
preferredValues: [
|
|
590
|
+
{ type: 'COMPONENT', key: chevronRightComp.key },
|
|
591
|
+
{ type: 'COMPONENT', key: chevronLeftComp.key },
|
|
592
|
+
{ type: 'COMPONENT', key: closeComp.key },
|
|
593
|
+
],
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Icon library tip:** Create all icon components on a dedicated `Icons` page before building any UI components. Then reference their IDs when wiring INSTANCE_SWAP properties.
|
|
598
|
+
|
|
599
|
+
### `componentPropertyReferences` mapping
|
|
600
|
+
|
|
601
|
+
The `componentPropertyReferences` object maps a node's own property to a component property key:
|
|
602
|
+
|
|
603
|
+
| Node property | Component property type | Used for |
|
|
604
|
+
|---|---|---|
|
|
605
|
+
| `characters` | TEXT | Editable text content |
|
|
606
|
+
| `visible` | BOOLEAN | Show/hide toggle |
|
|
607
|
+
| `mainComponent` | INSTANCE_SWAP | Swap nested instances |
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## 7. `pluginData` Tagging for Idempotency
|
|
612
|
+
|
|
613
|
+
Tag EVERY created node immediately after creation. This enables safe cleanup, resumability, and idempotency checks.
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
// After creating any node:
|
|
617
|
+
node.setPluginData('dsb_run_id', RUN_ID); // identifies the build run
|
|
618
|
+
node.setPluginData('dsb_phase', 'phase3'); // which phase created it
|
|
619
|
+
node.setPluginData('dsb_key', KEY); // unique logical key for this entity
|
|
620
|
+
|
|
621
|
+
// Reading back:
|
|
622
|
+
const runId = node.getPluginData('dsb_run_id'); // '' if not set
|
|
623
|
+
const key = node.getPluginData('dsb_key');
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Key naming convention:** use `/`-separated logical paths that mirror the entity hierarchy:
|
|
627
|
+
```
|
|
628
|
+
'component/button/base'
|
|
629
|
+
'component/button/variant/Medium/Primary/Default'
|
|
630
|
+
'componentset/button'
|
|
631
|
+
'doc/button'
|
|
632
|
+
'page/button'
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Idempotency check before creating:** before creating a node, scan the current page for an existing node with the same `dsb_key`:
|
|
636
|
+
|
|
637
|
+
```javascript
|
|
638
|
+
const existing = figma.currentPage.findAll(n =>
|
|
639
|
+
n.getPluginData('dsb_key') === 'componentset/button'
|
|
640
|
+
);
|
|
641
|
+
if (existing.length > 0) {
|
|
642
|
+
// Skip creation — already done. Return existing node's ID.
|
|
643
|
+
figma.closePlugin(JSON.stringify({ componentSetId: existing[0].id }));
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
## 8. Documentation
|
|
651
|
+
|
|
652
|
+
### Page title + description frame
|
|
653
|
+
|
|
654
|
+
The documentation frame (see Section 2) should contain:
|
|
655
|
+
1. Component name as a large title (32px+ Bold)
|
|
656
|
+
2. 1–3 sentence description of what the component is and when to use it
|
|
657
|
+
3. Spec notes (sizes, spacing values, accessibility notes)
|
|
658
|
+
|
|
659
|
+
### Component `description` property
|
|
660
|
+
|
|
661
|
+
Set the description on the ComponentSet — it appears in the Figma properties panel and is exported as documentation:
|
|
662
|
+
|
|
663
|
+
```javascript
|
|
664
|
+
cs.description = 'Buttons allow users to take actions and make choices. Use Primary for the highest-emphasis action on a page.';
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### `documentationLinks`
|
|
668
|
+
|
|
669
|
+
Link to external documentation (Storybook, design spec, tokens reference):
|
|
670
|
+
|
|
671
|
+
```javascript
|
|
672
|
+
cs.documentationLinks = [
|
|
673
|
+
{ uri: 'https://your-storybook.com/button' }
|
|
674
|
+
];
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Node names and organization
|
|
678
|
+
|
|
679
|
+
- ComponentSet: plain component name — `'Button'`
|
|
680
|
+
- Individual variants: `'Property=Value, Property=Value'` format (match the file's existing casing)
|
|
681
|
+
- Child nodes: semantic names — `'label'`, `'icon'`, `'container'`, `'state-layer'`
|
|
682
|
+
- Documentation frames: `'ComponentName / Documentation'`
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## 9. Validation
|
|
687
|
+
|
|
688
|
+
Always validate after creating or modifying a component before proceeding to the next one.
|
|
689
|
+
|
|
690
|
+
### `get_metadata` structural checks
|
|
691
|
+
|
|
692
|
+
After creating the component set, call `get_metadata` on the ComponentSet node and verify:
|
|
693
|
+
- `variantGroupProperties` lists the expected axes with the correct value arrays
|
|
694
|
+
- `componentPropertyDefinitions` contains the expected TEXT/BOOLEAN/INSTANCE_SWAP properties
|
|
695
|
+
- `children.length` equals the expected variant count (e.g., 18 for 3×2×3)
|
|
696
|
+
- No children are named `'Component 1'` (unnamed components are a sign of a bug)
|
|
697
|
+
|
|
698
|
+
### `get_screenshot` — Visual Validation (Critical)
|
|
699
|
+
|
|
700
|
+
`get_screenshot` returns an **image** of the specified node. Call it on the **component page node** (not the component set) to see the full page including documentation and grid labels.
|
|
701
|
+
|
|
702
|
+
```
|
|
703
|
+
Tool: get_screenshot
|
|
704
|
+
Args: { nodeId: "PAGE_NODE_ID", fileKey: "FILE_KEY" }
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**How to use the screenshot:**
|
|
708
|
+
|
|
709
|
+
1. **Display it to the user** — this is the primary purpose. Show the screenshot as part of the user checkpoint: "Here's the Button component. Does it look right?"
|
|
710
|
+
2. **Analyze it yourself** — if you have vision capabilities, check the visual checklist below. If you don't (text-only agent), fall back to structural validation only via `get_metadata` and describe what you created textually.
|
|
711
|
+
|
|
712
|
+
**Visual validation checklist** (check each item when viewing the screenshot):
|
|
713
|
+
|
|
714
|
+
| # | Check | What "good" looks like | What "broken" looks like |
|
|
715
|
+
|---|-------|----------------------|------------------------|
|
|
716
|
+
| 1 | **Grid layout** | Variants in neat rows and columns with consistent spacing | All variants piled at top-left (0,0 stacking bug) |
|
|
717
|
+
| 2 | **Color fills** | Components show distinct, correct colors per style variant | All components are black or same color (variable binding failed) |
|
|
718
|
+
| 3 | **Size differentiation** | Small variants are visibly smaller than Large variants | All variants are the same size (height/padding not bound to variables) |
|
|
719
|
+
| 4 | **Text readability** | Labels are visible with correct font and color | Text is invisible (white on white), missing, or shows "undefined" |
|
|
720
|
+
| 5 | **Spacing/padding** | Interior padding visible, components aren't "shrink-wrapped" | Components look cramped or have no visible internal space |
|
|
721
|
+
| 6 | **State differentiation** | Hover/Pressed variants have visible color differences from Default | All states look identical (state-specific fills not applied) |
|
|
722
|
+
| 7 | **Disabled state** | Lower opacity or muted colors compared to active states | Disabled looks identical to Default |
|
|
723
|
+
| 8 | **Documentation frame** | Title + description text visible above or beside the component grid | No documentation, or it overlaps the component set |
|
|
724
|
+
| 9 | **Grid labels** | Row/column headers visible around the component set (if added) | Labels overlap the grid or are missing |
|
|
725
|
+
| 10 | **Component set boundary** | Gray background frame wraps all variants with even padding | Frame is too small (variants clipped) or way too large |
|
|
726
|
+
|
|
727
|
+
**Screenshot → diagnosis → fix mapping:**
|
|
728
|
+
|
|
729
|
+
| Screenshot shows | Diagnosis | Fix script |
|
|
730
|
+
|-----------------|-----------|------------|
|
|
731
|
+
| All variants stacked top-left | Grid layout wasn't applied after `combineAsVariants` | Re-run the grid layout script (§5) |
|
|
732
|
+
| Everything black/same color | Variable bindings failed or variables don't have values for the active mode | Re-run variable binding, check mode values |
|
|
733
|
+
| No text visible | Font wasn't loaded, or text fill is same color as background | Check `loadFontAsync` was called; bind text fill to `color/text/*` variable |
|
|
734
|
+
| Variants all same size | Padding/height not bound to size variables | Re-run `bindVariablesToComponent` with size-specific tokens |
|
|
735
|
+
| Component set frame tiny | `resizeWithoutConstraints` wasn't called or used wrong dimensions | Re-calculate bounds from children and resize |
|
|
736
|
+
| Doc frame overlaps components | Component set positioned at same x,y as doc frame | Move component set: `cs.x = docFrame.x + docFrame.width + 60` |
|
|
737
|
+
|
|
738
|
+
**When visual analysis isn't available:**
|
|
739
|
+
If your model can't process images (text-only mode), validate structurally instead:
|
|
740
|
+
1. Call `get_metadata` on the component set — verify child count, property definitions, variant names
|
|
741
|
+
2. Run an `use_figma` that samples key properties:
|
|
742
|
+
```javascript
|
|
743
|
+
(async () => {
|
|
744
|
+
try {
|
|
745
|
+
const cs = await figma.getNodeByIdAsync(CS_ID);
|
|
746
|
+
const sample = cs.children.slice(0, 3).map(c => ({
|
|
747
|
+
name: c.name,
|
|
748
|
+
width: c.width, height: c.height,
|
|
749
|
+
x: c.x, y: c.y,
|
|
750
|
+
fills: c.fills?.map(f => f.type === 'SOLID' ?
|
|
751
|
+
{ r: f.color.r.toFixed(2), g: f.color.g.toFixed(2), b: f.color.b.toFixed(2), boundVar: f.boundVariables?.color?.id } : f.type
|
|
752
|
+
),
|
|
753
|
+
}));
|
|
754
|
+
figma.closePlugin(JSON.stringify({ sampleVariants: sample, totalChildren: cs.children.length }));
|
|
755
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
756
|
+
})();
|
|
757
|
+
```
|
|
758
|
+
This gives you positions (grid working?), dimensions (size differentiation?), and fill info (bindings working?) without needing vision.
|
|
759
|
+
|
|
760
|
+
**When to take a screenshot:**
|
|
761
|
+
- After EVERY completed component (mandatory — part of the user checkpoint)
|
|
762
|
+
- After creating the foundations documentation page
|
|
763
|
+
- After final QA (screenshot every page)
|
|
764
|
+
- Do NOT screenshot after every intermediate step (wastes tool calls)
|
|
765
|
+
|
|
766
|
+
### Common issues
|
|
767
|
+
|
|
768
|
+
| Symptom | Likely cause | Fix |
|
|
769
|
+
|---|---|---|
|
|
770
|
+
| All variants stacked at (0,0) | `combineAsVariants` was called but children were never repositioned | Re-run grid layout script |
|
|
771
|
+
| Variants show wrong colors | Variable bindings applied after `combineAsVariants` instead of before | Rebind on component set children |
|
|
772
|
+
| Variant count wrong | Clone loop indexing error | Print `components.map(c => c.name)` before combining |
|
|
773
|
+
| BOOLEAN property has no effect | `componentPropertyReferences` was set on the component set frame, not on the child node | Find the actual child node and set references there |
|
|
774
|
+
| INSTANCE_SWAP shows no swap option | Default value was not a valid component ID | Pass a real existing component ID as `defaultValue` |
|
|
775
|
+
| `combineAsVariants` throws | At least one node in the array is not a `ComponentNode` | Filter array: `nodes.filter(n => n.type === 'COMPONENT')` |
|
|
776
|
+
| `addComponentProperty` returns unexpected key | Expected — the key gets a `#id:id` suffix | Save the returned value immediately: `const key = cs.addComponentProperty(...)` |
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
## 10. Complete Worked Example: Button Component
|
|
781
|
+
|
|
782
|
+
This shows the full sequence of `use_figma` calls for a Button component, including state passing between calls. Replace `RUN_ID` and variable IDs with your actual values from the state ledger.
|
|
783
|
+
|
|
784
|
+
### Call 1: Create the component page
|
|
785
|
+
|
|
786
|
+
**Goal:** Create (or find) the Button page.
|
|
787
|
+
**State input:** None
|
|
788
|
+
**State output:** `{ pageId }`
|
|
789
|
+
|
|
790
|
+
```javascript
|
|
791
|
+
(async () => {
|
|
792
|
+
try {
|
|
793
|
+
let page = figma.root.children.find(p => p.name === 'Button');
|
|
794
|
+
if (!page) { page = figma.createPage(); page.name = 'Button'; }
|
|
795
|
+
page.setPluginData('dsb_run_id', 'ds-build-2024-001');
|
|
796
|
+
page.setPluginData('dsb_key', 'page/button');
|
|
797
|
+
figma.closePlugin(JSON.stringify({ pageId: page.id }));
|
|
798
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
799
|
+
})();
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Call 2: Create documentation frame
|
|
803
|
+
|
|
804
|
+
**Goal:** Add title + description frame.
|
|
805
|
+
**State input:** `{ pageId }`
|
|
806
|
+
**State output:** `{ docFrameId }`
|
|
807
|
+
|
|
808
|
+
```javascript
|
|
809
|
+
(async () => {
|
|
810
|
+
try {
|
|
811
|
+
const PAGE_ID = 'PAGE_ID_FROM_STATE';
|
|
812
|
+
const page = await figma.getNodeByIdAsync(PAGE_ID);
|
|
813
|
+
await figma.setCurrentPageAsync(page);
|
|
814
|
+
|
|
815
|
+
// Idempotency check
|
|
816
|
+
const existing = page.findAll(n => n.getPluginData('dsb_key') === 'doc/button');
|
|
817
|
+
if (existing.length > 0) {
|
|
818
|
+
figma.closePlugin(JSON.stringify({ docFrameId: existing[0].id }));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' });
|
|
823
|
+
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
|
|
824
|
+
|
|
825
|
+
const docFrame = figma.createFrame();
|
|
826
|
+
docFrame.name = 'Button / Documentation';
|
|
827
|
+
docFrame.x = 40; docFrame.y = 40;
|
|
828
|
+
docFrame.layoutMode = 'VERTICAL';
|
|
829
|
+
docFrame.primaryAxisSizingMode = 'AUTO';
|
|
830
|
+
docFrame.counterAxisSizingMode = 'FIXED';
|
|
831
|
+
docFrame.resize(560, 100);
|
|
832
|
+
docFrame.paddingTop = 40; docFrame.paddingBottom = 40;
|
|
833
|
+
docFrame.paddingLeft = 40; docFrame.paddingRight = 40;
|
|
834
|
+
docFrame.itemSpacing = 16;
|
|
835
|
+
docFrame.fills = [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }];
|
|
836
|
+
|
|
837
|
+
const title = figma.createText();
|
|
838
|
+
title.fontName = { family: 'Inter', style: 'Bold' };
|
|
839
|
+
title.fontSize = 32;
|
|
840
|
+
title.characters = 'Button';
|
|
841
|
+
docFrame.appendChild(title);
|
|
842
|
+
|
|
843
|
+
const desc = figma.createText();
|
|
844
|
+
desc.fontName = { family: 'Inter', style: 'Regular' };
|
|
845
|
+
desc.fontSize = 14;
|
|
846
|
+
desc.characters = 'Buttons allow users to take actions with a single tap. Use Primary for the highest-emphasis action on a page, Secondary for supporting actions.';
|
|
847
|
+
desc.layoutSizingHorizontal = 'FILL';
|
|
848
|
+
docFrame.appendChild(desc);
|
|
849
|
+
|
|
850
|
+
docFrame.setPluginData('dsb_run_id', 'ds-build-2024-001');
|
|
851
|
+
docFrame.setPluginData('dsb_key', 'doc/button');
|
|
852
|
+
|
|
853
|
+
figma.closePlugin(JSON.stringify({ docFrameId: docFrame.id }));
|
|
854
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
855
|
+
})();
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Call 3: Create base component
|
|
859
|
+
|
|
860
|
+
**Goal:** Create the base component with auto-layout and all variable bindings.
|
|
861
|
+
**State input:** `{ pageId }` + variable IDs from Phase 1
|
|
862
|
+
**State output:** `{ baseCompId }`
|
|
863
|
+
|
|
864
|
+
*(See Section 3 for full code — substituting the actual variable IDs from the state ledger.)*
|
|
865
|
+
|
|
866
|
+
### Call 4: Create all variants
|
|
867
|
+
|
|
868
|
+
**Goal:** Clone base and produce all 18 variants (3 Size × 2 Style × 3 State).
|
|
869
|
+
**State input:** `{ pageId, baseCompId }` + variable IDs
|
|
870
|
+
**State output:** `{ variantIds: ['id1', 'id2', ..., 'id18'] }`
|
|
871
|
+
|
|
872
|
+
```javascript
|
|
873
|
+
(async () => {
|
|
874
|
+
try {
|
|
875
|
+
const RUN_ID = 'ds-build-2024-001';
|
|
876
|
+
const BASE_ID = 'BASE_COMP_ID_FROM_STATE';
|
|
877
|
+
const PAGE_ID = 'PAGE_ID_FROM_STATE';
|
|
878
|
+
// Variable IDs from state ledger:
|
|
879
|
+
const VAR = {
|
|
880
|
+
bg_primary: 'VAR_ID_1',
|
|
881
|
+
text_primary: 'VAR_ID_2',
|
|
882
|
+
bg_secondary: 'VAR_ID_3',
|
|
883
|
+
text_secondary: 'VAR_ID_4',
|
|
884
|
+
bg_disabled: 'VAR_ID_5',
|
|
885
|
+
text_disabled: 'VAR_ID_6',
|
|
886
|
+
padding_sm: 'VAR_ID_7',
|
|
887
|
+
padding_md: 'VAR_ID_8',
|
|
888
|
+
padding_lg: 'VAR_ID_9',
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
const page = await figma.getNodeByIdAsync(PAGE_ID);
|
|
892
|
+
await figma.setCurrentPageAsync(page);
|
|
893
|
+
|
|
894
|
+
const base = await figma.getNodeByIdAsync(BASE_ID);
|
|
895
|
+
|
|
896
|
+
// Load all variables
|
|
897
|
+
const vars = {};
|
|
898
|
+
for (const [k, v] of Object.entries(VAR)) {
|
|
899
|
+
vars[k] = await figma.variables.getVariableByIdAsync(v);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const axes = {
|
|
903
|
+
Size: ['Small', 'Medium', 'Large'],
|
|
904
|
+
Style: ['Primary', 'Secondary'],
|
|
905
|
+
State: ['Default', 'Hover', 'Disabled'],
|
|
906
|
+
};
|
|
907
|
+
const paddingMap = { Small: vars.padding_sm, Medium: vars.padding_md, Large: vars.padding_lg };
|
|
908
|
+
|
|
909
|
+
const components = [];
|
|
910
|
+
for (const size of axes.Size) {
|
|
911
|
+
for (const style of axes.Style) {
|
|
912
|
+
for (const state of axes.State) {
|
|
913
|
+
const clone = base.clone();
|
|
914
|
+
clone.name = `Size=${size}, Style=${style}, State=${state}`;
|
|
915
|
+
|
|
916
|
+
clone.setBoundVariable('paddingTop', paddingMap[size]);
|
|
917
|
+
clone.setBoundVariable('paddingBottom', paddingMap[size]);
|
|
918
|
+
clone.setBoundVariable('paddingLeft', paddingMap[size]);
|
|
919
|
+
clone.setBoundVariable('paddingRight', paddingMap[size]);
|
|
920
|
+
|
|
921
|
+
const isDisabled = state === 'Disabled';
|
|
922
|
+
const bgV = isDisabled ? vars.bg_disabled : (style === 'Primary' ? vars.bg_primary : vars.bg_secondary);
|
|
923
|
+
const txV = isDisabled ? vars.text_disabled : (style === 'Primary' ? vars.text_primary : vars.text_secondary);
|
|
924
|
+
|
|
925
|
+
clone.fills = [figma.variables.setBoundVariableForPaint(
|
|
926
|
+
{ type: 'SOLID', color: { r: 0, g: 0, b: 0 } }, 'color', bgV
|
|
927
|
+
)];
|
|
928
|
+
|
|
929
|
+
const labelNode = clone.findOne(n => n.name === 'label');
|
|
930
|
+
labelNode.fills = [figma.variables.setBoundVariableForPaint(
|
|
931
|
+
{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }, 'color', txV
|
|
932
|
+
)];
|
|
933
|
+
|
|
934
|
+
clone.setPluginData('dsb_run_id', RUN_ID);
|
|
935
|
+
clone.setPluginData('dsb_key', `component/button/variant/${size}/${style}/${state}`);
|
|
936
|
+
components.push(clone);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
figma.closePlugin(JSON.stringify({ variantIds: components.map(c => c.id) }));
|
|
942
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
943
|
+
})();
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Call 5: combineAsVariants + grid layout
|
|
947
|
+
|
|
948
|
+
**Goal:** Combine all 18 variants into a ComponentSet and lay them out in a grid.
|
|
949
|
+
**State input:** `{ pageId, variantIds }` (18 IDs)
|
|
950
|
+
**State output:** `{ componentSetId }`
|
|
951
|
+
|
|
952
|
+
*(See Section 5 for full code.)*
|
|
953
|
+
|
|
954
|
+
### Call 6: Add component properties
|
|
955
|
+
|
|
956
|
+
**Goal:** Add TEXT, BOOLEAN, INSTANCE_SWAP properties and wire them to child nodes.
|
|
957
|
+
**State input:** `{ pageId, componentSetId }`
|
|
958
|
+
**State output:** `{ componentSetId, properties: { labelKey, showIconKey, iconKey } }`
|
|
959
|
+
|
|
960
|
+
```javascript
|
|
961
|
+
(async () => {
|
|
962
|
+
try {
|
|
963
|
+
const CS_ID = 'CS_ID_FROM_STATE';
|
|
964
|
+
const DEFAULT_ICON_ID = 'ICON_COMP_ID_FROM_STATE';
|
|
965
|
+
const page = figma.root.children.find(p => p.name === 'Button');
|
|
966
|
+
await figma.setCurrentPageAsync(page);
|
|
967
|
+
|
|
968
|
+
const cs = await figma.getNodeByIdAsync(CS_ID);
|
|
969
|
+
cs.description = 'Buttons allow users to take actions and make choices with a single tap.';
|
|
970
|
+
cs.documentationLinks = [{ uri: 'https://your-storybook.com/button' }];
|
|
971
|
+
|
|
972
|
+
// Add properties — save returned keys
|
|
973
|
+
const labelKey = cs.addComponentProperty('Label', 'TEXT', 'Button');
|
|
974
|
+
const showIconKey = cs.addComponentProperty('Show Icon', 'BOOLEAN', true);
|
|
975
|
+
const iconKey = cs.addComponentProperty('Icon', 'INSTANCE_SWAP', DEFAULT_ICON_ID);
|
|
976
|
+
|
|
977
|
+
// Wire to children
|
|
978
|
+
for (const child of cs.children) {
|
|
979
|
+
const labelNode = child.findOne(n => n.name === 'label');
|
|
980
|
+
if (labelNode) labelNode.componentPropertyReferences = { characters: labelKey };
|
|
981
|
+
|
|
982
|
+
const iconNode = child.findOne(n => n.name === 'icon');
|
|
983
|
+
if (iconNode) {
|
|
984
|
+
iconNode.componentPropertyReferences = {
|
|
985
|
+
visible: showIconKey,
|
|
986
|
+
...(iconNode.type === 'INSTANCE' ? { mainComponent: iconKey } : {}),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
figma.closePlugin(JSON.stringify({
|
|
992
|
+
componentSetId: cs.id,
|
|
993
|
+
properties: { labelKey, showIconKey, iconKey },
|
|
994
|
+
}));
|
|
995
|
+
} catch (e) { figma.closePluginWithFailure(e.toString()); }
|
|
996
|
+
})();
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
### Call 7: Validate with get_metadata
|
|
1000
|
+
|
|
1001
|
+
**Goal:** Structural check — variant count, properties, axes.
|
|
1002
|
+
**Action:** Call `get_metadata` on the ComponentSet node ID (from state). Verify in the result:
|
|
1003
|
+
- `children.length === 18`
|
|
1004
|
+
- `variantGroupProperties` has `Size`, `Style`, `State` keys with correct value arrays
|
|
1005
|
+
- `componentPropertyDefinitions` has `Label`, `Show Icon`, `Icon` entries
|
|
1006
|
+
|
|
1007
|
+
### Call 8: Validate with get_screenshot
|
|
1008
|
+
|
|
1009
|
+
**Goal:** Visual check — layout, colors, text.
|
|
1010
|
+
**Action:** Call `get_screenshot` on the Button page. Inspect the screenshot. If variants are stacked, re-run Call 5. If colors look wrong, inspect variable bindings.
|
|
1011
|
+
|
|
1012
|
+
### Checkpoint
|
|
1013
|
+
|
|
1014
|
+
After Call 8: show the screenshot to the user. Ask: "Here's the Button component with 18 variants. Does this look correct?" Do not proceed to the next component until the user approves.
|