@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,95 @@
1
+ import { isVariableAlias, sanitizeVariableId } from "@/utils/figma-variable";
2
+ import type { Variable, VariableScope, VariableValueResolved } from "./variable.interface";
3
+ import type { VariableRepository } from "./variable.repository";
4
+
5
+ export interface VariableService {
6
+ getVariableName: (id: string) => string;
7
+ inferVariableName: (scope: VariableScope, value: number | string | boolean) => string | undefined;
8
+ }
9
+
10
+ export interface VariableServiceDeps {
11
+ variableRepository: VariableRepository;
12
+ variableNameTransformer: ({ slug }: { slug: string[] }) => string;
13
+ }
14
+
15
+ export function createVariableService({
16
+ variableRepository,
17
+ variableNameTransformer,
18
+ }: VariableServiceDeps): VariableService {
19
+ const variables = variableRepository.getVariableList();
20
+
21
+ // private
22
+ function getFigmaVariableName(key: string) {
23
+ const sanitizedId = sanitizeVariableId(key);
24
+ const variable = variableRepository.findVariableByKey(sanitizedId);
25
+
26
+ if (!variable) {
27
+ return "UNKNOWN_VARIABLE";
28
+ }
29
+
30
+ return variable.name;
31
+ }
32
+
33
+ function getFigmaVariableSlug(key: string): string[] {
34
+ const name = getFigmaVariableName(key);
35
+ return name.split("/");
36
+ }
37
+
38
+ function getDefaultModeId(variable: Variable) {
39
+ const variableCollection = variableRepository.findVariableCollectionById(
40
+ variable.variableCollectionId,
41
+ );
42
+
43
+ if (!variableCollection) {
44
+ // Variable collection not found: ${variable.variableCollectionId}, falling back to variable.valuesByMode key
45
+ return Object.keys(variable.valuesByMode)[0]!;
46
+ }
47
+
48
+ return variableCollection.defaultModeId;
49
+ }
50
+
51
+ function resolveVariableValue(id: string, mode: string): VariableValueResolved {
52
+ const variable = variableRepository.findVariableById(id);
53
+ if (!variable) {
54
+ throw new Error(`Variable not found: ${id}`);
55
+ }
56
+
57
+ const value = variable.valuesByMode[mode];
58
+
59
+ if (value === undefined) {
60
+ throw new Error(`Variable value not found: ${id} ${mode}`);
61
+ }
62
+
63
+ if (isVariableAlias(value)) {
64
+ return resolveVariableValue(value.id, mode);
65
+ }
66
+
67
+ return value;
68
+ }
69
+
70
+ // public
71
+ function getVariableName(key: string) {
72
+ const slug = getFigmaVariableSlug(key);
73
+ return variableNameTransformer({ slug });
74
+ }
75
+
76
+ function inferVariableName(scope: VariableScope, value: number | string | boolean) {
77
+ // NOTE: We assume that the variable is in the default mode or value is equal between all modes for simplicity.
78
+ const inferredVariable = variables.find(
79
+ (variable) =>
80
+ variable.scopes.includes(scope) &&
81
+ resolveVariableValue(variable.id, getDefaultModeId(variable)) === value,
82
+ );
83
+
84
+ if (!inferredVariable) {
85
+ return undefined;
86
+ }
87
+
88
+ return getVariableName(inferredVariable.key);
89
+ }
90
+
91
+ return {
92
+ getVariableName,
93
+ inferVariableName,
94
+ };
95
+ }
@@ -0,0 +1,13 @@
1
+ export * from "./core";
2
+ export * from "./domain";
3
+
4
+ import type { NormalizedSceneNode } from "@/normalizer";
5
+ import { codegenService, devCodegenService } from "./context";
6
+
7
+ export function generateJsxTree(node: NormalizedSceneNode) {
8
+ return codegenService.transform(node);
9
+ }
10
+
11
+ export function generateCode(node: NormalizedSceneNode, { dev }: { dev?: boolean } = {}) {
12
+ return dev ? devCodegenService.transformToString(node) : codegenService.transformToString(node);
13
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { generateCode } from "./generate-code";
2
- export { createPluginNormalizer, createRestNormalizer } from "./normalizer";
1
+ export * from "./normalizer";
2
+ export * from "./codegen";
@@ -41,7 +41,11 @@ export function createPluginNormalizer() {
41
41
  return normalizeInstanceNode(node);
42
42
  }
43
43
 
44
- throw new Error(`Unimplemented node type: ${node.type}`);
44
+ return {
45
+ type: "UNHANDLED",
46
+ id: node.id,
47
+ original: node,
48
+ };
45
49
  }
