@seed-design/figma 1.3.2 → 1.3.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../../../../src/normalizer/types.ts","../../../../src/codegen/core/jsx.ts","../../../../src/codegen/core/element-transformer.ts","../../../../src/codegen/core/codegen.ts","../../../../src/codegen/core/component-handler.ts","../../../../src/codegen/core/props-converter.ts","../../../../src/codegen/core/value-resolver.ts","../../../../src/codegen/targets/react/value-resolver.ts","../../../../src/codegen/targets/react/props.ts","../../../../src/codegen/targets/react/shape.ts","../../../../src/codegen/targets/react/frame.ts","../../../../src/codegen/targets/react/icon.ts","../../../../src/codegen/targets/react/instance.ts","../../../../src/codegen/targets/react/text.ts","../../../../src/codegen/targets/react/component/deps.interface.ts","../../../../src/codegen/targets/react/component/index.ts","../../../../src/codegen/targets/react/pipeline.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","import { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n\n const [namespace] = tag.split(\".\");\n\n if (existing) {\n existing.add(namespace);\n } else {\n importMap.set(importPath, new Set([namespace]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\"${value.replaceAll(\"\\n\", \"\\\\n\")}\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n if (value === true) return key;\n\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n const elementStr = recursive(value, depth + 1);\n\n const commentMatch = elementStr.match(/\\{\\/\\* (.+?)\\*\\/\\}$/);\n\n if (commentMatch) {\n const elementWithoutComment = elementStr.replace(/\\{\\/\\* .+? \\*\\/\\}$/, \"\");\n\n return `${key}={${elementWithoutComment}}/* ${commentMatch[1]} */`;\n }\n\n return `${key}={${elementStr}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n\n return undefined;\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n}\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 NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShadowPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultEffectStyleNameFormatter,\n defaultFillStyleResolver,\n defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n textStyleNameFormatter: defaultTextStyleNameFormatter,\n effectStyleNameFormatter: defaultEffectStyleNameFormatter,\n fillStyleResolver: defaultFillStyleResolver,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const shadowPropsConverter = createShadowPropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n shadow: shadowPropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;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;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;;ACFO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACO;;AClGA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
1
+ {"version":3,"file":"index.d.ts","sources":["../../../../src/normalizer/types.ts","../../../../src/codegen/core/jsx.ts","../../../../src/codegen/core/element-transformer.ts","../../../../src/codegen/core/codegen.ts","../../../../src/codegen/core/component-handler.ts","../../../../src/codegen/core/props-converter.ts","../../../../src/codegen/core/value-resolver.ts","../../../../src/codegen/targets/react/value-resolver.ts","../../../../src/codegen/targets/react/props.ts","../../../../src/codegen/targets/react/shape.ts","../../../../src/codegen/targets/react/frame.ts","../../../../src/codegen/targets/react/icon.ts","../../../../src/codegen/targets/react/instance.ts","../../../../src/codegen/targets/react/text.ts","../../../../src/codegen/targets/react/component/deps.interface.ts","../../../../src/codegen/targets/react/component/index.ts","../../../../src/codegen/targets/react/pipeline.ts"],"sourcesContent":["import type * as FigmaRestSpec from \"@figma/rest-api-spec\";\n\nexport type NormalizedIsLayerTrait = Pick<FigmaRestSpec.IsLayerTrait, \"type\" | \"id\" | \"name\"> & {\n boundVariables?: Pick<\n NonNullable<FigmaRestSpec.IsLayerTrait[\"boundVariables\"]>,\n | \"fills\"\n | \"strokes\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n | \"bottomLeftRadius\"\n | \"bottomRightRadius\"\n | \"topLeftRadius\"\n | \"topRightRadius\"\n | \"paddingBottom\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"maxHeight\"\n | \"minHeight\"\n | \"maxWidth\"\n | \"minWidth\"\n | \"fontSize\"\n | \"fontWeight\"\n | \"lineHeight\"\n | \"size\"\n >;\n};\n\nexport type NormalizedCornerTrait = Pick<\n FigmaRestSpec.CornerTrait,\n \"cornerRadius\" | \"rectangleCornerRadii\"\n>;\n\nexport type NormalizedHasChildrenTrait = {\n children: NormalizedSceneNode[];\n};\n\nexport type NormalizedHasLayoutTrait = Pick<\n FigmaRestSpec.HasLayoutTrait,\n | \"layoutAlign\"\n | \"layoutGrow\"\n | \"absoluteBoundingBox\"\n | \"relativeTransform\"\n | \"layoutPositioning\"\n | \"layoutSizingHorizontal\"\n | \"layoutSizingVertical\"\n | \"minHeight\"\n | \"minWidth\"\n | \"maxHeight\"\n | \"maxWidth\"\n>;\n\nexport type NormalizedSolidPaint = FigmaRestSpec.SolidPaint;\n\nexport type NormalizedPaint =\n | NormalizedSolidPaint\n | FigmaRestSpec.GradientPaint\n | FigmaRestSpec.ImagePaint;\n\nexport type NormalizedHasGeometryTrait = Omit<\n Pick<FigmaRestSpec.HasGeometryTrait, \"fills\" | \"strokes\" | \"strokeWeight\">,\n \"fills\" | \"strokes\"\n> & {\n fills: NormalizedPaint[];\n strokes: NormalizedPaint[];\n fillStyleKey?: string;\n};\n\nexport type NormalizedShadow =\n | (Pick<\n FigmaRestSpec.DropShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.DropShadowEffect, \"type\">>)\n | (Pick<\n FigmaRestSpec.InnerShadowEffect,\n \"color\" | \"offset\" | \"radius\" | \"spread\" | \"boundVariables\"\n > &\n Required<Pick<FigmaRestSpec.InnerShadowEffect, \"type\">>);\n\nexport type NormalizedHasEffectsTrait = Omit<FigmaRestSpec.HasEffectsTrait, \"effects\"> & {\n effects: NormalizedShadow[];\n effectStyleKey?: string;\n};\n\nexport type NormalizedHasFramePropertiesTrait = Pick<\n FigmaRestSpec.HasFramePropertiesTrait,\n | \"layoutMode\"\n | \"layoutWrap\"\n | \"paddingLeft\"\n | \"paddingRight\"\n | \"paddingTop\"\n | \"paddingBottom\"\n | \"primaryAxisAlignItems\"\n | \"primaryAxisSizingMode\"\n | \"counterAxisAlignItems\"\n | \"counterAxisSizingMode\"\n | \"itemSpacing\"\n | \"counterAxisSpacing\"\n>;\n\nexport interface NormalizedTextSegment {\n characters: string;\n start: number;\n end: number;\n style: {\n fontFamily?: string;\n fontWeight?: number;\n fontSize?: number;\n italic?: boolean;\n textDecoration?: string;\n letterSpacing?: number;\n /**\n * in pixels\n */\n lineHeight?: number;\n };\n}\n\nexport type NormalizedTypePropertiesTrait = Pick<\n FigmaRestSpec.TypePropertiesTrait,\n \"style\" | \"characters\"\n> & {\n segments: NormalizedTextSegment[];\n\n textStyleKey?: string;\n};\n\nexport type NormalizedDefaultShapeTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait;\n\nexport type NormalizedFrameTrait = NormalizedIsLayerTrait &\n NormalizedHasLayoutTrait &\n NormalizedHasGeometryTrait &\n NormalizedHasEffectsTrait &\n NormalizedHasChildrenTrait &\n NormalizedCornerTrait &\n NormalizedHasFramePropertiesTrait;\n\nexport interface NormalizedFrameNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.FrameNode[\"type\"];\n}\n\nexport interface NormalizedRectangleNode\n extends NormalizedDefaultShapeTrait,\n NormalizedCornerTrait {\n type: FigmaRestSpec.RectangleNode[\"type\"];\n}\n\nexport interface NormalizedTextNode\n extends NormalizedDefaultShapeTrait,\n NormalizedTypePropertiesTrait {\n type: FigmaRestSpec.TextNode[\"type\"];\n}\n\nexport interface NormalizedComponentNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.ComponentNode[\"type\"];\n}\n\nexport interface NormalizedInstanceNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.InstanceNode[\"type\"];\n\n componentProperties: {\n [key: string]: FigmaRestSpec.ComponentProperty & {\n componentKey?: string;\n componentSetKey?: string;\n };\n };\n\n componentKey: string;\n\n componentSetKey?: string;\n\n overrides?: FigmaRestSpec.InstanceNode[\"overrides\"];\n\n children: NormalizedSceneNode[];\n}\n\nexport interface NormalizedVectorNode extends NormalizedDefaultShapeTrait, NormalizedCornerTrait {\n type: FigmaRestSpec.VectorNode[\"type\"];\n}\n\nexport interface NormalizedBooleanOperationNode\n extends NormalizedIsLayerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasLayoutTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasEffectsTrait {\n type: FigmaRestSpec.BooleanOperationNode[\"type\"];\n}\n\nexport interface NormalizedUnhandledNode {\n type: \"UNHANDLED\";\n id: string;\n original: FigmaRestSpec.Node | SceneNode;\n}\n\nexport type NormalizedSceneNode =\n | NormalizedFrameNode\n | NormalizedRectangleNode\n | NormalizedTextNode\n | NormalizedComponentNode\n | NormalizedInstanceNode\n | NormalizedVectorNode\n | NormalizedBooleanOperationNode\n | NormalizedUnhandledNode;\n","import { ensureArray, exists } from \"@/utils/common\";\n\nexport interface ElementNode {\n __IS_JSX_ELEMENT_NODE: true;\n tag: string;\n props: Record<string, string | number | boolean | ElementNode | object | undefined>;\n children: (ElementNode | string)[];\n\n meta: {\n comment?: string;\n source?: string;\n importPath?: string;\n };\n}\n\nexport function createElement(\n tag: string,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n meta?: ElementNode[\"meta\"],\n): ElementNode {\n return {\n __IS_JSX_ELEMENT_NODE: true,\n tag,\n props,\n children: ensureArray(children).filter(exists),\n meta: meta ?? {},\n };\n}\n\nexport function cloneElement(\n element: ElementNode,\n props: Record<string, string | number | boolean | object | undefined> = {},\n children?: ElementNode | string | undefined | (ElementNode | string | undefined)[],\n) {\n return {\n ...element,\n props: { ...element.props, ...props },\n children: children ? ensureArray(children).filter(exists) : element.children,\n };\n}\n\nexport function appendSource(element: ElementNode, source: string) {\n return {\n ...element,\n source,\n };\n}\n\nexport function isElement(node: unknown): node is ElementNode {\n return (\n typeof node === \"object\" &&\n node != null &&\n \"__IS_JSX_ELEMENT_NODE\" in node &&\n node.__IS_JSX_ELEMENT_NODE === true\n );\n}\n\nexport function stringifyElement(element: ElementNode, options: { printSource?: boolean } = {}) {\n const importMap = new Map<string, Set<string>>();\n\n function recursive(node: ElementNode | string, depth: number): string {\n if (typeof node === \"string\") {\n return node;\n }\n\n const {\n tag,\n props,\n children,\n meta: { comment, source, importPath },\n } = node;\n\n if (importPath) {\n const existing = importMap.get(importPath);\n\n const [namespace] = tag.split(\".\");\n\n if (existing) {\n existing.add(namespace);\n } else {\n importMap.set(importPath, new Set([namespace]));\n }\n }\n\n const propEntries = Object.entries(\n options.printSource ? { ...props, \"data-figma-node-id\": source } : props,\n );\n const propFragments = propEntries\n .map(([key, value]) => {\n if (typeof value === \"string\") {\n if (value.includes(\"\\n\")) {\n return `${key}={\"${value.replaceAll(\"\\n\", \"\\\\n\")}\"}`;\n }\n\n return `${key}=\"${value}\"`;\n }\n\n if (typeof value === \"number\") {\n return `${key}={${value}}`;\n }\n\n if (typeof value === \"boolean\") {\n if (value === true) return key;\n\n return `${key}={${value}}`;\n }\n\n if (isElement(value)) {\n const elementStr = recursive(value, depth + 1);\n\n const commentMatch = elementStr.match(/\\{\\/\\* (.+?)\\*\\/\\}$/);\n\n if (commentMatch) {\n const elementWithoutComment = elementStr.replace(/\\{\\/\\* .+? \\*\\/\\}$/, \"\");\n\n return `${key}={${elementWithoutComment}}/* ${commentMatch[1]} */`;\n }\n\n return `${key}={${elementStr}}`;\n }\n\n if (typeof value === \"object\") {\n return `${key}={${JSON.stringify(value)}}`;\n }\n\n if (typeof value === \"undefined\") {\n return undefined;\n }\n\n return undefined;\n })\n .filter(exists);\n\n const oneLiner = propFragments.join(\" \");\n const propsString =\n propEntries.length === 0\n ? \"\"\n : ` ${\n oneLiner.length < 80\n ? oneLiner\n : `\\n${\" \".repeat(depth + 1)}${propFragments.join(\n `\\n${\" \".repeat(depth + 1)}`,\n )}\\n${\" \".repeat(depth)}`\n }`;\n\n if (children == null || children.length === 0) {\n return `<${tag}${propsString} />${comment ? `{/* ${comment} */}` : \"\"}`;\n }\n\n const result = [\n `<${tag}${propsString}>`,\n ...ensureArray(children)\n .filter(exists)\n .map((child) => recursive(child, depth + 1))\n .map((str) => \" \".repeat(depth + 1) + str),\n `${\" \".repeat(depth)}</${tag}>${comment ? `{/* ${comment} */}` : \"\"}`,\n ].join(\"\\n\");\n\n return result;\n }\n\n const jsx = recursive(element, 0);\n\n const imports = Array.from(importMap.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([importPath, tags]) => `import { ${Array.from(tags).join(\", \")} } from \"${importPath}\";`)\n .join(\"\\n\");\n\n return {\n imports,\n jsx,\n };\n}\n","import type { NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport type ElementTransformer<T extends NormalizedSceneNode> = (\n node: T,\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n) => ElementNode | undefined;\n\nexport function defineElementTransformer<T extends NormalizedSceneNode>(\n transformer: ElementTransformer<T>,\n) {\n return transformer;\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n NormalizedRectangleNode,\n NormalizedSceneNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n skipComponentKeys?: Set<string>;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys,\n}: CodeGeneratorDeps): CodeGenerator {\n function isSkippedInstance(node: NormalizedSceneNode): boolean {\n if (!skipComponentKeys || skipComponentKeys.size === 0) return false;\n if (node.type !== \"INSTANCE\") return false;\n\n const { componentKey, componentSetKey } = node;\n\n return (\n skipComponentKeys.has(componentKey) ||\n (!!componentSetKey && skipComponentKeys.has(componentSetKey))\n );\n }\n\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n if (isSkippedInstance(node)) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, () => createElement(\"UnhandledFigmaNode\"))\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n if (isSkippedInstance(node)) {\n return { imports: \"\", jsx: \"// This component is intentionally excluded from codegen\" };\n }\n\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode, NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport { SKIP_COMPONENT_KEYS } from \"@/codegen/skip-components\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShadowPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultEffectStyleNameFormatter,\n defaultFillStyleResolver,\n defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n textStyleNameFormatter: defaultTextStyleNameFormatter,\n effectStyleNameFormatter: defaultEffectStyleNameFormatter,\n fillStyleResolver: defaultFillStyleResolver,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const shadowPropsConverter = createShadowPropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n shadow: shadowPropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys: SKIP_COMPONENT_KEYS,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACWA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnBO;AACP;AACA;AACA;AACA;AACA;;ACNO;;ACGA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;;ACFO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACO;;AClGA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
@@ -436,11 +436,20 @@ function applyInferredLayout(parentNode, result) {
436
436
  };
437
437
  }
438
438
 
439
- function createCodeGenerator({ frameTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer, shouldInferAutoLayout }) {
439
+ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer, shouldInferAutoLayout, skipComponentKeys }) {
440
+ function isSkippedInstance(node) {
441
+ if (!skipComponentKeys || skipComponentKeys.size === 0) return false;
442
+ if (node.type !== "INSTANCE") return false;
443
+ const { componentKey, componentSetKey } = node;
444
+ return skipComponentKeys.has(componentKey) || !!componentSetKey && skipComponentKeys.has(componentSetKey);
445
+ }
440
446
  function traverse(node) {
441
447
  if ("visible" in node && !node.visible) {
442
448
  return;
443
449
  }
450
+ if (isSkippedInstance(node)) {
451
+ return;
452
+ }
444
453
  const result = match(node).with({
445
454
  type: "FRAME"
446
455
  }, (node)=>shouldInferAutoLayout ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse) : frameTransformer(node, traverse)).with({
@@ -468,6 +477,12 @@ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTrans
468
477
  return traverse(node);
469
478
  }
470
479
  function generateCode(node, options) {
480
+ if (isSkippedInstance(node)) {
481
+ return {
482
+ imports: "",
483
+ jsx: "// This component is intentionally excluded from codegen"
484
+ };
485
+ }
471
486
  const jsxTree = generateJsxTree(node);
472
487
  if (!jsxTree) {
473
488
  return undefined;
@@ -5296,8 +5311,8 @@ var archivedHandlers = {
5296
5311
  createToggleButtonHandler: createToggleButtonHandler$1
5297
5312
  };
5298
5313
 
5299
- const privateTemplateAttechmentField = {
5300
- "name": "privateTemplateAttechmentField",
5314
+ const privateTemplateAttachmentField = {
5315
+ "name": "privateTemplateAttachmentField",
5301
5316
  "key": "5ba20e248e9cd0292fc285488b2ed3b3145d37b0",
5302
5317
  "componentPropertyDefinitions": {
5303
5318
  "Show Header#40606:8": {
@@ -7302,11 +7317,11 @@ const componentBottomSheet = {
7302
7317
  "Header Layout": {
7303
7318
  "type": "VARIANT",
7304
7319
  "variantOptions": [
7320
+ "Top Left",
7321
+ "Top Center",
7305
7322
  "Bottom Left",
7306
- "None",
7307
7323
  "Bottom Center",
7308
- "Top Center",
7309
- "Top Left"
7324
+ "None"
7310
7325
  ]
7311
7326
  }
7312
7327
  }
@@ -7854,7 +7869,7 @@ const componentImageFrame = {
7854
7869
  "80",
7855
7870
  "96",
7856
7871
  "120",
7857
- "\bFree"
7872
+ "Free"
7858
7873
  ]
7859
7874
  },
7860
7875
  "Rounded": {
@@ -8713,8 +8728,7 @@ const componentSwitch = {
8713
8728
  "type": "VARIANT",
8714
8729
  "variantOptions": [
8715
8730
  "Label Last",
8716
- "Label First",
8717
- "🚫[Switch Mark 사용] Switch Only"
8731
+ "Label First"
8718
8732
  ]
8719
8733
  }
8720
8734
  }
@@ -9527,7 +9541,7 @@ var FIGMA_COMPONENTS = {
9527
9541
  privateComponentUnderlineTextInputInputReadOnly: privateComponentUnderlineTextInputInputReadOnly,
9528
9542
  privateComponentUnderlineTextInputPrefix: privateComponentUnderlineTextInputPrefix,
9529
9543
  privateComponentUnderlineTextInputSuffix: privateComponentUnderlineTextInputSuffix,
9530
- privateTemplateAttechmentField: privateTemplateAttechmentField,
9544
+ privateTemplateAttachmentField: privateTemplateAttachmentField,
9531
9545
  privateTemplateChipGroup: privateTemplateChipGroup,
9532
9546
  privateTemplateChipGroupField: privateTemplateChipGroupField,
9533
9547
  privateTemplatePhoneNumberField: privateTemplatePhoneNumberField,
@@ -9551,7 +9565,7 @@ var FIGMA_COMPONENTS = {
9551
9565
  };
9552
9566
 
9553
9567
  const { createLocalSnippetElement: createLocalSnippetElement$A } = createLocalSnippetHelper("action-button");
9554
- const createActionButtonHandler = (ctx)=>defineComponentHandler(componentActionButton.key, ({ componentProperties: props })=>{
9568
+ const createActionButtonHandler = (ctx)=>defineComponentHandler(componentActionButton.key, ({ componentProperties: props, layoutGrow })=>{
9555
9569
  const states = props.State.value.split("-");
9556
9570
  const { layout, children } = match(props.Layout.value).with("Icon Only", ()=>({
9557
9571
  layout: "iconOnly",
@@ -9589,7 +9603,10 @@ const createActionButtonHandler = (ctx)=>defineComponentHandler(componentActionB
9589
9603
  },
9590
9604
  size: handleSizeProp(props.Size.value),
9591
9605
  variant: camelCase(props.Variant.value),
9592
- layout
9606
+ layout,
9607
+ ...layoutGrow === 1 && {
9608
+ flexGrow: true
9609
+ }
9593
9610
  };
9594
9611
  return createLocalSnippetElement$A("ActionButton", commonProps, children);
9595
9612
  });
@@ -9646,6 +9663,9 @@ const createActionButtonGhostHandler = (ctx)=>defineComponentHandler(componentAc
9646
9663
  ...props.Bleed.value === "true" && {
9647
9664
  bleedX: "asPadding",
9648
9665
  bleedY: "asPadding"
9666
+ },
9667
+ ...node.layoutGrow === 1 && {
9668
+ flexGrow: true
9649
9669
  }
9650
9670
  };
9651
9671
  return createLocalSnippetElement$A("ActionButton", commonProps, children);
@@ -9734,6 +9754,13 @@ const privateComponentTopNavigationLeftIconButton = {
9734
9754
  "key": "c3e708bab11d8ea90a909b4539b6ba6b2a4e7b9c"};
9735
9755
  const componentChipSuffixIcon = {
9736
9756
  "key": "2f79e3c5a78315c854d7bd4499d142cfcc94548f"};
9757
+ const componentImageFrameBadge = {
9758
+ "key": "6a1feb47139040d6f7522528f7c91bf4fe7bcc84"
9759
+ };
9760
+ const componentImageFrameIcon = {
9761
+ "key": "4f495448eeda5d10f41e6195e16b4eff49aaec17"};
9762
+ const componentImageFrameOverlayIndicator = {
9763
+ "key": "e3e3596f8c535facae4d23c21bc1d62dd721fe23"};
9737
9764
  const componentListItemPrefixAvatar = {
9738
9765
  "key": "27e33754113178be97e07195528c4ea020b3d3b7"
9739
9766
  };
@@ -9870,6 +9897,19 @@ const createAppBarHandler = (ctx)=>{
9870
9897
  });
9871
9898
  });
9872
9899
  };
9900
+ const createAppBarPresetHandler = (ctx)=>{
9901
+ const appBarHandler = createAppBarHandler(ctx);
9902
+ return defineComponentHandler(templateTopNavigationPreset.key, (node, traverse)=>{
9903
+ const [appBarNode] = findAllInstances({
9904
+ node,
9905
+ key: componentTopNavigation.key
9906
+ });
9907
+ if (!appBarNode) {
9908
+ return createLocalSnippetElement$y("AppBar");
9909
+ }
9910
+ return appBarHandler.transform(appBarNode, traverse);
9911
+ });
9912
+ };
9873
9913
 
9874
9914
  // hardcoded since this lives in a different figma file
9875
9915
  const IDENTITY_PLACEHOLDER_KEY = "b3563b6f16ba4cfe4240c9b33eef7edad4c304eb";
@@ -10478,6 +10518,99 @@ const createHelpBubbleHandler = (_ctx)=>defineComponentHandler(componentHelpBubb
10478
10518
  });
10479
10519
  });
10480
10520
 
10521
+ const CORNER_CONFIGS = [
10522
+ {
10523
+ showKey: "ㄴ Left Top#58686:165",
10524
+ placement: "top-start"
10525
+ },
10526
+ {
10527
+ showKey: "ㄴ Right Top#58686:198",
10528
+ placement: "top-end"
10529
+ },
10530
+ {
10531
+ showKey: "ㄴ Left Bottom#58686:231",
10532
+ placement: "bottom-start"
10533
+ },
10534
+ {
10535
+ showKey: "ㄴ Right Bottom#58686:264",
10536
+ placement: "bottom-end"
10537
+ }
10538
+ ];
10539
+ function formatRatio(ratioStr) {
10540
+ const [w, h] = ratioStr.split(":");
10541
+ return `${w} / ${h}`;
10542
+ }
10543
+ const createImageFrameBadgeHandler = (ctx)=>{
10544
+ const badgeHandler = createBadgeHandler();
10545
+ return defineComponentHandler(componentImageFrameBadge.key, (node, traverse)=>{
10546
+ const [badge] = findAllInstances({
10547
+ node,
10548
+ key: badgeHandler.key
10549
+ });
10550
+ if (!badge) throw new Error("Badge component not found within ImageFrameBadge");
10551
+ return {
10552
+ ...badgeHandler.transform(badge, traverse),
10553
+ tag: "ImageFrameBadge"
10554
+ };
10555
+ });
10556
+ };
10557
+ const createImageFrameReactionButtonHandler = (_ctx)=>{
10558
+ return defineComponentHandler(componentImageFrameReactionButton.key, ({ componentProperties: props })=>{
10559
+ return createSeedReactElement("ImageFrameReactionButton", {
10560
+ ...props.Selected.value === "True" && {
10561
+ defaultPressed: true
10562
+ }
10563
+ });
10564
+ });
10565
+ };
10566
+ const createImageFrameOverlayIndicatorHandler = (_ctx)=>{
10567
+ return defineComponentHandler(componentImageFrameOverlayIndicator.key, ({ componentProperties: props })=>{
10568
+ return createSeedReactElement("ImageFrameIndicator", undefined, props["Text#58708:0"].value);
10569
+ });
10570
+ };
10571
+ const createImageFrameIconHandler = (ctx)=>{
10572
+ return defineComponentHandler(componentImageFrameIcon.key, ({ componentProperties: props })=>{
10573
+ return createSeedReactElement("ImageFrameIcon", {
10574
+ svg: ctx.iconHandler.transform(props["Icon#58686:297"])
10575
+ });
10576
+ });
10577
+ };
10578
+ const createImageFrameHandler = (ctx)=>{
10579
+ return defineComponentHandler(componentImageFrame.key, (node, traverse)=>{
10580
+ const props = node.componentProperties;
10581
+ const floaters = [];
10582
+ for (const { showKey, placement } of CORNER_CONFIGS){
10583
+ if (!props[showKey].value) continue;
10584
+ const slotName = showKey.split("#")[0];
10585
+ const slotNode = findOne(node, (n)=>"name" in n && n.name === slotName);
10586
+ if (!slotNode) continue;
10587
+ const child = traverse(slotNode);
10588
+ if (!child) continue;
10589
+ floaters.push(createSeedReactElement("ImageFrameFloater", {
10590
+ placement
10591
+ }, child));
10592
+ }
10593
+ const commonProps = {
10594
+ src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,
10595
+ alt: "",
10596
+ ratio: formatRatio(props.Ratio.value),
10597
+ ...props.Rounded.value === "True" && {
10598
+ borderRadius: ctx.valueResolver.getFormattedValue.topLeftRadius(node)
10599
+ },
10600
+ ...node.layoutGrow === 1 ? {
10601
+ flexGrow: true
10602
+ } : node.layoutAlign === "STRETCH" ? {
10603
+ alignSelf: "stretch"
10604
+ } : {
10605
+ width: ctx.valueResolver.getFormattedValue.width(node)
10606
+ }
10607
+ };
10608
+ return createSeedReactElement("ImageFrame", commonProps, props["Show Overlay#58686:33"].value && floaters.length > 0 ? floaters : undefined, {
10609
+ comment: "alt 텍스트를 제공해야 합니다."
10610
+ });
10611
+ });
10612
+ };
10613
+
10481
10614
  const { createLocalSnippetElement: createLocalSnippetElement$l } = createLocalSnippetHelper("select-box");
10482
10615
  const createLegacySelectBoxHandler = (_ctx)=>defineComponentHandler(componentDeprecatedSelectBox.key, ({ componentProperties: props })=>{
10483
10616
  const tag = match(props.Control.value).with("Checkbox", ()=>"CheckSelectBox").with("Radio", ()=>"RadioSelectBoxItem").exhaustive();
@@ -11459,9 +11592,6 @@ const createSwitchHandler = (_ctx)=>defineComponentHandler(componentSwitch.key,
11459
11592
  tone,
11460
11593
  size: props.Size.value
11461
11594
  };
11462
- if (props["Layout(Figma Only)"].value === "🚫[Switch Mark 사용] Switch Only") {
11463
- return createLocalSnippetElement$4("Switchmark", commonProps);
11464
- }
11465
11595
  return createLocalSnippetElement$4("Switch", {
11466
11596
  ...commonProps,
11467
11597
  label: props["Label#36578:0"].value,
@@ -11939,6 +12069,7 @@ var currentHandlers = {
11939
12069
  createAddressFieldHandler: createAddressFieldHandler,
11940
12070
  createAlertDialogHandler: createAlertDialogHandler,
11941
12071
  createAppBarHandler: createAppBarHandler,
12072
+ createAppBarPresetHandler: createAppBarPresetHandler,
11942
12073
  createAvatarHandler: createAvatarHandler,
11943
12074
  createAvatarStackHandler: createAvatarStackHandler,
11944
12075
  createBadgeHandler: createBadgeHandler,
@@ -11955,6 +12086,11 @@ var currentHandlers = {
11955
12086
  createFloatingActionButtonHandler: createFloatingActionButtonHandler,
11956
12087
  createHelpBubbleHandler: createHelpBubbleHandler,
11957
12088
  createIdentityPlaceholderHandler: createIdentityPlaceholderHandler,
12089
+ createImageFrameBadgeHandler: createImageFrameBadgeHandler,
12090
+ createImageFrameHandler: createImageFrameHandler,
12091
+ createImageFrameIconHandler: createImageFrameIconHandler,
12092
+ createImageFrameOverlayIndicatorHandler: createImageFrameOverlayIndicatorHandler,
12093
+ createImageFrameReactionButtonHandler: createImageFrameReactionButtonHandler,
11958
12094
  createLegacyMultilineTextFieldHandler: createLegacyMultilineTextFieldHandler,
11959
12095
  createLegacySelectBoxGroupHandler: createLegacySelectBoxGroupHandler,
11960
12096
  createLegacyTextFieldHandler: createLegacyTextFieldHandler,
@@ -31501,6 +31637,12 @@ const iconService = createIconService({
31501
31637
  iconRepository
31502
31638
  });
31503
31639
 
31640
+ const SKIP_COMPONENT_KEYS = new Set([
31641
+ componentOsBottomIndicatorFigmaOnly.key,
31642
+ componentOsKeyboardFigmaOnly.key,
31643
+ componentOsStatusBarFigmaOnly.key
31644
+ ]);
31645
+
31504
31646
  const defaultIconNameFormatter = ({ name, weight })=>pascalCase(`${name}${weight ? weight : ""}`);
31505
31647
  function createIconHandler({ iconService, iconNameFormatter = defaultIconNameFormatter }) {
31506
31648
  function isIconInstance(node) {
@@ -31667,7 +31809,8 @@ function createPipeline(options = {}) {
31667
31809
  instanceTransformer,
31668
31810
  vectorTransformer,
31669
31811
  booleanOperationTransformer,
31670
- shouldInferAutoLayout
31812
+ shouldInferAutoLayout,
31813
+ skipComponentKeys: SKIP_COMPONENT_KEYS
31671
31814
  });
31672
31815
  return codeGenerator;
31673
31816
  }