@seed-design/figma 0.0.4 → 0.0.6

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.
Files changed (211) hide show
  1. package/lib/index.cjs +10589 -12261
  2. package/lib/index.d.ts +1732 -41
  3. package/lib/index.js +10565 -12262
  4. package/package.json +2 -2
  5. package/src/codegen/context.ts +148 -0
  6. package/src/{component/type-helper.ts → codegen/core/component.ts} +1 -12
  7. package/src/codegen/core/index.ts +14 -0
  8. package/src/{jsx.ts → codegen/core/jsx.ts} +13 -3
  9. package/src/codegen/core/transformer.ts +40 -0
  10. package/src/{data → codegen/data}/icons.ts +2 -6
  11. package/src/{data → codegen/data}/styles.ts +87 -29
  12. package/src/codegen/data/variable-collections.ts +310 -0
  13. package/src/{data → codegen/data}/variables.ts +2821 -5887
  14. package/src/codegen/domain/codegen.service.ts +69 -0
  15. package/src/codegen/domain/figma-component.service.ts +21 -0
  16. package/src/codegen/domain/frame.service.ts +108 -0
  17. package/src/codegen/domain/icon.interface.ts +5 -0
  18. package/src/codegen/domain/icon.repository.ts +11 -0
  19. package/src/codegen/domain/icon.service.ts +35 -0
  20. package/src/codegen/domain/index.ts +22 -0
  21. package/src/codegen/domain/instance.service.ts +91 -0
  22. package/src/codegen/domain/props/container-layout-props.service.ts +248 -0
  23. package/src/codegen/domain/props/fill-props.service.ts +75 -0
  24. package/src/codegen/domain/props/radius-props.service.ts +105 -0
  25. package/src/codegen/domain/props/self-layout-props.service.ts +127 -0
  26. package/src/codegen/domain/props/stroke-props.service.ts +45 -0
  27. package/src/codegen/domain/props/type-style-props.service.ts +31 -0
  28. package/src/codegen/domain/rectangle.service.ts +31 -0
  29. package/src/codegen/domain/seed-component/deps.interface.ts +6 -0
  30. package/src/codegen/domain/seed-component/index.ts +75 -0
  31. package/src/{component/type.ts → codegen/domain/seed-component/properties.type.ts} +2 -2
  32. package/src/{component/properties.ts → codegen/domain/seed-component/size.ts} +4 -2
  33. package/src/codegen/domain/seed-component/transformers/action-button.ts +69 -0
  34. package/src/codegen/domain/seed-component/transformers/action-chip.ts +82 -0
  35. package/src/codegen/domain/seed-component/transformers/action-sheet.ts +78 -0
  36. package/src/{component/handlers → codegen/domain/seed-component/transformers}/app-bar.ts +114 -111
  37. package/src/codegen/domain/seed-component/transformers/avatar-stack.ts +30 -0
  38. package/src/codegen/domain/seed-component/transformers/avatar.ts +39 -0
  39. package/src/codegen/domain/seed-component/transformers/badge.ts +22 -0
  40. package/src/codegen/domain/seed-component/transformers/callout.ts +90 -0
  41. package/src/codegen/domain/seed-component/transformers/checkbox.ts +34 -0
  42. package/src/codegen/domain/seed-component/transformers/chip-tabs.ts +55 -0
  43. package/src/codegen/domain/seed-component/transformers/control-chip.ts +86 -0
  44. package/src/codegen/domain/seed-component/transformers/error-state.ts +39 -0
  45. package/src/codegen/domain/seed-component/transformers/extended-action-sheet.ts +99 -0
  46. package/src/codegen/domain/seed-component/transformers/extended-fab.ts +25 -0
  47. package/src/codegen/domain/seed-component/transformers/fab.ts +18 -0
  48. package/src/codegen/domain/seed-component/transformers/help-bubble.ts +68 -0
  49. package/src/codegen/domain/seed-component/transformers/identity-placeholder.ts +18 -0
  50. package/src/{component/handlers → codegen/domain/seed-component/transformers}/inline-banner.ts +11 -13
  51. package/src/codegen/domain/seed-component/transformers/manner-temp-badge.ts +19 -0
  52. package/src/codegen/domain/seed-component/transformers/multiline-text-field.ts +82 -0
  53. package/src/codegen/domain/seed-component/transformers/progress-circle.ts +51 -0
  54. package/src/codegen/domain/seed-component/transformers/reaction-button.ts +37 -0
  55. package/src/codegen/domain/seed-component/transformers/segmented-control.ts +59 -0
  56. package/src/codegen/domain/seed-component/transformers/select-box.ts +82 -0
  57. package/src/codegen/domain/seed-component/transformers/skeleton.ts +52 -0
  58. package/src/codegen/domain/seed-component/transformers/snackbar.ts +23 -0
  59. package/src/codegen/domain/seed-component/transformers/switch.ts +31 -0
  60. package/src/codegen/domain/seed-component/transformers/tabs.ts +129 -0
  61. package/src/{component/handlers → codegen/domain/seed-component/transformers}/text-button.ts +16 -18
  62. package/src/codegen/domain/seed-component/transformers/text-field.ts +109 -0
  63. package/src/codegen/domain/seed-component/transformers/toggle-button.ts +47 -0
  64. package/src/codegen/domain/style.interface.ts +5 -0
  65. package/src/codegen/domain/style.repository.ts +23 -0
  66. package/src/codegen/domain/style.service.ts +38 -0
  67. package/src/codegen/domain/text.service.ts +62 -0
  68. package/src/codegen/domain/variable.interface.ts +18 -0
  69. package/src/codegen/domain/variable.repository.ts +44 -0
  70. package/src/codegen/domain/variable.service.ts +95 -0
  71. package/src/codegen/index.ts +13 -0
  72. package/src/index.ts +2 -2
  73. package/src/normalizer/from-plugin.ts +29 -28
  74. package/src/normalizer/from-rest.ts +5 -1
  75. package/src/normalizer/types.ts +80 -32
  76. package/src/utils/common.ts +19 -0
  77. package/src/utils/css.ts +13 -0
  78. package/src/utils/figma-variable.ts +13 -0
  79. package/src/component/handlers/action-button.ts +0 -66
  80. package/src/component/handlers/action-chip.ts +0 -71
  81. package/src/component/handlers/action-sheet.ts +0 -74
  82. package/src/component/handlers/avatar-stack.ts +0 -35
  83. package/src/component/handlers/avatar.ts +0 -37
  84. package/src/component/handlers/badge.ts +0 -20
  85. package/src/component/handlers/callout.ts +0 -87
  86. package/src/component/handlers/checkbox.ts +0 -32
  87. package/src/component/handlers/chip-tabs.ts +0 -51
  88. package/src/component/handlers/control-chip.ts +0 -75
  89. package/src/component/handlers/error-state.ts +0 -37
  90. package/src/component/handlers/extended-action-sheet.ts +0 -86
  91. package/src/component/handlers/extended-fab.ts +0 -24
  92. package/src/component/handlers/fab.ts +0 -17
  93. package/src/component/handlers/help-bubble.ts +0 -66
  94. package/src/component/handlers/identity-placeholder.ts +0 -16
  95. package/src/component/handlers/manner-temp-badge.ts +0 -17
  96. package/src/component/handlers/multiline-text-field.ts +0 -80
  97. package/src/component/handlers/progress-circle.ts +0 -49
  98. package/src/component/handlers/reaction-button.ts +0 -36
  99. package/src/component/handlers/segmented-control.ts +0 -51
  100. package/src/component/handlers/select-box.ts +0 -76
  101. package/src/component/handlers/skeleton.ts +0 -51
  102. package/src/component/handlers/snackbar.ts +0 -21
  103. package/src/component/handlers/switch.ts +0 -29
  104. package/src/component/handlers/tabs.ts +0 -107
  105. package/src/component/handlers/text-field.ts +0 -108
  106. package/src/component/handlers/toggle-button.ts +0 -44
  107. package/src/component/index.ts +0 -76
  108. package/src/generate-code.ts +0 -251
  109. package/src/icon.ts +0 -46
  110. package/src/layout.ts +0 -31
  111. package/src/props/color.ts +0 -78
  112. package/src/props/layout.ts +0 -292
  113. package/src/props/sizing.ts +0 -58
  114. package/src/props/text.ts +0 -21
  115. package/src/props/variable.ts +0 -66
  116. /package/src/{data → codegen/data}/__generated__/component-sets/action-button.d.ts +0 -0
  117. /package/src/{data → codegen/data}/__generated__/component-sets/action-button.mjs +0 -0
  118. /package/src/{data → codegen/data}/__generated__/component-sets/action-chip.d.ts +0 -0
  119. /package/src/{data → codegen/data}/__generated__/component-sets/action-chip.mjs +0 -0
  120. /package/src/{data → codegen/data}/__generated__/component-sets/action-sheet.d.ts +0 -0
  121. /package/src/{data → codegen/data}/__generated__/component-sets/action-sheet.mjs +0 -0
  122. /package/src/{data → codegen/data}/__generated__/component-sets/avatar-stack.d.ts +0 -0
  123. /package/src/{data → codegen/data}/__generated__/component-sets/avatar-stack.mjs +0 -0
  124. /package/src/{data → codegen/data}/__generated__/component-sets/avatar.d.ts +0 -0
  125. /package/src/{data → codegen/data}/__generated__/component-sets/avatar.mjs +0 -0
  126. /package/src/{data → codegen/data}/__generated__/component-sets/badge.d.ts +0 -0
  127. /package/src/{data → codegen/data}/__generated__/component-sets/badge.mjs +0 -0
  128. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-navigation-global.d.ts +0 -0
  129. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-navigation-global.mjs +0 -0
  130. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-navigation-kr.d.ts +0 -0
  131. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-navigation-kr.mjs +0 -0
  132. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-sheet.d.ts +0 -0
  133. /package/src/{data → codegen/data}/__generated__/component-sets/bottom-sheet.mjs +0 -0
  134. /package/src/{data → codegen/data}/__generated__/component-sets/callout.d.ts +0 -0
  135. /package/src/{data → codegen/data}/__generated__/component-sets/callout.mjs +0 -0
  136. /package/src/{data → codegen/data}/__generated__/component-sets/checkbox.d.ts +0 -0
  137. /package/src/{data → codegen/data}/__generated__/component-sets/checkbox.mjs +0 -0
  138. /package/src/{data → codegen/data}/__generated__/component-sets/chip-tablist.d.ts +0 -0
  139. /package/src/{data → codegen/data}/__generated__/component-sets/chip-tablist.mjs +0 -0
  140. /package/src/{data → codegen/data}/__generated__/component-sets/control-chip.d.ts +0 -0
  141. /package/src/{data → codegen/data}/__generated__/component-sets/control-chip.mjs +0 -0
  142. /package/src/{data → codegen/data}/__generated__/component-sets/divider.d.ts +0 -0
  143. /package/src/{data → codegen/data}/__generated__/component-sets/divider.mjs +0 -0
  144. /package/src/{data → codegen/data}/__generated__/component-sets/error-state.d.ts +0 -0
  145. /package/src/{data → codegen/data}/__generated__/component-sets/error-state.mjs +0 -0
  146. /package/src/{data → codegen/data}/__generated__/component-sets/extended-action-sheet.d.ts +0 -0
  147. /package/src/{data → codegen/data}/__generated__/component-sets/extended-action-sheet.mjs +0 -0
  148. /package/src/{data → codegen/data}/__generated__/component-sets/extended-floating-action-button.d.ts +0 -0
  149. /package/src/{data → codegen/data}/__generated__/component-sets/extended-floating-action-button.mjs +0 -0
  150. /package/src/{data → codegen/data}/__generated__/component-sets/floating-action-button.d.ts +0 -0
  151. /package/src/{data → codegen/data}/__generated__/component-sets/floating-action-button.mjs +0 -0
  152. /package/src/{data → codegen/data}/__generated__/component-sets/help-bubble.d.ts +0 -0
  153. /package/src/{data → codegen/data}/__generated__/component-sets/help-bubble.mjs +0 -0
  154. /package/src/{data → codegen/data}/__generated__/component-sets/identity-placeholder.d.ts +0 -0
  155. /package/src/{data → codegen/data}/__generated__/component-sets/identity-placeholder.mjs +0 -0
  156. /package/src/{data → codegen/data}/__generated__/component-sets/index.d.ts +0 -0
  157. /package/src/{data → codegen/data}/__generated__/component-sets/index.mjs +0 -0
  158. /package/src/{data → codegen/data}/__generated__/component-sets/inline-banner.d.ts +0 -0
  159. /package/src/{data → codegen/data}/__generated__/component-sets/inline-banner.mjs +0 -0
  160. /package/src/{data → codegen/data}/__generated__/component-sets/main-tab-navigation-global.d.ts +0 -0
  161. /package/src/{data → codegen/data}/__generated__/component-sets/main-tab-navigation-global.mjs +0 -0
  162. /package/src/{data → codegen/data}/__generated__/component-sets/main-tab-navigation-kr.d.ts +0 -0
  163. /package/src/{data → codegen/data}/__generated__/component-sets/main-tab-navigation-kr.mjs +0 -0
  164. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp-badge.d.ts +0 -0
  165. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp-badge.mjs +0 -0
  166. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp-bar.d.ts +0 -0
  167. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp-bar.mjs +0 -0
  168. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp.d.ts +0 -0
  169. /package/src/{data → codegen/data}/__generated__/component-sets/manner-temp.mjs +0 -0
  170. /package/src/{data → codegen/data}/__generated__/component-sets/multiline-text-field.d.ts +0 -0
  171. /package/src/{data → codegen/data}/__generated__/component-sets/multiline-text-field.mjs +0 -0
  172. /package/src/{data → codegen/data}/__generated__/component-sets/progress-circle.d.ts +0 -0
  173. /package/src/{data → codegen/data}/__generated__/component-sets/progress-circle.mjs +0 -0
  174. /package/src/{data → codegen/data}/__generated__/component-sets/radio.d.ts +0 -0
  175. /package/src/{data → codegen/data}/__generated__/component-sets/radio.mjs +0 -0
  176. /package/src/{data → codegen/data}/__generated__/component-sets/range-slider.d.ts +0 -0
  177. /package/src/{data → codegen/data}/__generated__/component-sets/range-slider.mjs +0 -0
  178. /package/src/{data → codegen/data}/__generated__/component-sets/reaction-button.d.ts +0 -0
  179. /package/src/{data → codegen/data}/__generated__/component-sets/reaction-button.mjs +0 -0
  180. /package/src/{data → codegen/data}/__generated__/component-sets/segmented-control.d.ts +0 -0
  181. /package/src/{data → codegen/data}/__generated__/component-sets/segmented-control.mjs +0 -0
  182. /package/src/{data → codegen/data}/__generated__/component-sets/select-box.d.ts +0 -0
  183. /package/src/{data → codegen/data}/__generated__/component-sets/select-box.mjs +0 -0
  184. /package/src/{data → codegen/data}/__generated__/component-sets/skeleton.d.ts +0 -0
  185. /package/src/{data → codegen/data}/__generated__/component-sets/skeleton.mjs +0 -0
  186. /package/src/{data → codegen/data}/__generated__/component-sets/slider.d.ts +0 -0
  187. /package/src/{data → codegen/data}/__generated__/component-sets/slider.mjs +0 -0
  188. /package/src/{data → codegen/data}/__generated__/component-sets/snackbar.d.ts +0 -0
  189. /package/src/{data → codegen/data}/__generated__/component-sets/snackbar.mjs +0 -0
  190. /package/src/{data → codegen/data}/__generated__/component-sets/standard-navigation.d.ts +0 -0
  191. /package/src/{data → codegen/data}/__generated__/component-sets/standard-navigation.mjs +0 -0
  192. /package/src/{data → codegen/data}/__generated__/component-sets/switch.d.ts +0 -0
  193. /package/src/{data → codegen/data}/__generated__/component-sets/switch.mjs +0 -0
  194. /package/src/{data → codegen/data}/__generated__/component-sets/tablist.d.ts +0 -0
  195. /package/src/{data → codegen/data}/__generated__/component-sets/tablist.mjs +0 -0
  196. /package/src/{data → codegen/data}/__generated__/component-sets/template-bottom-fixed-bar.d.ts +0 -0
  197. /package/src/{data → codegen/data}/__generated__/component-sets/template-bottom-fixed-bar.mjs +0 -0
  198. /package/src/{data → codegen/data}/__generated__/component-sets/template-button-group.d.ts +0 -0
  199. /package/src/{data → codegen/data}/__generated__/component-sets/template-button-group.mjs +0 -0
  200. /package/src/{data → codegen/data}/__generated__/component-sets/template-chip-group.d.ts +0 -0
  201. /package/src/{data → codegen/data}/__generated__/component-sets/template-chip-group.mjs +0 -0
  202. /package/src/{data → codegen/data}/__generated__/component-sets/template-select-box-group.d.ts +0 -0
  203. /package/src/{data → codegen/data}/__generated__/component-sets/template-select-box-group.mjs +0 -0
  204. /package/src/{data → codegen/data}/__generated__/component-sets/template-top-navigation.d.ts +0 -0
  205. /package/src/{data → codegen/data}/__generated__/component-sets/template-top-navigation.mjs +0 -0
  206. /package/src/{data → codegen/data}/__generated__/component-sets/text-button.d.ts +0 -0
  207. /package/src/{data → codegen/data}/__generated__/component-sets/text-button.mjs +0 -0
  208. /package/src/{data → codegen/data}/__generated__/component-sets/text-field.d.ts +0 -0
  209. /package/src/{data → codegen/data}/__generated__/component-sets/text-field.mjs +0 -0
  210. /package/src/{data → codegen/data}/__generated__/component-sets/toggle-button.d.ts +0 -0
  211. /package/src/{data → codegen/data}/__generated__/component-sets/toggle-button.mjs +0 -0
