@seed-design/figma 0.0.21 → 0.0.23

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 (81) hide show
  1. package/lib/codegen/index.cjs +8012 -0
  2. package/lib/codegen/index.d.ts +1828 -0
  3. package/lib/codegen/index.js +7983 -0
  4. package/lib/codegen/targets/react/index.cjs +12198 -0
  5. package/lib/codegen/targets/react/index.d.ts +267 -0
  6. package/lib/codegen/targets/react/index.js +12180 -0
  7. package/lib/index.cjs +79 -2741
  8. package/lib/index.d.ts +17 -1937
  9. package/lib/index.js +61 -2715
  10. package/package.json +12 -2
  11. package/src/codegen/{targets/react/component/properties.type.ts → component-properties.ts} +43 -43
  12. package/src/codegen/core/codegen.ts +17 -6
  13. package/src/codegen/core/{component.ts → component-handler.ts} +3 -3
  14. package/src/codegen/core/component-type-helper.ts +35 -0
  15. package/src/codegen/core/index.ts +14 -14
  16. package/src/codegen/core/{props.ts → props-converter.ts} +10 -13
  17. package/src/codegen/core/{value.ts → value-resolver.ts} +87 -50
  18. package/src/codegen/default-services.ts +44 -0
  19. package/src/codegen/index.ts +1 -44
  20. package/src/codegen/targets/figma/frame.ts +8 -8
  21. package/src/codegen/targets/figma/index.ts +1 -1
  22. package/src/codegen/targets/figma/pipeline.ts +94 -0
  23. package/src/codegen/targets/figma/props.ts +59 -70
  24. package/src/codegen/targets/figma/shape.ts +18 -18
  25. package/src/codegen/targets/figma/text.ts +6 -6
  26. package/src/codegen/targets/figma/value-resolver.ts +19 -0
  27. package/src/codegen/targets/react/component/deps.interface.ts +5 -4
  28. package/src/codegen/targets/react/component/{transformers → handlers}/action-button.ts +8 -14
  29. package/src/codegen/targets/react/component/{transformers → handlers}/action-chip.ts +10 -20
  30. package/src/codegen/targets/react/component/{transformers → handlers}/action-sheet.ts +13 -10
  31. package/src/codegen/targets/react/component/{transformers → handlers}/app-bar.ts +28 -36
  32. package/src/codegen/targets/react/component/handlers/avatar-stack.ts +29 -0
  33. package/src/codegen/targets/react/component/{transformers → handlers}/avatar.ts +12 -9
  34. package/src/codegen/targets/react/component/handlers/badge.ts +18 -0
  35. package/src/codegen/targets/react/component/{transformers → handlers}/callout.ts +6 -8
  36. package/src/codegen/targets/react/component/{transformers → handlers}/checkbox.ts +5 -5
  37. package/src/codegen/targets/react/component/{transformers → handlers}/chip-tabs.ts +10 -10
  38. package/src/codegen/targets/react/component/{transformers → handlers}/control-chip.ts +10 -20
  39. package/src/codegen/targets/react/component/{transformers → handlers}/error-state.ts +9 -9
  40. package/src/codegen/targets/react/component/{transformers → handlers}/extended-action-sheet.ts +16 -18
  41. package/src/codegen/targets/react/component/{transformers → handlers}/extended-fab.ts +6 -6
  42. package/src/codegen/targets/react/component/handlers/fab.ts +18 -0
  43. package/src/codegen/targets/react/component/{transformers → handlers}/help-bubble.ts +5 -5
  44. package/src/codegen/targets/react/component/{transformers → handlers}/identity-placeholder.ts +5 -5
  45. package/src/codegen/targets/react/component/{transformers → handlers}/inline-banner.ts +7 -10
  46. package/src/codegen/targets/react/component/{transformers → handlers}/manner-temp-badge.ts +5 -5
  47. package/src/codegen/targets/react/component/{transformers → handlers}/multiline-text-field.ts +5 -5
  48. package/src/codegen/targets/react/component/{transformers → handlers}/progress-circle.ts +5 -5
  49. package/src/codegen/targets/react/component/{transformers → handlers}/reaction-button.ts +6 -6
  50. package/src/codegen/targets/react/component/{transformers → handlers}/segmented-control.ts +10 -10
  51. package/src/codegen/targets/react/component/{transformers → handlers}/select-box.ts +10 -10
  52. package/src/codegen/targets/react/component/handlers/skeleton.ts +25 -0
  53. package/src/codegen/targets/react/component/{transformers → handlers}/snackbar.ts +5 -5
  54. package/src/codegen/targets/react/component/{transformers → handlers}/switch.ts +5 -5
  55. package/src/codegen/targets/react/component/{transformers → handlers}/tabs.ts +15 -15
  56. package/src/codegen/targets/react/component/{transformers → handlers}/text-button.ts +7 -13
  57. package/src/codegen/targets/react/component/{transformers → handlers}/text-field.ts +9 -9
  58. package/src/codegen/targets/react/component/{transformers → handlers}/toggle-button.ts +7 -11
  59. package/src/codegen/targets/react/component/index.ts +79 -75
  60. package/src/codegen/targets/react/frame.ts +8 -8
  61. package/src/codegen/targets/react/icon.ts +50 -0
  62. package/src/codegen/targets/react/index.ts +1 -1
  63. package/src/codegen/targets/react/instance.ts +19 -50
  64. package/src/codegen/targets/react/pipeline.ts +124 -0
  65. package/src/codegen/targets/react/props.ts +95 -73
  66. package/src/codegen/targets/react/shape.ts +5 -5
  67. package/src/codegen/targets/react/text.ts +6 -6
  68. package/src/codegen/targets/react/value-resolver.ts +32 -0
  69. package/src/entities/icon.repository.ts +2 -2
  70. package/src/entities/icon.service.ts +9 -20
  71. package/src/entities/style.service.ts +5 -17
  72. package/src/entities/variable.service.ts +36 -68
  73. package/src/utils/figma-variable.ts +39 -3
  74. package/src/codegen/core/component.types.ts +0 -29
  75. package/src/codegen/targets/figma/context.ts +0 -139
  76. package/src/codegen/targets/react/component/transformers/avatar-stack.ts +0 -29
  77. package/src/codegen/targets/react/component/transformers/badge.ts +0 -21
  78. package/src/codegen/targets/react/component/transformers/fab.ts +0 -18
  79. package/src/codegen/targets/react/component/transformers/skeleton.ts +0 -51
  80. package/src/codegen/targets/react/context.ts +0 -176
  81. /package/src/codegen/core/{element.ts → element-transformer.ts} +0 -0
