Skip to content

Commit eec653a

Browse files
committed
Improve componentProperties
1 parent b17e4b1 commit eec653a

File tree

11 files changed

+213
-117
lines changed

11 files changed

+213
-117
lines changed

apps/plugin/plugin-src/code.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ const codegenMode = async () => {
341341
// },
342342
{
343343
title: "Tailwind Colors",
344-
code: retrieveGenericSolidUIColors("Tailwind")
344+
code: (await retrieveGenericSolidUIColors("Tailwind"))
345345
.map((d) => {
346346
let str = `${d.hex};`;
347347
if (d.colorName !== d.hex) {

apps/plugin/ui-src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default function App() {
3232
const [state, setState] = useState<AppState>({
3333
code: "",
3434
selectedFramework: "HTML",
35-
isLoading: true,
35+
isLoading: false,
3636
htmlPreview: emptyPreview,
3737
settings: null,
3838
colors: [],

packages/backend/src/altNodes/jsonNodeConversion.ts

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const memoizedVariableToColorName = async (
4444
* Process color variables in a paint style and add pre-computed variable names
4545
* @param paint The paint style to process (fill or stroke)
4646
*/
47-
const processColorVariables = async (paint: Paint) => {
47+
export const processColorVariables = async (paint: Paint) => {
4848
const start = Date.now();
4949
processColorVariablesCalls++;
5050

@@ -241,19 +241,29 @@ const processNodePair = async (
241241
if (
242242
Array.isArray(jsonNode.children) &&
243243
figmaNode &&
244-
"children" in figmaNode &&
245-
figmaNode.children.length === jsonNode.children.length
244+
"children" in figmaNode
246245
) {
247-
for (let i = 0; i < jsonNode.children.length; i++) {
248-
const child = jsonNode.children[i];
249-
const figmaChild = figmaNode.children[i];
250-
if (!figmaChild) continue;
246+
// Get visible JSON children (filters out nodes with visible: false)
247+
const visibleJsonChildren = jsonNode.children.filter(
248+
(child) => child.visible !== false,
249+
);
250+
251+
// Map figma children to their IDs for matching
252+
const figmaChildrenById = new Map();
253+
figmaNode.children.forEach((child) => {
254+
figmaChildrenById.set(child.id, child);
255+
});
256+
257+
// Process all visible JSON children that have matching Figma nodes
258+
for (const child of visibleJsonChildren) {
259+
const figmaChild = figmaChildrenById.get(child.id);
260+
if (!figmaChild) continue; // Skip if no matching Figma node found
251261

252262
const processedChild = await processNodePair(
253263
child,
254264
figmaChild,
255265
settings,
256-
parentNode, // The groups parent
266+
parentNode, // The group's parent
257267
parentCumulativeRotation + (jsonNode.rotation || 0),
258268
);
259269

@@ -268,7 +278,7 @@ const processNodePair = async (
268278
}
269279
}
270280

271-
// Simply return the processed children; skip splicing parents children
281+
// Simply return the processed children; skip splicing parent's children
272282
return processedChildren;
273283
}
274284

@@ -365,11 +375,6 @@ const processNodePair = async (
365375
}
366376
}
367377

368-
// Extract component metadata from instances
369-
if ("variantProperties" in figmaNode && figmaNode.variantProperties) {
370-
jsonNode.variantProperties = figmaNode.variantProperties;
371-
}
372-
373378
// Always copy size and position
374379
if ("absoluteBoundingBox" in jsonNode && jsonNode.absoluteBoundingBox) {
375380
if (jsonNode.parent) {
@@ -457,19 +462,34 @@ const processNodePair = async (
457462
"children" in jsonNode &&
458463
jsonNode.children &&
459464
Array.isArray(jsonNode.children) &&
460-
"children" in figmaNode &&
461-
figmaNode.children.length === jsonNode.children.length
465+
"children" in figmaNode
462466
) {
467+
// Get only visible JSON children
468+
const visibleJsonChildren = jsonNode.children.filter(
469+
(child) => child.visible !== false,
470+
);
471+
472+
// Create a map of figma children by ID for easier matching
473+
const figmaChildrenById = new Map();
474+
figmaNode.children.forEach((child) => {
475+
figmaChildrenById.set(child.id, child);
476+
});
477+
463478
const cumulative =
464479
parentCumulativeRotation +
465480
(jsonNode.type === "GROUP" ? jsonNode.rotation || 0 : 0);
466481

467482
// Process children and handle potential null returns
468483
const processedChildren = [];
469-
for (let i = 0; i < jsonNode.children.length; i++) {
484+
485+
// Process all visible JSON children that have matching Figma nodes
486+
for (const child of visibleJsonChildren) {
487+
const figmaChild = figmaChildrenById.get(child.id);
488+
if (!figmaChild) continue; // Skip if no matching Figma node found
489+
470490
const processedChild = await processNodePair(
471-
jsonNode.children[i],
472-
figmaNode.children[i],
491+
child,
492+
figmaChild,
473493
settings,
474494
jsonNode,
475495
cumulative,
@@ -498,13 +518,6 @@ const processNodePair = async (
498518
}
499519

500520
adjustChildrenOrder(jsonNode);
501-
} else if (
502-
"children" in figmaNode &&
503-
figmaNode.children.length !== jsonNode.children.length
504-
) {
505-
addWarning(
506-
"Error: JSON and Figma nodes have different child counts. Please report this issue.",
507-
);
508521
}
509522

510523
return jsonNode;

packages/backend/src/code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ export const run = async (settings: PluginSettings) => {
7070
);
7171

7272
const colorPanelStart = Date.now();
73-
const colors = retrieveGenericSolidUIColors(framework);
74-
const gradients = retrieveGenericLinearGradients(framework);
73+
const colors = await retrieveGenericSolidUIColors(framework);
74+
const gradients = await retrieveGenericLinearGradients(framework);
7575
console.log(
7676
`[benchmark] color and gradient panel: ${Date.now() - colorPanelStart}ms`,
7777
);

packages/backend/src/common/retrieveUI/retrieveColors.ts

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
flutterGradient,
1313
} from "../../flutter/builderImpl/flutterColor";
1414
import {
15-
htmlColor,
15+
htmlColorFromFill,
1616
htmlGradientFromFills,
1717
} from "../../html/builderImpl/htmlColor";
1818
import { calculateContrastRatio } from "./commonUI";
@@ -21,31 +21,41 @@ import {
2121
SolidColorConversion,
2222
Framework,
2323
} from "types";
24+
import { processColorVariables } from "../../altNodes/jsonNodeConversion";
2425

25-
export const retrieveGenericSolidUIColors = (
26+
export const retrieveGenericSolidUIColors = async (
2627
framework: Framework,
27-
): Array<SolidColorConversion> => {
28+
): Promise<Array<SolidColorConversion>> => {
2829
const selectionColors = figma.getSelectionColors();
2930
if (!selectionColors || selectionColors.paints.length === 0) return [];
3031

3132
const colors: Array<SolidColorConversion> = [];
32-
selectionColors.paints.forEach((paint) => {
33-
const fill = convertSolidColor(paint, framework);
34-
if (fill) {
35-
const exists = colors.find((col) => col.exportValue === fill.exportValue);
36-
if (!exists) {
37-
colors.push(fill);
33+
34+
// Process all paints in parallel to handle variables
35+
await Promise.all(
36+
selectionColors.paints.map(async (d) => {
37+
const paint = { ...d } as Paint;
38+
await processColorVariables(paint as any);
39+
40+
const fill = await convertSolidColor(paint, framework);
41+
if (fill) {
42+
const exists = colors.find(
43+
(col) => col.exportValue === fill.exportValue,
44+
);
45+
if (!exists) {
46+
colors.push(fill);
47+
}
3848
}
39-
}
40-
});
49+
}),
50+
);
4151

4252
return colors.sort((a, b) => a.hex.localeCompare(b.hex));
4353
};
4454

45-
const convertSolidColor = (
55+
const convertSolidColor = async (
4656
fill: Paint,
4757
framework: Framework,
48-
): SolidColorConversion | null => {
58+
): Promise<SolidColorConversion | null> => {
4959
const black = { r: 0, g: 0, b: 0 };
5060
const white = { r: 1, g: 1, b: 1 };
5161

@@ -63,55 +73,80 @@ const convertSolidColor = (
6373
if (framework === "Flutter") {
6474
output.exportValue = flutterColor(fill.color, opacity);
6575
} else if (framework === "HTML") {
66-
output.exportValue = htmlColor(fill.color, opacity);
76+
output.exportValue = htmlColorFromFill(fill as any);
6777
} else if (framework === "Tailwind") {
68-
Object.assign(output, tailwindColor(fill));
78+
// Pass true to use CSS variable syntax for variables
79+
output.exportValue = tailwindColor(fill as any, true).exportValue;
6980
} else if (framework === "SwiftUI") {
7081
output.exportValue = swiftuiColor(fill.color, opacity);
7182
}
7283

7384
return output;
7485
};
7586

76-
export const retrieveGenericLinearGradients = (
87+
export const retrieveGenericLinearGradients = async (
7788
framework: Framework,
78-
): Array<LinearGradientConversion> => {
89+
): Promise<Array<LinearGradientConversion>> => {
7990
const selectionColors = figma.getSelectionColors();
8091
const colorStr: Array<LinearGradientConversion> = [];
8192

82-
console.log("selectionColors", selectionColors);
83-
84-
selectionColors?.paints.forEach((paint) => {
85-
if (paint.type === "GRADIENT_LINEAR") {
86-
let fill = { ...paint };
87-
const t = fill.gradientTransform;
88-
fill.gradientHandlePositions = [
89-
{ x: t[0][2], y: t[1][2] }, // Start: (e, f)
90-
{ x: t[0][0] + t[0][2], y: t[1][0] + t[1][2] }, // End: (a + e, b + f)
91-
];
92-
console.log("fill is", { ...fill });
93-
94-
let exportValue = "";
95-
switch (framework) {
96-
case "Flutter":
97-
exportValue = flutterGradient(fill);
98-
break;
99-
case "HTML":
100-
exportValue = htmlGradientFromFills(fill);
101-
break;
102-
case "Tailwind":
103-
exportValue = tailwindGradient(fill);
104-
break;
105-
case "SwiftUI":
106-
exportValue = swiftuiGradient(fill);
107-
break;
93+
if (!selectionColors || selectionColors.paints.length === 0) return [];
94+
95+
// Process all gradient paints
96+
await Promise.all(
97+
selectionColors.paints.map(async (paint) => {
98+
if (paint.type === "GRADIENT_LINEAR") {
99+
let fill = { ...paint };
100+
const t = fill.gradientTransform;
101+
fill.gradientHandlePositions = [
102+
{ x: t[0][2], y: t[1][2] }, // Start: (e, f)
103+
{ x: t[0][0] + t[0][2], y: t[1][0] + t[1][2] }, // End: (a + e, b + f)
104+
];
105+
106+
// Process gradient stops for variables
107+
if (fill.gradientStops) {
108+
for (const stop of fill.gradientStops) {
109+
if (stop.boundVariables?.color) {
110+
try {
111+
const variableId = stop.boundVariables.color.id;
112+
const variable = figma.variables.getVariableById(variableId);
113+
if (variable) {
114+
(stop as any).variableColorName = variable.name
115+
.replace(/\s+/g, "-")
116+
.toLowerCase();
117+
}
118+
} catch (e) {
119+
console.error(
120+
"Error retrieving variable for gradient stop:",
121+
e,
122+
);
123+
}
124+
}
125+
}
126+
}
127+
128+
let exportValue = "";
129+
switch (framework) {
130+
case "Flutter":
131+
exportValue = flutterGradient(fill);
132+
break;
133+
case "HTML":
134+
exportValue = htmlGradientFromFills(fill);
135+
break;
136+
case "Tailwind":
137+
exportValue = tailwindGradient(fill);
138+
break;
139+
case "SwiftUI":
140+
exportValue = swiftuiGradient(fill);
141+
break;
142+
}
143+
colorStr.push({
144+
cssPreview: htmlGradientFromFills(fill),
145+
exportValue,
146+
});
108147
}
109-
colorStr.push({
110-
cssPreview: htmlGradientFromFills(fill),
111-
exportValue,
112-
});
113-
}
114-
});
148+
}),
149+
);
115150

116151
return colorStr;
117152
};

packages/backend/src/html/builderImpl/htmlColor.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { GradientPaint, Paint } from "../../api_types";
55
/**
66
* Helper to process a color with variable binding if present
77
*/
8-
const processColorWithVariable = (fill: {
8+
export const processColorWithVariable = (fill: {
99
color: RGB;
1010
opacity?: number;
1111
variableColorName?: string;
@@ -67,6 +67,13 @@ export const htmlColorFromFills = (
6767
return "";
6868
};
6969

70+
/**
71+
* Convert fills to an HTML color string
72+
*/
73+
export const htmlColorFromFill = (fill: Paint): string => {
74+
return processColorWithVariable(fill as any);
75+
};
76+
7077
/**
7178
* Convert RGB color to CSS color string
7279
*/

packages/backend/src/html/htmlDefaultBuilder.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -437,18 +437,28 @@ export class HtmlDefaultBuilder {
437437
if (this.name) {
438438
this.addData("layer", this.name.trim());
439439

440-
if (mode !== "svelte" && mode !== "styled-components") {
441-
const layerNameClass = stringToClassName(this.name.trim());
442-
if (layerNameClass !== "") {
443-
classNames.push(layerNameClass);
444-
}
445-
}
440+
// if (mode !== "svelte" && mode !== "styled-components") {
441+
// const layerNameClass = stringToClassName(this.name.trim());
442+
// if (layerNameClass !== "") {
443+
// classNames.push(layerNameClass);
444+
// }
445+
// }
446446
}
447447

448-
if ("variantProperties" in this.node && this.node.variantProperties) {
449-
// console.log("this.node.variantProperties", this.node.variantProperties);
450-
Object.entries(this.node.variantProperties)
451-
?.map((prop) => formatDataAttribute(prop[0], prop[1]))
448+
if ("componentProperties" in this.node && this.node.componentProperties) {
449+
Object.entries(this.node.componentProperties)
450+
?.map((prop) => {
451+
if (prop[1].type === "VARIANT" || prop[1].type === "BOOLEAN") {
452+
const cleanName = prop[0]
453+
.split("#")[0]
454+
.replace(/\s+/g, "-")
455+
.toLowerCase();
456+
457+
return formatDataAttribute(cleanName, String(prop[1].value));
458+
}
459+
return "";
460+
})
461+
.filter(Boolean)
452462
.sort()
453463
.forEach((d) => this.data.push(d));
454464
}

packages/backend/src/html/htmlMain.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,6 @@ const htmlContainer = async (
601601
let src = "";
602602

603603
if (nodeHasImageFill(node)) {
604-
// ...existing image handling code...
605604
const altNode = node as AltNode<ExportableNode>;
606605
const hasChildren = "children" in node && node.children.length > 0;
607606
let imgUrl = "";

0 commit comments

Comments
 (0)