@seed-design/figma 0.0.22 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,5 @@
1
1
  import { match } from 'ts-pattern';
2
2
  import { camelCase, pascalCase } from 'change-case';
3
- import { AsyncLocalStorage } from 'async_hooks';
4
3
 
5
4
  function ensureArray(maybeArray) {
6
5
  if (Array.isArray(maybeArray)) {
@@ -105,15 +104,6 @@ function stringifyElement(element, options = {}) {
105
104
  return recursive(element, 0);
106
105
  }
107
106
 
108
- const codegenOptionsContext = new AsyncLocalStorage();
109
- function useCodegenOptions() {
110
- const options = codegenOptionsContext.getStore();
111
- if (!options) {
112
- throw new Error("Trying to get codegen options outside of codegen context. Did you forget to call `codegenOptionsContext.run`?");
113
- }
114
- return options;
115
- }
116
-
117
107
  // --- Helper Functions ---
118
108
  function getCollectiveBoundingBox(nodes) {
119
109
  if (nodes.length === 0) {
@@ -421,12 +411,11 @@ function applyInferredLayout(parentNode, result) {
421
411
  };
422
412
  }
423
413
 
424
- function createCodeGenerator({ frameTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer }) {
414
+ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTransformer, instanceTransformer, vectorTransformer, booleanOperationTransformer, shouldInferAutoLayout }) {
425
415
  function traverse(node) {
426
416
  if ("visible" in node && !node.visible) {
427
417
  return;
428
418
  }
429
- const { shouldInferAutoLayout } = useCodegenOptions();
430
419
  const result = match(node).with({
431
420
  type: "FRAME"
432
421
  }, (node)=>shouldInferAutoLayout ? frameTransformer(applyInferredLayout(node, inferLayout(node)), traverse) : frameTransformer(node, traverse)).with({
@@ -450,11 +439,11 @@ function createCodeGenerator({ frameTransformer, textTransformer, rectangleTrans
450
439
  }
451
440
  return;
452
441
  }
453
- function generateJsxTree(node, options) {
454
- return codegenOptionsContext.run(options, ()=>traverse(node));
442
+ function generateJsxTree(node) {
443
+ return traverse(node);
455
444
  }
456
445
  function generateCode(node, options) {
457
- const jsxTree = generateJsxTree(node, options);
446
+ const jsxTree = generateJsxTree(node);
458
447
  return jsxTree ? stringifyElement(jsxTree, {
459
448
  printSource: options.shouldPrintSource
460
449
  }) : undefined;
@@ -554,7 +543,7 @@ function getFirstStrokeVariable(node) {
554
543
  return node.boundVariables?.strokes?.[0];
555
544
  }
556
545
 
557
- function createValueResolver({ variableService, variableNameFormatter, styleService, styleNameFormatter, rawValueFormatters }) {
546
+ function createValueResolver({ variableService, variableNameFormatter, styleService, styleNameFormatter, rawValueFormatters, shouldInferVariableName }) {
558
547
  function getVariableName(key) {
559
548
  const slug = variableService.getSlug(key);
560
549
  if (!slug) {
@@ -565,7 +554,6 @@ function createValueResolver({ variableService, variableNameFormatter, styleServ
565
554
  });
566
555
  }
567
556
  function inferVariableName(value, scope) {
568
- const { shouldInferVariableName } = useCodegenOptions();
569
557
  if (!shouldInferVariableName) {
570
558
  return undefined;
571
559
  }
@@ -12102,26 +12090,30 @@ function toCssRgba(color) {
12102
12090
  return `rgba(${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}, ${color.a})`;
12103
12091
  }
12104
12092
 
12105
- const valueResolver = createValueResolver({
12106
- variableService,
12107
- variableNameFormatter: ({ slug })=>slug.filter((s)=>!(s === "dimension" || s === "radius" || s === "font-size" || s === "font-weight" || s === "line-height")).map((s)=>s.replaceAll(",", "_")).map(camelCasePreserveUnderscoreBetweenNumbers).join("."),
12108
- styleService,
12109
- styleNameFormatter: ({ slug })=>camelCase(slug[slug.length - 1], {
12110
- mergeAmbiguousCharacters: true
12111
- }),
12112
- rawValueFormatters: {
12113
- color: (value)=>toCssRgba(value),
12114
- dimension: (value)=>toCssPixel(value),
12115
- fontDimension: (value)=>toCssPixel(value),
12116
- fontWeight: (value)=>value
12117
- }
12118
- });
12093
+ const defaultVariableNameFormatter = ({ slug })=>slug.filter((s)=>!(s === "dimension" || s === "radius" || s === "font-size" || s === "font-weight" || s === "line-height")).map((s)=>s.replaceAll(",", "_")).map(camelCasePreserveUnderscoreBetweenNumbers).join(".");
12094
+ const defaultStyleNameFormatter = ({ slug })=>camelCase(slug[slug.length - 1], {
12095
+ mergeAmbiguousCharacters: true
12096
+ });
12097
+ const defaultRawValueFormatters = {
12098
+ color: (value)=>toCssRgba(value),
12099
+ dimension: (value)=>toCssPixel(value),
12100
+ fontDimension: (value)=>toCssPixel(value),
12101
+ fontWeight: (value)=>value
12102
+ };
12119
12103
 
12120
12104
  const iconHandler = createIconHandler({
12121
12105
  iconService
12122
12106
  });
12123
12107
  function createPipeline(options = {}) {
12124
- const { extend = {} } = options;
12108
+ const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;
12109
+ const valueResolver = createValueResolver({
12110
+ variableService,
12111
+ variableNameFormatter: defaultVariableNameFormatter,
12112
+ styleService,
12113
+ styleNameFormatter: defaultStyleNameFormatter,
12114
+ rawValueFormatters: defaultRawValueFormatters,
12115
+ shouldInferVariableName
12116
+ });
12125
12117
  const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);
12126
12118
  const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);
12127
12119
  const iconSelfLayoutPropsConverter = createIconSelfLayoutPropsConverter(valueResolver);
@@ -12179,7 +12171,8 @@ function createPipeline(options = {}) {
12179
12171
  rectangleTransformer,
12180
12172
  instanceTransformer,
12181
12173
  vectorTransformer,
12182
- booleanOperationTransformer
12174
+ booleanOperationTransformer,
12175
+ shouldInferAutoLayout
12183
12176
  });
12184
12177
  return codeGenerator;
12185
12178
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/figma",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -38,7 +38,7 @@
38
38
  "lint:publish": "bun publint"
39
39
  },
40
40
  "dependencies": {
41
- "@seed-design/css": "0.0.21",
41
+ "@seed-design/css": "0.0.23",
42
42
  "change-case": "^5.4.4",
43
43
  "ts-pattern": "^5.7.0"
44
44
  },
@@ -10,7 +10,6 @@ import type {
10
10
  } from "@/normalizer";
11
11
  import { match } from "ts-pattern";
12
12
  import { appendSource, createElement, stringifyElement, type ElementNode } from "../core/jsx";
13
- import { codegenOptionsContext, useCodegenOptions, type CodegenOptions } from "./context";
14
13
  import type { ElementTransformer } from "./element-transformer";
15
14
  import { applyInferredLayout, inferLayout } from "./infer-layout";
16
15
 
@@ -23,6 +22,7 @@ export interface CodeGeneratorDeps {
23
22
  instanceTransformer: ElementTransformer<NormalizedInstanceNode>;
24
23
  vectorTransformer: ElementTransformer<NormalizedVectorNode>;
25
24
  booleanOperationTransformer: ElementTransformer<NormalizedBooleanOperationNode>;
25
+ shouldInferAutoLayout: boolean;
26
26
  }
27
27
 
28
28
  export function createCodeGenerator({
@@ -32,14 +32,13 @@ export function createCodeGenerator({
32
32
  instanceTransformer,
33
33
  vectorTransformer,
34
34
  booleanOperationTransformer,
35
+ shouldInferAutoLayout,
35
36
  }: CodeGeneratorDeps) {
36
37
  function traverse(node: NormalizedSceneNode): ElementNode | undefined {
37
38
  if ("visible" in node && !node.visible) {
38
39
  return;
39
40
  }
40
41
 
41
- const { shouldInferAutoLayout } = useCodegenOptions();
42
-
43
42
  const result = match(node)
44
43
  .with({ type: "FRAME" }, (node) =>
45
44
  shouldInferAutoLayout
@@ -62,15 +61,12 @@ export function createCodeGenerator({
62
61
  return;
63
62
  }
64
63
 
65
- function generateJsxTree(node: NormalizedSceneNode, options: CodegenOptions) {
66
- return codegenOptionsContext.run(options, () => traverse(node));
64
+ function generateJsxTree(node: NormalizedSceneNode) {
65
+ return traverse(node);
67
66
  }
68
67
 
69
- function generateCode(
70
- node: NormalizedSceneNode,
71
- options: CodegenOptions & { shouldPrintSource: boolean },
72
- ) {
73
- const jsxTree = generateJsxTree(node, options);
68
+ function generateCode(node: NormalizedSceneNode, options: { shouldPrintSource: boolean }) {
69
+ const jsxTree = generateJsxTree(node);
74
70
  return jsxTree
75
71
  ? stringifyElement(jsxTree, { printSource: options.shouldPrintSource })
76
72
  : undefined;
@@ -5,7 +5,6 @@ export type {
5
5
  InferComponentDefinition,
6
6
  InferComponentPropertyType,
7
7
  } from "./component-type-helper";
8
- export type { CodegenOptions } from "./context";
9
8
  export type { ElementTransformer } from "./element-transformer";
10
9
  export type { ElementNode } from "./jsx";
11
10
  export type { PropsConverter } from "./props-converter";
@@ -13,7 +12,6 @@ export type { ValueResolver } from "./value-resolver";
13
12
 
14
13
  export { createCodeGenerator } from "./codegen";
15
14
  export { defineComponentHandler } from "./component-handler";
16
- export { useCodegenOptions } from "./context";
17
15
  export { defineElementTransformer } from "./element-transformer";
18
16
  export { inferLayout } from "./infer-layout";
19
17
  export { cloneElement, createElement } from "./jsx";
@@ -15,7 +15,6 @@ import {
15
15
  } from "@/utils/figma-node";
16
16
  import type { RGBA } from "@figma/rest-api-spec";
17
17
  import type { VariableService } from "../../entities/variable.service";
18
- import { useCodegenOptions } from "./context";
19
18
 
20
19
  export interface ValueResolver<TColor, TDimension, TFontDimension, TFontWeight> {
21
20
  getFormattedValue: {
@@ -102,6 +101,7 @@ export interface ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeig
102
101
  fontDimension: (value: number) => string | TFontDimension;
103
102
  fontWeight: (value: number) => string | TFontWeight;
104
103
  };
104
+ shouldInferVariableName: boolean;
105
105
  }
106
106
 
107
107
  export function createValueResolver<TColor, TDimension, TFontDimension, TFontWeight>({
@@ -110,6 +110,7 @@ export function createValueResolver<TColor, TDimension, TFontDimension, TFontWei
110
110
  styleService,
111
111
  styleNameFormatter,
112
112
  rawValueFormatters,
113
+ shouldInferVariableName,
113
114
  }: ValueResolverDeps<TColor, TDimension, TFontDimension, TFontWeight>): ValueResolver<
114
115
  TColor,
115
116
  TDimension,
@@ -127,8 +128,6 @@ export function createValueResolver<TColor, TDimension, TFontDimension, TFontWei
127
128
  }
128
129
 
129
130
  function inferVariableName(value: VariableValueResolved, scope: VariableScope) {
130
- const { shouldInferVariableName } = useCodegenOptions();
131
-
132
131
  if (!shouldInferVariableName) {
133
132
  return undefined;
134
133
  }
@@ -1,4 +1,5 @@
1
- import { createCodeGenerator } from "@/codegen/core";
1
+ import { createCodeGenerator, createValueResolver } from "@/codegen/core";
2
+ import { styleService, variableService } from "@/codegen/default-services";
2
3
  import { createFrameTransformer } from "./frame";
3
4
  import { createInstanceTransformer } from "./instance";
4
5
  import {
@@ -17,9 +18,29 @@ import {
17
18
  createVectorTransformer,
18
19
  } from "./shape";
19
20
  import { createTextTransformer } from "./text";
20
- import { valueResolver } from "./value-resolver";
21
+ import {
22
+ defaultRawValueFormatters,
23
+ defaultStyleNameFormatter,
24
+ defaultVariableNameFormatter,
25
+ } from "./value-resolver";
26
+
27
+ export interface CreatePipelineConfig {
28
+ shouldInferAutoLayout?: boolean;
29
+ shouldInferVariableName?: boolean;
30
+ }
31
+
32
+ export function createPipeline(options: CreatePipelineConfig = {}) {
33
+ const { shouldInferAutoLayout = true, shouldInferVariableName = true } = options;
34
+
35
+ const valueResolver = createValueResolver({
36
+ variableService,
37
+ variableNameFormatter: defaultVariableNameFormatter,
38
+ styleService,
39
+ styleNameFormatter: defaultStyleNameFormatter,
40
+ rawValueFormatters: defaultRawValueFormatters,
41
+ shouldInferVariableName,
42
+ });
21
43
 
22
- export function createPipeline() {
23
44
  const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);
24
45
  const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);
25
46
  const frameFillPropsConverter = createFrameFillPropsConverter(valueResolver);
@@ -28,6 +49,7 @@ export function createPipeline() {
28
49
  const radiusPropsConverter = createRadiusPropsConverter(valueResolver);
29
50
  const strokePropsConverter = createStrokePropsConverter(valueResolver);
30
51
  const typeStylePropsConverter = createTypeStylePropsConverter(valueResolver);
52
+
31
53
  const propsConverters = {
32
54
  containerLayout: containerLayoutPropsConverter,
33
55
  selfLayout: selfLayoutPropsConverter,
@@ -65,6 +87,7 @@ export function createPipeline() {
65
87
  instanceTransformer,
66
88
  vectorTransformer,
67
89
  booleanOperationTransformer,
90
+ shouldInferAutoLayout,
68
91
  });
69
92
 
70
93
  return codegenTransformer;
@@ -1,22 +1,19 @@
1
- import { createValueResolver, type ValueResolver } from "@/codegen/core";
2
- import { styleService, variableService } from "@/codegen/default-services";
1
+ import type { ValueResolver } from "@/codegen/core";
3
2
  import { toCssRgba } from "@/utils/css";
4
3
 
5
4
  export type FigmaValueResolver = ValueResolver<string, number, number, number>;
6
5
 
7
- export const valueResolver = createValueResolver({
8
- variableService,
9
- variableNameFormatter: ({ slug }) =>
10
- slug
11
- .filter((s) => s !== "dimension")
12
- .map((s) => s.replaceAll(",", "_"))
13
- .join("/"),
14
- styleService,
15
- styleNameFormatter: ({ slug }) => slug[slug.length - 1]!,
16
- rawValueFormatters: {
17
- color: (value: RGBA) => toCssRgba(value),
18
- dimension: (value: number) => value,
19
- fontDimension: (value: number) => value,
20
- fontWeight: (value: number) => value,
21
- },
22
- });
6
+ export const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>
7
+ slug
8
+ .filter((s) => s !== "dimension")
9
+ .map((s) => s.replaceAll(",", "_"))
10
+ .join("/");
11
+
12
+ export const defaultStyleNameFormatter = ({ slug }: { slug: string[] }) => slug[slug.length - 1]!;
13
+
14
+ export const defaultRawValueFormatters = {
15
+ color: (value: RGBA) => toCssRgba(value),
16
+ dimension: (value: number) => value,
17
+ fontDimension: (value: number) => value,
18
+ fontWeight: (value: number) => value,
19
+ };
@@ -1,5 +1,5 @@
1
- import { createCodeGenerator } from "@/codegen/core";
2
- import { iconService } from "@/codegen/default-services";
1
+ import { createCodeGenerator, createValueResolver } from "@/codegen/core";
2
+ import { iconService, styleService, variableService } from "@/codegen/default-services";
3
3
  import {
4
4
  type UnboundComponentHandler,
5
5
  bindComponentHandler,
@@ -26,9 +26,15 @@ import {
26
26
  createVectorTransformer,
27
27
  } from "./shape";
28
28
  import { createTextTransformer } from "./text";
29
- import { valueResolver } from "./value-resolver";
29
+ import {
30
+ defaultRawValueFormatters,
31
+ defaultStyleNameFormatter,
32
+ defaultVariableNameFormatter,
33
+ } from "./value-resolver";
30
34
 
31
35
  export interface CreatePipelineConfig {
36
+ shouldInferAutoLayout?: boolean;
37
+ shouldInferVariableName?: boolean;
32
38
  extend?: {
33
39
  componentHandlers?: Array<UnboundComponentHandler<any>>;
34
40
  };
@@ -39,7 +45,16 @@ const iconHandler = createIconHandler({
39
45
  });
40
46
 
41
47
  export function createPipeline(options: CreatePipelineConfig = {}) {
42
- const { extend = {} } = options;
48
+ const { shouldInferAutoLayout = true, shouldInferVariableName = true, extend = {} } = options;
49
+
50
+ const valueResolver = createValueResolver({
51
+ variableService,
52
+ variableNameFormatter: defaultVariableNameFormatter,
53
+ styleService,
54
+ styleNameFormatter: defaultStyleNameFormatter,
55
+ rawValueFormatters: defaultRawValueFormatters,
56
+ shouldInferVariableName,
57
+ });
43
58
 
44
59
  const containerLayoutPropsConverter = createContainerLayoutPropsConverter(valueResolver);
45
60
  const selfLayoutPropsConverter = createSelfLayoutPropsConverter(valueResolver);
@@ -102,6 +117,7 @@ export function createPipeline(options: CreatePipelineConfig = {}) {
102
117
  instanceTransformer,
103
118
  vectorTransformer,
104
119
  booleanOperationTransformer,
120
+ shouldInferAutoLayout,
105
121
  });
106
122
 
107
123
  return codeGenerator;
@@ -1,35 +1,32 @@
1
- import { createValueResolver, type ValueResolver } from "@/codegen/core";
2
- import { styleService, variableService } from "@/codegen/default-services";
1
+ import type { ValueResolver } from "@/codegen/core";
3
2
  import { camelCasePreserveUnderscoreBetweenNumbers } from "@/utils/common";
4
3
  import { toCssPixel, toCssRgba } from "@/utils/css";
5
4
  import { camelCase } from "change-case";
6
5
 
7
6
  export type ReactValueResolver = ValueResolver<string, string, string, number>;
8
7
 
9
- export const valueResolver = createValueResolver({
10
- variableService,
11
- variableNameFormatter: ({ slug }) =>
12
- slug
13
- .filter(
14
- (s) =>
15
- !(
16
- s === "dimension" ||
17
- s === "radius" ||
18
- s === "font-size" ||
19
- s === "font-weight" ||
20
- s === "line-height"
21
- ),
22
- )
23
- .map((s) => s.replaceAll(",", "_"))
24
- .map(camelCasePreserveUnderscoreBetweenNumbers)
25
- .join("."),
26
- styleService,
27
- styleNameFormatter: ({ slug }) =>
28
- camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true }),
29
- rawValueFormatters: {
30
- color: (value: RGBA) => toCssRgba(value),
31
- dimension: (value: number) => toCssPixel(value),
32
- fontDimension: (value: number) => toCssPixel(value),
33
- fontWeight: (value: number) => value,
34
- },
35
- });
8
+ export const defaultVariableNameFormatter = ({ slug }: { slug: string[] }) =>
9
+ slug
10
+ .filter(
11
+ (s) =>
12
+ !(
13
+ s === "dimension" ||
14
+ s === "radius" ||
15
+ s === "font-size" ||
16
+ s === "font-weight" ||
17
+ s === "line-height"
18
+ ),
19
+ )
20
+ .map((s) => s.replaceAll(",", "_"))
21
+ .map(camelCasePreserveUnderscoreBetweenNumbers)
22
+ .join(".");
23
+
24
+ export const defaultStyleNameFormatter = ({ slug }: { slug: string[] }) =>
25
+ camelCase(slug[slug.length - 1]!, { mergeAmbiguousCharacters: true });
26
+
27
+ export const defaultRawValueFormatters = {
28
+ color: (value: RGBA) => toCssRgba(value),
29
+ dimension: (value: number) => toCssPixel(value),
30
+ fontDimension: (value: number) => toCssPixel(value),
31
+ fontWeight: (value: number) => value,
32
+ };
@@ -1,20 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks";
2
-
3
- export interface CodegenOptions {
4
- shouldInferAutoLayout: boolean;
5
- shouldInferVariableName: boolean;
6
- }
7
-
8
- export const codegenOptionsContext = new AsyncLocalStorage<CodegenOptions>();
9
-
10
- export function useCodegenOptions(): CodegenOptions {
11
- const options = codegenOptionsContext.getStore();
12
-
13
- if (!options) {
14
- throw new Error(
15
- "Trying to get codegen options outside of codegen context. Did you forget to call `codegenOptionsContext.run`?",
16
- );
17
- }
18
-
19
- return options;
20
- }