@seed-design/figma 1.1.9-alpha-20251125084640 → 1.1.10
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 +1426 -437
- package/lib/codegen/index.d.ts +76 -27
- package/lib/codegen/index.d.ts.map +1 -1
- package/lib/codegen/index.js +1426 -437
- package/lib/codegen/targets/react/index.cjs +1503 -453
- package/lib/codegen/targets/react/index.d.ts.map +1 -1
- package/lib/codegen/targets/react/index.js +1503 -453
- package/lib/index.cjs +1534 -491
- package/lib/index.js +1534 -491
- package/package.json +3 -3
- package/src/codegen/component-properties.ts +4 -4
- package/src/codegen/targets/react/component/handlers/list-item.ts +1 -1
- package/src/codegen/targets/react/component/handlers/menu-sheet.ts +4 -4
- package/src/codegen/targets/react/component/handlers/result-section.ts +67 -0
- package/src/codegen/targets/react/component/index.ts +2 -2
- package/src/entities/data/__generated__/component-sets/index.d.ts +105 -113
- package/src/entities/data/__generated__/component-sets/index.mjs +105 -113
- package/src/entities/data/__generated__/components/index.d.ts +13 -5
- package/src/entities/data/__generated__/components/index.mjs +13 -5
- package/src/entities/data/__generated__/icons/index.mjs +54 -0
- package/src/entities/data/__generated__/variable-collections/index.mjs +62 -19
- package/src/entities/data/__generated__/variables/index.mjs +1199 -245
- package/src/codegen/targets/react/component/handlers/error-state.ts +0 -56
|
@@ -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<\n FigmaRestSpec.IsLayerTrait,\n \"type\" | \"id\" | \"name\" | \"boundVariables\"\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 NormalizedHasGeometryTrait = Pick<\n FigmaRestSpec.HasGeometryTrait,\n \"fills\" | \"strokes\" | \"strokeWeight\" | \"styles\"\n> & {\n fillStyleKey?: 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 lineHeight?: number | { unit: string; value: 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\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\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 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}\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}: CodeGeneratorDeps): CodeGenerator {\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\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 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 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 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 };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\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 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 };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\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 key: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (key) {\n return getVariableName(key);\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 key: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (key) {\n return getVariableName(key);\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 key: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(key: string | undefined, value: number | undefined) {\n if (key) {\n return getVariableName(key);\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 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 };\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 return {\n getFormattedValue,\n getTextStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\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 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\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};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\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 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 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","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 { ...propsConverters.selfLayout(node), background: \"palette.gray200\" },\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 };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\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 switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren);\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren);\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 {\n createActionButtonHandler,\n createActionButtonGhostHandler,\n} from \"./handlers/action-button\";\nimport { createAlertDialogHandler } from \"./handlers/alert-dialog\";\nimport { createAppBarHandler } from \"./handlers/app-bar\";\nimport { createAvatarHandler } from \"./handlers/avatar\";\nimport { createAvatarStackHandler } from \"./handlers/avatar-stack\";\nimport { createBadgeHandler } from \"./handlers/badge\";\nimport { createBottomSheetHandler } from \"./handlers/bottom-sheet\";\nimport { createCalloutHandler } from \"./handlers/callout\";\nimport { createCheckboxHandler } from \"./handlers/checkbox\";\nimport { createCheckmarkHandler } from \"@/codegen/targets/react/component/handlers/checkmark\";\nimport { createChipHandler } from \"./handlers/chip\";\nimport { createContextualFloatingButtonHandler } from \"./handlers/contextual-floating-button\";\nimport { createDividerHandler } from \"./handlers/divider\";\nimport { createErrorStateHandler } from \"./handlers/error-state\";\nimport {\n createFieldButtonHandler,\n createAddressFieldHandler,\n createDatePickerFieldHandler,\n createSelectFieldHandler,\n createTimePickerFieldHandler,\n} from \"@/codegen/targets/react/component/handlers/field-button\";\nimport { createFloatingActionButtonHandler } from \"./handlers/floating-action-button\";\nimport { createHelpBubbleHandler } from \"./handlers/help-bubble\";\nimport { createIdentityPlaceholderHandler } from \"./handlers/identity-placeholder\";\nimport { createListHeaderHandler } from \"@/codegen/targets/react/component/handlers/list-header\";\nimport { createListItemHandler } from \"@/codegen/targets/react/component/handlers/list-item\";\nimport { createMannerTempBadgeHandler } from \"./handlers/manner-temp-badge\";\nimport { createMannerTempHandler } from \"./handlers/manner-temp\";\nimport { createMenuSheetHandler } from \"./handlers/menu-sheet\";\nimport { createPageBannerHandler } from \"./handlers/page-banner\";\nimport { createProgressCircleHandler } from \"./handlers/progress-circle\";\nimport { createRadioGroupItemHandler } from \"@/codegen/targets/react/component/handlers/radio-group\";\nimport { createRadioMarkHandler } from \"@/codegen/targets/react/component/handlers/radio-mark\";\nimport { createReactionButtonHandler } from \"./handlers/reaction-button\";\nimport { createSegmentedControlHandler } from \"./handlers/segmented-control\";\nimport { createSelectBoxGroupHandler, createSelectBoxHandler } from \"./handlers/select-box\";\nimport { createSkeletonHandler } from \"./handlers/skeleton\";\nimport {\n createSliderHandler,\n createSliderFieldHandler,\n} from \"@/codegen/targets/react/component/handlers/slider\";\nimport { createSnackbarHandler } from \"./handlers/snackbar\";\nimport { createSwitchMarkHandler } from \"@/codegen/targets/react/component/handlers/switch-mark\";\nimport { createSwitchHandler } from \"./handlers/switch\";\nimport { createTabsHandler } from \"@/codegen/targets/react/component/handlers/tabs\";\nimport { createTextInputFieldHandler, createTextareaFieldHandler } from \"./handlers/text-field\";\nimport { createToggleButtonHandler } from \"./handlers/toggle-button\";\nimport {\n createTagGroupHandler,\n createTagGroupItemHandler,\n} from \"@/codegen/targets/react/component/handlers/tag-group\";\nimport {\n createLegacyTextFieldHandler,\n createLegacyMultilineTextFieldHandler,\n} from \"./handlers/legacy-text-field\";\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\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n createActionButtonGhostHandler,\n createActionButtonHandler,\n createTextInputFieldHandler,\n createTextareaFieldHandler,\n\n createLegacyTextFieldHandler,\n createLegacyMultilineTextFieldHandler,\n\n createBadgeHandler,\n createChipHandler,\n createCalloutHandler,\n createPageBannerHandler,\n createCheckboxHandler,\n createCheckmarkHandler,\n createRadioGroupItemHandler,\n createRadioMarkHandler,\n createSwitchHandler,\n createSwitchMarkHandler,\n createAlertDialogHandler,\n createDividerHandler,\n createAvatarHandler,\n createAvatarStackHandler,\n createSegmentedControlHandler,\n createSelectBoxGroupHandler,\n createSelectBoxHandler,\n createSliderHandler,\n createSliderFieldHandler,\n createTabsHandler,\n createTagGroupHandler,\n createTagGroupItemHandler,\n createToggleButtonHandler,\n createAppBarHandler,\n createBottomSheetHandler,\n createFieldButtonHandler,\n createAddressFieldHandler,\n createDatePickerFieldHandler,\n createSelectFieldHandler,\n createTimePickerFieldHandler,\n createErrorStateHandler,\n createContextualFloatingButtonHandler,\n createFloatingActionButtonHandler,\n createHelpBubbleHandler,\n createIdentityPlaceholderHandler,\n createListHeaderHandler,\n createListItemHandler,\n createMannerTempBadgeHandler,\n createMannerTempHandler,\n createMenuSheetHandler,\n createProgressCircleHandler,\n createReactionButtonHandler,\n createSkeletonHandler,\n createSnackbarHandler,\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\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 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 defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultFillStyleResolver,\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 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 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 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 });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACA;AACA;AACP;AACA;AACO;AACA;AACP;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;;ACtEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACUA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClBO;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;;AC7BO;AACP;AACA;AACA;;ACDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;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;;AC5FA;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<\n FigmaRestSpec.IsLayerTrait,\n \"type\" | \"id\" | \"name\" | \"boundVariables\"\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 NormalizedHasGeometryTrait = Pick<\n FigmaRestSpec.HasGeometryTrait,\n \"fills\" | \"strokes\" | \"strokeWeight\" | \"styles\"\n> & {\n fillStyleKey?: 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 lineHeight?: number | { unit: string; value: 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\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\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 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}\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}: CodeGeneratorDeps): CodeGenerator {\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\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 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 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 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 };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\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 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 };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\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 key: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (key) {\n return getVariableName(key);\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 key: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (key) {\n return getVariableName(key);\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 key: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (key) {\n return getVariableName(key);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(key: string | undefined, value: number | undefined) {\n if (key) {\n return getVariableName(key);\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 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 };\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 return {\n getFormattedValue,\n getTextStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\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 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\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};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\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 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 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","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 { ...propsConverters.selfLayout(node), background: \"palette.gray200\" },\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 };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\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 switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren);\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren);\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 {\n createActionButtonHandler,\n createActionButtonGhostHandler,\n} from \"./handlers/action-button\";\nimport { createAlertDialogHandler } from \"./handlers/alert-dialog\";\nimport { createAppBarHandler } from \"./handlers/app-bar\";\nimport { createAvatarHandler } from \"./handlers/avatar\";\nimport { createAvatarStackHandler } from \"./handlers/avatar-stack\";\nimport { createBadgeHandler } from \"./handlers/badge\";\nimport { createBottomSheetHandler } from \"./handlers/bottom-sheet\";\nimport { createCalloutHandler } from \"./handlers/callout\";\nimport { createCheckboxHandler } from \"./handlers/checkbox\";\nimport { createCheckmarkHandler } from \"@/codegen/targets/react/component/handlers/checkmark\";\nimport { createChipHandler } from \"./handlers/chip\";\nimport { createContextualFloatingButtonHandler } from \"./handlers/contextual-floating-button\";\nimport { createDividerHandler } from \"./handlers/divider\";\nimport {\n createFieldButtonHandler,\n createAddressFieldHandler,\n createDatePickerFieldHandler,\n createSelectFieldHandler,\n createTimePickerFieldHandler,\n} from \"@/codegen/targets/react/component/handlers/field-button\";\nimport { createFloatingActionButtonHandler } from \"./handlers/floating-action-button\";\nimport { createHelpBubbleHandler } from \"./handlers/help-bubble\";\nimport { createIdentityPlaceholderHandler } from \"./handlers/identity-placeholder\";\nimport { createListHeaderHandler } from \"@/codegen/targets/react/component/handlers/list-header\";\nimport { createListItemHandler } from \"@/codegen/targets/react/component/handlers/list-item\";\nimport { createMannerTempBadgeHandler } from \"./handlers/manner-temp-badge\";\nimport { createMannerTempHandler } from \"./handlers/manner-temp\";\nimport { createMenuSheetHandler } from \"./handlers/menu-sheet\";\nimport { createPageBannerHandler } from \"./handlers/page-banner\";\nimport { createProgressCircleHandler } from \"./handlers/progress-circle\";\nimport { createRadioGroupItemHandler } from \"@/codegen/targets/react/component/handlers/radio-group\";\nimport { createRadioMarkHandler } from \"@/codegen/targets/react/component/handlers/radio-mark\";\nimport { createReactionButtonHandler } from \"./handlers/reaction-button\";\nimport { createResultSectionHandler } from \"./handlers/result-section\";\nimport { createSegmentedControlHandler } from \"./handlers/segmented-control\";\nimport { createSelectBoxGroupHandler, createSelectBoxHandler } from \"./handlers/select-box\";\nimport { createSkeletonHandler } from \"./handlers/skeleton\";\nimport {\n createSliderHandler,\n createSliderFieldHandler,\n} from \"@/codegen/targets/react/component/handlers/slider\";\nimport { createSnackbarHandler } from \"./handlers/snackbar\";\nimport { createSwitchMarkHandler } from \"@/codegen/targets/react/component/handlers/switch-mark\";\nimport { createSwitchHandler } from \"./handlers/switch\";\nimport { createTabsHandler } from \"@/codegen/targets/react/component/handlers/tabs\";\nimport { createTextInputFieldHandler, createTextareaFieldHandler } from \"./handlers/text-field\";\nimport { createToggleButtonHandler } from \"./handlers/toggle-button\";\nimport {\n createTagGroupHandler,\n createTagGroupItemHandler,\n} from \"@/codegen/targets/react/component/handlers/tag-group\";\nimport {\n createLegacyTextFieldHandler,\n createLegacyMultilineTextFieldHandler,\n} from \"./handlers/legacy-text-field\";\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\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n createActionButtonGhostHandler,\n createActionButtonHandler,\n createTextInputFieldHandler,\n createTextareaFieldHandler,\n\n createLegacyTextFieldHandler,\n createLegacyMultilineTextFieldHandler,\n\n createBadgeHandler,\n createChipHandler,\n createCalloutHandler,\n createPageBannerHandler,\n createCheckboxHandler,\n createCheckmarkHandler,\n createRadioGroupItemHandler,\n createRadioMarkHandler,\n createSwitchHandler,\n createSwitchMarkHandler,\n createAlertDialogHandler,\n createDividerHandler,\n createAvatarHandler,\n createAvatarStackHandler,\n createSegmentedControlHandler,\n createSelectBoxGroupHandler,\n createSelectBoxHandler,\n createSliderHandler,\n createSliderFieldHandler,\n createTabsHandler,\n createTagGroupHandler,\n createTagGroupItemHandler,\n createToggleButtonHandler,\n createAppBarHandler,\n createBottomSheetHandler,\n createFieldButtonHandler,\n createAddressFieldHandler,\n createDatePickerFieldHandler,\n createSelectFieldHandler,\n createTimePickerFieldHandler,\n createResultSectionHandler,\n createContextualFloatingButtonHandler,\n createFloatingActionButtonHandler,\n createHelpBubbleHandler,\n createIdentityPlaceholderHandler,\n createListHeaderHandler,\n createListItemHandler,\n createMannerTempBadgeHandler,\n createMannerTempHandler,\n createMenuSheetHandler,\n createProgressCircleHandler,\n createReactionButtonHandler,\n createSkeletonHandler,\n createSnackbarHandler,\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\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 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 defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultFillStyleResolver,\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 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 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 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 });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACA;AACA;AACP;AACA;AACO;AACA;AACP;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;;ACtEA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACUA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClBO;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;;AC7BO;AACP;AACA;AACA;;ACDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;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;;AC5FA;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;;;"}
|