46
50
 
47
51
  async function normalizeFrameNode(node: FrameNode): Promise<NormalizedFrameNode> {
@@ -117,7 +121,7 @@ export function createPluginNormalizer() {
117
121
  name: node.name,
118
122
  boundVariables: await normalizeBoundVariables(node),
119
123
  children: await normalizeNodes(node.children),
120
- fills: normalizePaints(node.fills),
124
+ ...normalizeShapeProps(node),
121
125
  };
122
126
  }
123
127
  async function normalizeTextNode(node: TextNode): Promise<NormalizedTextNode> {
@@ -132,7 +136,7 @@ export function createPluginNormalizer() {
132
136
  "fills",
133
137
  "boundVariables",
134
138
  ]);
135
- const first = segments[0]!; // TODO: handle multiple segments
139
+ const first = segments[0]!;
136
140
 
137
141
  const textStyleKey =
138
142
  typeof node.textStyleId === "string"
@@ -144,8 +148,6 @@ export function createPluginNormalizer() {
144
148
  id: node.id,
145
149
  name: node.name,
146
150
  boundVariables: await normalizeBoundVariables(node),
147
- layoutGrow: node.layoutGrow as 0 | 1 | undefined,
148
- layoutAlign: node.layoutAlign,
149
151
  style: {
150
152
  fontSize: first.fontSize,
151
153
  fontWeight: first.fontWeight,
@@ -159,29 +161,20 @@ export function createPluginNormalizer() {
159
161
  },
160
162
  ...(textStyleKey ? { textStyleKey } : {}),
161
163
  characters: node.characters,
162
- segments: node
163
- .getStyledTextSegments([
164
- "fontSize",
165
- "fontWeight",
166
- "fontName",
167
- "letterSpacing",
168
- "lineHeight",
169
- ])
170
- .map((segment) => ({
171
- characters: segment.characters,
172
- start: segment.start,
173
- end: segment.end,
174
- style: {
175
- fontSize: segment.fontSize,
176
- fontWeight: segment.fontWeight,
177
- fontFamily: segment.fontName.family,
178
- letterSpacing:
179
- segment.letterSpacing.unit === "PIXELS" ? segment.letterSpacing.value : undefined,
180
- lineHeightPx:
181
- segment.lineHeight.unit === "PIXELS" ? segment.lineHeight.value : undefined,
182
- },
183
- })),
184
- fills: normalizePaints(node.fills),
164
+ segments: segments.map((segment) => ({
165
+ characters: segment.characters,
166
+ start: segment.start,
167
+ end: segment.end,
168
+ style: {
169
+ fontSize: segment.fontSize,
170
+ fontWeight: segment.fontWeight,
171
+ fontFamily: segment.fontName.family,
172
+ letterSpacing:
173
+ segment.letterSpacing.unit === "PIXELS" ? segment.letterSpacing.value : undefined,
174
+ lineHeightPx: segment.lineHeight.unit === "PIXELS" ? segment.lineHeight.value : undefined,
175
+ },
176
+ })),
177
+ ...normalizeShapeProps(node),
185
178
  };
186
179
  }
187
180
 
@@ -303,6 +296,10 @@ export function createPluginNormalizer() {
303
296
  | "layoutSizingHorizontal"
304
297
  | "layoutSizingVertical"
305
298
  | "absoluteBoundingBox"
299
+ | "minHeight"
300
+ | "minWidth"
301
+ | "maxHeight"
302
+ | "maxWidth"
306
303
  > &
307
304
  Partial<Pick<FrameNode, "inferredAutoLayout">>,
308
305
  ) {
@@ -315,6 +312,10 @@ export function createPluginNormalizer() {
315
312
  fills: normalizePaints(node.fills),
316
313
  strokes: normalizePaints(node.strokes),
317
314
  strokeWeight: node.strokeWeight === figma.mixed ? undefined : node.strokeWeight,
315
+ minHeight: node.minHeight ?? undefined,
316
+ minWidth: node.minWidth ?? undefined,
317
+ maxHeight: node.maxHeight ?? undefined,
318
+ maxWidth: node.maxWidth ?? undefined,
318
319
  };
319
320
  }
320
321
 
@@ -49,7 +49,11 @@ export function createRestNormalizer(ctx: RestNormalizerContext) {
49
49
  return normalizeInstanceNode(node);
50
50
  }
