@seed-design/figma 1.3.4 → 1.3.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.
- package/lib/codegen/index.cjs +40 -1
- package/lib/codegen/index.d.ts.map +1 -1
- package/lib/codegen/index.js +41 -2
- package/lib/codegen/targets/react/index.cjs +48 -14
- package/lib/codegen/targets/react/index.d.ts.map +1 -1
- package/lib/codegen/targets/react/index.js +48 -14
- package/lib/index.cjs +39 -0
- package/lib/index.js +39 -0
- package/package.json +2 -2
- package/src/codegen/component-properties.ts +4 -0
- package/src/codegen/core/codegen.ts +5 -2
- package/src/codegen/targets/react/component/handlers/avatar.ts +5 -5
- package/src/codegen/targets/react/component/handlers/bottom-sheet.ts +1 -2
- package/src/codegen/targets/react/component/handlers/identity-placeholder.ts +4 -13
- package/src/codegen/targets/react/component/handlers/menu-sheet.ts +1 -1
- package/src/codegen/targets/react/component/handlers/tabs.ts +7 -3
- package/src/entities/data/__generated__/component-sets/index.d.ts +15 -1
- package/src/entities/data/__generated__/component-sets/index.mjs +15 -1
- package/src/entities/data/__generated__/components/index.d.ts +1 -1
- package/src/entities/data/__generated__/components/index.mjs +1 -1
- package/src/entities/data/__generated__/variable-collections/index.mjs +1 -0
- package/src/entities/data/__generated__/variables/index.mjs +24 -0
package/lib/codegen/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { match } from 'ts-pattern';
|
|
2
|
-
import { camelCase } from 'change-case';
|
|
2
|
+
import { pascalCase, camelCase } from 'change-case';
|
|
3
3
|
import * as index_js from './targets/react/index.js';
|
|
4
4
|
export { index_js as react };
|
|
5
5
|
|
|
@@ -460,7 +460,7 @@ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTrans
|
|
|
460
460
|
type: "BOOLEAN_OPERATION"
|
|
461
461
|
}, (node)=>booleanOperationTransformer(node, traverse)).with({
|
|
462
462
|
type: "UNHANDLED"
|
|
463
|
-
}, ()=>createElement(
|
|
463
|
+
}, (node)=>createElement(`Unhandled${pascalCase(node.original.type)}Node`)).exhaustive();
|
|
464
464
|
if (result) {
|
|
465
465
|
return appendSource(result, node.id);
|
|
466
466
|
}
|
|
@@ -11513,6 +11513,7 @@ const FIGMA_VARIABLE_COLLECTIONS = {
|
|
|
11513
11513
|
"VariableID:576:22878",
|
|
11514
11514
|
"VariableID:59176:2",
|
|
11515
11515
|
"VariableID:59176:3",
|
|
11516
|
+
"VariableID:61337:3",
|
|
11516
11517
|
"VariableID:654:20851",
|
|
11517
11518
|
"VariableID:670:2700",
|
|
11518
11519
|
"VariableID:6782:76317",
|
|
@@ -18346,6 +18347,30 @@ const FIGMA_VARIABLES = {
|
|
|
18346
18347
|
],
|
|
18347
18348
|
"codeSyntax": {}
|
|
18348
18349
|
},
|
|
18350
|
+
"VariableID:61337:3": {
|
|
18351
|
+
"name": "stroke/focus-ring",
|
|
18352
|
+
"id": "VariableID:61337:3",
|
|
18353
|
+
"remote": false,
|
|
18354
|
+
"key": "ca937a0bb83fda59d06959cfb5d960f00b5e9add",
|
|
18355
|
+
"variableCollectionId": "VariableCollectionId:1:3",
|
|
18356
|
+
"resolvedType": "COLOR",
|
|
18357
|
+
"description": "",
|
|
18358
|
+
"hiddenFromPublishing": false,
|
|
18359
|
+
"valuesByMode": {
|
|
18360
|
+
"1928:7": {
|
|
18361
|
+
"type": "VARIABLE_ALIAS",
|
|
18362
|
+
"id": "VariableID:1883:92938"
|
|
18363
|
+
},
|
|
18364
|
+
"1928:8": {
|
|
18365
|
+
"type": "VARIABLE_ALIAS",
|
|
18366
|
+
"id": "VariableID:1883:92938"
|
|
18367
|
+
}
|
|
18368
|
+
},
|
|
18369
|
+
"scopes": [
|
|
18370
|
+
"ALL_SCOPES"
|
|
18371
|
+
],
|
|
18372
|
+
"codeSyntax": {}
|
|
18373
|
+
},
|
|
18349
18374
|
"VariableID:41338:806": {
|
|
18350
18375
|
"name": "stroke/informative-solid",
|
|
18351
18376
|
"id": "VariableID:41338:806",
|
|
@@ -18861,6 +18886,19 @@ const privateComponentFieldHeaderSuffix = {
|
|
|
18861
18886
|
}
|
|
18862
18887
|
}
|
|
18863
18888
|
};
|
|
18889
|
+
const privateComponentIdentityPlaceholder = {
|
|
18890
|
+
"name": "privateComponentIdentityPlaceholder",
|
|
18891
|
+
"key": "7d7ae18cabac6ebad19934516de8e7e256854eba",
|
|
18892
|
+
"componentPropertyDefinitions": {
|
|
18893
|
+
"Identity": {
|
|
18894
|
+
"type": "VARIANT",
|
|
18895
|
+
"variantOptions": [
|
|
18896
|
+
"Person",
|
|
18897
|
+
"Business"
|
|
18898
|
+
]
|
|
18899
|
+
}
|
|
18900
|
+
}
|
|
18901
|
+
};
|
|
18864
18902
|
const privateComponentInputButtonPrefix = {
|
|
18865
18903
|
"name": "privateComponentInputButtonPrefix",
|
|
18866
18904
|
"key": "c7d12be4056a8171f6857ec4ef15d81bd18d8cae",
|
|
@@ -22833,6 +22871,7 @@ var FIGMA_COMPONENTS = {
|
|
|
22833
22871
|
privateComponentFieldFooterCharacterCount: privateComponentFieldFooterCharacterCount,
|
|
22834
22872
|
privateComponentFieldHeaderIndicator: privateComponentFieldHeaderIndicator,
|
|
22835
22873
|
privateComponentFieldHeaderSuffix: privateComponentFieldHeaderSuffix,
|
|
22874
|
+
privateComponentIdentityPlaceholder: privateComponentIdentityPlaceholder,
|
|
22836
22875
|
privateComponentInputButtonPrefix: privateComponentInputButtonPrefix,
|
|
22837
22876
|
privateComponentInputButtonSuffix: privateComponentInputButtonSuffix,
|
|
22838
22877
|
privateComponentInputButtonValue: privateComponentInputButtonValue,
|
|
@@ -467,7 +467,7 @@ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTrans
|
|
|
467
467
|
type: "BOOLEAN_OPERATION"
|
|
468
468
|
}, (node)=>booleanOperationTransformer(node, traverse)).with({
|
|
469
469
|
type: "UNHANDLED"
|
|
470
|
-
}, ()=>createElement(
|
|
470
|
+
}, (node)=>createElement(`Unhandled${changeCase.pascalCase(node.original.type)}Node`)).exhaustive();
|
|
471
471
|
if (result) {
|
|
472
472
|
return appendSource(result, node.id);
|
|
473
473
|
}
|
|
@@ -3464,9 +3464,9 @@ const createAppBarHandler$1 = (ctx)=>{
|
|
|
3464
3464
|
});
|
|
3465
3465
|
};
|
|
3466
3466
|
|
|
3467
|
-
const IDENTITY_PLACEHOLDER_KEY
|
|
3467
|
+
const IDENTITY_PLACEHOLDER_KEY = "b3563b6f16ba4cfe4240c9b33eef7edad4c304eb";
|
|
3468
3468
|
const { createLocalSnippetElement: createLocalSnippetElement$15 } = createLocalSnippetHelper("identity-placeholder");
|
|
3469
|
-
const createIdentityPlaceholderHandler$1 = (_ctx)=>defineComponentHandler(IDENTITY_PLACEHOLDER_KEY
|
|
3469
|
+
const createIdentityPlaceholderHandler$1 = (_ctx)=>defineComponentHandler(IDENTITY_PLACEHOLDER_KEY, ({ componentProperties: props })=>{
|
|
3470
3470
|
const commonProps = {
|
|
3471
3471
|
identity: changeCase.camelCase(props.Identity.value)
|
|
3472
3472
|
};
|
|
@@ -5525,6 +5525,19 @@ const privateComponentFieldHeaderSuffix = {
|
|
|
5525
5525
|
}
|
|
5526
5526
|
}
|
|
5527
5527
|
};
|
|
5528
|
+
const privateComponentIdentityPlaceholder = {
|
|
5529
|
+
"name": "privateComponentIdentityPlaceholder",
|
|
5530
|
+
"key": "7d7ae18cabac6ebad19934516de8e7e256854eba",
|
|
5531
|
+
"componentPropertyDefinitions": {
|
|
5532
|
+
"Identity": {
|
|
5533
|
+
"type": "VARIANT",
|
|
5534
|
+
"variantOptions": [
|
|
5535
|
+
"Person",
|
|
5536
|
+
"Business"
|
|
5537
|
+
]
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5540
|
+
};
|
|
5528
5541
|
const privateComponentInputButtonPrefix = {
|
|
5529
5542
|
"name": "privateComponentInputButtonPrefix",
|
|
5530
5543
|
"key": "c7d12be4056a8171f6857ec4ef15d81bd18d8cae",
|
|
@@ -9497,6 +9510,7 @@ var FIGMA_COMPONENTS = {
|
|
|
9497
9510
|
privateComponentFieldFooterCharacterCount: privateComponentFieldFooterCharacterCount,
|
|
9498
9511
|
privateComponentFieldHeaderIndicator: privateComponentFieldHeaderIndicator,
|
|
9499
9512
|
privateComponentFieldHeaderSuffix: privateComponentFieldHeaderSuffix,
|
|
9513
|
+
privateComponentIdentityPlaceholder: privateComponentIdentityPlaceholder,
|
|
9500
9514
|
privateComponentInputButtonPrefix: privateComponentInputButtonPrefix,
|
|
9501
9515
|
privateComponentInputButtonSuffix: privateComponentInputButtonSuffix,
|
|
9502
9516
|
privateComponentInputButtonValue: privateComponentInputButtonValue,
|
|
@@ -9747,9 +9761,6 @@ const privateTemplateSelectField = {
|
|
|
9747
9761
|
const privateTemplateTimePickerField = {
|
|
9748
9762
|
"key": "e38df17cf1e0f96e09774b015739dfde30d46115"
|
|
9749
9763
|
};
|
|
9750
|
-
const privateComponentBottomSheetContentsPlaceholder = {
|
|
9751
|
-
"key": "e68b006d572300d3c987776192c8ab387fa45e05"
|
|
9752
|
-
};
|
|
9753
9764
|
const privateComponentTopNavigationLeftIconButton = {
|
|
9754
9765
|
"key": "c3e708bab11d8ea90a909b4539b6ba6b2a4e7b9c"};
|
|
9755
9766
|
const componentChipSuffixIcon = {
|
|
@@ -9911,10 +9922,8 @@ const createAppBarPresetHandler = (ctx)=>{
|
|
|
9911
9922
|
});
|
|
9912
9923
|
};
|
|
9913
9924
|
|
|
9914
|
-
// hardcoded since this lives in a different figma file
|
|
9915
|
-
const IDENTITY_PLACEHOLDER_KEY = "b3563b6f16ba4cfe4240c9b33eef7edad4c304eb";
|
|
9916
9925
|
const { createLocalSnippetElement: createLocalSnippetElement$x } = createLocalSnippetHelper("identity-placeholder");
|
|
9917
|
-
const createIdentityPlaceholderHandler = (_ctx)=>defineComponentHandler(
|
|
9926
|
+
const createIdentityPlaceholderHandler = (_ctx)=>defineComponentHandler(privateComponentIdentityPlaceholder.key, ({ componentProperties: props })=>{
|
|
9918
9927
|
const commonProps = {
|
|
9919
9928
|
identity: changeCase.camelCase(props.Identity.value)
|
|
9920
9929
|
};
|
|
@@ -9997,7 +10006,7 @@ const createBottomSheetHandler = (_ctx)=>defineComponentHandler(componentBottomS
|
|
|
9997
10006
|
};
|
|
9998
10007
|
const bodyNodes = findAllInstances({
|
|
9999
10008
|
node,
|
|
10000
|
-
key:
|
|
10009
|
+
key: props["Contents#25320:0"].componentKey
|
|
10001
10010
|
});
|
|
10002
10011
|
const bottomSheetBody = bodyNodes.length === 1 ? createLocalSnippetElement$u("BottomSheetBody", {}, bodyNodes[0].children.map(traverse)) : createLocalSnippetElement$u("BottomSheetBody", {}, createElement("div", undefined, "No content available"));
|
|
10003
10012
|
const footerNodes = findAllInstances({
|
|
@@ -10968,7 +10977,7 @@ const createMenuSheetHandler = (ctx)=>{
|
|
|
10968
10977
|
const trigger = createLocalSnippetElement$f("MenuSheetTrigger", {
|
|
10969
10978
|
asChild: true
|
|
10970
10979
|
}, createLocalSnippetElementTrigger("ActionButton", {}, "MenuSheet 열기"));
|
|
10971
|
-
return createLocalSnippetElement$f("
|
|
10980
|
+
return createLocalSnippetElement$f("MenuSheetRoot", undefined, [
|
|
10972
10981
|
trigger,
|
|
10973
10982
|
content
|
|
10974
10983
|
]);
|
|
@@ -11713,7 +11722,7 @@ const createLineTriggerHugHandler = (_ctx)=>defineComponentHandler(privateCompon
|
|
|
11713
11722
|
notification: true
|
|
11714
11723
|
}
|
|
11715
11724
|
};
|
|
11716
|
-
return createTabsLocalSnippetElement("TabsTrigger", commonProps);
|
|
11725
|
+
return createTabsLocalSnippetElement("TabsTrigger", commonProps, props["Label#4478:2"].value);
|
|
11717
11726
|
});
|
|
11718
11727
|
const createLineTriggerFillHandler = (_ctx)=>defineComponentHandler(privateComponentTabItemLineFill.key, ({ componentProperties: props })=>{
|
|
11719
11728
|
const commonProps = {
|
|
@@ -11725,7 +11734,7 @@ const createLineTriggerFillHandler = (_ctx)=>defineComponentHandler(privateCompo
|
|
|
11725
11734
|
notification: true
|
|
11726
11735
|
}
|
|
11727
11736
|
};
|
|
11728
|
-
return createTabsLocalSnippetElement("TabsTrigger", commonProps);
|
|
11737
|
+
return createTabsLocalSnippetElement("TabsTrigger", commonProps, props["Label#4478:2"].value);
|
|
11729
11738
|
});
|
|
11730
11739
|
/*
|
|
11731
11740
|
<ChipTabsRoot
|
|
@@ -11813,7 +11822,7 @@ const createChipTabsTriggerHandler = (_ctx)=>defineComponentHandler(privateCompo
|
|
|
11813
11822
|
notification: true
|
|
11814
11823
|
}
|
|
11815
11824
|
};
|
|
11816
|
-
return createChipTabsLocalSnippetElement("ChipTabsTrigger", commonProps);
|
|
11825
|
+
return createChipTabsLocalSnippetElement("ChipTabsTrigger", commonProps, chipProps["Label#7185:0"].value);
|
|
11817
11826
|
});
|
|
11818
11827
|
|
|
11819
11828
|
const { createLocalSnippetElement: createLocalSnippetElement$2 } = createLocalSnippetHelper("tag-group");
|
|
@@ -24332,6 +24341,7 @@ const FIGMA_VARIABLE_COLLECTIONS = {
|
|
|
24332
24341
|
"VariableID:576:22878",
|
|
24333
24342
|
"VariableID:59176:2",
|
|
24334
24343
|
"VariableID:59176:3",
|
|
24344
|
+
"VariableID:61337:3",
|
|
24335
24345
|
"VariableID:654:20851",
|
|
24336
24346
|
"VariableID:670:2700",
|
|
24337
24347
|
"VariableID:6782:76317",
|
|
@@ -31165,6 +31175,30 @@ const FIGMA_VARIABLES = {
|
|
|
31165
31175
|
],
|
|
31166
31176
|
"codeSyntax": {}
|
|
31167
31177
|
},
|
|
31178
|
+
"VariableID:61337:3": {
|
|
31179
|
+
"name": "stroke/focus-ring",
|
|
31180
|
+
"id": "VariableID:61337:3",
|
|
31181
|
+
"remote": false,
|
|
31182
|
+
"key": "ca937a0bb83fda59d06959cfb5d960f00b5e9add",
|
|
31183
|
+
"variableCollectionId": "VariableCollectionId:1:3",
|
|
31184
|
+
"resolvedType": "COLOR",
|
|
31185
|
+
"description": "",
|
|
31186
|
+
"hiddenFromPublishing": false,
|
|
31187
|
+
"valuesByMode": {
|
|
31188
|
+
"1928:7": {
|
|
31189
|
+
"type": "VARIABLE_ALIAS",
|
|
31190
|
+
"id": "VariableID:1883:92938"
|
|
31191
|
+
},
|
|
31192
|
+
"1928:8": {
|
|
31193
|
+
"type": "VARIABLE_ALIAS",
|
|
31194
|
+
"id": "VariableID:1883:92938"
|
|
31195
|
+
}
|
|
31196
|
+
},
|
|
31197
|
+
"scopes": [
|
|
31198
|
+
"ALL_SCOPES"
|
|
31199
|
+
],
|
|
31200
|
+
"codeSyntax": {}
|
|
31201
|
+
},
|
|
31168
31202
|
"VariableID:41338:806": {
|
|
31169
31203
|
"name": "stroke/informative-solid",
|
|
31170
31204
|
"id": "VariableID:41338:806",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sources":["../../../../src/normalizer/types.ts","../../../../src/codegen/core/jsx.ts","../../../../src/codegen/core/element-transformer.ts","../../../../src/codegen/core/codegen.ts","../../../../src/codegen/core/component-handler.ts","../../../../src/codegen/core/props-converter.ts","../../../../src/codegen/core/value-resolver.ts","../../../../src/codegen/targets/react/value-resolver.ts","../../../../src/codegen/targets/react/props.ts","../../../../src/codegen/targets/react/shape.ts","../../../../src/codegen/targets/react/frame.ts","../../../../src/codegen/targets/react/icon.ts","../../../../src/codegen/targets/react/instance.ts","../../../../src/codegen/targets/react/text.ts","../../../../src/codegen/targets/react/component/deps.interface.ts","../../../../src/codegen/targets/react/component/index.ts","../../../../src/codegen/targets/react/pipeline.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","import { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n\n const [namespace] = tag.split(\".\");\n\n if (existing) {\n existing.add(namespace);\n } else {\n importMap.set(importPath, new Set([namespace]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\"${value.replaceAll(\"\\n\", \"\\\\n\")}\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n if (value === true) return key;\n\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n const elementStr = recursive(value, depth + 1);\n\n const commentMatch = elementStr.match(/\\{\\/\\* (.+?)\\*\\/\\}$/);\n\n if (commentMatch) {\n const elementWithoutComment = elementStr.replace(/\\{\\/\\* .+? \\*\\/\\}$/, \"\");\n\n return `${key}={${elementWithoutComment}}/* ${commentMatch[1]} */`;\n }\n\n return `${key}={${elementStr}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n\n return undefined;\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n skipComponentKeys?: Set<string>;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys,\n}: CodeGeneratorDeps): CodeGenerator {\n function isSkippedInstance(node: NormalizedSceneNode): boolean {\n if (!skipComponentKeys || skipComponentKeys.size === 0) return false;\n if (node.type !== \"INSTANCE\") return false;\n\n const { componentKey, componentSetKey } = node;\n\n return (\n skipComponentKeys.has(componentKey) ||\n (!!componentSetKey && skipComponentKeys.has(componentSetKey))\n );\n }\n\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n if (isSkippedInstance(node)) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, () => createElement(\"UnhandledFigmaNode\"))\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n if (isSkippedInstance(node)) {\n return { imports: \"\", jsx: \"// This component is intentionally excluded from codegen\" };\n }\n\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode, NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport { SKIP_COMPONENT_KEYS } from \"@/codegen/skip-components\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShadowPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultEffectStyleNameFormatter,\n defaultFillStyleResolver,\n defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n textStyleNameFormatter: defaultTextStyleNameFormatter,\n effectStyleNameFormatter: defaultEffectStyleNameFormatter,\n fillStyleResolver: defaultFillStyleResolver,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const shadowPropsConverter = createShadowPropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n shadow: shadowPropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys: SKIP_COMPONENT_KEYS,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACWA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnBO;AACP;AACA;AACA;AACA;AACA;;ACNO;;ACGA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;;ACFO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACO;;AClGA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sources":["../../../../src/normalizer/types.ts","../../../../src/codegen/core/jsx.ts","../../../../src/codegen/core/element-transformer.ts","../../../../src/codegen/core/codegen.ts","../../../../src/codegen/core/component-handler.ts","../../../../src/codegen/core/props-converter.ts","../../../../src/codegen/core/value-resolver.ts","../../../../src/codegen/targets/react/value-resolver.ts","../../../../src/codegen/targets/react/props.ts","../../../../src/codegen/targets/react/shape.ts","../../../../src/codegen/targets/react/frame.ts","../../../../src/codegen/targets/react/icon.ts","../../../../src/codegen/targets/react/instance.ts","../../../../src/codegen/targets/react/text.ts","../../../../src/codegen/targets/react/component/deps.interface.ts","../../../../src/codegen/targets/react/component/index.ts","../../../../src/codegen/targets/react/pipeline.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","import { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n\n const [namespace] = tag.split(\".\");\n\n if (existing) {\n existing.add(namespace);\n } else {\n importMap.set(importPath, new Set([namespace]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\"${value.replaceAll(\"\\n\", \"\\\\n\")}\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n if (value === true) return key;\n\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n const elementStr = recursive(value, depth + 1);\n\n const commentMatch = elementStr.match(/\\{\\/\\* (.+?)\\*\\/\\}$/);\n\n if (commentMatch) {\n const elementWithoutComment = elementStr.replace(/\\{\\/\\* .+? \\*\\/\\}$/, \"\");\n\n return `${key}={${elementWithoutComment}}/* ${commentMatch[1]} */`;\n }\n\n return `${key}={${elementStr}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n\n return undefined;\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\nimport { pascalCase } from \"change-case\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n skipComponentKeys?: Set<string>;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys,\n}: CodeGeneratorDeps): CodeGenerator {\n function isSkippedInstance(node: NormalizedSceneNode): boolean {\n if (!skipComponentKeys || skipComponentKeys.size === 0) return false;\n if (node.type !== \"INSTANCE\") return false;\n\n const { componentKey, componentSetKey } = node;\n\n return (\n skipComponentKeys.has(componentKey) ||\n (!!componentSetKey && skipComponentKeys.has(componentSetKey))\n );\n }\n\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n if (isSkippedInstance(node)) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, (node) =>\n createElement(`Unhandled${pascalCase(node.original.type)}Node`),\n )\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n if (isSkippedInstance(node)) {\n return { imports: \"\", jsx: \"// This component is intentionally excluded from codegen\" };\n }\n\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode, NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport { SKIP_COMPONENT_KEYS } from \"@/codegen/skip-components\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShadowPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultEffectStyleNameFormatter,\n defaultFillStyleResolver,\n defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n textStyleNameFormatter: defaultTextStyleNameFormatter,\n effectStyleNameFormatter: defaultEffectStyleNameFormatter,\n fillStyleResolver: defaultFillStyleResolver,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const shadowPropsConverter = createShadowPropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n shadow: shadowPropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys: SKIP_COMPONENT_KEYS,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACWA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnBO;AACP;AACA;AACA;AACA;AACA;;ACNO;;ACGA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;;ACFO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACO;;AClGA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
|