@@ -1,10 +1,4 @@
1
- import {
2
- createPropsTransformer,
3
- definePropsTransformer,
4
- type PropsTransformer,
5
- type ValueTransformer,
6
- } from "@/codegen/core";
7
- import type { StyleService } from "@/entities";
1
+ import { createPropsConverter, definePropsConverter, type PropsConverter } from "@/codegen/core";
8
2
  import type {
9
3
  NormalizedCornerTrait,
10
4
  NormalizedHasChildrenTrait,
@@ -15,17 +9,19 @@ import type {
15
9
  NormalizedTypePropertiesTrait,
16
10
  } from "@/normalizer";
17
11
  import { match } from "ts-pattern";
18
-
19
- export interface PropsTransformers {
20
- containerLayout: PropsTransformer<ContainerLayoutTrait, ContainerLayoutProps>;
21
- selfLayout: PropsTransformer<SelfLayoutTrait, SelfLayoutProps>;
22
- iconSelfLayout: PropsTransformer<SelfLayoutTrait, IconSelfLayoutProps>;
23
- radius: PropsTransformer<RadiusTrait, RadiusProps>;
24
- frameFill: PropsTransformer<FillTrait, FrameFillProps>;
25
- shapeFill: PropsTransformer<FillTrait, ShapeFillProps>;
26
- textFill: PropsTransformer<FillTrait, TextFillProps>;
27
- stroke: PropsTransformer<StrokeTrait, StrokeProps>;
28
- typeStyle: PropsTransformer<TypeStyleTrait, TypeStyleProps>;
12
+ import type { ReactValueResolver } from "./value-resolver";
13
+
14
+ export interface PropsConverters {
15
+ containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;
16
+ selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;
17
+ iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;
18
+ radius: PropsConverter<RadiusTrait, RadiusProps>;
19
+ frameFill: PropsConverter<FillTrait, FrameFillProps>;
20
+ shapeFill: PropsConverter<FillTrait, ShapeFillProps>;
21
+ textFill: PropsConverter<FillTrait, TextFillProps>;
22
+ vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;
23
+ stroke: PropsConverter<StrokeTrait, StrokeProps>;
24
+ typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;
29
25
  }
30
26
 
31
27
  export type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &
@@ -58,12 +54,10 @@ export interface ContainerLayoutProps {
58
54
  p?: string | 0;
59
55
  }
60
56
 
61
- type ReactValueTransformer = ValueTransformer<string, string, string, number>;
62
-
63
- export function createContainerLayoutPropsTransformer(
64
- valueTransformer: ReactValueTransformer,
65
- ): PropsTransformer<ContainerLayoutTrait, ContainerLayoutProps> {
66
- return createPropsTransformer({
57
+ export function createContainerLayoutPropsConverter(
58
+ valueResolver: ReactValueResolver,
59
+ ): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {
60
+ return createPropsConverter({
67
61
  _types: {
68
62
  trait: {} as ContainerLayoutTrait,
69
63
  props: {} as ContainerLayoutProps,
@@ -120,12 +114,12 @@ export function createContainerLayoutPropsTransformer(
120
114
  return undefined;
121
115
  }
122
116
 
123
- return valueTransformer.getFormattedValue.itemSpacing(node);
117
+ return valueResolver.getFormattedValue.itemSpacing(node);
124
118
  },
125
- pt: (node) => valueTransformer.getFormattedValue.paddingTop(node),
126
- pb: (node) => valueTransformer.getFormattedValue.paddingBottom(node),
127
- pl: (node) => valueTransformer.getFormattedValue.paddingLeft(node),
128
- pr: (node) => valueTransformer.getFormattedValue.paddingRight(node),
119
+ pt: (node) => valueResolver.getFormattedValue.paddingTop(node),
120
+ pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),
121
+ pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),
122
+ pr: (node) => valueResolver.getFormattedValue.paddingRight(node),
129
123
  },
130
124
  shorthands: {
131
125
  p: ["pt", "pb", "pl", "pr"],
@@ -159,10 +153,10 @@ export interface SelfLayoutProps {
159
153
  maxHeight?: string | number;
160
154
  }
161
155
 
162
- export function createSelfLayoutPropsTransformer(
163
- valueTransformer: ReactValueTransformer,
164
- ): PropsTransformer<SelfLayoutTrait, SelfLayoutProps> {
165
- return createPropsTransformer({
156
+ export function createSelfLayoutPropsConverter(
157
+ valueResolver: ReactValueResolver,
158
+ ): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {
159
+ return createPropsConverter({
166
160
  _types: {
167
161
  trait: {} as SelfLayoutTrait,
168
162
  props: {} as SelfLayoutProps,
@@ -180,27 +174,27 @@ export function createSelfLayoutPropsTransformer(
180
174
  .exhaustive(),
181
175
  height: (node) =>
182
176
  node.layoutSizingVertical === "FIXED"
183
- ? valueTransformer.getFormattedValue.height(node)
177
+ ? valueResolver.getFormattedValue.height(node)
184
178
  : undefined,
185
179
  width: (node) =>
186
180
  node.layoutSizingHorizontal === "FIXED"
187
- ? valueTransformer.getFormattedValue.width(node)
181
+ ? valueResolver.getFormattedValue.width(node)
188
182
  : undefined,
189
183
  minHeight: (node) =>
190
184
  node.layoutSizingVertical === "HUG"
191
- ? valueTransformer.getFormattedValue.minHeight(node)
185
+ ? valueResolver.getFormattedValue.minHeight(node)
192
186
  : undefined,
193
187
  maxHeight: (node) =>
194
188
  node.layoutSizingVertical === "HUG"
195
- ? valueTransformer.getFormattedValue.maxHeight(node)
189
+ ? valueResolver.getFormattedValue.maxHeight(node)
196
190
  : undefined,
197
191
  minWidth: (node) =>
198
192
  node.layoutSizingHorizontal === "HUG"
199
- ? valueTransformer.getFormattedValue.minWidth(node)
193
+ ? valueResolver.getFormattedValue.minWidth(node)
200
194
  : undefined,
201
195
  maxWidth: (node) =>
202
196
  node.layoutSizingHorizontal === "HUG"
203
- ? valueTransformer.getFormattedValue.maxWidth(node)
197
+ ? valueResolver.getFormattedValue.maxWidth(node)
204
198
  : undefined,
205
199
  },
206
200
  defaults: {
@@ -213,14 +207,14 @@ export interface IconSelfLayoutProps {
213
207
  size?: string | number;
214
208
  }
215
209
 
216
- export function createIconSelfLayoutPropsTransformer(valueTransformer: ReactValueTransformer) {
217
- return createPropsTransformer({
210
+ export function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {
211
+ return createPropsConverter({
218
212
  _types: {
219
213
  trait: {} as SelfLayoutTrait,
220
214
  props: {} as IconSelfLayoutProps,
221
215
  },
222
216
  handlers: {
223
- size: (node) => valueTransformer.getFormattedValue.width(node),
217
+ size: (node) => valueResolver.getFormattedValue.width(node),
224
218
  },
225
219
  });
226
220
  }
@@ -233,17 +227,17 @@ export interface RadiusProps {
233
227
  borderBottomRightRadius?: string | 0;
234
228
  }
235
229
 
236
- export function createRadiusPropsTransformer(valueTransformer: ReactValueTransformer) {
237
- return createPropsTransformer({
230
+ export function createRadiusPropsConverter(valueResolver: ReactValueResolver) {
231
+ return createPropsConverter({
238
232
  _types: {
239
233
  trait: {} as RadiusTrait,
240
234
  props: {} as RadiusProps,
241
235
  },
242
236
  handlers: {
243
- borderTopLeftRadius: (node) => valueTransformer.getFormattedValue.topLeftRadius(node),
244
- borderTopRightRadius: (node) => valueTransformer.getFormattedValue.topRightRadius(node),
245
- borderBottomLeftRadius: (node) => valueTransformer.getFormattedValue.bottomLeftRadius(node),
246
- borderBottomRightRadius: (node) => valueTransformer.getFormattedValue.bottomRightRadius(node),
237
+ borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),
238
+ borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),
239
+ borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),
240
+ borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),
247
241
  },
248
242
  shorthands: {
249
243
  borderRadius: [
@@ -271,15 +265,13 @@ export interface TypeStyleProps {
271
265
  maxLines?: number;
272
266
  }
273
267
 
274
- export function createTypeStylePropsTransformer({
275
- valueTransformer,
276
- styleService,
268
+ export function createTypeStylePropsConverter({
269
+ valueResolver,
277
270
  }: {
278
- valueTransformer: ReactValueTransformer;
279
- styleService: StyleService;
280
- }): PropsTransformer<TypeStyleTrait, TypeStyleProps> {
281
- return definePropsTransformer((node) => {
282
- const styleName = node.textStyleKey ? styleService.getStyleName(node.textStyleKey) : undefined;
271
+ valueResolver: ReactValueResolver;
272
+ }): PropsConverter<TypeStyleTrait, TypeStyleProps> {
273
+ return definePropsConverter((node) => {
274
+ const styleName = valueResolver.getTextStyleValue(node);
283
275
  const maxLines =
284
276
  node.style.textTruncation === "ENDING" ? (node.style.maxLines ?? undefined) : undefined;
285
277
 
@@ -291,9 +283,9 @@ export function createTypeStylePropsTransformer({
291
283
  }
292
284
 
293
285
  return {
294
- fontSize: valueTransformer.getFormattedValue.fontSize(node),
295
- fontWeight: valueTransformer.getFormattedValue.fontWeight(node),
296
- lineHeight: valueTransformer.getFormattedValue.lineHeight(node),
286
+ fontSize: valueResolver.getFormattedValue.fontSize(node),
287
+ fontWeight: valueResolver.getFormattedValue.fontWeight(node),
288
+ lineHeight: valueResolver.getFormattedValue.lineHeight(node),
297
289
  maxLines,
298
290
  };
299
291
  });
@@ -303,9 +295,9 @@ export interface FrameFillProps {
303
295
  bg?: string;
304
296
  }
305
297
 
306
- export function createFrameFillPropsTransformer(valueTransformer: ReactValueTransformer) {
307
- return definePropsTransformer<FillTrait, FrameFillProps>((node) => {
308
- const bg = valueTransformer.getFormattedValue.frameFill(node);
298
+ export function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {
299
+ return definePropsConverter<FillTrait, FrameFillProps>((node) => {
300
+ const bg = valueResolver.getFormattedValue.frameFill(node);
309
301
 
310
302
  return {
311
303
  bg,
@@ -317,9 +309,9 @@ export interface ShapeFillProps {
317
309
  color?: string;
318
310
  }
319
311
 
320
- export function createShapeFillPropsTransformer(valueTransformer: ReactValueTransformer) {
321
- return definePropsTransformer<FillTrait, ShapeFillProps>((node) => {
322
- const color = valueTransformer.getFormattedValue.shapeFill(node);
312
+ export function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {
313
+ return definePropsConverter<FillTrait, ShapeFillProps>((node) => {
314
+ const color = valueResolver.getFormattedValue.shapeFill(node);
323
315
 
324
316
  return {
325
317
  color,
@@ -331,9 +323,9 @@ export interface TextFillProps {
331
323
  color?: string;
332
324
  }
333
325
 
334
- export function createTextFillPropsTransformer(valueTransformer: ReactValueTransformer) {
335
- return definePropsTransformer<FillTrait, TextFillProps>((node) => {
336
- const color = valueTransformer.getFormattedValue.textFill(node);
326
+ export function createTextFillPropsConverter(valueResolver: ReactValueResolver) {
327
+ return definePropsConverter<FillTrait, TextFillProps>((node) => {
328
+ const color = valueResolver.getFormattedValue.textFill(node);
337
329
 
338
330
  return {
339
331
  color,
@@ -341,16 +333,46 @@ export function createTextFillPropsTransformer(valueTransformer: ReactValueTrans
341
333
  });
342
334
  }
343
335
 
336
+ export interface VectorChildrenFillProps {
337
+ color?: string;
338
+ }
339
+
340
+ export function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {
341
+ return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {
342
+ if (node.children.length === 0) {
343
+ console.warn(
344
+ `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,
345
+ );
346
+ return {};
347
+ }
348
+
349
+ const vectors = node.children.filter(
350
+ (child) => child.type === "VECTOR" || child.type === "BOOLEAN_OPERATION",
351
+ );
352
+
353
+ const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));
354
+
355
+ const fills = new Set(colors.filter((color) => color !== undefined));
356
+
357
+ // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.
358
+ if (fills.size > 1) {
359
+ return {};
360
+ }
361
+
362
+ return { color: fills.values().next().value };
363
+ });
364
+ }
365
+
344
366
  export interface StrokeProps {
345
367
  borderWidth?: number;
346
368
  borderColor?: string;
347
369
  }
348
370
 
349
- export function createStrokePropsTransformer(
350
- valueTransformer: ReactValueTransformer,
351
- ): PropsTransformer<StrokeTrait, StrokeProps> {
352
- return definePropsTransformer((node) => {
353
- const borderColor = valueTransformer.getFormattedValue.stroke(node);
371
+ export function createStrokePropsConverter(
372
+ valueResolver: ReactValueResolver,
373
+ ): PropsConverter<StrokeTrait, StrokeProps> {
374
+ return definePropsConverter((node) => {
375
+ const borderColor = valueResolver.getFormattedValue.stroke(node);
354
376
  const borderWidth = borderColor ? node.strokeWeight : undefined;
355
377
 
356
378
  return {
@@ -4,19 +4,19 @@ import type {
4
4
  NormalizedVectorNode,
5
5
  } from "@/normalizer";
6
6
  import { createElement, defineElementTransformer, type ElementTransformer } from "../../core";
7
- import type { PropsTransformers } from "./props";
7
+ import type { PropsConverters } from "./props";
8
8
 
9
9
  export interface RectangleTransformerDeps {
10
- propsTransformers: PropsTransformers;
10
+ propsConverters: PropsConverters;
11
11
  }
12
12
 
13
13
  export function createRectangleTransformer({
14
- propsTransformers,
14
+ propsConverters,
15
15
  }: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {
16
- return defineElementTransformer((node: NormalizedRectangleNode, traverse) => {
16
+ return defineElementTransformer((node: NormalizedRectangleNode) => {
17
17
  return createElement(
18
18
  "Box",
19
- { ...propsTransformers.selfLayout(node, traverse), background: "palette.gray200" },
19
+ { ...propsConverters.selfLayout(node), background: "palette.gray200" },
20
20
  undefined,
21
21
  "Rectangle Node Placeholder",
22
22
  );
@@ -1,20 +1,20 @@
1
1
  import type { NormalizedTextNode } from "@/normalizer";
2
2
  import { compactObject } from "@/utils/common";
3
3
  import { createElement, defineElementTransformer, type ElementTransformer } from "../../core";
4
- import type { PropsTransformers } from "./props";
4
+ import type { PropsConverters } from "./props";
5
5
 
6
6
  export interface TextTransformerDeps {
7
- propsTransformers: PropsTransformers;
7
+ propsConverters: PropsConverters;
8
8
  }
9
9
 
10
10
  export function createTextTransformer({
11
- propsTransformers,
11
+ propsConverters,
12
12
  }: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {
13
- return defineElementTransformer((node: NormalizedTextNode, traverse) => {
13
+ return defineElementTransformer((node: NormalizedTextNode) => {
14
14
  const hasMultipleFills = node.fills.length > 1;
15
15
 
16
- const fillProps = propsTransformers.textFill(node, traverse);
17
- const typeStyleProps = propsTransformers.typeStyle(node, traverse);
16
+ const fillProps = propsConverters.textFill(node);
17
+ const typeStyleProps = propsConverters.typeStyle(node);
18
18
 
19
19
  const props = compactObject({
20
20
  ...typeStyleProps,
@@ -0,0 +1,32 @@
1
+ import type { ValueResolver } from "@/codegen/core";
2
+ import { camelCasePreserveUnderscoreBetweenNumbers } from "@/utils/common";
3
+ import { toCssPixel, toCssRgba } from "@/utils/css";
4
+ import { camelCase } from "change-case";
5
+
6
+ export type ReactValueResolver = ValueResolver<string, string, string, number>;
7
+
8
+ export const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>
9
+ slug
10
+ .filter(
11
+ (s) =>
12
+ !(
13
+ s === "dimension" ||
14
+ s === "radius" ||
15
+ s === "font-size" ||
16
+ s === "font-weight" ||
17
+ s === "line-height"
18
+ ),
19
+ )
20
+ .map((s) => s.replaceAll(",", "_"))
21
+ .map(camelCasePreserveUnderscoreBetweenNumbers)
22
+ .join(".");
23
+
24
+ export const defaultStyleNameFormatter = ({ slug }: { slug: string[] }) =>
25
+ camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });
26
+
27
+ export const defaultRawValueFormatters = {
28
+ color: (value: RGBA) => toCssRgba(value),
29
+ dimension: (value: number) => toCssPixel(value),
30
+ fontDimension: (value: number) => toCssPixel(value),
31
+ fontWeight: (value: number) => value,
32
+ };
@@ -1,11 +1,11 @@
1
1
  import type { IconData } from "./icon.interface";
2
2
 
3
3
  export interface IconRepository {
4
- getIconData(key: string): IconData;
4
+ getOne(key: string): IconData;
5
5
  }
6
6
 
7
7
  export function createStaticIconRepository(iconRecord: Record<string, IconData>) {
8
8
  return {
9
- getIconData: (key: string) => iconRecord[key],
9
+ getOne: (key: string) => iconRecord[key],
10
10
  };
11
11
  }
@@ -1,35 +1,24 @@
1
- import { pascalCase } from "change-case";
1
+ import type { IconData } from "./icon.interface";
2
2
  import type { IconRepository } from "./icon.repository";
3
3
 
4
4
  export interface IconService {
5
- isIconComponent: (componentKey: string) => boolean;
6
- createIconTagName: (key?: string) => string;
5
+ isAvailable: (componentKey: string) => boolean;
6
+ getOne: (componentKey: string) => IconData;
7
7
  }
8
8
 
9
9
  export function createIconService({
10
10
  iconRepository,
11
11
  }: { iconRepository: IconRepository }): IconService {
12
- function isIconComponent(componentKey: string) {
13
- return iconRepository.getIconData(componentKey) !== undefined;
12
+ function isAvailable(componentKey: string) {
13
+ return iconRepository.getOne(componentKey) !== undefined;
14
14
  }
15
15
 
16
- function createIconTagName(key?: string) {
17
- if (!key) {
18
- return "UnknownIcon";
19
- }
20
-
21
- const iconData = iconRepository.getIconData(key);
22
- if (!iconData) {
23
- return "UnknownIcon";
24
- }
25
-
26
- const { name, weight } = iconData;
27
-
28
- return pascalCase(`${name}${weight ? weight : ""}`);
16
+ function getOne(componentKey: string) {
17
+ return iconRepository.getOne(componentKey);
29
18
  }
30
19
 
31
20
  return {
32
- isIconComponent,
33
- createIconTagName,
21
+ isAvailable,
22
+ getOne,
34
23
  };
35
24
  }
@@ -1,18 +1,16 @@
1
1
  import type { StyleRepository } from "./style.repository";
2
2
 
3
3
  export interface StyleService {
4
- getStyleName: (id: string) => string | undefined;
4
+ getSlug: (id: string) => string[] | undefined;
5
5
  }
6
6
 
7
7
  // TODO: inferStyleName 추가해야 함, rest api에서 style value가 제공되지 않고 있어 보류
8
8
  export function createStyleService({
9
9
  styleRepository,
10
- styleNameTransformer,
11
10
  }: {
12
11
  styleRepository: StyleRepository;
13
- styleNameTransformer: ({ slug }: { slug: string[] }) => string;
14
12
  }): StyleService {
15
- function getFigmaStyleName(id: string) {
13
+ function getName(id: string) {
16
14
  const style = styleRepository.findOneByKey(id);
17
15
 
18
16
  if (!style) {
@@ -22,8 +20,8 @@ export function createStyleService({
22
20
  return style.name;
23
21
  }
24
22
 
25
- function getFigmaStyleSlug(id: string): string[] | undefined {
26
- const name = getFigmaStyleName(id);
23
+ function getSlug(id: string): string[] | undefined {
24
+ const name = getName(id);
27
25
 
28
26
  if (!name) {
29
27
  return undefined;
@@ -32,17 +30,7 @@ export function createStyleService({
32
30
  return name.split("/");
33
31
  }
34
32
 
35
- function getStyleName(id: string) {
36
- const slug = getFigmaStyleSlug(id);
37
-
38
- if (!slug) {
39
- return undefined;
40
- }
41
-
42
- return styleNameTransformer({ slug });
43
- }
44
-
45
33
  return {
46
- getStyleName,
34
+ getSlug,
47
35
  };
48
36
  }
@@ -1,42 +1,41 @@
1
- import { isVariableAlias, sanitizeVariableId } from "@/utils/figma-variable";
1
+ import {
2
+ isIdenticalVariableValue,
3
+ isInsideScope,
4
+ isVariableAlias,
5
+ sanitizeVariableId,
6
+ } from "@/utils/figma-variable";
2
7
  import type { Variable, VariableScope, VariableValueResolved } from "./variable.interface";
3
8
  import type { VariableRepository } from "./variable.repository";
4
9
 
5
10
  export interface VariableService {
6
- getVariableName: (id: string) => string;
7
- inferVariableName: (value: VariableValueResolved, scope: VariableScope) => string | undefined;
11
+ getSlug: (id: string) => string[] | undefined;
12
+ resolveValue: (variable: Variable, mode: string) => VariableValueResolved;
13
+ infer: (value: VariableValueResolved, scope: VariableScope) => Variable | undefined;
8
14
  }
9
15
 
10
16
  export interface VariableServiceDeps {
11
17
  variableRepository: VariableRepository;
12
- variableNameTransformer: ({ slug }: { slug: string[] }) => string;
13
- inferCompareFunction: (name1: string, name2: string) => number;
18
+ inferCompareFunction: (a: Variable, b: Variable) => number;
14
19
  }
15
20
 
16
21
  export function createVariableService({
17
22
  variableRepository,
18
- variableNameTransformer,
19
23
  inferCompareFunction,
20
24
  }: VariableServiceDeps): VariableService {
21
25
  const variables = variableRepository.getVariableList();
22
26
 
23
27
  // private
24
- function getFigmaVariableName(key: string) {
28
+ function getName(key: string) {
25
29
  const sanitizedId = sanitizeVariableId(key);
26
30
  const variable = variableRepository.findVariableByKey(sanitizedId);
27
31
 
28
32
  if (!variable) {
29
- return "UNKNOWN_VARIABLE";
33
+ return undefined;
30
34
  }
31
35
 
32
36
  return variable.name;
33
37
  }
34
38
 
35
- function getFigmaVariableSlug(key: string): string[] {
36
- const name = getFigmaVariableName(key);
37
- return name.split("/");
38
- }
39
-
40
39
  function getDefaultModeId(variable: Variable) {
41
40
  const variableCollection = variableRepository.findVariableCollectionById(
42
41
  variable.variableCollectionId,
@@ -50,84 +49,53 @@ export function createVariableService({
50
49
  return variableCollection.defaultModeId;
51
50
  }
52
51
 
53
- function resolveVariableValue(id: string, mode: string): VariableValueResolved {
54
- const variable = variableRepository.findVariableById(id);
55
- if (!variable) {
56
- throw new Error(`Variable not found: ${id}`);
57
- }
58
-
59
- const value = variable.valuesByMode[mode];
60
-
61
- if (value === undefined) {
62
- throw new Error(`Variable value not found: ${id} ${mode}`);
63
- }
52
+ // public
53
+ function getSlug(key: string): string[] | undefined {
54
+ const name = getName(key);
64
55
 
65
- if (isVariableAlias(value)) {
66
- return resolveVariableValue(value.id, mode);
56
+ if (!name) {
57
+ return undefined;
67
58
  }
68
59
 
69
- return value;
60
+ return name.split("/");
70
61
  }
71
62
 
72
- function isIdenticalVariableValue(value1: VariableValueResolved, value2: VariableValueResolved) {
73
- if (typeof value1 !== typeof value2) {
74
- return false;
75
- }
63
+ function resolveValue(variable: Variable, mode: string): VariableValueResolved {
64
+ const value = variable.valuesByMode[mode];
76
65
 
77
- if (typeof value1 === "string" || typeof value1 === "number" || typeof value1 === "boolean") {
78
- return value1 === value2;
66
+ if (value === undefined) {
67
+ throw new Error(`Variable value not found: ${variable.id} ${mode}`);
79
68
  }
80
69
 
81
- return (
82
- value1.r === (value2 as RGBA).r &&
83
- value1.g === (value2 as RGBA).g &&
84
- value1.b === (value2 as RGBA).b &&
85
- value1.a === (value2 as RGBA).a
86
- );
87
- }
88
-
89
- function isInsideScope(variable: Variable, scope: VariableScope) {
90
- if (variable.scopes.includes("ALL_SCOPES")) {
91
- return true;
92
- }
70
+ if (isVariableAlias(value)) {
71
+ const resolvedVariable = variableRepository.findVariableById(value.id);
93
72
 
94
- if (variable.scopes.includes("ALL_FILLS")) {
95
- if (scope === "FRAME_FILL" || scope === "SHAPE_FILL" || scope === "TEXT_FILL") {
96
- return true;
73
+ if (!resolvedVariable) {
74
+ throw new Error(`Variable not found: ${value.id}`);
97
75
  }
98
- }
99
76
 
100
- return variable.scopes.includes(scope);
101
- }
77
+ return resolveValue(resolvedVariable, mode);
78
+ }
102
79
 
103
- // public
104
- function getVariableName(key: string) {
105
- const slug = getFigmaVariableSlug(key);
106
- return variableNameTransformer({ slug });
80
+ return value;
107
81
  }
108
82
 
109
- function inferVariableName(value: VariableValueResolved, scope: VariableScope) {
83
+ function infer(value: VariableValueResolved, scope: VariableScope) {
110
84
  // NOTE: We assume that the variable is in the default mode or value is equal between all modes for simplicity.
111
85
  const inferredVariables = variables.filter(
112
86
  (variable) =>
113
87
  isInsideScope(variable, scope) &&
114
- isIdenticalVariableValue(
115
- resolveVariableValue(variable.id, getDefaultModeId(variable)),
116
- value,
117
- ),
118
- );
119
-
120
- const inferredVariableNames = inferredVariables.map((variable) =>
121
- getVariableName(variable.key),
88
+ isIdenticalVariableValue(resolveValue(variable, getDefaultModeId(variable)), value),
122
89
  );
123
90
 
124
- const sortedVariableNames = inferredVariableNames.sort(inferCompareFunction);
91
+ const sortedVariables = inferredVariables.sort(inferCompareFunction);
125
92
 
126
- return sortedVariableNames[0];
93
+ return sortedVariables[0];
127
94
  }
128
95
 
129
96
  return {
130
- getVariableName,
131
- inferVariableName,
97
+ getSlug,
98
+ resolveValue,
99
+ infer,
132
100
  };
133
101
  }