51
51
 
52
- throw new Error(`Unimplemented node type: ${node.type}, ${node.name}`);
52
+ return {
53
+ type: "UNHANDLED",
54
+ id: node.id,
55
+ original: node,
56
+ };
53
57
  }
54
58
 
55
59
  function normalizeFrameNode(node: FigmaRestSpec.FrameNode): NormalizedFrameNode {
@@ -1,20 +1,40 @@
1
1
  import type * as FigmaRestSpec from "@figma/rest-api-spec";
2
2
 
3
- export type CommonProps = "type" | "id" | "name" | "boundVariables";
3
+ export type NormalizedIsLayerTrait = Pick<
4
+ FigmaRestSpec.IsLayerTrait,
5
+ "type" | "id" | "name" | "boundVariables"
6
+ >;
4
7
 
5
- export type RadiusProps = "cornerRadius" | "rectangleCornerRadii";
8
+ export type NormalizedCornerTrait = Pick<
9
+ FigmaRestSpec.CornerTrait,
10
+ "cornerRadius" | "rectangleCornerRadii"
11
+ >;
6
12
 
7
- export type ShapeProps =
8
- | "layoutGrow"
13
+ export type NormalizedHasChildrenTrait = {
14
+ children: NormalizedSceneNode[];
15
+ };
16
+
17
+ export type NormalizedHasLayoutTrait = Pick<
18
+ FigmaRestSpec.HasLayoutTrait,
9
19
  | "layoutAlign"
20
+ | "layoutGrow"
21
+ | "absoluteBoundingBox"
22
+ | "layoutPositioning"
10
23
  | "layoutSizingHorizontal"
11
24
  | "layoutSizingVertical"
12
- | "absoluteBoundingBox"
13
- | "fills"
14
- | "strokes"
15
- | "strokeWeight";
16
-
17
- export type LayoutProps =
25
+ | "minHeight"
26
+ | "minWidth"
27
+ | "maxHeight"
28
+ | "maxWidth"
29
+ >;
30
+
31
+ export type NormalizedHasGeometryTrait = Pick<
32
+ FigmaRestSpec.HasGeometryTrait,
33
+ "fills" | "strokes" | "strokeWeight" | "styles"
34
+ >;
35
+
36
+ export type NormalizedHasFramePropertiesTrait = Pick<
37
+ FigmaRestSpec.HasFramePropertiesTrait,
18
38
  | "layoutMode"
19
39
  | "layoutWrap"
20
40
  | "paddingLeft"
@@ -22,26 +42,43 @@ export type LayoutProps =
22
42
  | "paddingTop"
23
43
  | "paddingBottom"
24
44
  | "primaryAxisAlignItems"
25
- | "counterAxisAlignItems"
26
45
  | "primaryAxisSizingMode"
46
+ | "counterAxisAlignItems"
27
47
  | "counterAxisSizingMode"
28
48
  | "itemSpacing"
29
- | "counterAxisSpacing";
30
-
31
- export interface NormalizedFrameNode
32
- extends Pick<FigmaRestSpec.FrameNode, CommonProps | ShapeProps | RadiusProps | LayoutProps> {
33
- children: NormalizedSceneNode[];
49
+ | "counterAxisSpacing"
50
+ >;
51
+
52
+ export type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &
53
+ NormalizedHasLayoutTrait &
54
+ NormalizedHasGeometryTrait;
55
+
56
+ export type NormalizedFrameTrait = NormalizedIsLayerTrait &
57
+ NormalizedHasLayoutTrait &
58
+ NormalizedHasGeometryTrait &
59
+ NormalizedHasChildrenTrait &
60
+ NormalizedCornerTrait &
61
+ NormalizedHasFramePropertiesTrait;
62
+
63
+ export interface NormalizedFrameNode extends NormalizedFrameTrait {
64
+ type: FigmaRestSpec.FrameNode["type"];
34
65
  }
35
66
 
36
67
  export interface NormalizedRectangleNode
37
- extends Pick<FigmaRestSpec.RectangleNode, CommonProps | ShapeProps | RadiusProps> {}
68
+ extends NormalizedDefaultShapeTrait,
69
+ NormalizedCornerTrait {
70
+ type: FigmaRestSpec.RectangleNode["type"];
71
+ }
72
+
73
+ export interface NormalizedTextNode extends NormalizedDefaultShapeTrait {
74
+ type: FigmaRestSpec.TextNode["type"];
75
+
76
+ style: FigmaRestSpec.TextNode["style"];
77
+
78
+ characters: FigmaRestSpec.TextNode["characters"];
38
79
 
39
- export interface NormalizedTextNode
40
- extends Pick<
41
- FigmaRestSpec.TextNode,
42
- CommonProps | "layoutGrow" | "layoutAlign" | "style" | "characters" | "fills"
43
- > {
44
80
  segments: NormalizedTextSegment[];
81
+
45
82
  textStyleKey?: string;
46
83
  }
47
84
 
@@ -60,13 +97,13 @@ export interface NormalizedTextSegment {
60
97
  };
61
98
  }
62
99
 
63
- export interface NormalizedComponentNode
64
- extends Pick<FigmaRestSpec.ComponentNode, CommonProps | ShapeProps | RadiusProps | LayoutProps> {
65
- children: NormalizedSceneNode[];
100
+ export interface NormalizedComponentNode extends NormalizedFrameTrait {
101
+ type: FigmaRestSpec.ComponentNode["type"];
66
102
  }
67
103
 
68
- export interface NormalizedInstanceNode
69
- extends Pick<FigmaRestSpec.InstanceNode, CommonProps | ShapeProps | RadiusProps | LayoutProps> {
104
+ export interface NormalizedInstanceNode extends NormalizedFrameTrait {
105
+ type: FigmaRestSpec.InstanceNode["type"];
106
+
70
107
  componentProperties: {
71
108
  [key: string]: FigmaRestSpec.ComponentProperty & { componentKey?: string };
72
109
  };
@@ -78,12 +115,22 @@ export interface NormalizedInstanceNode
78
115
  children: NormalizedSceneNode[];
79
116
  }
80
117
 
81
- export interface NormalizedVectorNode
82
- extends Pick<FigmaRestSpec.VectorNode, CommonProps | ShapeProps> {}
118
+ export interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {
119
+ type: FigmaRestSpec.VectorNode["type"];
120
+ }
83
121
 
84
122
  export interface NormalizedBooleanOperationNode
85
- extends Pick<FigmaRestSpec.BooleanOperationNode, CommonProps | "fills"> {
86
- children: NormalizedSceneNode[];
123
+ extends NormalizedIsLayerTrait,
124
+ NormalizedHasChildrenTrait,
125
+ NormalizedHasLayoutTrait,
126
+ NormalizedHasGeometryTrait {
127
+ type: FigmaRestSpec.BooleanOperationNode["type"];
128
+ }
129
+
130
+ export interface NormalizedUnhandledNode {
131
+ type: "UNHANDLED";
132
+ id: string;
133
+ original: FigmaRestSpec.Node | SceneNode;
87
134
  }
88
135
 
89
136
  export type NormalizedSceneNode =
@@ -93,4 +140,5 @@ export type NormalizedSceneNode =
93
140
  | NormalizedComponentNode
94
141
  | NormalizedInstanceNode
95
142
  | NormalizedVectorNode
96
- | NormalizedBooleanOperationNode;
143
+ | NormalizedBooleanOperationNode
144
+ | NormalizedUnhandledNode;
@@ -1,3 +1,5 @@
1
+ import { camelCase } from "change-case";
2
+
1
3
  export function ensureArray<T>(maybeArray: T | T[]): T[] {
2
4
  if (Array.isArray(maybeArray)) {
3
5
  return maybeArray;
@@ -13,3 +15,20 @@ export function exists<T>(value: T | null | undefined): value is T {
13
15
  export function compactObject<T extends Record<string, unknown>>(obj: T): T {
14
16
  return Object.fromEntries(Object.entries(obj).filter(([, value]) => value != null)) as T;
15
17
  }
18
+
19
+ export function objectEntries<T extends Record<string, unknown>>(obj: T) {
20
+ return Object.entries(obj) as [keyof T, T[keyof T]][];
21
+ }
22
+
23
+ /**
24
+ * camelCase but preserve underscore between numbers.
25
+ * temporary workaround to avoid x1_5 -> x15
26
+ * @example "color-1_5" -> "color1_5"
27
+ */
28
+ export function camelCasePreserveUnderscoreBetweenNumbers(input: string) {
29
+ return camelCase(input, {
30
+ mergeAmbiguousCharacters: false,
31
+ })
32
+ .replaceAll(/(\D)_(\d)/g, "$1$2")
33
+ .replaceAll(/(\d)_(\D)/g, "$1$2");
34
+ }
@@ -0,0 +1,13 @@
1
+ import type { RGBA } from "@figma/rest-api-spec";
2
+
3
+ export function toCssPixel(value: number) {
4
+ return `${value}px`;
5
+ }
6
+
7
+ export function toCssRgba(color: RGBA) {
8
+ if (color.a === 1) {
9
+ return `rgb(${color.r * 255}, ${color.g * 255}, ${color.b * 255})`;
10
+ }
11
+
12
+ return `rgba(${color.r * 255}, ${color.g * 255}, ${color.b * 255}, ${color.a})`;
13
+ }
@@ -0,0 +1,13 @@
1
+ export function isVariableAlias(value: unknown): value is VariableAlias {
2
+ return (
3
+ typeof value === "object" &&
4
+ value !== null &&
5
+ "type" in value &&
6
+ value.type === "VARIABLE_ALIAS"
7
+ );
8
+ }
9
+
10
+ // boundVariable.id is formatted as "VariableID:{key}/{localId}", we have to extract the key
11
+ export function sanitizeVariableId(id: string) {
12
+ return id.replace("VariableID:", "").split("/")[0]!;
13
+ }
@@ -1,66 +0,0 @@
1
- import { camelCase } from "change-case";
2
- import { match } from "ts-pattern";
3
- import * as metadata from "../../data/__generated__/component-sets";
4
- import { createIconTagNameFromKey } from "../../icon";
5
- import { createElement } from "../../jsx";
6
- import { handleSize } from "../properties";
7
- import type { ActionButtonProperties } from "../type";
8
- import type { ComponentHandler } from "../type-helper";
9
-
10
- export const actionButtonHandler: ComponentHandler<ActionButtonProperties> = {
11
- key: metadata.actionButton.key,
12
- codegen: async ({ componentProperties: props }) => {
13
- const states = props.State.value.split("-");
14
-
15
- const { layout, children } = await match(props.Layout.value)
16
- .with("Icon Only", async () => ({
17
- layout: "iconOnly",
18
- children: [
19
- createElement("Icon", {
20
- svg: createElement(createIconTagNameFromKey(props["Icon#7574:0"].componentKey)),
21
- }),
22
- ],
23
- }))
24
- .with("Icon First", async () => ({
25
- layout: "withText",
26
- children: [
27
- createElement("PrefixIcon", {
28
- svg: createElement(
29
- createIconTagNameFromKey(props["Prefix Icon#5987:305"].componentKey),
30
- ),
31
- }),
32
- props["Label#5987:61"].value,
33
- ],
34
- }))
35
- .with("Icon Last", async () => ({
36
- layout: "withText",
37
- children: [
38
- props["Label#5987:61"].value,
39
- createElement("SuffixIcon", {
40
- svg: createElement(
41
- createIconTagNameFromKey(props["Suffix Icon#5987:244"].componentKey),
42
- ),
43
- }),
44
- ],
45
- }))
46
- .with("Text Only", () => ({
47
- layout: "withText",
48
- children: props["Label#5987:61"].value,
49
- }))
50
- .exhaustive();
51
-
52
- const commonProps = {
53
- ...(states.includes("Disabled") && {
54
- disabled: true,
55
- }),
56
- ...(states.includes("Loading") && {
57
- loading: true,
58
- }),
59
- size: handleSize(props.Size.value),
60
- variant: camelCase(props.Variant.value),
61
- layout,
62
- };
63
-
64
- return createElement("ActionButton", commonProps, children);
65
- },
66
- };
@@ -1,71 +0,0 @@
1
- import { match } from "ts-pattern";
2
- import * as metadata from "../../data/__generated__/component-sets";
3
- import { createIconTagNameFromKey } from "../../icon";
4
- import { createElement } from "../../jsx";
5
- import { handleSize } from "../properties";
6
- import type { ActionChipProperties } from "../type";
7
- import type { ComponentHandler } from "../type-helper";
8
-
9
- export const actionChipHandler: ComponentHandler<ActionChipProperties> = {
10
- key: metadata.actionChip.key,
11
- codegen: async ({ componentProperties: props }) => {
12
- const states = props.State.value.split("-");
13
-
14
- const { layout, children } = await match(props.Layout.value)
15
- .with("Icon Only", async () => ({
16
- layout: "iconOnly",
17
- children: [
18
- createElement("Icon", {
19
- svg: createElement(createIconTagNameFromKey(props["Icon#8714:0"].componentKey)),
20
- }),
21
- ],
22
- }))
23
- .with("Icon First", async () => ({
24
- layout: "withText",
25
- children: [
26
- createElement("PrefixIcon", {
27
- svg: createElement(createIconTagNameFromKey(props["Prefix Icon#8711:0"].componentKey)),
28
- }),
29
- props["Label#7185:0"].value,
30
- ],
31
- }))
32
- .with("Icon Last", async () => ({
33
- layout: "withText",
34
- children: [
35
- props["Label#7185:0"].value,
36
- createElement("SuffixIcon", {
37
- svg: createElement(createIconTagNameFromKey(props["Suffix Icon#8711:3"].componentKey)),
38
- }),
39
- ],
40
- }))
41
- .with("Icon Both", async () => ({
42
- layout: "withText",
43
- children: [
44
- createElement("PrefixIcon", {
45
- svg: createElement(createIconTagNameFromKey(props["Prefix Icon#8711:0"].componentKey)),
46
- }),
47
- props["Label#7185:0"].value,
48
- createElement("SuffixIcon", {
49
- svg: createElement(createIconTagNameFromKey(props["Suffix Icon#8711:3"].componentKey)),
50
- }),
51
- ],
52
- }))
53
- .with("Text Only", () => ({
54
- layout: "withText",
55
- children: props["Label#7185:0"].value,
56
- }))
57
- .exhaustive();
58
-
59
- const commonProps = {
60
- size: handleSize(props.Size.value),
61
- layout,
62
- ...(states.includes("Disabled") && {
63
- disabled: true,
64
- }),
65
- ...(props["Show Count#7185:42"].value && {
66
- count: Number(props["Count#7185:21"].value),
67
- }),
68
- };
69
- return createElement("ActionChip", commonProps, children);
70
- },
71
- };
@@ -1,74 +0,0 @@
1
- import { camelCase } from "change-case";
2
- import { match } from "ts-pattern";
3
- import * as metadata from "../../data/__generated__/component-sets";
4
- import { createElement } from "../../jsx";
5
- import { findAllInstances } from "../../utils/figma-node";
6
- import type { ActionSheetItemProperties, ActionSheetProperties } from "../type";
7
- import type { ComponentHandler } from "../type-helper";
8
-
9
- export const actionSheetHandler: ComponentHandler<ActionSheetProperties> = {
10
- key: metadata.actionSheet.key,
11
- codegen: async (node) => {
12
- const { componentProperties: props } = node;
13
-
14
- const contentProps = match(props.Header.value)
15
- .with("None", () => ({
16
- title: undefined,
17
- description: undefined,
18
- }))
19
- .with("Title Only", () => ({
20
- title: props["Title#15641:37"].value,
21
- description: undefined,
22
- }))
23
- .with("Description Only", () => ({
24
- title: undefined,
25
- description: props["Description#15641:70"].value,
26
- }))
27
- .with("Title With Description", () => ({
28
- title: props["Title#15641:37"].value,
29
- description: props["Description#15641:70"].value,
30
- }))
31
- .exhaustive();
32
-
33
- const items = await findAllInstances<ActionSheetItemProperties>({
34
- node,
35
- key: actionSheetItemHandler.key,
36
- });
37
-
38
- const contentChildren = await Promise.all(items.map(actionSheetItemHandler.codegen));
39
-
40
- const content = createElement(
41
- "ActionSheetContent",
42
- contentProps,
43
- contentChildren,
44
- contentProps.title
45
- ? ""
46
- : "title을 제공하지 않는 경우 aria-label이나 aria-labelledby 중 하나를 제공해야 합니다.",
47
- );
48
-
49
- const trigger = createElement(
50
- "ActionSheetTrigger",
51
- { asChild: true },
52
- createElement("ActionButton", undefined, "열기", "ActionSheet을 여는 요소를 제공해주세요."),
53
- );
54
-
55
- return createElement("ActionSheet", undefined, [trigger, content]);
56
- },
57
- };
58
-
59
- const actionSheetItemHandler: ComponentHandler<ActionSheetItemProperties> = {
60
- key: "c3cafd3a3fdcd45fecb6971019d88eaf39a2e381",
61
- codegen: async ({ componentProperties: props }) => {
62
- const states = props.State.value.split("-");
63
-
64
- const commonProps = {
65
- label: props["Label#15420:4"].value,
66
- tone: camelCase(props.Tone.value),
67
- ...(states.includes("Disabled") && {
68
- disabled: true,
69
- }),
70
- };
71
-
72
- return createElement("ActionSheetItem", commonProps);
73
- },
74
- };