@@ -0,0 +1,69 @@
1
+ import type { NormalizedSceneNode } from "@/normalizer";
2
+ import type { ElementNode } from "../core";
3
+ import { appendSource, createElement, stringifyElement } from "../core/jsx";
4
+ import type { FrameService } from "./frame.service";
5
+ import type { InstanceService } from "./instance.service";
6
+ import type { RectangleService } from "./rectangle.service";
7
+ import type { TextService } from "./text.service";
8
+ import { match } from "ts-pattern";
9
+
10
+ export interface CodegenService {
11
+ transform: (node: NormalizedSceneNode) => ElementNode | undefined;
12
+ transformToString: (node: NormalizedSceneNode) => string | undefined;
13
+ }
14
+
15
+ export interface SeedCodegenServiceDeps {
16
+ frameService: FrameService;
17
+ textService: TextService;
18
+ rectangleService: RectangleService;
19
+ instanceService: InstanceService;
20
+ shouldPrintSource: boolean;
21
+ }
22
+
23
+ export function createCodegenService({
24
+ frameService,
25
+ textService,
26
+ rectangleService,
27
+ instanceService,
28
+ shouldPrintSource,
29
+ }: SeedCodegenServiceDeps): CodegenService {
30
+ function traverse(node: NormalizedSceneNode): ElementNode | undefined {
31
+ if ("visible" in node && !node.visible) {
32
+ return;
33
+ }
34
+
35
+ const result = match(node)
36
+ .with({ type: "FRAME" }, (node) => frameService.transform(node, traverse))
37
+ .with({ type: "TEXT" }, (node) => textService.transform(node, traverse))
38
+ .with({ type: "RECTANGLE" }, (node) => rectangleService.transform(node, traverse))
39
+ .with({ type: "COMPONENT" }, (node) => frameService.transform(node, traverse)) // NOTE: Treat component node as Frame for now
40
+ .with({ type: "INSTANCE" }, (node) => instanceService.transform(node, traverse))
41
+ .with({ type: "VECTOR" }, () => createElement("svg", {}, "Vector Node Placeholder"))
42
+ .with({ type: "BOOLEAN_OPERATION" }, () =>
43
+ createElement("svg", {}, "Boolean Operation Node Placeholder"),
44
+ )
45
+ .with({ type: "UNHANDLED" }, () => createElement("UnhandledFigmaNode"))
46
+ .exhaustive();
47
+
48
+ if (result) {
49
+ return appendSource(result, node.id);
50
+ }
51
+
52
+ return;
53
+ }
54
+
55
+ function transform(node: NormalizedSceneNode): ElementNode | undefined {
56
+ return traverse(node);
57
+ }
58
+
59
+ function transformToString(node: NormalizedSceneNode): string | undefined {
60
+ const result = transform(node);
61
+ if (!result) return undefined;
62
+
63
+ return stringifyElement(result, {
64
+ printSource: shouldPrintSource,
65
+ });
66
+ }
67
+
68
+ return { transform, transformToString };
69
+ }
@@ -0,0 +1,21 @@
1
+ import type { ComponentTransformer } from "@/codegen/core";
2
+
3
+ export interface FigmaComponentService {
4
+ getTransformer: (key: string) => ComponentTransformer | undefined;
5
+ }
6
+
7
+ export function createFigmaComponentService({
8
+ transformers,
9
+ }: {
10
+ transformers: ComponentTransformer[];
11
+ }): FigmaComponentService {
12
+ const transformerServiceMap = new Map(
13
+ transformers.map((transformer) => [transformer.key, transformer]),
14
+ );
15
+
16
+ return {
17
+ getTransformer: (key: string) => {
18
+ return transformerServiceMap.get(key);
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,108 @@
1
+ import type {
2
+ NormalizedComponentNode,
3
+ NormalizedFrameNode,
4
+ NormalizedInstanceNode,
5
+ } from "@/normalizer";
6
+ import { createElement, defineElementTransformer, type ElementTransformer } from "../core";
7
+ import type {
8
+ ContainerLayoutPropsService,
9
+ SeedContainerLayoutProps,
10
+ } from "./props/container-layout-props.service";
11
+ import type { FillPropsService, FrameFillProps } from "./props/fill-props.service";
12
+ import type { RadiusPropsService, SeedRadiusProps } from "./props/radius-props.service";
13
+ import type {
14
+ SeedSelfLayoutProps,
15
+ SelfLayoutPropsService,
16
+ } from "./props/self-layout-props.service";
17
+ import type { SeedFrameStrokeProps, StrokePropsService } from "./props/stroke-props.service";
18
+
19
+ export interface FrameService {
20
+ transform: ElementTransformer<
21
+ NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode
22
+ >;
23
+ }
24
+
25
+ export interface SeedFrameServiceDeps {
26
+ containerLayoutPropsService: ContainerLayoutPropsService<SeedContainerLayoutProps>;
27
+ selfLayoutPropsService: SelfLayoutPropsService<SeedSelfLayoutProps>;
28
+ radiusPropsService: RadiusPropsService<SeedRadiusProps>;
29
+ fillPropsService: FillPropsService<FrameFillProps>;
30
+ strokePropsService: StrokePropsService<SeedFrameStrokeProps>;
31
+ }
32
+
33
+ export function createSeedFrameService({
34
+ containerLayoutPropsService,
35
+ selfLayoutPropsService,
36
+ radiusPropsService,
37
+ fillPropsService,
38
+ strokePropsService,
39
+ }: SeedFrameServiceDeps): FrameService {
40
+ function inferLayoutComponent(props: SeedContainerLayoutProps) {
41
+ if (
42
+ props.flexDirection === "row" &&
43
+ props.alignItems === "flexStart" &&
44
+ props.justifyContent === "flexStart" &&
45
+ props.flexWrap === "wrap"
46
+ ) {
47
+ return "Inline";
48
+ }
49
+
50
+ if (
51
+ props.flexDirection === "row" &&
52
+ props.justifyContent === "flexStart" &&
53
+ props.flexWrap === "nowrap"
54
+ ) {
55
+ return "Columns";
56
+ }
57
+
58
+ if (props.flexDirection === "column") {
59
+ return "Stack";
60
+ }
61
+
62
+ return "Flex";
63
+ }
64
+
65
+ const transform = defineElementTransformer(
66
+ (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {
67
+ const children = node.children;
68
+
69
+ const props = {
70
+ ...radiusPropsService.transform(node, traverse),
71
+ ...containerLayoutPropsService.transform(node, traverse),
72
+ ...selfLayoutPropsService.transform(node, traverse),
73
+ ...fillPropsService.transform(node, traverse),
74
+ ...strokePropsService.transform(node, traverse),
75
+ };
76
+
77
+ const layoutComponent = inferLayoutComponent(props);
78
+
79
+ if (layoutComponent === "Stack") {
80
+ const { flexDirection, ...rest } = props;
81
+
82
+ return createElement("Stack", rest, children.map(traverse));
83
+ }
84
+
85
+ if (layoutComponent === "Inline") {
86
+ const { flexDirection, flexWrap, alignItems, justifyContent, ...rest } = props;
87
+
88
+ return createElement("Inline", rest, children.map(traverse));
89
+ }
90
+
91
+ if (layoutComponent === "Columns") {
92
+ const { flexDirection, flexWrap, justifyContent, ...rest } = props;
93
+
94
+ const childrenResult = children.map(traverse);
95
+
96
+ return createElement(
97
+ "Columns",
98
+ rest,
99
+ childrenResult.map((child) => createElement("Column", {}, child)),
100
+ );
101
+ }
102
+ },
103
+ );
104
+
105
+ return {
106
+ transform,
107
+ };
108
+ }
@@ -0,0 +1,5 @@
1
+ export interface IconData {
2
+ name: string;
3
+ type: "monochrome" | "multicolor";
4
+ weight?: string;
5
+ }
@@ -0,0 +1,11 @@
1
+ import type { IconData } from "./icon.interface";
2
+
3
+ export interface IconRepository {
4
+ getIconData(key: string): IconData;
5
+ }
6
+
7
+ export function createStaticIconRepository(iconRecord: Record<string, IconData>) {
8
+ return {
9
+ getIconData: (key: string) => iconRecord[key],
10
+ };
11
+ }
@@ -0,0 +1,35 @@
1
+ import { pascalCase } from "change-case";
2
+ import type { IconRepository } from "./icon.repository";
3
+
4
+ export interface IconService {
5
+ isIconComponent: (componentKey: string) => boolean;
6
+ createIconTagName: (key?: string) => string;
7
+ }
8
+
9
+ export function createIconService({
10
+ iconRepository,
11
+ }: { iconRepository: IconRepository }): IconService {
12
+ function isIconComponent(componentKey: string) {
13
+ return iconRepository.getIconData(componentKey) !== undefined;
14
+ }
15
+
16
+ function createIconTagName(key?: string) {
17
+ if (!key) {
18
+ return "UnknownIcon";
19
+ }
20
+
21
+ const iconData = iconRepository.getIconData(key);
22
+ if (!iconData) {
23
+ throw new Error(`Icon not found: ${key}`);
24
+ }
25
+
26
+ const { name, weight } = iconData;
27
+
28
+ return pascalCase(`${name}${weight ? weight : ""}`);
29
+ }
30
+
31
+ return {
32
+ isIconComponent,
33
+ createIconTagName,
34
+ };
35
+ }
@@ -0,0 +1,22 @@
1
+ export * from "./codegen.service";
2
+ export * from "./figma-component.service";
3
+ export * from "./frame.service";
4
+ export * from "./icon.interface";
5
+ export * from "./icon.repository";
6
+ export * from "./icon.service";
7
+ export * from "./instance.service";
8
+ export * from "./props/container-layout-props.service";
9
+ export * from "./props/fill-props.service";
10
+ export * from "./props/radius-props.service";
11
+ export * from "./props/self-layout-props.service";
12
+ export * from "./props/stroke-props.service";
13
+ export * from "./props/type-style-props.service";
14
+ export * from "./rectangle.service";
15
+ export * from "./seed-component";
16
+ export * from "./style.interface";
17
+ export * from "./style.repository";
18
+ export * from "./style.service";
19
+ export * from "./text.service";
20
+ export * from "./variable.interface";
21
+ export * from "./variable.repository";
22
+ export * from "./variable.service";
@@ -0,0 +1,91 @@
1
+ import type { NormalizedFrameTrait, NormalizedInstanceNode } from "@/normalizer";
2
+ import {
3
+ createElement,
4
+ defineElementTransformer,
5
+ definePropsTransformer,
6
+ type ElementTransformer,
7
+ } from "../core";
8
+ import type { FigmaComponentService } from "./figma-component.service";
9
+ import type { FrameService } from "./frame.service";
10
+ import type { IconService } from "./icon.service";
11
+ import type { FillPropsService, ShapeFillProps } from "./props/fill-props.service";
12
+ import type {
13
+ SeedSelfLayoutProps,
14
+ SelfLayoutPropsService,
15
+ } from "./props/self-layout-props.service";
16
+
17
+ export interface InstanceService {
18
+ transform: ElementTransformer<NormalizedInstanceNode>;
19
+ }
20
+
21
+ export interface SeedInstanceServiceDeps {
22
+ figmaComponentService: FigmaComponentService;
23
+ fillPropsService: FillPropsService<ShapeFillProps>;
24
+ selfLayoutPropsService: SelfLayoutPropsService<SeedSelfLayoutProps>;
25
+ iconService?: IconService;
26
+ frameService?: FrameService;
27
+ ignoredComponentKeys?: Set<string>;
28
+ }
29
+
30
+ export function createSeedInstanceService({
31
+ figmaComponentService,
32
+ fillPropsService,
33
+ selfLayoutPropsService,
34
+ iconService,
35
+ frameService,
36
+ ignoredComponentKeys,
37
+ }: SeedInstanceServiceDeps): InstanceService {
38
+ const transformIconColorProps = definePropsTransformer((node: NormalizedFrameTrait, traverse) => {
39
+ if (node.children.length === 0) {
40
+ throw new Error("Node has no children");
41
+ }
42
+
43
+ const vectors = node.children.filter(
44
+ (child) => child.type === "VECTOR" || child.type === "BOOLEAN_OPERATION",
45
+ );
46
+
47
+ const colorProps = vectors.map((vector) => fillPropsService.transform(vector, traverse));
48
+
49
+ const fills = new Set(
50
+ colorProps.map((props) => props.color).filter((color) => color !== undefined),
51
+ );
52
+
53
+ // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.
54
+ if (fills.size > 1) {
55
+ return {};
56
+ }
57
+
58
+ return { color: fills.values().next().value };
59
+ });
60
+
61
+ const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {
62
+ const { componentKey, componentSetKey } = node;
63
+
64
+ if (ignoredComponentKeys?.has(componentKey)) {
65
+ return undefined;
66
+ }
67
+
68
+ if (iconService?.isIconComponent(componentKey)) {
69
+ const tagName = iconService.createIconTagName(componentKey);
70
+ const props = {
71
+ ...selfLayoutPropsService.transform(node, traverse),
72
+ ...transformIconColorProps(node, traverse),
73
+ };
74
+ return createElement(tagName, props);
75
+ }
76
+
77
+ const componentTransformer = componentSetKey
78
+ ? figmaComponentService.getTransformer(componentSetKey)
79
+ : figmaComponentService.getTransformer(componentKey);
80
+
81
+ if (componentTransformer) {
82
+ return componentTransformer.transform(node);
83
+ }
84
+
85
+ return frameService?.transform(node, traverse);
86
+ });
87
+
88
+ return {
89
+ transform,
90
+ };
91
+ }
@@ -0,0 +1,248 @@
1
+ import { definePropsTransformer, type PropsTransformer } from "@/codegen/core";
2
+ import type {
3
+ NormalizedHasChildrenTrait,
4
+ NormalizedHasFramePropertiesTrait,
5
+ NormalizedIsLayerTrait,
6
+ } from "@/normalizer";
7
+ import { objectEntries } from "@/utils/common";
8
+ import { toCssPixel } from "@/utils/css";
9
+ import type { VariableService } from "../variable.service";
10
+
11
+ type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &
12
+ NormalizedHasChildrenTrait &
13
+ NormalizedIsLayerTrait;
14
+
15
+ export interface ContainerLayoutPropsService<T extends Record<string, any>> {
16
+ transform: PropsTransformer<ContainerLayoutTrait, T>;
17
+ }
18
+
19
+ // Seed Implementation
20
+ type LayoutPropsKey =
21
+ | "flexDirection"
22
+ | "justifyContent"
23
+ | "alignItems"
24
+ | "flexWrap"
25
+ | "gap"
26
+ | "paddingBottom"
27
+ | "paddingLeft"
28
+ | "paddingRight"
29
+ | "paddingTop";
30
+
31
+ type LayoutShorthandPropsKey = "padding" | "paddingX" | "paddingY";
32
+
33
+ type LayoutPropHandler = (props: ContainerLayoutTrait) => string | number | boolean | undefined;
34
+
35
+ type LayoutShorthandHandler = (
36
+ props: Partial<Record<LayoutPropsKey, string | number | boolean | undefined>>,
37
+ ) =>
38
+ | {
39
+ value: string | number | boolean | undefined;
40
+ exclude: LayoutPropsKey[];
41
+ }
42
+ | undefined;
43
+
44
+ export type SeedContainerLayoutProps = Partial<
45
+ Record<LayoutPropsKey | LayoutShorthandPropsKey, string | number | boolean>
46
+ >;
47
+
48
+ export function createContainerLayoutPropsService({
49
+ variableService,
50
+ shouldInferVariableName,
51
+ }: {
52
+ variableService: VariableService;
53
+ shouldInferVariableName: boolean;
54
+ }): ContainerLayoutPropsService<SeedContainerLayoutProps> {
55
+ const getLayoutVariableName = (id: string) => variableService.getVariableName(id);
56
+ const inferSpacingVariableName = (value?: number) => {
57
+ if (value === undefined) {
58
+ return undefined;
59
+ }
60
+
61
+ if (shouldInferVariableName) {
62
+ const inferred = variableService.inferVariableName("GAP", value);
63
+ if (inferred) {
64
+ return inferred;
65
+ }
66
+ }
67
+
68
+ return value === 0 ? 0 : toCssPixel(value);
69
+ };
70
+
71
+ const layoutPropHandlers: Record<LayoutPropsKey, LayoutPropHandler> = {
72
+ flexDirection: ({ layoutMode }) => (layoutMode === "HORIZONTAL" ? "row" : "column"),
73
+ justifyContent: ({ primaryAxisAlignItems }) => {
74
+ switch (primaryAxisAlignItems) {
75
+ case "MIN":
76
+ return "flexStart";
77
+ case "CENTER":
78
+ return "center";
79
+ case "MAX":
80
+ return "flexEnd";
81
+ case "SPACE_BETWEEN":
82
+ return "spaceBetween";
83
+ }
84
+ },
85
+ alignItems: ({ counterAxisAlignItems, children }) => {
86
+ const isStretch = children.every((child) => {
87
+ if (!("layoutAlign" in child)) {
88
+ return false;
89
+ }
90
+
91
+ return child.layoutAlign === "STRETCH";
92
+ });
93
+
94
+ if (isStretch) {
95
+ return "stretch";
96
+ }
97
+
98
+ switch (counterAxisAlignItems) {
99
+ case "MIN":
100
+ return "flexStart";
101
+ case "CENTER":
102
+ return "center";
103
+ case "MAX":
104
+ return "flexEnd";
105
+ case "BASELINE":
106
+ return "baseline";
107
+ }
108
+ },
109
+ flexWrap: ({ layoutWrap }) => (layoutWrap === "WRAP" ? "wrap" : "nowrap"),
110
+ gap: ({ itemSpacing, boundVariables, primaryAxisAlignItems, children }) =>
111
+ children.length <= 1
112
+ ? 0
113
+ : primaryAxisAlignItems === "SPACE_BETWEEN"
114
+ ? 0
115
+ : boundVariables?.itemSpacing
116
+ ? getLayoutVariableName(boundVariables.itemSpacing.id)
117
+ : inferSpacingVariableName(itemSpacing),
118
+ paddingTop: ({ paddingTop, boundVariables }) =>
119
+ boundVariables?.paddingTop
120
+ ? getLayoutVariableName(boundVariables.paddingTop.id)
121
+ : inferSpacingVariableName(paddingTop),
122
+ paddingBottom: ({ paddingBottom, boundVariables }) =>
123
+ boundVariables?.paddingBottom
124
+ ? getLayoutVariableName(boundVariables.paddingBottom.id)
125
+ : inferSpacingVariableName(paddingBottom),
126
+ paddingLeft: ({ paddingLeft, boundVariables }) =>
127
+ boundVariables?.paddingLeft
128
+ ? getLayoutVariableName(boundVariables.paddingLeft.id)
129
+ : inferSpacingVariableName(paddingLeft),
130
+ paddingRight: ({ paddingRight, boundVariables }) =>
131
+ boundVariables?.paddingRight
132
+ ? getLayoutVariableName(boundVariables.paddingRight.id)
133
+ : inferSpacingVariableName(paddingRight),
134
+ };
135
+
136
+ const layoutShorthandHandlers: Record<LayoutShorthandPropsKey, LayoutShorthandHandler> = {
137
+ paddingX: ({ paddingLeft, paddingRight, paddingTop, paddingBottom }) => {
138
+ if (paddingLeft === undefined || paddingRight === undefined) {
139
+ return undefined;
140
+ }
141
+
142
+ if (
143
+ paddingLeft === paddingRight &&
144
+ paddingTop === paddingBottom &&
145
+ paddingLeft === paddingTop
146
+ ) {
147
+ return undefined;
148
+ }
149
+
150
+ if (paddingLeft === paddingRight) {
151
+ const value = paddingLeft;
152
+ return {
153
+ value,
154
+ exclude: ["paddingLeft", "paddingRight"],
155
+ };
156
+ }
157
+ return undefined;
158
+ },
159
+ paddingY: ({ paddingLeft, paddingRight, paddingTop, paddingBottom }) => {
160
+ if (paddingTop === undefined || paddingBottom === undefined) {
161
+ return undefined;
162
+ }
163
+
164
+ if (
165
+ paddingLeft === paddingRight &&
166
+ paddingTop === paddingBottom &&
167
+ paddingLeft === paddingTop
168
+ ) {
169
+ return undefined;
170
+ }
171
+
172
+ if (paddingTop === paddingBottom) {
173
+ return {
174
+ value: paddingTop,
175
+ exclude: ["paddingTop", "paddingBottom"],
176
+ };
177
+ }
178
+ return undefined;
179
+ },
180
+ padding: ({ paddingLeft, paddingRight, paddingTop, paddingBottom }) => {
181
+ if (
182
+ paddingLeft === undefined ||
183
+ paddingRight === undefined ||
184
+ paddingTop === undefined ||
185
+ paddingBottom === undefined
186
+ ) {
187
+ return undefined;
188
+ }
189
+
190
+ if (
191
+ paddingLeft === paddingRight &&
192
+ paddingTop === paddingBottom &&
193
+ paddingLeft === paddingTop
194
+ ) {
195
+ return {
196
+ value: paddingLeft,
197
+ exclude: ["paddingLeft", "paddingRight", "paddingTop", "paddingBottom"],
198
+ };
199
+ }
200
+ return undefined;
201
+ },
202
+ };
203
+
204
+ // Default values
205
+ const layoutPropDefaults: SeedContainerLayoutProps = {
206
+ flexDirection: "row",
207
+ justifyContent: "flexStart",
208
+ alignItems: "stretch",
209
+ flexWrap: "nowrap",
210
+ gap: 0,
211
+ padding: 0,
212
+ paddingX: 0,
213
+ paddingY: 0,
214
+ paddingBottom: 0,
215
+ paddingLeft: 0,
216
+ paddingRight: 0,
217
+ paddingTop: 0,
218
+ };
219
+
220
+ const transform = definePropsTransformer((node: ContainerLayoutTrait) => {
221
+ const result: SeedContainerLayoutProps = {};
222
+
223
+ for (const [prop, handler] of objectEntries(layoutPropHandlers)) {
224
+ const value = handler(node);
225
+ if (value !== undefined && value !== layoutPropDefaults[prop]) {
226
+ result[prop] = value;
227
+ }
228
+ }
229
+
230
+ for (const [prop, handler] of objectEntries(layoutShorthandHandlers)) {
231
+ const shorthandResult = handler(result);
232
+ if (shorthandResult === undefined) {
233
+ continue;
234
+ }
235
+ const { value, exclude } = shorthandResult;
236
+ if (value !== undefined && value !== layoutPropDefaults[prop]) {
237
+ result[prop] = value;
238
+ for (const excludedProp of exclude) {
239
+ delete result[excludedProp];
240
+ }
241
+ }
242
+ }
243
+
244
+ return result;
245
+ });
246
+
247
+ return { transform };
248
+ }
@@ -0,0 +1,75 @@
1
+ import { definePropsTransformer, type PropsTransformer } from "@/codegen/core";
2
+ import type { NormalizedHasGeometryTrait, NormalizedIsLayerTrait } from "@/normalizer";
3
+ import { toCssRgba } from "@/utils/css";
4
+ import type { VariableService } from "../variable.service";
5
+
6
+ type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;
7
+
8
+ export interface FillPropsService<T extends Record<string, any>> {
9
+ transform: PropsTransformer<FillTrait, T>;
10
+ }
11
+
12
+ export interface FrameFillProps {
13
+ background?: string;
14
+ }
15
+
16
+ export function createFrameFillPropsService({
17
+ variableService,
18
+ }: { variableService: VariableService }): FillPropsService<FrameFillProps> {
19
+ const transform = definePropsTransformer((node: FillTrait) => {
20
+ const fills = node.fills;
21
+ if (fills.length === 0) {
22
+ return {};
23
+ }
24
+
25
+ const fill = fills[0];
26
+ if (!fill || ("visible" in fill && !fill.visible) || fill.type !== "SOLID") {
27
+ return {};
28
+ }
29
+
30
+ if (node.boundVariables?.fills?.length === 1) {
31
+ return {
32
+ background: variableService.getVariableName(node.boundVariables.fills[0]!.id),
33
+ };
34
+ }
35
+
36
+ const color = fill.color;
37
+ return {
38
+ background: toCssRgba(color),
39
+ };
40
+ });
41
+
42
+ return { transform };
43
+ }
44
+
45
+ export interface ShapeFillProps {
46
+ color?: string;
47
+ }
48
+
49
+ export function createShapeFillPropsService({
50
+ variableService,
51
+ }: { variableService: VariableService }): FillPropsService<ShapeFillProps> {
52
+ const transform = definePropsTransformer((node: FillTrait) => {
53
+ const fills = node.fills;
54
+ if (fills.length === 0) {
55
+ return {};
56
+ }
57
+
58
+ const fill = fills[0];
59
+ if (!fill || ("visible" in fill && !fill.visible) || fill.type !== "SOLID") {
60
+ return {};
61
+ }
62
+ if (node.boundVariables?.fills?.length === 1) {
63
+ return {
64
+ color: variableService.getVariableName(node.boundVariables.fills[0]!.id),
65
+ };
66
+ }
67
+
68
+ const color = fill.color;
69
+ return {
70
+ color: toCssRgba(color),
71
+ };
72
+ });
73
+
74
+ return { transform };
75
+ }