@sarjallab09/figma-intelligence 1.1.0 → 1.2.1
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/README.md +69 -36
- package/dist/bin/cli.js +2 -0
- package/dist/design-bridge/bridge.js +2 -0
- package/dist/figma-bridge-plugin/bridge-relay.js +2 -0
- package/dist/figma-bridge-plugin/code.js +1 -0
- package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package-lock.json +0 -3
- package/dist/figma-bridge-plugin/ui.html +4970 -0
- package/dist/figma-intelligence-layer/dist/index.js +2 -0
- package/dist/scripts/clean-existing-chunks.js +2 -0
- package/dist/scripts/connect-ai-tool.js +2 -0
- package/dist/scripts/convert-hub-pdfs.js +2 -0
- package/dist/scripts/figma-mcp-status.js +2 -0
- package/dist/scripts/register-codex-mcp.js +2 -0
- package/dist/scripts/test-copilot-chat.js +2 -0
- package/package.json +11 -8
- package/bin/cli.js +0 -859
- package/design-bridge/bridge.js +0 -196
- package/design-bridge/lib/assets.js +0 -367
- package/design-bridge/lib/prompt.js +0 -85
- package/design-bridge/lib/server.js +0 -66
- package/design-bridge/lib/stitch.js +0 -37
- package/design-bridge/lib/tokens.js +0 -82
- package/design-bridge/package-lock.json +0 -579
- package/figma-bridge-plugin/README.md +0 -97
- package/figma-bridge-plugin/anthropic-chat-runner.js +0 -192
- package/figma-bridge-plugin/bridge-relay.js +0 -2505
- package/figma-bridge-plugin/chat-runner.js +0 -485
- package/figma-bridge-plugin/code.js +0 -1534
- package/figma-bridge-plugin/codex-runner.js +0 -505
- package/figma-bridge-plugin/component-schemas.js +0 -110
- package/figma-bridge-plugin/content-context.js +0 -869
- package/figma-bridge-plugin/create-button.js +0 -216
- package/figma-bridge-plugin/gemini-cli-runner.js +0 -291
- package/figma-bridge-plugin/gemini-runner.js +0 -187
- package/figma-bridge-plugin/html-to-figma.js +0 -927
- package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +0 -162
- package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +0 -148
- package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +0 -175
- package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +0 -165
- package/figma-bridge-plugin/perplexity-runner.js +0 -188
- package/figma-bridge-plugin/references/SKILL.md +0 -178
- package/figma-bridge-plugin/references/anatomy-spec.md +0 -159
- package/figma-bridge-plugin/references/api-spec.md +0 -162
- package/figma-bridge-plugin/references/color-spec.md +0 -148
- package/figma-bridge-plugin/references/full-spec-template.md +0 -314
- package/figma-bridge-plugin/references/property-spec.md +0 -175
- package/figma-bridge-plugin/references/screen-reader-spec.md +0 -180
- package/figma-bridge-plugin/references/structure-spec.md +0 -165
- package/figma-bridge-plugin/shared-prompt-config.js +0 -645
- package/figma-bridge-plugin/spec-helpers/build-table.js +0 -269
- package/figma-bridge-plugin/spec-helpers/classify-elements.js +0 -189
- package/figma-bridge-plugin/spec-helpers/index.js +0 -35
- package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +0 -49
- package/figma-bridge-plugin/spec-helpers/position-markers.js +0 -158
- package/figma-bridge-plugin/stitch-auth.js +0 -322
- package/figma-bridge-plugin/stitch-runner.js +0 -1427
- package/figma-bridge-plugin/token-resolver.js +0 -107
- package/figma-bridge-plugin/ui.html +0 -4542
- package/figma-intelligence-layer/.env.example +0 -39
- package/figma-intelligence-layer/docs/local-image-generation.md +0 -60
- package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +0 -101
- package/figma-intelligence-layer/jest.config.js +0 -14
- package/figma-intelligence-layer/mcp-config.json +0 -19
- package/figma-intelligence-layer/package-lock.json +0 -5892
- package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +0 -67
- package/figma-intelligence-layer/scripts/start-comfyui.sh +0 -33
- package/figma-intelligence-layer/src/index.ts +0 -2233
- package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +0 -404
- package/figma-intelligence-layer/src/shared/cache.ts +0 -187
- package/figma-intelligence-layer/src/shared/color-operations.ts +0 -533
- package/figma-intelligence-layer/src/shared/color-utils.ts +0 -138
- package/figma-intelligence-layer/src/shared/component-script-builder.ts +0 -413
- package/figma-intelligence-layer/src/shared/component-templates.ts +0 -2767
- package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +0 -694
- package/figma-intelligence-layer/src/shared/decision-log.ts +0 -128
- package/figma-intelligence-layer/src/shared/design-system-context.ts +0 -568
- package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +0 -131
- package/figma-intelligence-layer/src/shared/design-system-matcher.ts +0 -184
- package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +0 -196
- package/figma-intelligence-layer/src/shared/design-system-tokens.ts +0 -295
- package/figma-intelligence-layer/src/shared/dtcg-validator.ts +0 -530
- package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +0 -671
- package/figma-intelligence-layer/src/shared/figma-bridge.ts +0 -1418
- package/figma-intelligence-layer/src/shared/font-config.ts +0 -126
- package/figma-intelligence-layer/src/shared/icon-catalog.ts +0 -360
- package/figma-intelligence-layer/src/shared/icon-fetch.ts +0 -80
- package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +0 -162
- package/figma-intelligence-layer/src/shared/response-compression.ts +0 -440
- package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +0 -324
- package/figma-intelligence-layer/src/shared/token-binder.ts +0 -505
- package/figma-intelligence-layer/src/shared/token-math.ts +0 -427
- package/figma-intelligence-layer/src/shared/token-naming.ts +0 -468
- package/figma-intelligence-layer/src/shared/token-utils.ts +0 -420
- package/figma-intelligence-layer/src/shared/types.ts +0 -346
- package/figma-intelligence-layer/src/shared/typography-presets.ts +0 -94
- package/figma-intelligence-layer/src/shared/unsplash.ts +0 -165
- package/figma-intelligence-layer/src/shared/vision-client.ts +0 -607
- package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +0 -334
- package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +0 -446
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +0 -782
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +0 -496
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +0 -230
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +0 -66
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +0 -810
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +0 -1191
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +0 -1346
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +0 -148
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +0 -499
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +0 -910
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +0 -989
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +0 -1160
- package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +0 -424
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +0 -38
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +0 -111
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +0 -114
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +0 -103
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +0 -1060
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +0 -39
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +0 -298
- package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +0 -197
- package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +0 -494
- package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +0 -356
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +0 -123
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +0 -663
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +0 -56
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +0 -614
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +0 -113
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +0 -178
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +0 -470
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +0 -429
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +0 -226
- package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +0 -660
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +0 -209
- package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +0 -540
- package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +0 -391
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +0 -2019
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +0 -131
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +0 -381
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +0 -565
- package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +0 -764
- package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +0 -535
- package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +0 -84
- package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +0 -401
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +0 -68
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +0 -93
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +0 -596
- package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +0 -462
- package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +0 -1470
- package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +0 -829
- package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +0 -702
- package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +0 -483
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +0 -501
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +0 -106
- package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +0 -676
- package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +0 -560
- package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +0 -1043
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +0 -620
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +0 -331
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +0 -77
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +0 -54
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +0 -287
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +0 -43
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +0 -221
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +0 -166
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +0 -234
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +0 -249
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +0 -231
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +0 -240
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +0 -307
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +0 -227
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +0 -233
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +0 -276
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +0 -223
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +0 -290
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +0 -238
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +0 -128
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +0 -286
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +0 -255
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +0 -330
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +0 -247
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +0 -264
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +0 -261
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +0 -248
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +0 -251
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +0 -142
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +0 -282
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +0 -250
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +0 -258
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +0 -265
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +0 -256
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +0 -239
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +0 -252
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +0 -270
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +0 -244
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +0 -143
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +0 -243
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +0 -259
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +0 -293
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +0 -144
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +0 -289
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +0 -267
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +0 -232
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +0 -257
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +0 -319
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +0 -121
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +0 -430
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +0 -312
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +0 -129
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +0 -78
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +0 -2333
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +0 -100
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +0 -32
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +0 -59
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +0 -18
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +0 -53
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +0 -91
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +0 -71
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +0 -110
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +0 -19
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +0 -67
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +0 -58
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +0 -79
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +0 -50
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +0 -33
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +0 -55
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +0 -73
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +0 -81
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +0 -198
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +0 -701
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +0 -88
- package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +0 -135
- package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +0 -491
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +0 -416
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +0 -722
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +0 -449
- package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +0 -393
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +0 -406
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +0 -292
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +0 -24
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +0 -172
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +0 -409
- package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +0 -594
- package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +0 -710
- package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +0 -458
- package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +0 -134
- package/figma-intelligence-layer/tests/apg-doc.test.ts +0 -101
- package/figma-intelligence-layer/tests/design-system-context.test.ts +0 -152
- package/figma-intelligence-layer/tests/design-system-matcher.test.ts +0 -144
- package/figma-intelligence-layer/tests/figma-bridge.test.ts +0 -83
- package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +0 -56
- package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +0 -69
- package/figma-intelligence-layer/tests/smoke.test.ts +0 -174
- package/figma-intelligence-layer/tests/spec-generator.test.ts +0 -127
- package/figma-intelligence-layer/tests/token-migrate.test.ts +0 -21
- package/figma-intelligence-layer/tests/token-naming.test.ts +0 -30
- package/figma-intelligence-layer/tsconfig.json +0 -19
- package/scripts/clean-existing-chunks.js +0 -179
- package/scripts/connect-ai-tool.js +0 -490
- package/scripts/convert-hub-pdfs.js +0 -425
- package/scripts/figma-mcp-status.js +0 -349
- package/scripts/register-codex-mcp.js +0 -96
- /package/{design-bridge → dist/design-bridge}/.env.example +0 -0
- /package/{design-bridge → dist/design-bridge}/package.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/manifest.json +0 -0
- /package/{figma-bridge-plugin → dist/figma-bridge-plugin}/package.json +0 -0
- /package/{figma-intelligence-layer → dist/figma-intelligence-layer}/package.json +0 -0
|
@@ -1,1470 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// figma_export_tokens — Export Figma variables to code-ready formats
|
|
3
|
-
//
|
|
4
|
-
// Reads all variables from the connected Figma file via the bridge and exports
|
|
5
|
-
// them as CSS custom properties, SCSS, Tailwind config, Style Dictionary JSON,
|
|
6
|
-
// DTCG (W3C Design Token Community Group) JSON, Swift, Kotlin, Flutter, Android
|
|
7
|
-
// XML, JS, TS, Less, React Native, Tailwind v4, CSS rem, or plain JSON.
|
|
8
|
-
//
|
|
9
|
-
// Falls back to the built-in semantic token catalog when no Figma connection
|
|
10
|
-
// is available, so specs and code can still be generated offline.
|
|
11
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
-
|
|
13
|
-
import { getBridge } from "../../../shared/figma-bridge.js";
|
|
14
|
-
import { SEMANTIC_TOKEN_CATALOG, SemanticTokenEntry } from "../../../shared/semantic-token-catalog.js";
|
|
15
|
-
import { figmaRgbaToHex } from "../../../shared/token-utils.js";
|
|
16
|
-
import { formatCssColor } from "../../../shared/color-operations.js";
|
|
17
|
-
import { pxToRem } from "../../../shared/token-math.js";
|
|
18
|
-
|
|
19
|
-
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
export interface ExportTokensArgs {
|
|
22
|
-
format:
|
|
23
|
-
| "css"
|
|
24
|
-
| "scss"
|
|
25
|
-
| "tailwind"
|
|
26
|
-
| "style-dictionary"
|
|
27
|
-
| "dtcg"
|
|
28
|
-
| "swift"
|
|
29
|
-
| "kotlin"
|
|
30
|
-
| "json"
|
|
31
|
-
| "flutter"
|
|
32
|
-
| "android-xml"
|
|
33
|
-
| "js"
|
|
34
|
-
| "ts"
|
|
35
|
-
| "less"
|
|
36
|
-
| "react-native"
|
|
37
|
-
| "tailwind-v4"
|
|
38
|
-
| "css-rem"
|
|
39
|
-
| "all";
|
|
40
|
-
/** Filter by collection name (substring match, case-insensitive) */
|
|
41
|
-
collectionFilter?: string;
|
|
42
|
-
/** Filter by token type */
|
|
43
|
-
tokenTypes?: Array<"COLOR" | "FLOAT" | "STRING" | "BOOLEAN">;
|
|
44
|
-
/** Which mode to export (e.g. "Light", "Dark"). Exports all modes if omitted. */
|
|
45
|
-
mode?: string;
|
|
46
|
-
/** Include alias chain comments showing semantic → primitive → raw */
|
|
47
|
-
includeAliasChains?: boolean;
|
|
48
|
-
/** CSS selector to wrap custom properties in (default ":root") */
|
|
49
|
-
cssSelector?: string;
|
|
50
|
-
/** Tailwind: prefix for custom token keys (default "ds") */
|
|
51
|
-
tailwindPrefix?: string;
|
|
52
|
-
/** Base font size for rem conversion (default 16) */
|
|
53
|
-
remBase?: number;
|
|
54
|
-
/** Include $deprecated field in DTCG output (default true) */
|
|
55
|
-
deprecated?: boolean;
|
|
56
|
-
/** Color space for DTCG output */
|
|
57
|
-
colorSpace?: "srgb" | "display-p3" | "oklch";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface NormalizedToken {
|
|
61
|
-
name: string;
|
|
62
|
-
cssName: string;
|
|
63
|
-
type: "COLOR" | "FLOAT" | "STRING" | "BOOLEAN";
|
|
64
|
-
collection: string;
|
|
65
|
-
description: string;
|
|
66
|
-
values: Record<string, string | number | boolean>;
|
|
67
|
-
aliasOf?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
function tokenNameToCssVar(name: string): string {
|
|
73
|
-
return "--" + name
|
|
74
|
-
.replace(/\//g, "-")
|
|
75
|
-
.replace(/\s+/g, "-")
|
|
76
|
-
.replace(/[^a-zA-Z0-9\-_]/g, "")
|
|
77
|
-
.toLowerCase();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function tokenNameToSwiftCase(name: string): string {
|
|
81
|
-
const parts = name.replace(/\//g, "-").replace(/\s+/g, "-").split("-").filter(Boolean);
|
|
82
|
-
return parts
|
|
83
|
-
.map((p, i) => (i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()))
|
|
84
|
-
.join("");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function tokenNameToKotlinCase(name: string): string {
|
|
88
|
-
const parts = name.replace(/\//g, "-").replace(/\s+/g, "-").split("-").filter(Boolean);
|
|
89
|
-
return parts
|
|
90
|
-
.map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
|
|
91
|
-
.join("");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function tokenNameToCamelCase(name: string): string {
|
|
95
|
-
const parts = name
|
|
96
|
-
.replace(/\//g, "-")
|
|
97
|
-
.replace(/\s+/g, "-")
|
|
98
|
-
.split("-")
|
|
99
|
-
.filter(Boolean);
|
|
100
|
-
return parts
|
|
101
|
-
.map((p, i) => (i === 0 ? p.toLowerCase() : p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()))
|
|
102
|
-
.join("");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function tokenNameToSnakeCase(name: string): string {
|
|
106
|
-
return name
|
|
107
|
-
.replace(/\//g, "_")
|
|
108
|
-
.replace(/\s+/g, "_")
|
|
109
|
-
.replace(/[^a-zA-Z0-9_]/g, "")
|
|
110
|
-
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
111
|
-
.toLowerCase();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function tokenNameToDtcgPath(name: string): string[] {
|
|
115
|
-
return name.split("/").map((s) => s.trim()).filter(Boolean);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function figmaColorToHex(color: Record<string, number>): string {
|
|
119
|
-
const r = color.r ?? 0;
|
|
120
|
-
const g = color.g ?? 0;
|
|
121
|
-
const b = color.b ?? 0;
|
|
122
|
-
const a = color.a ?? 1;
|
|
123
|
-
const hex = figmaRgbaToHex(r, g, b);
|
|
124
|
-
if (a < 1) {
|
|
125
|
-
const alpha = Math.round(a * 255).toString(16).padStart(2, "0");
|
|
126
|
-
return hex + alpha;
|
|
127
|
-
}
|
|
128
|
-
return hex;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveValue(raw: unknown, type: string): string | number | boolean {
|
|
132
|
-
if (raw === null || raw === undefined) return type === "COLOR" ? "#000000" : 0;
|
|
133
|
-
|
|
134
|
-
// Figma color object {r, g, b, a} with 0-1 range
|
|
135
|
-
if (type === "COLOR" && typeof raw === "object" && raw !== null && "r" in raw) {
|
|
136
|
-
return figmaColorToHex(raw as Record<string, number>);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Variable alias {type: "VARIABLE_ALIAS", id: "..."}
|
|
140
|
-
if (typeof raw === "object" && raw !== null && "type" in raw) {
|
|
141
|
-
const alias = raw as { type: string; id?: string };
|
|
142
|
-
if (alias.type === "VARIABLE_ALIAS" && alias.id) {
|
|
143
|
-
return `alias:${alias.id}`;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (typeof raw === "number" || typeof raw === "string" || typeof raw === "boolean") {
|
|
148
|
-
return raw;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return String(raw);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ─── DTCG composite type parsers ───────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
function parseShadowString(val: string): Record<string, unknown> {
|
|
157
|
-
// Parse CSS shadow: "offsetX offsetY blur spread color" or "offsetX offsetY blur color"
|
|
158
|
-
const parts = val.trim().split(/\s+/);
|
|
159
|
-
if (parts.length >= 4) {
|
|
160
|
-
const color = parts.slice(3).join(" ") || parts[parts.length - 1];
|
|
161
|
-
return {
|
|
162
|
-
offsetX: { value: parseFloat(parts[0]) || 0, unit: "px" },
|
|
163
|
-
offsetY: { value: parseFloat(parts[1]) || 0, unit: "px" },
|
|
164
|
-
blur: { value: parseFloat(parts[2]) || 0, unit: "px" },
|
|
165
|
-
spread: { value: parts.length >= 5 ? parseFloat(parts[3]) || 0 : 0, unit: "px" },
|
|
166
|
-
color: parts.length >= 5 ? parts.slice(4).join(" ") || parts[4] : color,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
return {
|
|
170
|
-
offsetX: { value: 0, unit: "px" },
|
|
171
|
-
offsetY: { value: 0, unit: "px" },
|
|
172
|
-
blur: { value: 0, unit: "px" },
|
|
173
|
-
spread: { value: 0, unit: "px" },
|
|
174
|
-
color: val,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function parseCubicBezierString(val: string): number[] {
|
|
179
|
-
// Parse "cubic-bezier(P1x, P1y, P2x, P2y)" or just "P1x, P1y, P2x, P2y"
|
|
180
|
-
const match = val.match(/cubic-bezier\(\s*([^)]+)\s*\)/i);
|
|
181
|
-
const inner = match ? match[1] : val;
|
|
182
|
-
const nums = inner.split(",").map((s) => parseFloat(s.trim()));
|
|
183
|
-
if (nums.length === 4 && nums.every((n) => !isNaN(n))) {
|
|
184
|
-
return nums;
|
|
185
|
-
}
|
|
186
|
-
return [0, 0, 1, 1]; // linear fallback
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function parseTypographyString(val: string): Record<string, unknown> {
|
|
190
|
-
// Best-effort parse of typography composite strings
|
|
191
|
-
return {
|
|
192
|
-
fontFamily: val,
|
|
193
|
-
fontSize: { value: 16, unit: "px" },
|
|
194
|
-
fontWeight: 400,
|
|
195
|
-
letterSpacing: { value: 0, unit: "px" },
|
|
196
|
-
lineHeight: { value: 1.5, unit: "px" },
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function parseBorderString(val: string): Record<string, unknown> {
|
|
201
|
-
// Parse "width style color" e.g. "1px solid #000"
|
|
202
|
-
const parts = val.trim().split(/\s+/);
|
|
203
|
-
return {
|
|
204
|
-
width: { value: parseFloat(parts[0]) || 1, unit: "px" },
|
|
205
|
-
style: parts[1] || "solid",
|
|
206
|
-
color: parts.slice(2).join(" ") || "#000000",
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function parseGradientString(val: string): Array<Record<string, unknown>> {
|
|
211
|
-
// Best-effort gradient stop parse
|
|
212
|
-
const match = val.match(/linear-gradient\(\s*([^)]+)\s*\)/i);
|
|
213
|
-
if (!match) return [{ color: val, position: 0 }];
|
|
214
|
-
const inner = match[1];
|
|
215
|
-
const stopParts = inner.split(",").map((s) => s.trim());
|
|
216
|
-
const stops: Array<Record<string, unknown>> = [];
|
|
217
|
-
for (const part of stopParts) {
|
|
218
|
-
const m = part.match(/(#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))\s*(\d+%)?/);
|
|
219
|
-
if (m) {
|
|
220
|
-
stops.push({
|
|
221
|
-
color: m[1],
|
|
222
|
-
position: m[2] ? parseFloat(m[2]) / 100 : stops.length === 0 ? 0 : 1,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return stops.length > 0 ? stops : [{ color: val, position: 0 }];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function parseTransitionString(val: string): Record<string, unknown> {
|
|
230
|
-
// Parse "duration delay timingFunction" e.g. "200ms 0ms ease-in-out"
|
|
231
|
-
const parts = val.trim().split(/\s+/);
|
|
232
|
-
return {
|
|
233
|
-
duration: { value: parseFloat(parts[0]) || 200, unit: "ms" },
|
|
234
|
-
delay: { value: parts.length > 1 ? parseFloat(parts[1]) || 0 : 0, unit: "ms" },
|
|
235
|
-
timingFunction: parts.length > 2 ? parseCubicBezierString(parts.slice(2).join(" ")) : [0, 0, 1, 1],
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ─── Figma variable fetcher ─────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
async function fetchFigmaTokens(args: ExportTokensArgs): Promise<NormalizedToken[]> {
|
|
242
|
-
const bridge = await getBridge();
|
|
243
|
-
const rawVars = (await bridge.getVariables(undefined, "full")) as unknown as {
|
|
244
|
-
collections: Array<{
|
|
245
|
-
id: string;
|
|
246
|
-
name: string;
|
|
247
|
-
modes: Array<{ modeId: string; name: string }>;
|
|
248
|
-
variables: Array<{
|
|
249
|
-
id: string;
|
|
250
|
-
name: string;
|
|
251
|
-
resolvedType: string;
|
|
252
|
-
description?: string;
|
|
253
|
-
valuesByMode: Record<string, unknown>;
|
|
254
|
-
aliasOf?: string;
|
|
255
|
-
}>;
|
|
256
|
-
}>;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
if (!rawVars?.collections?.length) return [];
|
|
260
|
-
|
|
261
|
-
const tokens: NormalizedToken[] = [];
|
|
262
|
-
const collFilter = args.collectionFilter?.toLowerCase();
|
|
263
|
-
|
|
264
|
-
for (const coll of rawVars.collections) {
|
|
265
|
-
if (collFilter && !coll.name.toLowerCase().includes(collFilter)) continue;
|
|
266
|
-
|
|
267
|
-
const modeMap = new Map(coll.modes.map((m) => [m.modeId, m.name]));
|
|
268
|
-
|
|
269
|
-
for (const v of coll.variables) {
|
|
270
|
-
const type = v.resolvedType as NormalizedToken["type"];
|
|
271
|
-
if (args.tokenTypes?.length && !args.tokenTypes.includes(type)) continue;
|
|
272
|
-
|
|
273
|
-
const values: Record<string, string | number | boolean> = {};
|
|
274
|
-
|
|
275
|
-
for (const [modeId, rawVal] of Object.entries(v.valuesByMode)) {
|
|
276
|
-
const modeName = modeMap.get(modeId) ?? modeId;
|
|
277
|
-
if (args.mode && modeName.toLowerCase() !== args.mode.toLowerCase()) continue;
|
|
278
|
-
values[modeName] = resolveValue(rawVal, type);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (Object.keys(values).length === 0) continue;
|
|
282
|
-
|
|
283
|
-
tokens.push({
|
|
284
|
-
name: v.name,
|
|
285
|
-
cssName: tokenNameToCssVar(v.name),
|
|
286
|
-
type,
|
|
287
|
-
collection: coll.name,
|
|
288
|
-
description: v.description ?? "",
|
|
289
|
-
values,
|
|
290
|
-
aliasOf: v.aliasOf,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return tokens;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ─── Catalog fallback ───────────────────────────────────────────────────────
|
|
299
|
-
|
|
300
|
-
function catalogToTokens(args: ExportTokensArgs): NormalizedToken[] {
|
|
301
|
-
return SEMANTIC_TOKEN_CATALOG
|
|
302
|
-
.filter((entry) => {
|
|
303
|
-
if (args.tokenTypes?.length && !args.tokenTypes.includes(entry.type)) return false;
|
|
304
|
-
if (args.collectionFilter && !entry.category.toLowerCase().includes(args.collectionFilter.toLowerCase())) return false;
|
|
305
|
-
return true;
|
|
306
|
-
})
|
|
307
|
-
.map((entry) => {
|
|
308
|
-
const values: Record<string, string | number | boolean> = {};
|
|
309
|
-
|
|
310
|
-
if (!args.mode || args.mode.toLowerCase() === "light") {
|
|
311
|
-
values["Light"] = parseCatalogRef(entry.lightRef, entry.type);
|
|
312
|
-
}
|
|
313
|
-
if (!args.mode || args.mode.toLowerCase() === "dark") {
|
|
314
|
-
values["Dark"] = parseCatalogRef(entry.darkRef, entry.type);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
name: entry.name,
|
|
319
|
-
cssName: tokenNameToCssVar(entry.name),
|
|
320
|
-
type: entry.type,
|
|
321
|
-
collection: `Semantic/${entry.category}`,
|
|
322
|
-
description: entry.description,
|
|
323
|
-
values,
|
|
324
|
-
aliasOf: entry.lightRef.includes("/") ? entry.lightRef : undefined,
|
|
325
|
-
};
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function parseCatalogRef(ref: string, type: string): string | number {
|
|
330
|
-
if (type === "FLOAT") {
|
|
331
|
-
const num = parseFloat(ref);
|
|
332
|
-
return isNaN(num) ? ref : num;
|
|
333
|
-
}
|
|
334
|
-
return ref;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ─── Format generators ──────────────────────────────────────────────────────
|
|
338
|
-
|
|
339
|
-
function generateCSS(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
340
|
-
const selector = args.cssSelector ?? ":root";
|
|
341
|
-
const modes = collectModes(tokens);
|
|
342
|
-
const lines: string[] = [
|
|
343
|
-
`/* Design Tokens — CSS Custom Properties */`,
|
|
344
|
-
`/* Generated by figma_export_tokens */`,
|
|
345
|
-
`/* ${tokens.length} tokens across ${new Set(tokens.map((t) => t.collection)).size} collections */`,
|
|
346
|
-
``,
|
|
347
|
-
];
|
|
348
|
-
|
|
349
|
-
for (const mode of modes) {
|
|
350
|
-
const modeSelector = mode === "Light" || modes.length === 1
|
|
351
|
-
? selector
|
|
352
|
-
: mode === "Dark"
|
|
353
|
-
? `${selector === ":root" ? "[data-theme=\"dark\"]" : `${selector}[data-theme=\"dark\"]`}`
|
|
354
|
-
: `${selector === ":root" ? `[data-theme="${mode.toLowerCase()}"]` : `${selector}[data-theme="${mode.toLowerCase()}"]`}`;
|
|
355
|
-
|
|
356
|
-
const attrs = mode === "Dark" && selector === ":root"
|
|
357
|
-
? `@media (prefers-color-scheme: dark) {\n :root`
|
|
358
|
-
: modeSelector;
|
|
359
|
-
|
|
360
|
-
const isMediaWrapped = mode === "Dark" && selector === ":root";
|
|
361
|
-
|
|
362
|
-
lines.push(`${isMediaWrapped ? attrs : modeSelector} {`);
|
|
363
|
-
|
|
364
|
-
for (const token of tokens) {
|
|
365
|
-
const val = token.values[mode];
|
|
366
|
-
if (val === undefined) continue;
|
|
367
|
-
|
|
368
|
-
const cssVal = formatCssValue(val, token.type);
|
|
369
|
-
if (args.includeAliasChains && token.aliasOf) {
|
|
370
|
-
lines.push(` /* alias: ${token.aliasOf} */`);
|
|
371
|
-
}
|
|
372
|
-
if (token.description) {
|
|
373
|
-
lines.push(` /* ${token.description} */`);
|
|
374
|
-
}
|
|
375
|
-
lines.push(` ${token.cssName}: ${cssVal};`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
lines.push(`${isMediaWrapped ? " }\n}" : "}"}`);
|
|
379
|
-
lines.push(``);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
return lines.join("\n");
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function generateSCSS(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
386
|
-
const modes = collectModes(tokens);
|
|
387
|
-
const lines: string[] = [
|
|
388
|
-
`// Design Tokens — SCSS Variables`,
|
|
389
|
-
`// Generated by figma_export_tokens`,
|
|
390
|
-
`// ${tokens.length} tokens`,
|
|
391
|
-
``,
|
|
392
|
-
];
|
|
393
|
-
|
|
394
|
-
for (const mode of modes) {
|
|
395
|
-
if (modes.length > 1) {
|
|
396
|
-
lines.push(`// ─── Mode: ${mode} ${"─".repeat(50)}`);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
for (const token of tokens) {
|
|
400
|
-
const val = token.values[mode];
|
|
401
|
-
if (val === undefined) continue;
|
|
402
|
-
|
|
403
|
-
const scssName = token.cssName.replace(/^--/, "$");
|
|
404
|
-
const suffix = modes.length > 1 && mode !== "Light" ? `-${mode.toLowerCase()}` : "";
|
|
405
|
-
const cssVal = formatCssValue(val, token.type);
|
|
406
|
-
|
|
407
|
-
if (token.description) {
|
|
408
|
-
lines.push(`/// ${token.description}`);
|
|
409
|
-
}
|
|
410
|
-
lines.push(`${scssName}${suffix}: ${cssVal};`);
|
|
411
|
-
}
|
|
412
|
-
lines.push(``);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Generate a mixin for mode switching
|
|
416
|
-
if (modes.length > 1) {
|
|
417
|
-
lines.push(`// ─── Theme mixin ─────────────────────────────────────────────`);
|
|
418
|
-
lines.push(`@mixin theme($mode: "light") {`);
|
|
419
|
-
for (const token of tokens) {
|
|
420
|
-
const cssVal = Object.values(token.values)[0];
|
|
421
|
-
if (cssVal === undefined) continue;
|
|
422
|
-
const scssName = token.cssName.replace(/^--/, "$");
|
|
423
|
-
lines.push(` ${token.cssName}: #{${scssName}-#{$mode}};`);
|
|
424
|
-
}
|
|
425
|
-
lines.push(`}`);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return lines.join("\n");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function generateTailwind(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
432
|
-
const prefix = args.tailwindPrefix ?? "ds";
|
|
433
|
-
|
|
434
|
-
const colors: Record<string, string> = {};
|
|
435
|
-
const spacing: Record<string, string> = {};
|
|
436
|
-
const borderRadius: Record<string, string> = {};
|
|
437
|
-
const fontSize: Record<string, string> = {};
|
|
438
|
-
const zIndex: Record<string, string> = {};
|
|
439
|
-
const opacity: Record<string, string> = {};
|
|
440
|
-
const boxShadow: Record<string, string> = {};
|
|
441
|
-
const transitionDuration: Record<string, string> = {};
|
|
442
|
-
const transitionTimingFunction: Record<string, string> = {};
|
|
443
|
-
const borderWidth: Record<string, string> = {};
|
|
444
|
-
|
|
445
|
-
for (const token of tokens) {
|
|
446
|
-
// Use first mode value (typically Light)
|
|
447
|
-
const val = Object.values(token.values)[0];
|
|
448
|
-
if (val === undefined) continue;
|
|
449
|
-
|
|
450
|
-
const key = token.name
|
|
451
|
-
.split("/")
|
|
452
|
-
.slice(-2)
|
|
453
|
-
.join("-")
|
|
454
|
-
.replace(/[^a-zA-Z0-9\-]/g, "")
|
|
455
|
-
.toLowerCase();
|
|
456
|
-
|
|
457
|
-
const cssRef = `var(${token.cssName})`;
|
|
458
|
-
|
|
459
|
-
if (token.type === "COLOR") {
|
|
460
|
-
colors[key] = cssRef;
|
|
461
|
-
} else if (token.name.includes("space") || token.name.includes("spacing") || token.name.includes("gap") || token.name.includes("inset") || token.name.includes("padding")) {
|
|
462
|
-
spacing[key] = typeof val === "number" ? `${val}px` : String(val);
|
|
463
|
-
} else if (token.name.includes("radius")) {
|
|
464
|
-
borderRadius[key] = typeof val === "number" ? `${val}px` : String(val);
|
|
465
|
-
} else if (token.name.includes("font-size") || token.name.includes("text-")) {
|
|
466
|
-
fontSize[key] = typeof val === "number" ? `${val}px` : String(val);
|
|
467
|
-
} else if (token.name.includes("z-index")) {
|
|
468
|
-
zIndex[key] = String(val);
|
|
469
|
-
} else if (token.name.includes("opacity")) {
|
|
470
|
-
opacity[key] = String(val);
|
|
471
|
-
} else if (token.name.includes("shadow") || token.name.includes("elevation")) {
|
|
472
|
-
boxShadow[key] = String(val);
|
|
473
|
-
} else if (token.name.includes("duration")) {
|
|
474
|
-
transitionDuration[key] = typeof val === "number" ? `${val}ms` : String(val);
|
|
475
|
-
} else if (token.name.includes("easing")) {
|
|
476
|
-
transitionTimingFunction[key] = String(val);
|
|
477
|
-
} else if (token.name.includes("border-width")) {
|
|
478
|
-
borderWidth[key] = typeof val === "number" ? `${val}px` : String(val);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const config: Record<string, unknown> = {
|
|
483
|
-
theme: {
|
|
484
|
-
extend: {
|
|
485
|
-
...(Object.keys(colors).length > 0 && { colors: { [prefix]: colors } }),
|
|
486
|
-
...(Object.keys(spacing).length > 0 && { spacing: { [prefix]: spacing } }),
|
|
487
|
-
...(Object.keys(borderRadius).length > 0 && { borderRadius: { [prefix]: borderRadius } }),
|
|
488
|
-
...(Object.keys(fontSize).length > 0 && { fontSize: { [prefix]: fontSize } }),
|
|
489
|
-
...(Object.keys(zIndex).length > 0 && { zIndex: { [prefix]: zIndex } }),
|
|
490
|
-
...(Object.keys(opacity).length > 0 && { opacity: { [prefix]: opacity } }),
|
|
491
|
-
...(Object.keys(boxShadow).length > 0 && { boxShadow: { [prefix]: boxShadow } }),
|
|
492
|
-
...(Object.keys(transitionDuration).length > 0 && { transitionDuration: { [prefix]: transitionDuration } }),
|
|
493
|
-
...(Object.keys(transitionTimingFunction).length > 0 && { transitionTimingFunction: { [prefix]: transitionTimingFunction } }),
|
|
494
|
-
...(Object.keys(borderWidth).length > 0 && { borderWidth: { [prefix]: borderWidth } }),
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
const lines = [
|
|
500
|
-
`// Design Tokens — Tailwind CSS Config`,
|
|
501
|
-
`// Generated by figma_export_tokens`,
|
|
502
|
-
`// Usage: import and spread into your tailwind.config.{js,ts}`,
|
|
503
|
-
``,
|
|
504
|
-
`/** @type {import('tailwindcss').Config} */`,
|
|
505
|
-
`module.exports = ${JSON.stringify(config, null, 2)};`,
|
|
506
|
-
];
|
|
507
|
-
|
|
508
|
-
return lines.join("\n");
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function generateStyleDictionary(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
512
|
-
const modes = collectModes(tokens);
|
|
513
|
-
const result: Record<string, unknown> = {};
|
|
514
|
-
|
|
515
|
-
for (const token of tokens) {
|
|
516
|
-
const path = token.name.split("/").filter(Boolean);
|
|
517
|
-
const val = Object.values(token.values)[0];
|
|
518
|
-
if (val === undefined) continue;
|
|
519
|
-
|
|
520
|
-
const leaf: Record<string, unknown> = {
|
|
521
|
-
value: formatRawValue(val, token.type),
|
|
522
|
-
type: sdType(token.type),
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
if (token.description) leaf.comment = token.description;
|
|
526
|
-
if (token.aliasOf) leaf.original = { value: `{${token.aliasOf.replace(/\//g, ".")}}` };
|
|
527
|
-
|
|
528
|
-
// Modes as attributes
|
|
529
|
-
if (modes.length > 1) {
|
|
530
|
-
const modeVals: Record<string, unknown> = {};
|
|
531
|
-
for (const [mode, modeVal] of Object.entries(token.values)) {
|
|
532
|
-
modeVals[mode.toLowerCase()] = formatRawValue(modeVal, token.type);
|
|
533
|
-
}
|
|
534
|
-
leaf.modes = modeVals;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
setNestedValue(result, path, leaf);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
return JSON.stringify(result, null, 2);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// ─── DTCG v2025.10 compliant generator ─────────────────────────────────────
|
|
544
|
-
|
|
545
|
-
function dtcgType(type: string, name: string): string {
|
|
546
|
-
if (type === "COLOR") return "color";
|
|
547
|
-
|
|
548
|
-
if (type === "FLOAT") {
|
|
549
|
-
if (name.includes("opacity") || name.includes("z-index")) return "number";
|
|
550
|
-
if (name.includes("font-weight") || name.includes("fontWeight")) return "fontWeight";
|
|
551
|
-
if (name.includes("duration")) return "duration";
|
|
552
|
-
// spacing, radius, border-width, font-size, letter-spacing, line-height, size, etc.
|
|
553
|
-
return "dimension";
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (type === "STRING") {
|
|
557
|
-
if (name.includes("easing") || name.includes("timing") || name.includes("cubic-bezier")) return "cubicBezier";
|
|
558
|
-
if (name.includes("shadow") || name.includes("elevation")) return "shadow";
|
|
559
|
-
if (name.includes("font-family") || name.includes("fontFamily")) return "fontFamily";
|
|
560
|
-
if (name.includes("typography")) return "typography";
|
|
561
|
-
if (name.includes("border") && !name.includes("border-width") && !name.includes("border-radius")) return "border";
|
|
562
|
-
if (name.includes("gradient")) return "gradient";
|
|
563
|
-
if (name.includes("transition")) return "transition";
|
|
564
|
-
return "string";
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return "string";
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function dtcgValue(
|
|
571
|
-
val: string | number | boolean,
|
|
572
|
-
type: string,
|
|
573
|
-
dtcgTypeName: string,
|
|
574
|
-
): unknown {
|
|
575
|
-
switch (dtcgTypeName) {
|
|
576
|
-
case "color":
|
|
577
|
-
return typeof val === "string" ? val : String(val);
|
|
578
|
-
|
|
579
|
-
case "dimension":
|
|
580
|
-
return { value: typeof val === "number" ? val : parseFloat(String(val)) || 0, unit: "px" };
|
|
581
|
-
|
|
582
|
-
case "number":
|
|
583
|
-
case "fontWeight":
|
|
584
|
-
return typeof val === "number" ? val : parseFloat(String(val)) || 0;
|
|
585
|
-
|
|
586
|
-
case "duration":
|
|
587
|
-
return { value: typeof val === "number" ? val : parseFloat(String(val)) || 0, unit: "ms" };
|
|
588
|
-
|
|
589
|
-
case "cubicBezier":
|
|
590
|
-
return typeof val === "string" ? parseCubicBezierString(val) : [0, 0, 1, 1];
|
|
591
|
-
|
|
592
|
-
case "shadow":
|
|
593
|
-
return typeof val === "string" ? parseShadowString(val) : val;
|
|
594
|
-
|
|
595
|
-
case "fontFamily":
|
|
596
|
-
return typeof val === "string" ? val : String(val);
|
|
597
|
-
|
|
598
|
-
case "typography":
|
|
599
|
-
return typeof val === "string" ? parseTypographyString(val) : val;
|
|
600
|
-
|
|
601
|
-
case "border":
|
|
602
|
-
return typeof val === "string" ? parseBorderString(val) : val;
|
|
603
|
-
|
|
604
|
-
case "gradient":
|
|
605
|
-
return typeof val === "string" ? parseGradientString(val) : val;
|
|
606
|
-
|
|
607
|
-
case "transition":
|
|
608
|
-
return typeof val === "string" ? parseTransitionString(val) : val;
|
|
609
|
-
|
|
610
|
-
default:
|
|
611
|
-
return val;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function generateDTCG(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
616
|
-
const includeDeprecated = args.deprecated !== false; // default true
|
|
617
|
-
const result: Record<string, unknown> = {};
|
|
618
|
-
|
|
619
|
-
// Group tokens by DTCG path prefix for $type inheritance
|
|
620
|
-
const groupTypes = new Map<string, Set<string>>();
|
|
621
|
-
|
|
622
|
-
for (const token of tokens) {
|
|
623
|
-
const path = tokenNameToDtcgPath(token.name);
|
|
624
|
-
const val = Object.values(token.values)[0];
|
|
625
|
-
if (val === undefined) continue;
|
|
626
|
-
|
|
627
|
-
const typeName = dtcgType(token.type, token.name);
|
|
628
|
-
|
|
629
|
-
// Track types per group for $type inheritance
|
|
630
|
-
if (path.length > 1) {
|
|
631
|
-
const groupKey = path.slice(0, -1).join("/");
|
|
632
|
-
if (!groupTypes.has(groupKey)) {
|
|
633
|
-
groupTypes.set(groupKey, new Set());
|
|
634
|
-
}
|
|
635
|
-
groupTypes.get(groupKey)!.add(typeName);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const leaf: Record<string, unknown> = {
|
|
639
|
-
$value: dtcgValue(val, token.type, typeName),
|
|
640
|
-
$type: typeName,
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
if (token.description) leaf.$description = token.description;
|
|
644
|
-
|
|
645
|
-
// $deprecated support
|
|
646
|
-
if (includeDeprecated && token.description?.toLowerCase().includes("deprecated")) {
|
|
647
|
-
leaf.$deprecated = true;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// DTCG extensions for modes
|
|
651
|
-
if (Object.keys(token.values).length > 1) {
|
|
652
|
-
const extensions: Record<string, unknown> = {};
|
|
653
|
-
for (const [mode, modeVal] of Object.entries(token.values)) {
|
|
654
|
-
extensions[mode.toLowerCase()] = dtcgValue(modeVal, token.type, typeName);
|
|
655
|
-
}
|
|
656
|
-
leaf.$extensions = { "com.figma.modes": extensions };
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
setNestedValue(result, path, leaf);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Apply $type inheritance: set $type on groups when all children share the same type,
|
|
663
|
-
// then remove $type from individual children
|
|
664
|
-
for (const [groupKey, types] of groupTypes.entries()) {
|
|
665
|
-
if (types.size === 1) {
|
|
666
|
-
const sharedType = [...types][0];
|
|
667
|
-
const groupPath = groupKey.split("/");
|
|
668
|
-
const group = getNestedValue(result, groupPath);
|
|
669
|
-
if (group && typeof group === "object" && group !== null) {
|
|
670
|
-
const groupObj = group as Record<string, unknown>;
|
|
671
|
-
// Set $type on the group
|
|
672
|
-
groupObj.$type = sharedType;
|
|
673
|
-
// Remove $type from children
|
|
674
|
-
for (const [childKey, childVal] of Object.entries(groupObj)) {
|
|
675
|
-
if (childKey.startsWith("$")) continue;
|
|
676
|
-
if (childVal && typeof childVal === "object" && childVal !== null) {
|
|
677
|
-
const child = childVal as Record<string, unknown>;
|
|
678
|
-
if (child.$type === sharedType) {
|
|
679
|
-
delete child.$type;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return JSON.stringify(result, null, 2);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function generateSwift(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
691
|
-
const modes = collectModes(tokens);
|
|
692
|
-
const colorTokens = tokens.filter((t) => t.type === "COLOR");
|
|
693
|
-
const floatTokens = tokens.filter((t) => t.type === "FLOAT");
|
|
694
|
-
const stringTokens = tokens.filter((t) => t.type === "STRING");
|
|
695
|
-
|
|
696
|
-
const lines: string[] = [
|
|
697
|
-
`// Design Tokens — Swift`,
|
|
698
|
-
`// Generated by figma_export_tokens`,
|
|
699
|
-
`// ${tokens.length} tokens`,
|
|
700
|
-
``,
|
|
701
|
-
`import SwiftUI`,
|
|
702
|
-
``,
|
|
703
|
-
];
|
|
704
|
-
|
|
705
|
-
// Color tokens
|
|
706
|
-
if (colorTokens.length > 0) {
|
|
707
|
-
lines.push(`// MARK: - Colors`);
|
|
708
|
-
lines.push(`extension Color {`);
|
|
709
|
-
lines.push(` enum DesignSystem {`);
|
|
710
|
-
|
|
711
|
-
for (const token of colorTokens) {
|
|
712
|
-
const val = Object.values(token.values)[0];
|
|
713
|
-
if (val === undefined || typeof val !== "string") continue;
|
|
714
|
-
|
|
715
|
-
const name = tokenNameToSwiftCase(token.name);
|
|
716
|
-
const hex = val.replace("#", "");
|
|
717
|
-
|
|
718
|
-
if (token.description) {
|
|
719
|
-
lines.push(` /// ${token.description}`);
|
|
720
|
-
}
|
|
721
|
-
lines.push(` static let ${name} = Color(hex: 0x${hex.toUpperCase().slice(0, 6)})`);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
lines.push(` }`);
|
|
725
|
-
lines.push(`}`);
|
|
726
|
-
lines.push(``);
|
|
727
|
-
|
|
728
|
-
// Dark mode support
|
|
729
|
-
if (modes.includes("Dark")) {
|
|
730
|
-
lines.push(`// MARK: - Dark Mode Colors`);
|
|
731
|
-
lines.push(`extension Color.DesignSystem {`);
|
|
732
|
-
lines.push(` static func resolved(_ colorScheme: ColorScheme) -> [String: Color] {`);
|
|
733
|
-
lines.push(` switch colorScheme {`);
|
|
734
|
-
lines.push(` case .dark:`);
|
|
735
|
-
lines.push(` return [`);
|
|
736
|
-
for (const token of colorTokens) {
|
|
737
|
-
const darkVal = token.values["Dark"];
|
|
738
|
-
if (darkVal === undefined || typeof darkVal !== "string") continue;
|
|
739
|
-
const hex = darkVal.replace("#", "");
|
|
740
|
-
lines.push(` "${tokenNameToSwiftCase(token.name)}": Color(hex: 0x${hex.toUpperCase().slice(0, 6)}),`);
|
|
741
|
-
}
|
|
742
|
-
lines.push(` ]`);
|
|
743
|
-
lines.push(` default:`);
|
|
744
|
-
lines.push(` return [:]`);
|
|
745
|
-
lines.push(` }`);
|
|
746
|
-
lines.push(` }`);
|
|
747
|
-
lines.push(`}`);
|
|
748
|
-
lines.push(``);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// Spacing / dimension tokens
|
|
753
|
-
if (floatTokens.length > 0) {
|
|
754
|
-
lines.push(`// MARK: - Dimensions`);
|
|
755
|
-
lines.push(`enum DSSpacing {`);
|
|
756
|
-
for (const token of floatTokens) {
|
|
757
|
-
const val = Object.values(token.values)[0];
|
|
758
|
-
if (val === undefined) continue;
|
|
759
|
-
const name = tokenNameToSwiftCase(token.name);
|
|
760
|
-
if (token.description) {
|
|
761
|
-
lines.push(` /// ${token.description}`);
|
|
762
|
-
}
|
|
763
|
-
lines.push(` static let ${name}: CGFloat = ${val}`);
|
|
764
|
-
}
|
|
765
|
-
lines.push(`}`);
|
|
766
|
-
lines.push(``);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// String tokens (shadows, easing)
|
|
770
|
-
if (stringTokens.length > 0) {
|
|
771
|
-
lines.push(`// MARK: - String Tokens`);
|
|
772
|
-
lines.push(`enum DSTokens {`);
|
|
773
|
-
for (const token of stringTokens) {
|
|
774
|
-
const val = Object.values(token.values)[0];
|
|
775
|
-
if (val === undefined) continue;
|
|
776
|
-
const name = tokenNameToSwiftCase(token.name);
|
|
777
|
-
lines.push(` static let ${name} = "${val}"`);
|
|
778
|
-
}
|
|
779
|
-
lines.push(`}`);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
return lines.join("\n");
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function generateKotlin(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
786
|
-
const colorTokens = tokens.filter((t) => t.type === "COLOR");
|
|
787
|
-
const floatTokens = tokens.filter((t) => t.type === "FLOAT");
|
|
788
|
-
const modes = collectModes(tokens);
|
|
789
|
-
|
|
790
|
-
const lines: string[] = [
|
|
791
|
-
`// Design Tokens — Kotlin (Jetpack Compose)`,
|
|
792
|
-
`// Generated by figma_export_tokens`,
|
|
793
|
-
`// ${tokens.length} tokens`,
|
|
794
|
-
``,
|
|
795
|
-
`package com.designsystem.tokens`,
|
|
796
|
-
``,
|
|
797
|
-
`import androidx.compose.ui.graphics.Color`,
|
|
798
|
-
`import androidx.compose.ui.unit.dp`,
|
|
799
|
-
`import androidx.compose.ui.unit.sp`,
|
|
800
|
-
``,
|
|
801
|
-
];
|
|
802
|
-
|
|
803
|
-
if (colorTokens.length > 0) {
|
|
804
|
-
lines.push(`object DSColors {`);
|
|
805
|
-
for (const token of colorTokens) {
|
|
806
|
-
const val = Object.values(token.values)[0];
|
|
807
|
-
if (val === undefined || typeof val !== "string") continue;
|
|
808
|
-
const name = tokenNameToKotlinCase(token.name);
|
|
809
|
-
const hex = val.replace("#", "").toUpperCase();
|
|
810
|
-
if (token.description) {
|
|
811
|
-
lines.push(` /** ${token.description} */`);
|
|
812
|
-
}
|
|
813
|
-
lines.push(` val ${name} = Color(0xFF${hex.slice(0, 6)})`);
|
|
814
|
-
}
|
|
815
|
-
lines.push(`}`);
|
|
816
|
-
lines.push(``);
|
|
817
|
-
|
|
818
|
-
if (modes.includes("Dark")) {
|
|
819
|
-
lines.push(`object DSColorsDark {`);
|
|
820
|
-
for (const token of colorTokens) {
|
|
821
|
-
const darkVal = token.values["Dark"];
|
|
822
|
-
if (darkVal === undefined || typeof darkVal !== "string") continue;
|
|
823
|
-
const name = tokenNameToKotlinCase(token.name);
|
|
824
|
-
const hex = darkVal.replace("#", "").toUpperCase();
|
|
825
|
-
lines.push(` val ${name} = Color(0xFF${hex.slice(0, 6)})`);
|
|
826
|
-
}
|
|
827
|
-
lines.push(`}`);
|
|
828
|
-
lines.push(``);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (floatTokens.length > 0) {
|
|
833
|
-
lines.push(`object DSDimensions {`);
|
|
834
|
-
for (const token of floatTokens) {
|
|
835
|
-
const val = Object.values(token.values)[0];
|
|
836
|
-
if (val === undefined) continue;
|
|
837
|
-
const name = tokenNameToKotlinCase(token.name);
|
|
838
|
-
const unit = token.name.includes("font-size") || token.name.includes("text") ? "sp" : "dp";
|
|
839
|
-
if (token.description) {
|
|
840
|
-
lines.push(` /** ${token.description} */`);
|
|
841
|
-
}
|
|
842
|
-
lines.push(` val ${name} = ${val}.${unit}`);
|
|
843
|
-
}
|
|
844
|
-
lines.push(`}`);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
return lines.join("\n");
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function generateJSON(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
851
|
-
const output = tokens.map((t) => ({
|
|
852
|
-
name: t.name,
|
|
853
|
-
cssVariable: t.cssName,
|
|
854
|
-
type: t.type,
|
|
855
|
-
collection: t.collection,
|
|
856
|
-
description: t.description,
|
|
857
|
-
values: t.values,
|
|
858
|
-
...(t.aliasOf ? { aliasOf: t.aliasOf } : {}),
|
|
859
|
-
}));
|
|
860
|
-
return JSON.stringify(output, null, 2);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// ─── New format generators ─────────────────────────────────────────────────
|
|
864
|
-
|
|
865
|
-
function generateFlutter(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
866
|
-
const colorTokens = tokens.filter((t) => t.type === "COLOR");
|
|
867
|
-
const floatTokens = tokens.filter((t) => t.type === "FLOAT");
|
|
868
|
-
const stringTokens = tokens.filter((t) => t.type === "STRING");
|
|
869
|
-
|
|
870
|
-
const lines: string[] = [
|
|
871
|
-
`// Design Tokens — Flutter/Dart`,
|
|
872
|
-
`// Generated by figma_export_tokens`,
|
|
873
|
-
`// ${tokens.length} tokens`,
|
|
874
|
-
``,
|
|
875
|
-
`import 'package:flutter/material.dart';`,
|
|
876
|
-
``,
|
|
877
|
-
];
|
|
878
|
-
|
|
879
|
-
if (colorTokens.length > 0) {
|
|
880
|
-
lines.push(`class DSColors {`);
|
|
881
|
-
for (const token of colorTokens) {
|
|
882
|
-
const val = Object.values(token.values)[0];
|
|
883
|
-
if (val === undefined || typeof val !== "string") continue;
|
|
884
|
-
const name = tokenNameToCamelCase(token.name);
|
|
885
|
-
const hex = val.replace("#", "").toUpperCase();
|
|
886
|
-
const alpha = hex.length > 6 ? hex.slice(6, 8) : "FF";
|
|
887
|
-
const rgb = hex.slice(0, 6);
|
|
888
|
-
if (token.description) {
|
|
889
|
-
lines.push(` /// ${token.description}`);
|
|
890
|
-
}
|
|
891
|
-
lines.push(` static const ${name} = Color(0x${alpha}${rgb});`);
|
|
892
|
-
}
|
|
893
|
-
lines.push(`}`);
|
|
894
|
-
lines.push(``);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
if (floatTokens.length > 0) {
|
|
898
|
-
lines.push(`class DSDimensions {`);
|
|
899
|
-
for (const token of floatTokens) {
|
|
900
|
-
const val = Object.values(token.values)[0];
|
|
901
|
-
if (val === undefined) continue;
|
|
902
|
-
const name = tokenNameToCamelCase(token.name);
|
|
903
|
-
const numVal = typeof val === "number" ? val : parseFloat(String(val)) || 0;
|
|
904
|
-
if (token.description) {
|
|
905
|
-
lines.push(` /// ${token.description}`);
|
|
906
|
-
}
|
|
907
|
-
lines.push(` static const ${name} = ${numVal.toFixed(1)};`);
|
|
908
|
-
}
|
|
909
|
-
lines.push(`}`);
|
|
910
|
-
lines.push(``);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
if (stringTokens.length > 0) {
|
|
914
|
-
lines.push(`class DSStrings {`);
|
|
915
|
-
for (const token of stringTokens) {
|
|
916
|
-
const val = Object.values(token.values)[0];
|
|
917
|
-
if (val === undefined) continue;
|
|
918
|
-
const name = tokenNameToCamelCase(token.name);
|
|
919
|
-
lines.push(` static const ${name} = '${String(val).replace(/'/g, "\\'")}';`);
|
|
920
|
-
}
|
|
921
|
-
lines.push(`}`);
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
return lines.join("\n");
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function generateAndroidXml(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
928
|
-
const lines: string[] = [
|
|
929
|
-
`<?xml version="1.0" encoding="utf-8"?>`,
|
|
930
|
-
`<!-- Design Tokens — Android Resources -->`,
|
|
931
|
-
`<!-- Generated by figma_export_tokens -->`,
|
|
932
|
-
`<!-- ${tokens.length} tokens -->`,
|
|
933
|
-
`<resources>`,
|
|
934
|
-
];
|
|
935
|
-
|
|
936
|
-
for (const token of tokens) {
|
|
937
|
-
const val = Object.values(token.values)[0];
|
|
938
|
-
if (val === undefined) continue;
|
|
939
|
-
|
|
940
|
-
const xmlName = "ds_" + tokenNameToSnakeCase(token.name);
|
|
941
|
-
|
|
942
|
-
if (token.description) {
|
|
943
|
-
lines.push(` <!-- ${token.description} -->`);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
if (token.type === "COLOR" && typeof val === "string") {
|
|
947
|
-
// Android expects #AARRGGBB format
|
|
948
|
-
const hex = val.replace("#", "").toUpperCase();
|
|
949
|
-
const alpha = hex.length > 6 ? hex.slice(6, 8) : "FF";
|
|
950
|
-
const rgb = hex.slice(0, 6);
|
|
951
|
-
lines.push(` <color name="${xmlName}">#${alpha}${rgb}</color>`);
|
|
952
|
-
} else if (token.type === "FLOAT") {
|
|
953
|
-
const numVal = typeof val === "number" ? val : parseFloat(String(val)) || 0;
|
|
954
|
-
if (token.name.includes("font-size") || token.name.includes("text")) {
|
|
955
|
-
lines.push(` <dimen name="${xmlName}">${numVal}sp</dimen>`);
|
|
956
|
-
} else {
|
|
957
|
-
lines.push(` <dimen name="${xmlName}">${numVal}dp</dimen>`);
|
|
958
|
-
}
|
|
959
|
-
} else if (token.type === "STRING") {
|
|
960
|
-
const escaped = String(val).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
961
|
-
lines.push(` <string name="${xmlName}">${escaped}</string>`);
|
|
962
|
-
} else if (token.type === "BOOLEAN") {
|
|
963
|
-
lines.push(` <bool name="${xmlName}">${val}</bool>`);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
lines.push(`</resources>`);
|
|
968
|
-
return lines.join("\n");
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
function generateJsModule(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
972
|
-
const lines: string[] = [
|
|
973
|
-
`// Design Tokens — JavaScript ES Module`,
|
|
974
|
-
`// Generated by figma_export_tokens`,
|
|
975
|
-
`// ${tokens.length} tokens`,
|
|
976
|
-
``,
|
|
977
|
-
];
|
|
978
|
-
|
|
979
|
-
for (const token of tokens) {
|
|
980
|
-
const val = Object.values(token.values)[0];
|
|
981
|
-
if (val === undefined) continue;
|
|
982
|
-
|
|
983
|
-
const name = tokenNameToCamelCase(token.name);
|
|
984
|
-
|
|
985
|
-
if (token.description) {
|
|
986
|
-
lines.push(`/** ${token.description} */`);
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
if (typeof val === "string") {
|
|
990
|
-
lines.push(`export const ${name} = "${val.replace(/"/g, '\\"')}";`);
|
|
991
|
-
} else if (typeof val === "number") {
|
|
992
|
-
lines.push(`export const ${name} = ${val};`);
|
|
993
|
-
} else {
|
|
994
|
-
lines.push(`export const ${name} = ${JSON.stringify(val)};`);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return lines.join("\n");
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
function generateTsModule(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
1002
|
-
const lines: string[] = [
|
|
1003
|
-
`// Design Tokens — TypeScript Module`,
|
|
1004
|
-
`// Generated by figma_export_tokens`,
|
|
1005
|
-
`// ${tokens.length} tokens`,
|
|
1006
|
-
``,
|
|
1007
|
-
];
|
|
1008
|
-
|
|
1009
|
-
const tokenNames: string[] = [];
|
|
1010
|
-
const colorNames: string[] = [];
|
|
1011
|
-
const dimensionNames: string[] = [];
|
|
1012
|
-
|
|
1013
|
-
for (const token of tokens) {
|
|
1014
|
-
const val = Object.values(token.values)[0];
|
|
1015
|
-
if (val === undefined) continue;
|
|
1016
|
-
|
|
1017
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1018
|
-
tokenNames.push(name);
|
|
1019
|
-
|
|
1020
|
-
if (token.type === "COLOR") colorNames.push(name);
|
|
1021
|
-
if (token.type === "FLOAT") dimensionNames.push(name);
|
|
1022
|
-
|
|
1023
|
-
if (token.description) {
|
|
1024
|
-
lines.push(`/** ${token.description} */`);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
if (typeof val === "string") {
|
|
1028
|
-
lines.push(`export const ${name} = "${val.replace(/"/g, '\\"')}" as const;`);
|
|
1029
|
-
} else if (typeof val === "number") {
|
|
1030
|
-
lines.push(`export const ${name} = ${val} as const;`);
|
|
1031
|
-
} else {
|
|
1032
|
-
lines.push(`export const ${name} = ${JSON.stringify(val)} as const;`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Type declarations
|
|
1037
|
-
lines.push(``);
|
|
1038
|
-
if (tokenNames.length > 0) {
|
|
1039
|
-
lines.push(`export type TokenName = ${tokenNames.map((n) => `"${n}"`).join(" | ")};`);
|
|
1040
|
-
}
|
|
1041
|
-
if (colorNames.length > 0) {
|
|
1042
|
-
lines.push(`export type ColorToken = ${colorNames.map((n) => `typeof ${n}`).join(" | ")};`);
|
|
1043
|
-
}
|
|
1044
|
-
if (dimensionNames.length > 0) {
|
|
1045
|
-
lines.push(`export type DimensionToken = ${dimensionNames.map((n) => `typeof ${n}`).join(" | ")};`);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
return lines.join("\n");
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
function generateLess(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
1052
|
-
const modes = collectModes(tokens);
|
|
1053
|
-
const lines: string[] = [
|
|
1054
|
-
`// Design Tokens — Less Variables`,
|
|
1055
|
-
`// Generated by figma_export_tokens`,
|
|
1056
|
-
`// ${tokens.length} tokens`,
|
|
1057
|
-
``,
|
|
1058
|
-
];
|
|
1059
|
-
|
|
1060
|
-
for (const mode of modes) {
|
|
1061
|
-
if (modes.length > 1) {
|
|
1062
|
-
lines.push(`// ─── Mode: ${mode} ${"─".repeat(50)}`);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
for (const token of tokens) {
|
|
1066
|
-
const val = token.values[mode];
|
|
1067
|
-
if (val === undefined) continue;
|
|
1068
|
-
|
|
1069
|
-
const lessName = "@" + token.cssName.replace(/^--/, "");
|
|
1070
|
-
const suffix = modes.length > 1 && mode !== "Light" ? `-${mode.toLowerCase()}` : "";
|
|
1071
|
-
const cssVal = formatCssValue(val, token.type);
|
|
1072
|
-
|
|
1073
|
-
if (token.description) {
|
|
1074
|
-
lines.push(`// ${token.description}`);
|
|
1075
|
-
}
|
|
1076
|
-
lines.push(`${lessName}${suffix}: ${cssVal};`);
|
|
1077
|
-
}
|
|
1078
|
-
lines.push(``);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
return lines.join("\n");
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
function generateReactNative(tokens: NormalizedToken[], _args: ExportTokensArgs): string {
|
|
1085
|
-
const colorTokens = tokens.filter((t) => t.type === "COLOR");
|
|
1086
|
-
const floatTokens = tokens.filter((t) => t.type === "FLOAT");
|
|
1087
|
-
|
|
1088
|
-
const lines: string[] = [
|
|
1089
|
-
`// Design Tokens — React Native`,
|
|
1090
|
-
`// Generated by figma_export_tokens`,
|
|
1091
|
-
`// ${tokens.length} tokens`,
|
|
1092
|
-
``,
|
|
1093
|
-
`import { StyleSheet } from 'react-native';`,
|
|
1094
|
-
``,
|
|
1095
|
-
];
|
|
1096
|
-
|
|
1097
|
-
// Colors object
|
|
1098
|
-
if (colorTokens.length > 0) {
|
|
1099
|
-
lines.push(`export const colors = {`);
|
|
1100
|
-
for (const token of colorTokens) {
|
|
1101
|
-
const val = Object.values(token.values)[0];
|
|
1102
|
-
if (val === undefined || typeof val !== "string") continue;
|
|
1103
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1104
|
-
lines.push(` ${name}: '${val}',`);
|
|
1105
|
-
}
|
|
1106
|
-
lines.push(`};`);
|
|
1107
|
-
lines.push(``);
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// Categorize float tokens
|
|
1111
|
-
const spacingTokens = floatTokens.filter((t) =>
|
|
1112
|
-
t.name.includes("space") || t.name.includes("spacing") || t.name.includes("gap") || t.name.includes("padding") || t.name.includes("inset")
|
|
1113
|
-
);
|
|
1114
|
-
const radiusTokens = floatTokens.filter((t) => t.name.includes("radius"));
|
|
1115
|
-
const fontSizeTokens = floatTokens.filter((t) => t.name.includes("font-size") || t.name.includes("text-"));
|
|
1116
|
-
const otherFloats = floatTokens.filter((t) =>
|
|
1117
|
-
!spacingTokens.includes(t) && !radiusTokens.includes(t) && !fontSizeTokens.includes(t)
|
|
1118
|
-
);
|
|
1119
|
-
|
|
1120
|
-
if (spacingTokens.length > 0) {
|
|
1121
|
-
lines.push(`export const spacing = {`);
|
|
1122
|
-
for (const token of spacingTokens) {
|
|
1123
|
-
const val = Object.values(token.values)[0];
|
|
1124
|
-
if (val === undefined) continue;
|
|
1125
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1126
|
-
lines.push(` ${name}: ${typeof val === "number" ? val : parseFloat(String(val)) || 0},`);
|
|
1127
|
-
}
|
|
1128
|
-
lines.push(`};`);
|
|
1129
|
-
lines.push(``);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
if (radiusTokens.length > 0) {
|
|
1133
|
-
lines.push(`export const borderRadius = {`);
|
|
1134
|
-
for (const token of radiusTokens) {
|
|
1135
|
-
const val = Object.values(token.values)[0];
|
|
1136
|
-
if (val === undefined) continue;
|
|
1137
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1138
|
-
lines.push(` ${name}: ${typeof val === "number" ? val : parseFloat(String(val)) || 0},`);
|
|
1139
|
-
}
|
|
1140
|
-
lines.push(`};`);
|
|
1141
|
-
lines.push(``);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
if (fontSizeTokens.length > 0) {
|
|
1145
|
-
lines.push(`export const fontSize = {`);
|
|
1146
|
-
for (const token of fontSizeTokens) {
|
|
1147
|
-
const val = Object.values(token.values)[0];
|
|
1148
|
-
if (val === undefined) continue;
|
|
1149
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1150
|
-
lines.push(` ${name}: ${typeof val === "number" ? val : parseFloat(String(val)) || 0},`);
|
|
1151
|
-
}
|
|
1152
|
-
lines.push(`};`);
|
|
1153
|
-
lines.push(``);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
if (otherFloats.length > 0) {
|
|
1157
|
-
lines.push(`export const dimensions = {`);
|
|
1158
|
-
for (const token of otherFloats) {
|
|
1159
|
-
const val = Object.values(token.values)[0];
|
|
1160
|
-
if (val === undefined) continue;
|
|
1161
|
-
const name = tokenNameToCamelCase(token.name);
|
|
1162
|
-
lines.push(` ${name}: ${typeof val === "number" ? val : parseFloat(String(val)) || 0},`);
|
|
1163
|
-
}
|
|
1164
|
-
lines.push(`};`);
|
|
1165
|
-
lines.push(``);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
return lines.join("\n");
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
function generateTailwindV4(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
1172
|
-
const prefix = args.tailwindPrefix ?? "ds";
|
|
1173
|
-
|
|
1174
|
-
const lines: string[] = [
|
|
1175
|
-
`/* Design Tokens — Tailwind CSS v4 @theme */`,
|
|
1176
|
-
`/* Generated by figma_export_tokens */`,
|
|
1177
|
-
`/* ${tokens.length} tokens */`,
|
|
1178
|
-
``,
|
|
1179
|
-
`@theme {`,
|
|
1180
|
-
];
|
|
1181
|
-
|
|
1182
|
-
for (const token of tokens) {
|
|
1183
|
-
const val = Object.values(token.values)[0];
|
|
1184
|
-
if (val === undefined) continue;
|
|
1185
|
-
|
|
1186
|
-
const rawKey = token.name
|
|
1187
|
-
.replace(/\//g, "-")
|
|
1188
|
-
.replace(/\s+/g, "-")
|
|
1189
|
-
.replace(/[^a-zA-Z0-9\-]/g, "")
|
|
1190
|
-
.toLowerCase();
|
|
1191
|
-
|
|
1192
|
-
if (token.type === "COLOR") {
|
|
1193
|
-
lines.push(` --color-${prefix}-${rawKey}: ${typeof val === "string" ? val : String(val)};`);
|
|
1194
|
-
} else if (token.name.includes("space") || token.name.includes("spacing") || token.name.includes("gap") || token.name.includes("padding") || token.name.includes("inset")) {
|
|
1195
|
-
lines.push(` --spacing-${prefix}-${rawKey}: ${typeof val === "number" ? `${val}px` : String(val)};`);
|
|
1196
|
-
} else if (token.name.includes("radius")) {
|
|
1197
|
-
lines.push(` --radius-${prefix}-${rawKey}: ${typeof val === "number" ? `${val}px` : String(val)};`);
|
|
1198
|
-
} else if (token.name.includes("font-size") || token.name.includes("text-")) {
|
|
1199
|
-
lines.push(` --font-size-${prefix}-${rawKey}: ${typeof val === "number" ? `${val}px` : String(val)};`);
|
|
1200
|
-
} else if (token.name.includes("shadow") || token.name.includes("elevation")) {
|
|
1201
|
-
lines.push(` --shadow-${prefix}-${rawKey}: ${String(val)};`);
|
|
1202
|
-
} else if (token.name.includes("duration")) {
|
|
1203
|
-
lines.push(` --duration-${prefix}-${rawKey}: ${typeof val === "number" ? `${val}ms` : String(val)};`);
|
|
1204
|
-
} else if (token.name.includes("easing")) {
|
|
1205
|
-
lines.push(` --ease-${prefix}-${rawKey}: ${String(val)};`);
|
|
1206
|
-
} else if (token.name.includes("border-width")) {
|
|
1207
|
-
lines.push(` --border-${prefix}-${rawKey}: ${typeof val === "number" ? `${val}px` : String(val)};`);
|
|
1208
|
-
} else if (token.name.includes("z-index")) {
|
|
1209
|
-
lines.push(` --z-${prefix}-${rawKey}: ${String(val)};`);
|
|
1210
|
-
} else if (token.name.includes("opacity")) {
|
|
1211
|
-
lines.push(` --opacity-${prefix}-${rawKey}: ${String(val)};`);
|
|
1212
|
-
} else {
|
|
1213
|
-
lines.push(` --${prefix}-${rawKey}: ${typeof val === "number" ? `${val}px` : String(val)};`);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
lines.push(`}`);
|
|
1218
|
-
return lines.join("\n");
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
function generateCssRem(tokens: NormalizedToken[], args: ExportTokensArgs): string {
|
|
1222
|
-
const selector = args.cssSelector ?? ":root";
|
|
1223
|
-
const remBase = args.remBase ?? 16;
|
|
1224
|
-
const modes = collectModes(tokens);
|
|
1225
|
-
const lines: string[] = [
|
|
1226
|
-
`/* Design Tokens — CSS Custom Properties (rem) */`,
|
|
1227
|
-
`/* Generated by figma_export_tokens */`,
|
|
1228
|
-
`/* Base font size: ${remBase}px */`,
|
|
1229
|
-
`/* ${tokens.length} tokens across ${new Set(tokens.map((t) => t.collection)).size} collections */`,
|
|
1230
|
-
``,
|
|
1231
|
-
];
|
|
1232
|
-
|
|
1233
|
-
for (const mode of modes) {
|
|
1234
|
-
const modeSelector = mode === "Light" || modes.length === 1
|
|
1235
|
-
? selector
|
|
1236
|
-
: mode === "Dark"
|
|
1237
|
-
? `${selector === ":root" ? "[data-theme=\"dark\"]" : `${selector}[data-theme=\"dark\"]`}`
|
|
1238
|
-
: `${selector === ":root" ? `[data-theme="${mode.toLowerCase()}"]` : `${selector}[data-theme="${mode.toLowerCase()}"]`}`;
|
|
1239
|
-
|
|
1240
|
-
const attrs = mode === "Dark" && selector === ":root"
|
|
1241
|
-
? `@media (prefers-color-scheme: dark) {\n :root`
|
|
1242
|
-
: modeSelector;
|
|
1243
|
-
|
|
1244
|
-
const isMediaWrapped = mode === "Dark" && selector === ":root";
|
|
1245
|
-
|
|
1246
|
-
lines.push(`${isMediaWrapped ? attrs : modeSelector} {`);
|
|
1247
|
-
|
|
1248
|
-
for (const token of tokens) {
|
|
1249
|
-
const val = token.values[mode];
|
|
1250
|
-
if (val === undefined) continue;
|
|
1251
|
-
|
|
1252
|
-
let cssVal: string;
|
|
1253
|
-
if (token.type === "COLOR" && typeof val === "string") {
|
|
1254
|
-
// Colors stay as-is
|
|
1255
|
-
cssVal = val;
|
|
1256
|
-
} else if (token.type === "FLOAT" && typeof val === "number") {
|
|
1257
|
-
// Convert px to rem
|
|
1258
|
-
cssVal = pxToRem(val, remBase);
|
|
1259
|
-
} else {
|
|
1260
|
-
cssVal = formatCssValue(val, token.type);
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
if (args.includeAliasChains && token.aliasOf) {
|
|
1264
|
-
lines.push(` /* alias: ${token.aliasOf} */`);
|
|
1265
|
-
}
|
|
1266
|
-
if (token.description) {
|
|
1267
|
-
lines.push(` /* ${token.description} */`);
|
|
1268
|
-
}
|
|
1269
|
-
lines.push(` ${token.cssName}: ${cssVal};`);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
lines.push(`${isMediaWrapped ? " }\n}" : "}"}`);
|
|
1273
|
-
lines.push(``);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
return lines.join("\n");
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// ─── Utility helpers ────────────────────────────────────────────────────────
|
|
1280
|
-
|
|
1281
|
-
function collectModes(tokens: NormalizedToken[]): string[] {
|
|
1282
|
-
const modes = new Set<string>();
|
|
1283
|
-
for (const token of tokens) {
|
|
1284
|
-
for (const mode of Object.keys(token.values)) {
|
|
1285
|
-
modes.add(mode);
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
// Sort: Light first, Dark second, rest alphabetical
|
|
1289
|
-
return [...modes].sort((a, b) => {
|
|
1290
|
-
if (a === "Light") return -1;
|
|
1291
|
-
if (b === "Light") return 1;
|
|
1292
|
-
if (a === "Dark") return -1;
|
|
1293
|
-
if (b === "Dark") return 1;
|
|
1294
|
-
return a.localeCompare(b);
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
function formatCssValue(val: string | number | boolean, type: string): string {
|
|
1299
|
-
if (type === "COLOR" && typeof val === "string") return val;
|
|
1300
|
-
if (typeof val === "number") return String(val);
|
|
1301
|
-
if (typeof val === "boolean") return val ? "1" : "0";
|
|
1302
|
-
return String(val);
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
function formatRawValue(val: string | number | boolean, type: string): unknown {
|
|
1306
|
-
if (type === "COLOR" && typeof val === "string") return val;
|
|
1307
|
-
return val;
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function sdType(type: string): string {
|
|
1311
|
-
switch (type) {
|
|
1312
|
-
case "COLOR": return "color";
|
|
1313
|
-
case "FLOAT": return "number";
|
|
1314
|
-
case "STRING": return "string";
|
|
1315
|
-
case "BOOLEAN": return "boolean";
|
|
1316
|
-
default: return "other";
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
function setNestedValue(obj: Record<string, unknown>, path: string[], value: unknown): void {
|
|
1321
|
-
let current = obj;
|
|
1322
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
1323
|
-
const key = path[i];
|
|
1324
|
-
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
|
|
1325
|
-
current[key] = {};
|
|
1326
|
-
}
|
|
1327
|
-
current = current[key] as Record<string, unknown>;
|
|
1328
|
-
}
|
|
1329
|
-
current[path[path.length - 1]] = value;
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
function getNestedValue(obj: Record<string, unknown>, path: string[]): unknown {
|
|
1333
|
-
let current: unknown = obj;
|
|
1334
|
-
for (const key of path) {
|
|
1335
|
-
if (current === null || current === undefined || typeof current !== "object") return undefined;
|
|
1336
|
-
current = (current as Record<string, unknown>)[key];
|
|
1337
|
-
}
|
|
1338
|
-
return current;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
// ─── Main handler ───────────────────────────────────────────────────────────
|
|
1342
|
-
|
|
1343
|
-
export async function exportTokensHandler(args: ExportTokensArgs): Promise<unknown> {
|
|
1344
|
-
// Try Figma bridge first, fall back to built-in catalog
|
|
1345
|
-
let tokens: NormalizedToken[];
|
|
1346
|
-
let source: "figma" | "catalog";
|
|
1347
|
-
|
|
1348
|
-
try {
|
|
1349
|
-
tokens = await fetchFigmaTokens(args);
|
|
1350
|
-
source = "figma";
|
|
1351
|
-
} catch {
|
|
1352
|
-
tokens = [];
|
|
1353
|
-
source = "catalog";
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
if (tokens.length === 0) {
|
|
1357
|
-
tokens = catalogToTokens(args);
|
|
1358
|
-
source = "catalog";
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
if (tokens.length === 0) {
|
|
1362
|
-
return {
|
|
1363
|
-
error: "No tokens found. Ensure the Figma file has variables or adjust your filters.",
|
|
1364
|
-
filters: {
|
|
1365
|
-
collectionFilter: args.collectionFilter,
|
|
1366
|
-
tokenTypes: args.tokenTypes,
|
|
1367
|
-
mode: args.mode,
|
|
1368
|
-
},
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
const formats = args.format === "all"
|
|
1373
|
-
? [
|
|
1374
|
-
"css", "scss", "tailwind", "style-dictionary", "dtcg", "swift", "kotlin",
|
|
1375
|
-
"json", "flutter", "android-xml", "js", "ts", "less", "react-native",
|
|
1376
|
-
"tailwind-v4", "css-rem",
|
|
1377
|
-
] as const
|
|
1378
|
-
: [args.format] as const;
|
|
1379
|
-
|
|
1380
|
-
const outputs: Record<string, string> = {};
|
|
1381
|
-
|
|
1382
|
-
for (const fmt of formats) {
|
|
1383
|
-
switch (fmt) {
|
|
1384
|
-
case "css":
|
|
1385
|
-
outputs["tokens.css"] = generateCSS(tokens, args);
|
|
1386
|
-
break;
|
|
1387
|
-
case "scss":
|
|
1388
|
-
outputs["_tokens.scss"] = generateSCSS(tokens, args);
|
|
1389
|
-
break;
|
|
1390
|
-
case "tailwind":
|
|
1391
|
-
outputs["tokens.tailwind.js"] = generateTailwind(tokens, args);
|
|
1392
|
-
break;
|
|
1393
|
-
case "style-dictionary":
|
|
1394
|
-
outputs["tokens.style-dictionary.json"] = generateStyleDictionary(tokens, args);
|
|
1395
|
-
break;
|
|
1396
|
-
case "dtcg":
|
|
1397
|
-
outputs["tokens.tokens.json"] = generateDTCG(tokens, args);
|
|
1398
|
-
break;
|
|
1399
|
-
case "swift":
|
|
1400
|
-
outputs["DesignTokens.swift"] = generateSwift(tokens, args);
|
|
1401
|
-
break;
|
|
1402
|
-
case "kotlin":
|
|
1403
|
-
outputs["DesignTokens.kt"] = generateKotlin(tokens, args);
|
|
1404
|
-
break;
|
|
1405
|
-
case "json":
|
|
1406
|
-
outputs["tokens.json"] = generateJSON(tokens, args);
|
|
1407
|
-
break;
|
|
1408
|
-
case "flutter":
|
|
1409
|
-
outputs["design_tokens.dart"] = generateFlutter(tokens, args);
|
|
1410
|
-
break;
|
|
1411
|
-
case "android-xml":
|
|
1412
|
-
outputs["tokens.xml"] = generateAndroidXml(tokens, args);
|
|
1413
|
-
break;
|
|
1414
|
-
case "js":
|
|
1415
|
-
outputs["tokens.mjs"] = generateJsModule(tokens, args);
|
|
1416
|
-
break;
|
|
1417
|
-
case "ts":
|
|
1418
|
-
outputs["tokens.ts"] = generateTsModule(tokens, args);
|
|
1419
|
-
break;
|
|
1420
|
-
case "less":
|
|
1421
|
-
outputs["tokens.less"] = generateLess(tokens, args);
|
|
1422
|
-
break;
|
|
1423
|
-
case "react-native":
|
|
1424
|
-
outputs["tokens.native.ts"] = generateReactNative(tokens, args);
|
|
1425
|
-
break;
|
|
1426
|
-
case "tailwind-v4":
|
|
1427
|
-
outputs["tokens.tailwind-v4.css"] = generateTailwindV4(tokens, args);
|
|
1428
|
-
break;
|
|
1429
|
-
case "css-rem":
|
|
1430
|
-
outputs["tokens.rem.css"] = generateCssRem(tokens, args);
|
|
1431
|
-
break;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
const collections = [...new Set(tokens.map((t) => t.collection))];
|
|
1436
|
-
const modes = collectModes(tokens);
|
|
1437
|
-
|
|
1438
|
-
return {
|
|
1439
|
-
source,
|
|
1440
|
-
summary: {
|
|
1441
|
-
totalTokens: tokens.length,
|
|
1442
|
-
collections,
|
|
1443
|
-
modes,
|
|
1444
|
-
types: {
|
|
1445
|
-
COLOR: tokens.filter((t) => t.type === "COLOR").length,
|
|
1446
|
-
FLOAT: tokens.filter((t) => t.type === "FLOAT").length,
|
|
1447
|
-
STRING: tokens.filter((t) => t.type === "STRING").length,
|
|
1448
|
-
BOOLEAN: tokens.filter((t) => t.type === "BOOLEAN").length,
|
|
1449
|
-
},
|
|
1450
|
-
},
|
|
1451
|
-
files: outputs,
|
|
1452
|
-
usage: {
|
|
1453
|
-
css: "Add tokens.css to your HTML <head> or @import in your main stylesheet.",
|
|
1454
|
-
scss: "Import _tokens.scss in your main SCSS file: @use 'tokens';",
|
|
1455
|
-
tailwind: "Spread into tailwind.config.js: const dsTokens = require('./tokens.tailwind'); module.exports = { ...dsTokens, ... }",
|
|
1456
|
-
"style-dictionary": "Use as Style Dictionary source: https://amzn.github.io/style-dictionary/",
|
|
1457
|
-
dtcg: "W3C Design Token Community Group v2025.10 format — compatible with Tokens Studio, Specify, and other tools. Supports composite types (shadow, typography, border, gradient, transition, cubicBezier) and $type inheritance.",
|
|
1458
|
-
swift: "Add DesignTokens.swift to your Xcode project. Requires Color+Hex extension.",
|
|
1459
|
-
kotlin: "Add DesignTokens.kt to your Compose project.",
|
|
1460
|
-
flutter: "Add design_tokens.dart to your Flutter project. Uses Material Color class.",
|
|
1461
|
-
"android-xml": "Add tokens.xml to res/values/. Colors use #AARRGGBB format, dimensions use dp/sp.",
|
|
1462
|
-
js: "Import as ES module: import { colorPrimary, spacingSm } from './tokens.mjs';",
|
|
1463
|
-
ts: "Import with full type safety: import { colorPrimary, type TokenName } from './tokens';",
|
|
1464
|
-
less: "Import in your Less file: @import 'tokens.less';",
|
|
1465
|
-
"react-native": "Import in your RN project: import { colors, spacing } from './tokens.native';",
|
|
1466
|
-
"tailwind-v4": "Import in your Tailwind v4 CSS: @import './tokens.tailwind-v4.css'; Uses @theme directive.",
|
|
1467
|
-
"css-rem": `CSS custom properties with rem units (base: ${args.remBase ?? 16}px). Colors remain as hex values.`,
|
|
1468
|
-
},
|
|
1469
|
-
};
|
|
1470
|
-
}
|