@seed-design/figma 1.3.8 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/codegen/index.cjs +51 -7
- package/lib/codegen/index.d.ts +9 -2
- package/lib/codegen/index.d.ts.map +1 -1
- package/lib/codegen/index.js +51 -7
- package/lib/codegen/targets/react/index.cjs +39 -17
- package/lib/codegen/targets/react/index.d.ts +7 -1
- package/lib/codegen/targets/react/index.d.ts.map +1 -1
- package/lib/codegen/targets/react/index.js +39 -17
- package/lib/index.cjs +63 -5
- package/lib/index.d.ts +8 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +63 -5
- package/package.json +3 -3
- package/src/codegen/core/codegen.ts +4 -0
- package/src/codegen/targets/figma/instance.ts +29 -11
- package/src/codegen/targets/figma/pipeline.ts +15 -1
- package/src/codegen/targets/react/component/handlers/bottom-sheet.ts +9 -13
- package/src/codegen/targets/react/component/handlers/image-frame.ts +1 -1
- package/src/codegen/targets/react/component/handlers/select-box.ts +16 -10
- package/src/codegen/targets/react/pipeline.ts +7 -1
- package/src/entities/data/__generated__/component-sets/index.d.ts +14 -3
- package/src/entities/data/__generated__/component-sets/index.mjs +14 -3
- package/src/entities/data/__generated__/components/index.d.ts +5 -15
- package/src/entities/data/__generated__/components/index.mjs +5 -15
- package/src/normalizer/from-plugin.ts +34 -1
- package/src/normalizer/from-rest.ts +33 -1
- package/src/normalizer/types.ts +8 -0
- package/src/utils/figma-node.ts +14 -8
|
@@ -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\";\nimport { pascalCase } from \"change-case\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n textTransformer: ElementTransformer<NormalizedTextNode>;\n rectangleTransformer: ElementTransformer<NormalizedRectangleNode>;\n instanceTransformer: ElementTransformer<NormalizedInstanceNode>;\n vectorTransformer: ElementTransformer<NormalizedVectorNode>;\n booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;\n shouldInferAutoLayout: boolean;\n skipComponentKeys?: Set<string>;\n}\n\nexport interface CodeGenerator {\n generateJsxTree: (node: NormalizedSceneNode) => ElementNode | undefined;\n generateCode: (\n node: NormalizedSceneNode,\n options: { shouldPrintSource: boolean },\n ) => { imports: string; jsx: string } | undefined;\n}\n\nexport function createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys,\n}: CodeGeneratorDeps): CodeGenerator {\n function isSkippedInstance(node: NormalizedSceneNode): boolean {\n if (!skipComponentKeys || skipComponentKeys.size === 0) return false;\n if (node.type !== \"INSTANCE\") return false;\n\n const { componentKey, componentSetKey } = node;\n\n return (\n skipComponentKeys.has(componentKey) ||\n (!!componentSetKey && skipComponentKeys.has(componentSetKey))\n );\n }\n\n function traverse(node: NormalizedSceneNode): ElementNode | undefined {\n if (\"visible\" in node && !node.visible) {\n return;\n }\n\n if (isSkippedInstance(node)) {\n return;\n }\n\n const result = match(node)\n .with({ type: \"FRAME\" }, (node) =>\n shouldInferAutoLayout\n ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse)\n : frameTransformer(node, traverse),\n )\n .with({ type: \"TEXT\" }, (node) => textTransformer(node, traverse))\n .with({ type: \"RECTANGLE\" }, (node) => rectangleTransformer(node, traverse))\n .with({ type: \"COMPONENT\" }, (node) => frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, (node) =>\n createElement(`Unhandled${pascalCase(node.original.type)}Node`),\n )\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n if (isSkippedInstance(node)) {\n return { imports: \"\", jsx: \"// This component is intentionally excluded from codegen\" };\n }\n\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode, NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver } from \"@/codegen/core\";\nimport { iconService, styleService, variableService } from \"@/codegen/default-services\";\nimport { SKIP_COMPONENT_KEYS } from \"@/codegen/skip-components\";\nimport {\n type UnboundComponentHandler,\n bindComponentHandler,\n unboundSeedComponentHandlers,\n} from \"./component\";\nimport { createFrameTransformer } from \"./frame\";\nimport { createIconHandler } from \"./icon\";\nimport { createInstanceTransformer } from \"./instance\";\nimport {\n createContainerLayoutPropsConverter,\n createFrameFillPropsConverter,\n createIconSelfLayoutPropsConverter,\n createRadiusPropsConverter,\n createSelfLayoutPropsConverter,\n createShadowPropsConverter,\n createShapeFillPropsConverter,\n createStrokePropsConverter,\n createTextFillPropsConverter,\n createTypeStylePropsConverter,\n createVectorChildrenFillPropsConverter,\n} from \"./props\";\nimport {\n createBooleanOperationTransformer,\n createRectangleTransformer,\n createVectorTransformer,\n} from \"./shape\";\nimport { createTextTransformer } from \"./text\";\nimport {\n defaultEffectStyleNameFormatter,\n defaultFillStyleResolver,\n defaultRawValueFormatters,\n defaultTextStyleNameFormatter,\n defaultVariableNameFormatter,\n} from \"./value-resolver\";\n\nexport interface CreatePipelineConfig {\n shouldInferAutoLayout?: boolean;\n shouldInferVariableName?: boolean;\n extend?: {\n componentHandlers?: Array<UnboundComponentHandler<any>>;\n };\n}\n\nconst iconHandler = createIconHandler({\n iconService,\n});\n\nexport function createPipeline(options: CreatePipelineConfig = {}) {\n const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;\n\n const valueResolver = createValueResolver({\n variableService,\n variableNameFormatter: defaultVariableNameFormatter,\n styleService,\n textStyleNameFormatter: defaultTextStyleNameFormatter,\n effectStyleNameFormatter: defaultEffectStyleNameFormatter,\n fillStyleResolver: defaultFillStyleResolver,\n rawValueFormatters: defaultRawValueFormatters,\n shouldInferVariableName,\n });\n\n const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);\n const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);\n const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);\n const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);\n const shapeFillPropsConverter = createShapeFillPropsConverter(valueResolver);\n const textFillPropsConverter = createTextFillPropsConverter(valueResolver);\n const vectorChildrenFillPropsConverter = createVectorChildrenFillPropsConverter(valueResolver);\n const radiusPropsConverter = createRadiusPropsConverter(valueResolver);\n const strokePropsConverter = createStrokePropsConverter(valueResolver);\n const shadowPropsConverter = createShadowPropsConverter(valueResolver);\n const typeStylePropsConverter = createTypeStylePropsConverter({\n valueResolver,\n });\n const propsConverters = {\n containerLayout: containerLayoutPropsConverter,\n selfLayout: selfLayoutPropsConverter,\n iconSelfLayout: iconSelfLayoutPropsConverter,\n frameFill: frameFillPropsConverter,\n shapeFill: shapeFillPropsConverter,\n textFill: textFillPropsConverter,\n vectorChildrenFill: vectorChildrenFillPropsConverter,\n radius: radiusPropsConverter,\n stroke: strokePropsConverter,\n shadow: shadowPropsConverter,\n typeStyle: typeStylePropsConverter,\n };\n\n const componentHandlers = Object.fromEntries(\n [...unboundSeedComponentHandlers, ...(extend.componentHandlers ?? [])]\n .map((h) =>\n bindComponentHandler(h, {\n valueResolver,\n iconHandler,\n }),\n )\n .map((t) => [t.key, t]),\n );\n\n const frameTransformer = createFrameTransformer({\n propsConverters,\n });\n const instanceTransformer = createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n });\n const textTransformer = createTextTransformer({\n propsConverters,\n });\n const rectangleTransformer = createRectangleTransformer({\n propsConverters,\n });\n const vectorTransformer = createVectorTransformer();\n const booleanOperationTransformer = createBooleanOperationTransformer();\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n textTransformer,\n rectangleTransformer,\n instanceTransformer,\n vectorTransformer,\n booleanOperationTransformer,\n shouldInferAutoLayout,\n skipComponentKeys: SKIP_COMPONENT_KEYS,\n });\n\n return codeGenerator;\n}\n"],"names":[],"mappings":";;AACO;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACA;AACA;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACjFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACWA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnBO;AACP;AACA;AACA;AACA;AACA;;ACNO;;ACGA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/BO;AACP;AACA;AACA;;ACFO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACO;AACA;AACP;AACA;AACA;AACO;AACA;AACP;AACA;AACO;;AClGA;AACP;AACA;AACO;AACA;AACA;;ACLA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;;ACLO;AACP;AACA;AACA;AACA;AACA;AACO;;ACPA;AACP;AACA;AACO;;ACJA;AACP;AACA;AACA;;ACDO;AACA;AACA;;ACLA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
|
|
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 NormalizedSlotNode extends NormalizedFrameTrait {\n type: FigmaRestSpec.SlotNode[\"type\"];\n componentPropertyReferences?: FigmaRestSpec.IsLayerTrait[\"componentPropertyReferences\"] & {\n slotContentId?: string;\n };\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 | NormalizedSlotNode\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 NormalizedSlotNode,\n NormalizedTextNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport { appendSource, createElement, stringifyElement, type ElementNode } from \"../core/jsx\";\nimport type { ElementTransformer } from \"./element-transformer\";\nimport { applyInferredLayout, inferLayout } from \"./infer-layout\";\nimport { pascalCase } from \"change-case\";\n\nexport interface CodeGeneratorDeps {\n frameTransformer: ElementTransformer<\n NormalizedFrameNode | NormalizedComponentNode | NormalizedInstanceNode\n >;\n slotTransformer: ElementTransformer<NormalizedSlotNode>;\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 slotTransformer,\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: \"SLOT\" }, (node) => slotTransformer(node, traverse))\n .with({ type: \"INSTANCE\" }, (node) => instanceTransformer(node, traverse))\n .with({ type: \"VECTOR\" }, (node) => vectorTransformer(node, traverse))\n .with({ type: \"BOOLEAN_OPERATION\" }, (node) => booleanOperationTransformer(node, traverse))\n .with({ type: \"UNHANDLED\" }, (node) =>\n createElement(`Unhandled${pascalCase(node.original.type)}Node`),\n )\n .exhaustive();\n\n if (result) {\n return appendSource(result, node.id);\n }\n\n return;\n }\n\n function generateJsxTree(node: NormalizedSceneNode) {\n return traverse(node);\n }\n\n function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {\n if (isSkippedInstance(node)) {\n return { imports: \"\", jsx: \"// This component is intentionally excluded from codegen\" };\n }\n\n const jsxTree = generateJsxTree(node);\n\n if (!jsxTree) {\n return undefined;\n }\n\n return stringifyElement(jsxTree, { printSource: options.shouldPrintSource });\n }\n\n return { generateJsxTree, generateCode };\n}\n","import type { NormalizedInstanceNode, NormalizedSceneNode } from \"@/normalizer\";\nimport type { ElementNode } from \"./jsx\";\n\nexport interface ComponentHandler<\n T extends\n NormalizedInstanceNode[\"componentProperties\"] = NormalizedInstanceNode[\"componentProperties\"],\n> {\n key: string;\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode;\n}\n\nexport function defineComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n key: string,\n transform: (\n node: Omit<NormalizedInstanceNode, \"componentProperties\"> & { componentProperties: T },\n traverse: (node: NormalizedSceneNode) => ElementNode | undefined,\n ) => ElementNode,\n): ComponentHandler<T> {\n return { key, transform };\n}\n","import type { VariableValueResolved } from \"@/entities\";\nimport { objectEntries } from \"@/utils/common\";\n\nexport type PropsConverter<\n T extends Record<string, any> = Record<string, any>,\n R extends Record<string, any> = Record<string, any>,\n> = (node: T) => R;\n\nexport function definePropsConverter<T extends Record<string, any>, R extends Record<string, any>>(\n converter: PropsConverter<T, R>,\n) {\n return converter;\n}\n\ntype Handlers<\n TTrait extends Record<string, VariableValueResolved>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps = keyof TProps,\n> = {\n [K in HandlerKeys]: (node: TTrait) => TProps[K];\n};\n\ntype Shorthands<TProps extends Record<string, any>, HandlerKeys extends keyof TProps> = Record<\n Exclude<keyof TProps, HandlerKeys>,\n HandlerKeys[]\n>;\n\nexport interface CreatePropsConverterConfig<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n> {\n _types: {\n trait: TTrait;\n props: TProps;\n };\n handlers: Handlers<TTrait, TProps, HandlerKeys>;\n shorthands?: Shorthands<TProps, HandlerKeys>;\n defaults?: Partial<TProps>;\n}\n\nexport function createPropsConverter<\n TTrait extends Record<string, any>,\n TProps extends Record<string, any>,\n HandlerKeys extends keyof TProps,\n>({\n handlers,\n shorthands,\n defaults,\n}: CreatePropsConverterConfig<TTrait, TProps, HandlerKeys>): PropsConverter<TTrait, TProps> {\n return definePropsConverter((node: TTrait) => {\n const result = {} as TProps;\n\n for (const [prop, handler] of objectEntries(handlers)) {\n const value = handler(node);\n if (value !== undefined && (!defaults || value !== defaults[prop as keyof TProps])) {\n result[prop as keyof TProps] = value as any;\n }\n }\n\n if (shorthands) {\n for (const [shorthand, props] of objectEntries(shorthands)) {\n const values = props.map((prop) => result[prop as keyof TProps]);\n const allDefined = values.every((value) => value !== undefined);\n const allEqual = allDefined && values.every((value) => value === values[0]);\n\n if (allEqual && values[0] !== undefined) {\n result[shorthand as keyof TProps] = values[0] as any;\n for (const prop of props) {\n delete result[prop as keyof TProps];\n }\n }\n }\n }\n\n return result;\n });\n}\n","import type { StyleService, VariableValueResolved } from \"@/entities\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport {\n getFirstFillVariable,\n getFirstSolidFill,\n getFirstStroke,\n getFirstStrokeVariable,\n} from \"@/utils/figma-node\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport type { VariableService } from \"../../entities/variable.service\";\n\nexport interface ValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n getFormattedValue: {\n frameFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | TGradient | undefined;\n shapeFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n textFill: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n stroke: (\n node: NormalizedHasGeometryTrait & NormalizedIsLayerTrait,\n ) => string | TColor | undefined;\n width: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n height: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n minHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxWidth: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n maxHeight: (\n node: NormalizedHasLayoutTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingLeft: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingRight: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingTop: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n paddingBottom: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n itemSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n counterAxisSpacing: (\n node: NormalizedHasFramePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n topRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomLeftRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n bottomRightRadius: (\n node: NormalizedCornerTrait & NormalizedIsLayerTrait,\n ) => string | TDimension | undefined;\n fontSize: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n fontWeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontWeight | undefined;\n lineHeight: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | TFontDimension | undefined;\n boxShadow: (node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) => string | undefined;\n };\n getTextStyleValue: (\n node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait,\n ) => string | undefined; // TODO: we might turn this into a generic; not sure yet\n getEffectStyleValue: (\n node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait,\n ) => string | undefined;\n}\n\nexport interface ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight> {\n variableService: VariableService;\n variableNameFormatter: (props: { slug: string[] }) => string;\n styleService: StyleService;\n textStyleNameFormatter: (props: { slug: string[] }) => string;\n effectStyleNameFormatter: (props: { slug: string[] }) => string;\n fillStyleResolver: (props: { slug: string[] }) => TGradient | undefined;\n rawValueFormatters: {\n color: (value: RGBA) => string | TColor;\n dimension: (value: number) => string | TDimension;\n fontDimension: (value: number) => string | TFontDimension;\n fontWeight: (value: number) => string | TFontWeight;\n boxShadow: (value: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n }) => string;\n };\n shouldInferVariableName: boolean;\n}\n\nexport function createValueResolver<TColor, TGradient, TDimension, TFontDimension, TFontWeight>({\n variableService,\n variableNameFormatter,\n styleService,\n textStyleNameFormatter,\n effectStyleNameFormatter,\n fillStyleResolver,\n rawValueFormatters,\n shouldInferVariableName,\n}: ValueResolverDeps<TColor, TGradient, TDimension, TFontDimension, TFontWeight>): ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n> {\n function getVariableName(key: string) {\n const slug = variableService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return variableNameFormatter({ slug });\n }\n\n function inferVariableName(value: VariableValueResolved, scope: VariableScope) {\n if (!shouldInferVariableName) {\n return undefined;\n }\n\n try {\n const inferred = variableService.infer(value, scope);\n\n if (!inferred) {\n return undefined;\n }\n\n return getVariableName(inferred.key);\n } catch {\n return undefined;\n }\n }\n\n function processColor(\n id: string | undefined,\n value: RGBA | undefined,\n scope: \"FRAME_FILL\" | \"SHAPE_FILL\" | \"STROKE_COLOR\" | \"TEXT_FILL\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.color(value);\n }\n\n return undefined;\n }\n\n function processFillStyle(key: string) {\n const slug = styleService.getSlug(key);\n\n if (!slug) {\n return undefined;\n }\n\n return fillStyleResolver({ slug });\n }\n\n function processDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"WIDTH_HEIGHT\" | \"GAP\" | \"CORNER_RADIUS\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.dimension(value);\n }\n\n return undefined;\n }\n\n function processFontDimension(\n id: string | undefined,\n value: number | undefined,\n scope: \"FONT_SIZE\" | \"LINE_HEIGHT\",\n ) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n return inferVariableName(value, scope) ?? rawValueFormatters.fontDimension(value);\n }\n\n return undefined;\n }\n\n function processFontWeight(id: string | undefined, value: number | undefined) {\n if (id) {\n return getVariableName(id);\n }\n\n if (value !== undefined) {\n const fontWeightToString: Record<number, string> = {\n 100: \"thin\",\n 200: \"extra-light\",\n 300: \"light\",\n 400: \"regular\",\n 500: \"medium\",\n 600: \"semi-bold\",\n 700: \"bold\",\n 800: \"extra-bold\",\n 900: \"black\",\n };\n\n return (\n inferVariableName(value, \"FONT_WEIGHT\") ??\n inferVariableName(fontWeightToString[value], \"FONT_STYLE\") ??\n rawValueFormatters.fontWeight(value)\n );\n }\n\n return undefined;\n }\n\n const getFormattedValue: ValueResolver<\n TColor,\n TGradient,\n TDimension,\n TFontDimension,\n TFontWeight\n >[\"getFormattedValue\"] = {\n width: (node) =>\n processDimension(\n node.boundVariables?.size?.x?.id,\n node.absoluteBoundingBox?.width,\n \"WIDTH_HEIGHT\",\n ),\n height: (node) =>\n processDimension(\n node.boundVariables?.size?.y?.id,\n node.absoluteBoundingBox?.height,\n \"WIDTH_HEIGHT\",\n ),\n minWidth: (node) =>\n processDimension(node.boundVariables?.minWidth?.id, node.minWidth, \"WIDTH_HEIGHT\"),\n minHeight: (node) =>\n processDimension(node.boundVariables?.minHeight?.id, node.minHeight, \"WIDTH_HEIGHT\"),\n maxWidth: (node) =>\n processDimension(node.boundVariables?.maxWidth?.id, node.maxWidth, \"WIDTH_HEIGHT\"),\n maxHeight: (node) =>\n processDimension(node.boundVariables?.maxHeight?.id, node.maxHeight, \"WIDTH_HEIGHT\"),\n paddingLeft: (node) =>\n processDimension(node.boundVariables?.paddingLeft?.id, node.paddingLeft, \"GAP\"),\n paddingRight: (node) =>\n processDimension(node.boundVariables?.paddingRight?.id, node.paddingRight, \"GAP\"),\n paddingTop: (node) =>\n processDimension(node.boundVariables?.paddingTop?.id, node.paddingTop, \"GAP\"),\n paddingBottom: (node) =>\n processDimension(node.boundVariables?.paddingBottom?.id, node.paddingBottom, \"GAP\"),\n itemSpacing: (node) =>\n processDimension(node.boundVariables?.itemSpacing?.id, node.itemSpacing, \"GAP\"),\n counterAxisSpacing: (node) =>\n processDimension(node.boundVariables?.counterAxisSpacing?.id, node.counterAxisSpacing, \"GAP\"),\n frameFill: (node) =>\n node.fillStyleKey\n ? processFillStyle(node.fillStyleKey)\n : processColor(\n getFirstFillVariable(node)?.id,\n getFirstSolidFill(node)?.color,\n \"FRAME_FILL\",\n ),\n shapeFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"SHAPE_FILL\"),\n textFill: (node) =>\n processColor(getFirstFillVariable(node)?.id, getFirstSolidFill(node)?.color, \"TEXT_FILL\"),\n stroke: (node) =>\n processColor(getFirstStrokeVariable(node)?.id, getFirstStroke(node)?.color, \"STROKE_COLOR\"),\n topLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.topLeftRadius?.id,\n node.rectangleCornerRadii?.[0] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n topRightRadius: (node) =>\n processDimension(\n node.boundVariables?.topRightRadius?.id,\n node.rectangleCornerRadii?.[1] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomLeftRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomLeftRadius?.id,\n node.rectangleCornerRadii?.[2] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n bottomRightRadius: (node) =>\n processDimension(\n node.boundVariables?.bottomRightRadius?.id,\n node.rectangleCornerRadii?.[3] ?? node.cornerRadius,\n \"CORNER_RADIUS\",\n ),\n fontSize: (node) =>\n processFontDimension(\n node.boundVariables?.fontSize?.[0]?.id,\n node.style.fontSize,\n \"FONT_SIZE\",\n ),\n fontWeight: (node) =>\n processFontWeight(node.boundVariables?.fontWeight?.[0]?.id, node.style.fontWeight),\n lineHeight: (node) =>\n processFontDimension(\n node.boundVariables?.lineHeight?.[0]?.id,\n node.style.lineHeightPx,\n \"LINE_HEIGHT\",\n ),\n boxShadow: (node) => {\n if (node.effects.length === 0) return undefined;\n\n return node.effects.map(rawValueFormatters.boxShadow).join(\", \");\n },\n };\n\n function getTextStyleValue(node: NormalizedTypePropertiesTrait & NormalizedIsLayerTrait) {\n if (!node.textStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.textStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return textStyleNameFormatter({ slug });\n }\n\n function getEffectStyleValue(node: NormalizedHasEffectsTrait & NormalizedIsLayerTrait) {\n if (!node.effectStyleKey) return undefined;\n\n const slug = styleService.getSlug(node.effectStyleKey);\n\n if (!slug) {\n return undefined;\n }\n\n return effectStyleNameFormatter({ slug });\n }\n\n return {\n getFormattedValue,\n getTextStyleValue,\n getEffectStyleValue,\n };\n}\n","import type { ValueResolver } from \"@/codegen/core\";\nimport { camelCasePreserveUnderscoreBetweenNumbers } from \"@/utils/common\";\nimport { toCssPixel, toCssRgba } from \"@/utils/css\";\nimport type { RGBA } from \"@figma/rest-api-spec\";\nimport { camelCase } from \"change-case\";\n\nexport type ReactValueResolver = ValueResolver<\n string,\n { value: string; direction?: string },\n string,\n string,\n number\n>;\n\nexport const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>\n slug\n .filter(\n (s) =>\n !(\n s === \"dimension\" ||\n s === \"radius\" ||\n s === \"font-size\" ||\n s === \"font-weight\" ||\n s === \"line-height\"\n ),\n )\n .map((s) => s.replaceAll(\",\", \"_\"))\n .map(camelCasePreserveUnderscoreBetweenNumbers)\n .join(\".\");\n\nexport const defaultTextStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultEffectStyleNameFormatter = ({ slug }: { slug: string[] }) => {\n return camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });\n};\n\nexport const defaultFillStyleResolver = ({ slug }: { slug: string[] }) => {\n const [, ...rest] = slug;\n\n if (rest[0] === \"fade\") {\n // [\"fade\", \"layer-default\", \"↓(to-bottom)\"]\n\n const last = rest[rest.length - 1];\n\n const direction = (() => {\n if (last.startsWith(\"↓\")) return \"to bottom\";\n if (last.startsWith(\"↑\")) return \"to top\";\n if (last.startsWith(\"→\")) return \"to right\";\n if (last.startsWith(\"←\")) return \"to left\";\n\n return \"unknown\";\n })();\n\n return {\n value: camelCase(rest.slice(0, -1).join(\"-\"), { mergeAmbiguousCharacters: true }),\n direction,\n };\n }\n\n return {\n value: camelCase(rest.join(\"-\"), { mergeAmbiguousCharacters: true }),\n };\n};\n\nfunction formatBoxShadow({\n type,\n color,\n offset,\n radius,\n spread,\n}: {\n type: \"DROP_SHADOW\" | \"INNER_SHADOW\";\n color: RGBA;\n offset: { x: number; y: number };\n radius: number;\n spread?: number;\n}): string {\n const inset = type === \"INNER_SHADOW\" ? \"inset \" : \"\";\n const colorStr = toCssRgba(color);\n const spreadStr = spread ? ` ${spread}px` : \"\";\n\n return `${inset}${offset.x}px ${offset.y}px ${radius}px${spreadStr} ${colorStr}`;\n}\n\nexport const defaultRawValueFormatters = {\n color: (value: RGBA) => toCssRgba(value),\n dimension: (value: number) => toCssPixel(value),\n fontDimension: (value: number) => toCssPixel(value),\n fontWeight: (value: number) => value,\n boxShadow: formatBoxShadow,\n};\n","import { createPropsConverter, definePropsConverter, type PropsConverter } from \"@/codegen/core\";\nimport type {\n NormalizedCornerTrait,\n NormalizedHasChildrenTrait,\n NormalizedHasEffectsTrait,\n NormalizedHasFramePropertiesTrait,\n NormalizedHasGeometryTrait,\n NormalizedHasLayoutTrait,\n NormalizedIsLayerTrait,\n NormalizedTypePropertiesTrait,\n} from \"@/normalizer\";\nimport { match } from \"ts-pattern\";\nimport type { ReactValueResolver } from \"./value-resolver\";\n\nexport interface PropsConverters {\n containerLayout: PropsConverter<ContainerLayoutTrait, ContainerLayoutProps>;\n selfLayout: PropsConverter<SelfLayoutTrait, SelfLayoutProps>;\n iconSelfLayout: PropsConverter<SelfLayoutTrait, IconSelfLayoutProps>;\n radius: PropsConverter<RadiusTrait, RadiusProps>;\n frameFill: PropsConverter<FillTrait, FrameFillProps>;\n shapeFill: PropsConverter<FillTrait, ShapeFillProps>;\n textFill: PropsConverter<FillTrait, TextFillProps>;\n vectorChildrenFill: PropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>;\n stroke: PropsConverter<StrokeTrait, StrokeProps>;\n shadow: PropsConverter<ShadowTrait, ShadowProps>;\n typeStyle: PropsConverter<TypeStyleTrait, TypeStyleProps>;\n}\n\nexport type ContainerLayoutTrait = NormalizedHasFramePropertiesTrait &\n NormalizedHasChildrenTrait &\n NormalizedHasLayoutTrait &\n NormalizedIsLayerTrait;\n\nexport type SelfLayoutTrait = NormalizedIsLayerTrait & NormalizedHasLayoutTrait;\n\nexport type RadiusTrait = NormalizedCornerTrait & NormalizedIsLayerTrait;\n\nexport type FillTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type StrokeTrait = NormalizedIsLayerTrait & NormalizedHasGeometryTrait;\n\nexport type ShadowTrait = NormalizedIsLayerTrait & NormalizedHasEffectsTrait;\n\nexport type TypeStyleTrait = NormalizedTypePropertiesTrait & NormalizedIsLayerTrait;\n\nexport interface ContainerLayoutProps {\n direction?: \"row\" | \"column\";\n justify?: \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\";\n align?: \"stretch\" | \"flex-start\" | \"center\" | \"flex-end\" | \"baseline\";\n wrap?: \"wrap\" | \"nowrap\" | true;\n gap?: string | 0;\n pb?: string | 0;\n pl?: string | 0;\n pr?: string | 0;\n pt?: string | 0;\n px?: string | 0;\n py?: string | 0;\n p?: string | 0;\n}\n\nexport function createContainerLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ContainerLayoutTrait, ContainerLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as ContainerLayoutTrait,\n props: {} as ContainerLayoutProps,\n },\n handlers: {\n direction: ({ layoutMode }) =>\n match(layoutMode)\n .with(\"HORIZONTAL\", () => \"row\" as const)\n .with(\"VERTICAL\", () => \"column\" as const)\n .with(\"GRID\", () => undefined)\n .with(\"NONE\", () => undefined)\n .with(undefined, () => undefined)\n .exhaustive(),\n justify: ({ primaryAxisAlignItems }) =>\n match(primaryAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"SPACE_BETWEEN\", () => \"space-between\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n align: ({ counterAxisAlignItems, children }) => {\n const isStretch = children.every((child) => {\n if (!(\"layoutAlign\" in child)) {\n return false;\n }\n\n return child.layoutAlign === \"STRETCH\";\n });\n\n if (isStretch) {\n return \"stretch\";\n }\n\n return match(counterAxisAlignItems)\n .with(\"MIN\", () => \"flex-start\" as const)\n .with(\"CENTER\", () => \"center\" as const)\n .with(\"MAX\", () => \"flex-end\" as const)\n .with(\"BASELINE\", () => \"baseline\" as const)\n .with(undefined, () => undefined)\n .exhaustive();\n },\n wrap: ({ layoutWrap }) =>\n match(layoutWrap)\n .with(\"WRAP\", () => true as const)\n .with(\"NO_WRAP\", () => \"nowrap\" as const)\n .with(undefined, () => undefined)\n .exhaustive(),\n gap: (node) => {\n if (node.children.length <= 1) {\n return undefined;\n }\n\n if (node.primaryAxisAlignItems === \"SPACE_BETWEEN\") {\n return undefined;\n }\n\n return valueResolver.getFormattedValue.itemSpacing(node);\n },\n pt: (node) => valueResolver.getFormattedValue.paddingTop(node),\n pb: (node) => valueResolver.getFormattedValue.paddingBottom(node),\n pl: (node) => valueResolver.getFormattedValue.paddingLeft(node),\n pr: (node) => valueResolver.getFormattedValue.paddingRight(node),\n },\n shorthands: {\n p: [\"pt\", \"pb\", \"pl\", \"pr\"],\n px: [\"pl\", \"pr\"],\n py: [\"pt\", \"pb\"],\n },\n defaults: {\n justify: \"flex-start\",\n align: \"stretch\",\n wrap: \"nowrap\",\n gap: \"0px\",\n p: \"0px\",\n px: \"0px\",\n py: \"0px\",\n pb: \"0px\",\n pl: \"0px\",\n pr: \"0px\",\n pt: \"0px\",\n },\n });\n}\n\nexport interface SelfLayoutProps {\n flexGrow?: 0 | 1 | true;\n alignSelf?: \"stretch\";\n width?: string | number;\n height?: string | number;\n minWidth?: string | number;\n minHeight?: string | number;\n maxWidth?: string | number;\n maxHeight?: string | number;\n}\n\nexport function createSelfLayoutPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<SelfLayoutTrait, SelfLayoutProps> {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as SelfLayoutProps,\n },\n handlers: {\n flexGrow: ({ layoutGrow }) => (layoutGrow === 1 ? true : layoutGrow),\n alignSelf: ({ layoutAlign }) =>\n match(layoutAlign)\n .with(\"STRETCH\", () => \"stretch\" as const)\n .with(\"INHERIT\", () => undefined)\n .with(\"MIN\", () => undefined) // Deprecated in Figma\n .with(\"CENTER\", () => undefined) // Deprecated in Figma\n .with(\"MAX\", () => undefined) // Deprecated in Figma\n .with(undefined, () => undefined)\n .exhaustive(),\n height: (node) =>\n node.layoutSizingVertical === \"FIXED\"\n ? valueResolver.getFormattedValue.height(node)\n : undefined,\n width: (node) =>\n node.layoutSizingHorizontal === \"FIXED\"\n ? valueResolver.getFormattedValue.width(node)\n : undefined,\n minHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.minHeight(node)\n : undefined,\n maxHeight: (node) =>\n node.layoutSizingVertical === \"HUG\"\n ? valueResolver.getFormattedValue.maxHeight(node)\n : undefined,\n minWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.minWidth(node)\n : undefined,\n maxWidth: (node) =>\n node.layoutSizingHorizontal === \"HUG\"\n ? valueResolver.getFormattedValue.maxWidth(node)\n : undefined,\n },\n defaults: {\n flexGrow: 0,\n },\n });\n}\n\nexport interface IconSelfLayoutProps {\n size?: string | number;\n}\n\nexport function createIconSelfLayoutPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as SelfLayoutTrait,\n props: {} as IconSelfLayoutProps,\n },\n handlers: {\n size: (node) => valueResolver.getFormattedValue.width(node),\n },\n });\n}\n\nexport interface RadiusProps {\n borderRadius?: string | 0;\n borderTopLeftRadius?: string | 0;\n borderTopRightRadius?: string | 0;\n borderBottomLeftRadius?: string | 0;\n borderBottomRightRadius?: string | 0;\n}\n\nexport function createRadiusPropsConverter(valueResolver: ReactValueResolver) {\n return createPropsConverter({\n _types: {\n trait: {} as RadiusTrait,\n props: {} as RadiusProps,\n },\n handlers: {\n borderTopLeftRadius: (node) => valueResolver.getFormattedValue.topLeftRadius(node),\n borderTopRightRadius: (node) => valueResolver.getFormattedValue.topRightRadius(node),\n borderBottomLeftRadius: (node) => valueResolver.getFormattedValue.bottomLeftRadius(node),\n borderBottomRightRadius: (node) => valueResolver.getFormattedValue.bottomRightRadius(node),\n },\n shorthands: {\n borderRadius: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n defaults: {\n borderRadius: \"0px\",\n borderTopLeftRadius: \"0px\",\n borderTopRightRadius: \"0px\",\n borderBottomLeftRadius: \"0px\",\n borderBottomRightRadius: \"0px\",\n },\n });\n}\n\nexport interface TypeStyleProps {\n textStyle?: string;\n fontSize?: string;\n fontWeight?: string | number;\n lineHeight?: string;\n maxLines?: number;\n}\n\nexport function createTypeStylePropsConverter({\n valueResolver,\n}: {\n valueResolver: ReactValueResolver;\n}): PropsConverter<TypeStyleTrait, TypeStyleProps> {\n return definePropsConverter((node) => {\n const styleName = valueResolver.getTextStyleValue(node);\n const maxLines =\n node.style.textTruncation === \"ENDING\" ? (node.style.maxLines ?? undefined) : undefined;\n\n if (styleName) {\n return {\n textStyle: styleName,\n maxLines,\n };\n }\n\n return {\n fontSize: valueResolver.getFormattedValue.fontSize(node),\n fontWeight: valueResolver.getFormattedValue.fontWeight(node),\n lineHeight: valueResolver.getFormattedValue.lineHeight(node),\n maxLines,\n };\n });\n}\n\nexport type FrameFillProps =\n | { bg?: string | undefined; bgGradient?: never; bgGradientDirection?: never }\n | { bg?: never; bgGradient: string; bgGradientDirection?: string };\n\nexport function createFrameFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, FrameFillProps>((node) => {\n const bg = valueResolver.getFormattedValue.frameFill(node);\n\n if (bg === undefined || typeof bg === \"string\") {\n return {\n bg,\n };\n }\n\n return {\n bgGradient: bg.value,\n ...(bg.direction && { bgGradientDirection: bg.direction }),\n };\n });\n}\n\nexport interface ShapeFillProps {\n color?: string;\n}\n\nexport function createShapeFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, ShapeFillProps>((node) => {\n const color = valueResolver.getFormattedValue.shapeFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface TextFillProps {\n color?: string;\n}\n\nexport function createTextFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<FillTrait, TextFillProps>((node) => {\n const color = valueResolver.getFormattedValue.textFill(node);\n\n return {\n color,\n };\n });\n}\n\nexport interface VectorChildrenFillProps {\n color?: string;\n}\n\nexport function createVectorChildrenFillPropsConverter(valueResolver: ReactValueResolver) {\n return definePropsConverter<ContainerLayoutTrait, VectorChildrenFillProps>((node) => {\n if (node.children.length === 0) {\n console.warn(\n `createVectorChildrenFillPropsConverter: Node has no children. Name:${node.name}, ID:${node.id}`,\n );\n return {};\n }\n\n const vectors = node.children.filter(\n (child) => child.type === \"VECTOR\" || child.type === \"BOOLEAN_OPERATION\",\n );\n\n const colors = vectors.map((vector) => valueResolver.getFormattedValue.shapeFill(vector));\n\n const fills = new Set(colors.filter((color) => color !== undefined));\n\n // If there are more than 1 color, colors are likely pre-defined in the icon component; we should ignore the color prop.\n if (fills.size > 1) {\n return {};\n }\n\n return { color: fills.values().next().value };\n });\n}\n\nexport interface StrokeProps {\n borderWidth?: string;\n borderColor?: string;\n}\n\nexport function createStrokePropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<StrokeTrait, StrokeProps> {\n return definePropsConverter((node) => {\n const borderColor = valueResolver.getFormattedValue.stroke(node);\n const borderWidth = borderColor && node.strokeWeight ? `${node.strokeWeight}` : undefined;\n\n return {\n borderColor,\n borderWidth,\n };\n });\n}\n\nexport interface ShadowProps {\n boxShadow?: string;\n}\n\nexport function createShadowPropsConverter(\n valueResolver: ReactValueResolver,\n): PropsConverter<ShadowTrait, ShadowProps> {\n return definePropsConverter((node: ShadowTrait) => {\n const effectStyleName = valueResolver.getEffectStyleValue(node);\n if (effectStyleName) {\n return {\n boxShadow: effectStyleName,\n };\n }\n\n const boxShadow = valueResolver.getFormattedValue.boxShadow(node);\n return {\n boxShadow,\n };\n });\n}\n","import type {\n NormalizedBooleanOperationNode,\n NormalizedRectangleNode,\n NormalizedVectorNode,\n} from \"@/normalizer\";\nimport { createElement, defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface RectangleTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createRectangleTransformer({\n propsConverters,\n}: RectangleTransformerDeps): ElementTransformer<NormalizedRectangleNode> {\n return defineElementTransformer((node: NormalizedRectangleNode) => {\n return createSeedReactElement(\n \"Box\",\n {\n ...propsConverters.selfLayout(node),\n ...propsConverters.shadow(node),\n background: \"palette.gray200\",\n },\n undefined,\n {\n comment: \"Rectangle Node Placeholder\",\n },\n );\n });\n}\n\nexport function createVectorTransformer(): ElementTransformer<NormalizedVectorNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Vector Node Placeholder\",\n });\n });\n}\n\nexport function createBooleanOperationTransformer(): ElementTransformer<NormalizedBooleanOperationNode> {\n return defineElementTransformer(() => {\n return createElement(\"svg\", {}, [], {\n comment: \"Boolean Operation Node Placeholder\",\n });\n });\n}\n","import type {\n NormalizedComponentNode,\n NormalizedFrameNode,\n NormalizedInstanceNode,\n} from \"@/normalizer\";\nimport {\n cloneElement,\n createElement,\n defineElementTransformer,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { ContainerLayoutProps, PropsConverters } from \"./props\";\n\nexport interface FrameTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createFrameTransformer({\n propsConverters,\n}: FrameTransformerDeps): ElementTransformer<\n NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode\n> {\n function inferLayoutComponent(props: ContainerLayoutProps, isFlex: boolean) {\n if (!isFlex) {\n return \"Box\";\n }\n\n if (props.direction === \"column\") {\n return \"VStack\";\n }\n\n return \"HStack\";\n }\n\n return defineElementTransformer(\n (node: NormalizedFrameNode | NormalizedInstanceNode | NormalizedComponentNode, traverse) => {\n const children = node.children;\n const transformedChildren = children.map(traverse);\n const isFlex = node.layoutMode === \"HORIZONTAL\" || node.layoutMode === \"VERTICAL\";\n\n const props = {\n ...propsConverters.radius(node),\n ...(isFlex ? propsConverters.containerLayout(node) : {}),\n ...propsConverters.selfLayout(node),\n ...propsConverters.frameFill(node),\n ...propsConverters.stroke(node),\n ...propsConverters.shadow(node),\n };\n\n const isStretch = props.align === undefined || props.align === \"stretch\";\n\n const layoutComponent = inferLayoutComponent(props, isFlex);\n\n const hasSpacingMismatch =\n node.layoutWrap === \"WRAP\" &&\n node.counterAxisSpacing !== undefined &&\n node.itemSpacing !== node.counterAxisSpacing;\n\n const hasImageFill = node.fills.some(({ type }) => type === \"IMAGE\");\n const imgElement = hasImageFill\n ? createElement(\"img\", {\n src: `https://placehold.co/${node.absoluteBoundingBox?.width ?? 100}x${node.absoluteBoundingBox?.height ?? 100}`,\n })\n : undefined;\n\n const processedChildren = [\n imgElement,\n ...(isStretch\n ? transformedChildren.map((child) =>\n child ? cloneElement(child, { alignSelf: undefined }) : child,\n )\n : transformedChildren),\n ];\n\n const comment = [\n hasSpacingMismatch &&\n // currently counterAxisSpacing is only supported when direction=row\n `row-gap과 column-gap이 다릅니다. (row-gap: ${node.counterAxisSpacing}, column-gap: ${node.itemSpacing})`,\n ]\n .filter((cmt) => cmt)\n .join(\" \");\n\n switch (layoutComponent) {\n case \"VStack\":\n case \"HStack\": {\n const { direction: _direction, ...rest } = props;\n\n return createSeedReactElement(layoutComponent, rest, processedChildren, { comment });\n }\n case \"Box\":\n return createSeedReactElement(\"Box\", props, processedChildren, { comment });\n }\n },\n );\n}\n","import type { IconService } from \"@/entities\";\nimport { pascalCase } from \"change-case\";\nimport { type ElementNode, createElement } from \"../../core\";\nimport { createMonochromeIconElement, createMulticolorIconElement } from \"./element-factories\";\n\nexport interface IconHandler {\n isIconInstance: (node: { componentKey: string }) => boolean;\n transform: (node: { componentKey: string }) => ElementNode;\n}\n\nexport interface IconHandlerDeps {\n iconService: IconService;\n iconNameFormatter?: (props: { name: string; weight?: string }) => string;\n}\n\nconst defaultIconNameFormatter = ({ name, weight }: { name: string; weight?: string }) =>\n pascalCase(`${name}${weight ? weight : \"\"}`);\n\nexport function createIconHandler({\n iconService,\n iconNameFormatter = defaultIconNameFormatter,\n}: IconHandlerDeps): IconHandler {\n function isIconInstance(node: { componentKey: string }): boolean {\n const key = node.componentKey;\n\n if (!key) {\n return false;\n }\n\n return iconService.isAvailable(key);\n }\n\n function transform(node: { componentKey: string }): ElementNode {\n const key = node.componentKey;\n const iconData = iconService.getOne(key);\n if (!iconData) {\n return createElement(\"UnknownIcon\");\n }\n\n const { name, weight, type } = iconData;\n\n const tagName = iconNameFormatter({ name, weight });\n\n if (type === \"multicolor\") {\n return createMulticolorIconElement(tagName);\n }\n\n return createMonochromeIconElement(tagName);\n }\n\n return {\n isIconInstance,\n transform,\n };\n}\n","import type { NormalizedInstanceNode } from \"@/normalizer\";\nimport {\n defineElementTransformer,\n type ComponentHandler,\n type ElementTransformer,\n} from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { IconHandler } from \"./icon\";\nimport type { PropsConverters } from \"./props\";\n\nconst OVERRIDE_ACCEPTABLE_PROPERTIES: Set<NodeChangeProperty> = new Set([\n \"characters\",\n \"parent\",\n \"locked\",\n \"visible\",\n \"name\",\n \"x\",\n \"y\",\n \"componentProperties\",\n \"componentPropertyDefinitions\",\n \"componentPropertyReferences\",\n] satisfies NodeChangeProperty[]);\n\nexport interface InstanceTransformerDeps {\n iconHandler?: IconHandler;\n propsConverters: PropsConverters;\n componentHandlers: Record<string, ComponentHandler>;\n frameTransformer: ElementTransformer<NormalizedInstanceNode>;\n}\n\nexport function createInstanceTransformer({\n iconHandler,\n propsConverters,\n componentHandlers,\n frameTransformer,\n}: InstanceTransformerDeps): ElementTransformer<NormalizedInstanceNode> {\n const transform = defineElementTransformer((node: NormalizedInstanceNode, traverse) => {\n const { componentKey, componentSetKey } = node;\n\n if (iconHandler?.isIconInstance(node)) {\n const props = {\n ...propsConverters.iconSelfLayout(node),\n ...propsConverters.vectorChildrenFill(node),\n };\n return createSeedReactElement(\"Icon\", { svg: iconHandler.transform(node), ...props });\n }\n\n const componentHandler = componentSetKey\n ? componentHandlers[componentSetKey]\n : componentHandlers[componentKey];\n\n if (componentHandler) {\n const handled = componentHandler.transform(node, traverse);\n\n if (node.overrides && node.overrides.length > 0) {\n const overriddenFields = node.overrides\n .flatMap(({ overriddenFields }) => overriddenFields)\n .filter(\n (field) => OVERRIDE_ACCEPTABLE_PROPERTIES.has(field as NodeChangeProperty) === false,\n );\n\n if (overriddenFields.length === 0) {\n return handled;\n }\n\n return {\n ...handled,\n meta: {\n ...handled.meta,\n comment: `${handled.meta.comment ? `${handled.meta.comment} ` : \"\"}오버라이드된 필드: ${Array.from(new Set(overriddenFields)).join(\", \")}`,\n },\n };\n }\n\n return handled;\n }\n\n return frameTransformer(node, traverse);\n });\n\n return transform;\n}\n","import type { NormalizedTextNode } from \"@/normalizer\";\nimport { compactObject } from \"@/utils/common\";\nimport { defineElementTransformer, type ElementTransformer } from \"../../core\";\nimport { createSeedReactElement } from \"./element-factories\";\nimport type { PropsConverters } from \"./props\";\n\nexport interface TextTransformerDeps {\n propsConverters: PropsConverters;\n}\n\nexport function createTextTransformer({\n propsConverters,\n}: TextTransformerDeps): ElementTransformer<NormalizedTextNode> {\n return defineElementTransformer((node: NormalizedTextNode) => {\n const hasMultipleFills = node.fills.length > 1;\n\n const fillProps = propsConverters.textFill(node);\n const typeStyleProps = propsConverters.typeStyle(node);\n\n const props = compactObject({\n ...typeStyleProps,\n ...fillProps,\n });\n\n return createSeedReactElement(\"Text\", props, node.characters.replace(/\\n/g, \"<br />\"), {\n comment: hasMultipleFills\n ? \"Multiple fills in Text node encountered, only the first fill is used.\"\n : undefined,\n });\n });\n}\n","import type { IconHandler } from \"../icon\";\nimport type { ReactValueResolver } from \"../value-resolver\";\n\nexport interface ComponentHandlerDeps {\n iconHandler: IconHandler;\n valueResolver: ReactValueResolver;\n}\n","import type { ComponentHandler } from \"@/codegen/core\";\nimport type { NormalizedInstanceNode } from \"@/normalizer\";\nimport type { ComponentHandlerDeps } from \"./deps.interface\";\n\nimport * as archivedHandlers from \"./handlers/archive\";\nimport * as currentHandlers from \"./handlers\";\n\nexport type { ComponentHandlerDeps };\nexport type UnboundComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]> = (\n deps: ComponentHandlerDeps,\n) => ComponentHandler<T>;\n\nexport function bindComponentHandler<T extends NormalizedInstanceNode[\"componentProperties\"]>(\n unbound: UnboundComponentHandler<T>,\n deps: ComponentHandlerDeps,\n): ComponentHandler<T> {\n return unbound(deps);\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: handlers have different component property types\nexport const unboundSeedComponentHandlers: Array<UnboundComponentHandler<any>> = [\n ...Object.values(archivedHandlers),\n ...Object.values(currentHandlers),\n];\n","import { createCodeGenerator, createValueResolver, defineElementTransformer } from \"@/codegen/core\";\nimport type { NormalizedSlotNode } from \"@/normalizer\";\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 slotTransformer = defineElementTransformer<NormalizedSlotNode>((node, traverse) =>\n frameTransformer({ ...node, type: \"FRAME\" }, traverse),\n );\n\n const codeGenerator = createCodeGenerator({\n frameTransformer,\n slotTransformer,\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;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACO;;ACvFA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACRO;;ACYA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACpBO;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,7 +436,7 @@ function applyInferredLayout(parentNode, result) {
|
|
|
436
436
|
};
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
function createCodeGenerator({ frameTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer, shouldInferAutoLayout, skipComponentKeys }) {
|
|
439
|
+
function createCodeGenerator({ frameTransformer, slotTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer, shouldInferAutoLayout, skipComponentKeys }) {
|
|
440
440
|
function isSkippedInstance(node) {
|
|
441
441
|
if (!skipComponentKeys || skipComponentKeys.size === 0) return false;
|
|
442
442
|
if (node.type !== "INSTANCE") return false;
|
|
@@ -460,6 +460,8 @@ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTrans
|
|
|
460
460
|
type: "COMPONENT"
|
|
461
461
|
}, (node)=>frameTransformer(node, traverse)) // NOTE: Treat component node as Frame for now
|
|
462
462
|
.with({
|
|
463
|
+
type: "SLOT"
|
|
464
|
+
}, (node)=>slotTransformer(node, traverse)).with({
|
|
463
465
|
type: "INSTANCE"
|
|
464
466
|
}, (node)=>instanceTransformer(node, traverse)).with({
|
|
465
467
|
type: "VECTOR"
|
|
@@ -565,8 +567,11 @@ function findAll(node, callback) {
|
|
|
565
567
|
function findAllInstances({ node, key }) {
|
|
566
568
|
return findAll(node, (n)=>n.type === "INSTANCE" && (n.componentKey === key || n.componentSetKey === key));
|
|
567
569
|
}
|
|
570
|
+
function findSlotNode(node, slotPropertyKey) {
|
|
571
|
+
return findOne(node, (child)=>child.type === "SLOT" && child.componentPropertyReferences?.slotContentId === slotPropertyKey);
|
|
572
|
+
}
|
|
568
573
|
function getFirstSolidFill(node) {
|
|
569
|
-
const fills = node.fills.filter((fill)=>fill.type === "SOLID"
|
|
574
|
+
const fills = node.fills.filter((fill)=>fill.type === "SOLID");
|
|
570
575
|
if (fills.length === 0) {
|
|
571
576
|
return undefined;
|
|
572
577
|
}
|
|
@@ -577,7 +582,7 @@ function getFirstFillVariable(node) {
|
|
|
577
582
|
return fill?.boundVariables?.color;
|
|
578
583
|
}
|
|
579
584
|
function getFirstStroke(node) {
|
|
580
|
-
const strokes = node.strokes?.filter((stroke)=>stroke.type === "SOLID"
|
|
585
|
+
const strokes = node.strokes?.filter((stroke)=>stroke.type === "SOLID") ?? [];
|
|
581
586
|
if (strokes.length === 0) {
|
|
582
587
|
return undefined;
|
|
583
588
|
}
|
|
@@ -7215,6 +7220,7 @@ const componentAvatar = {
|
|
|
7215
7220
|
"36",
|
|
7216
7221
|
"42",
|
|
7217
7222
|
"48",
|
|
7223
|
+
"56",
|
|
7218
7224
|
"64",
|
|
7219
7225
|
"80",
|
|
7220
7226
|
"96",
|
|
@@ -7268,6 +7274,7 @@ const componentAvatarStack = {
|
|
|
7268
7274
|
"36",
|
|
7269
7275
|
"42",
|
|
7270
7276
|
"48",
|
|
7277
|
+
"56",
|
|
7271
7278
|
"64",
|
|
7272
7279
|
"80",
|
|
7273
7280
|
"96",
|
|
@@ -7360,6 +7367,9 @@ const componentBottomSheet = {
|
|
|
7360
7367
|
"name": "componentBottomSheet",
|
|
7361
7368
|
"key": "ffe99a21452831c28bd9375aac0aaf37d7ee6a0d",
|
|
7362
7369
|
"componentPropertyDefinitions": {
|
|
7370
|
+
"Contents Slot#6752:0": {
|
|
7371
|
+
"type": "SLOT"
|
|
7372
|
+
},
|
|
7363
7373
|
"Title#19787:3": {
|
|
7364
7374
|
"type": "TEXT"
|
|
7365
7375
|
},
|
|
@@ -7375,9 +7385,6 @@ const componentBottomSheet = {
|
|
|
7375
7385
|
"Show Description#25192:0": {
|
|
7376
7386
|
"type": "BOOLEAN"
|
|
7377
7387
|
},
|
|
7378
|
-
"Contents#25320:0": {
|
|
7379
|
-
"type": "INSTANCE_SWAP"
|
|
7380
|
-
},
|
|
7381
7388
|
"Show Safe Area#25488:8": {
|
|
7382
7389
|
"type": "BOOLEAN"
|
|
7383
7390
|
},
|
|
@@ -7704,6 +7711,9 @@ const componentChlid = {
|
|
|
7704
7711
|
"name": "componentChlid",
|
|
7705
7712
|
"key": "ef79a21a39ceb4ce24b2fb93c9b430c1980a3e71",
|
|
7706
7713
|
"componentPropertyDefinitions": {
|
|
7714
|
+
"Slot#6081:0": {
|
|
7715
|
+
"type": "SLOT"
|
|
7716
|
+
},
|
|
7707
7717
|
"Type": {
|
|
7708
7718
|
"type": "VARIANT",
|
|
7709
7719
|
"variantOptions": [
|
|
@@ -9391,6 +9401,9 @@ const componentSelectBoxItemHorizontal = {
|
|
|
9391
9401
|
"name": "componentSelectBoxItemHorizontal",
|
|
9392
9402
|
"key": "8174af8ef3654dad996723883f5b84f44f791513",
|
|
9393
9403
|
"componentPropertyDefinitions": {
|
|
9404
|
+
"Content Slot#6752:6": {
|
|
9405
|
+
"type": "SLOT"
|
|
9406
|
+
},
|
|
9394
9407
|
"Title#28452:21": {
|
|
9395
9408
|
"type": "TEXT"
|
|
9396
9409
|
},
|
|
@@ -9438,6 +9451,9 @@ const componentSelectBoxItemVertical = {
|
|
|
9438
9451
|
"name": "componentSelectBoxItemVertical",
|
|
9439
9452
|
"key": "ccc88f0aae500c64e7d43be63c4f1a70baf76bfe",
|
|
9440
9453
|
"componentPropertyDefinitions": {
|
|
9454
|
+
"Content Slot#6765:0": {
|
|
9455
|
+
"type": "SLOT"
|
|
9456
|
+
},
|
|
9441
9457
|
"Title#58766:114": {
|
|
9442
9458
|
"type": "TEXT"
|
|
9443
9459
|
},
|
|
@@ -10068,11 +10084,8 @@ const createBottomSheetHandler = (_ctx)=>defineComponentHandler(componentBottomS
|
|
|
10068
10084
|
showHandle: props["Show Handle#49774:6"].value,
|
|
10069
10085
|
showCloseButton: props["Show Close Button#19787:11"].value
|
|
10070
10086
|
};
|
|
10071
|
-
const
|
|
10072
|
-
|
|
10073
|
-
key: props["Contents#25320:0"].componentKey
|
|
10074
|
-
});
|
|
10075
|
-
const bottomSheetBody = bodyNodes.length === 1 ? createLocalSnippetElement$v("BottomSheetBody", {}, bodyNodes[0].children.map(traverse)) : createLocalSnippetElement$v("BottomSheetBody", {}, createElement("div", undefined, "No content available"));
|
|
10087
|
+
const slotNode = findSlotNode(node, "Contents Slot#6752:0");
|
|
10088
|
+
const bottomSheetBody = slotNode ? createLocalSnippetElement$v("BottomSheetBody", {}, traverse(slotNode)) : createLocalSnippetElement$v("BottomSheetBody", {}, createElement("div", undefined, "No content available"));
|
|
10076
10089
|
const footerNodes = findAllInstances({
|
|
10077
10090
|
node,
|
|
10078
10091
|
// TODO: Bottom Action Bar (WIP) handler의 키. 해당 컴포넌트(템플릿) 핸들러 작성 시 handler.transform()으로 대체
|
|
@@ -11343,7 +11356,7 @@ const PREFIX_KEYS = [
|
|
|
11343
11356
|
componentSelectBoxItemPrefixCustom.key
|
|
11344
11357
|
];
|
|
11345
11358
|
const { createLocalSnippetElement: createLocalSnippetElement$7 } = createLocalSnippetHelper("select-box");
|
|
11346
|
-
const createSelectBoxHorizontalHandler = (ctx)=>defineComponentHandler(componentSelectBoxItemHorizontal.key, (node)=>{
|
|
11359
|
+
const createSelectBoxHorizontalHandler = (ctx)=>defineComponentHandler(componentSelectBoxItemHorizontal.key, (node, traverse)=>{
|
|
11347
11360
|
const { componentProperties: props } = node;
|
|
11348
11361
|
const { tag, suffix } = match(props.Control.value).with("Checkmark", ()=>({
|
|
11349
11362
|
tag: "CheckSelectBox",
|
|
@@ -11375,6 +11388,8 @@ const createSelectBoxHorizontalHandler = (ctx)=>defineComponentHandler(component
|
|
|
11375
11388
|
// return undefined if not icon
|
|
11376
11389
|
return undefined;
|
|
11377
11390
|
})();
|
|
11391
|
+
const contentSlotNode = props["Show Custom Content#56903:0"].value ? findSlotNode(node, "Content Slot#6752:6") : undefined;
|
|
11392
|
+
const footer = contentSlotNode ? traverse(contentSlotNode) : undefined;
|
|
11378
11393
|
const commonProps = {
|
|
11379
11394
|
// layout: "horizontal",
|
|
11380
11395
|
...prefixIcon && {
|
|
@@ -11387,8 +11402,8 @@ const createSelectBoxHorizontalHandler = (ctx)=>defineComponentHandler(component
|
|
|
11387
11402
|
...suffix && {
|
|
11388
11403
|
suffix
|
|
11389
11404
|
},
|
|
11390
|
-
...
|
|
11391
|
-
footer
|
|
11405
|
+
...footer && {
|
|
11406
|
+
footer
|
|
11392
11407
|
},
|
|
11393
11408
|
...tag === "RadioSelectBoxItem" && {
|
|
11394
11409
|
value: props["Title#28452:21"].value
|
|
@@ -11402,7 +11417,7 @@ const createSelectBoxHorizontalHandler = (ctx)=>defineComponentHandler(component
|
|
|
11402
11417
|
};
|
|
11403
11418
|
return createLocalSnippetElement$7(tag, commonProps, undefined);
|
|
11404
11419
|
});
|
|
11405
|
-
const createSelectBoxVerticalHandler = (ctx)=>defineComponentHandler(componentSelectBoxItemVertical.key, (node)=>{
|
|
11420
|
+
const createSelectBoxVerticalHandler = (ctx)=>defineComponentHandler(componentSelectBoxItemVertical.key, (node, traverse)=>{
|
|
11406
11421
|
const { componentProperties: props } = node;
|
|
11407
11422
|
const { tag, suffix } = match(props.Control.value).with("Checkmark", ()=>({
|
|
11408
11423
|
tag: "CheckSelectBox",
|
|
@@ -11434,6 +11449,8 @@ const createSelectBoxVerticalHandler = (ctx)=>defineComponentHandler(componentSe
|
|
|
11434
11449
|
// return undefined if not icon
|
|
11435
11450
|
return undefined;
|
|
11436
11451
|
})();
|
|
11452
|
+
const contentSlotNode = props["Show Custom Content#58766:119"].value ? findSlotNode(node, "Content Slot#6765:0") : undefined;
|
|
11453
|
+
const footer = contentSlotNode ? traverse(contentSlotNode) : undefined;
|
|
11437
11454
|
const commonProps = {
|
|
11438
11455
|
// layout: "vertical",
|
|
11439
11456
|
...prefixIcon && {
|
|
@@ -11446,8 +11463,8 @@ const createSelectBoxVerticalHandler = (ctx)=>defineComponentHandler(componentSe
|
|
|
11446
11463
|
...suffix && {
|
|
11447
11464
|
suffix
|
|
11448
11465
|
},
|
|
11449
|
-
...
|
|
11450
|
-
footer
|
|
11466
|
+
...footer && {
|
|
11467
|
+
footer
|
|
11451
11468
|
},
|
|
11452
11469
|
...tag === "RadioSelectBoxItem" && {
|
|
11453
11470
|
value: props["Title#58766:114"].value
|
|
@@ -31945,8 +31962,13 @@ function createPipeline(options = {}) {
|
|
|
31945
31962
|
});
|
|
31946
31963
|
const vectorTransformer = createVectorTransformer();
|
|
31947
31964
|
const booleanOperationTransformer = createBooleanOperationTransformer();
|
|
31965
|
+
const slotTransformer = defineElementTransformer((node, traverse)=>frameTransformer({
|
|
31966
|
+
...node,
|
|
31967
|
+
type: "FRAME"
|
|
31968
|
+
}, traverse));
|
|
31948
31969
|
const codeGenerator = createCodeGenerator({
|
|
31949
31970
|
frameTransformer,
|
|
31971
|
+
slotTransformer,
|
|
31950
31972
|
textTransformer,
|
|
31951
31973
|
rectangleTransformer,
|
|
31952
31974
|
instanceTransformer,
|