Skip to content

Commit 8449fb2

Browse files
committed
Add ring and custom border values to Tailwind
1 parent 813563b commit 8449fb2

6 files changed

Lines changed: 141 additions & 50 deletions

File tree

packages/backend/src/tailwind/builderImpl/tailwindBorder.ts

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,92 @@
11
import { getCommonRadius } from "../../common/commonRadius";
22
import { commonStroke } from "../../common/commonStroke";
3-
import { nearestValue, pxToBorderRadius } from "../conversionTables";
3+
import {
4+
nearestValue,
5+
pxToBorderRadius,
6+
pxToBorderWidth,
7+
pxToRing,
8+
} from "../conversionTables";
9+
import { numberToFixedString } from "../../common/numToAutoFixed";
10+
import { addWarning } from "../../common/commonConversionWarnings";
11+
12+
const getBorder = (weight: number, kind: string, isRing: boolean = false) => {
13+
// Use ring utilities for outside strokes
14+
if (isRing) {
15+
// Special case: ring (without width) is 3px in Tailwind
16+
if (weight === 3) {
17+
return "ring";
18+
}
19+
20+
const ringWidth = pxToRing(weight);
21+
if (ringWidth === null) {
22+
return `ring-[${numberToFixedString(weight)}px]`;
23+
} else if (ringWidth === "3") {
24+
// Ring is 3px
25+
return `ring`;
26+
} else {
27+
return `ring-${ringWidth}`;
28+
}
29+
}
30+
31+
// Special case: border (without width) is 1px in Tailwind
32+
if (weight === 1) {
33+
return `border${kind}`;
34+
}
35+
36+
// Use border utilities for default and inside strokes
37+
const borderWidth = pxToBorderWidth(weight);
38+
if (borderWidth === null) {
39+
return `border${kind}-[${numberToFixedString(weight)}px]`;
40+
} else if (borderWidth === "DEFAULT") {
41+
// Border is 1px
42+
return `border${kind}`;
43+
} else {
44+
return `border${kind}-${borderWidth}`;
45+
}
46+
};
447

548
/**
649
* https://tailwindcss.com/docs/border-width/
750
* example: border-2
851
*/
9-
export const tailwindBorderWidth = (node: SceneNode): string => {
52+
export const tailwindBorderWidth = (
53+
node: SceneNode,
54+
): {
55+
isRing: boolean;
56+
property: string;
57+
} => {
1058
const commonBorder = commonStroke(node);
1159
if (!commonBorder) {
12-
return "";
60+
return {
61+
isRing: false,
62+
property: "",
63+
};
1364
}
1465

15-
const getBorder = (weight: number, kind: string) => {
16-
const allowedValues = [1, 2, 4, 8];
17-
console.log("weight", weight);
18-
const nearest = nearestValue(weight, allowedValues);
19-
console.log("nearest", nearest);
20-
21-
if (nearest === 1) {
22-
// special case
23-
return `border${kind}`;
24-
} else {
25-
return `border${kind}-${nearest}`;
26-
}
27-
};
66+
// Check if stroke is outside
67+
const isRing =
68+
"strokeAlign" in node &&
69+
(node.strokeAlign === "OUTSIDE" || node.strokeAlign === "CENTER");
2870

2971
if ("all" in commonBorder) {
3072
if (commonBorder.all === 0) {
31-
return "";
73+
return {
74+
isRing: false,
75+
property: "",
76+
};
3277
}
33-
return getBorder(commonBorder.all, "");
78+
return {
79+
isRing,
80+
property: getBorder(commonBorder.all, "", isRing),
81+
};
82+
} else {
83+
addWarning(
84+
'Non-uniform borders are only supported with strokeAlign set to "inside". Will paint inside.',
85+
);
3486
}
3587

88+
// If borders are non-uniform, always use border utilities for better control
89+
// regardless of whether the stroke is outside or not
3690
const comp = [];
3791
if (commonBorder.left !== 0) {
3892
comp.push(getBorder(commonBorder.left, "-l"));
@@ -46,7 +100,11 @@ export const tailwindBorderWidth = (node: SceneNode): string => {
46100
if (commonBorder.bottom !== 0) {
47101
comp.push(getBorder(commonBorder.bottom, "-b"));
48102
}
49-
return comp.join(" ");
103+
104+
return {
105+
isRing,
106+
property: comp.join(" "),
107+
};
50108
};
51109

52110
/**

packages/backend/src/tailwind/conversionTables.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const pxToTailwind = (
7878
const keys = Object.keys(conversionMap).map((d) => +d);
7979
const convertedValue = exactValue(value, keys);
8080

81-
console.log("convertedValue", convertedValue);
81+
console.log("convertedValue", convertedValue, value, keys);
8282

8383
if (convertedValue) {
8484
return conversionMap[convertedValue];
@@ -110,6 +110,14 @@ export const pxToBorderRadius = (value: number): string => {
110110
return pxToRemToTailwind(value, config.borderRadius);
111111
};
112112

113+
export const pxToBorderWidth = (value: number): string | null => {
114+
return pxToTailwind(value, config.border);
115+
};
116+
117+
export const pxToRing = (value: number): string | null => {
118+
return pxToTailwind(value, config.ring);
119+
};
120+
113121
export const pxToBlur = (value: number): string | null => {
114122
return pxToTailwind(value, config.blur);
115123
};

packages/backend/src/tailwind/tailwindConfig.ts

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const layoutSize = {
2-
// '0: 0',
2+
"0": "0",
33
1: "px",
44
2: "0.5",
55
4: "1",
@@ -37,7 +37,7 @@ const layoutSize = {
3737
};
3838

3939
const borderRadius = {
40-
// 0: "none",
40+
0: "none",
4141
0.125: "sm",
4242
0.25: "",
4343
0.375: "md",
@@ -356,37 +356,54 @@ const fontWeight: Record<number, string> = {
356356
600: "semibold",
357357
700: "bold",
358358
800: "extrabold",
359-
900: "black"
359+
900: "black",
360360
};
361361

362362
const fontFamily = {
363363
sans: [
364-
'ui-sans-serif',
365-
'system-ui',
366-
'sans-serif',
367-
'Apple Color Emoji',
368-
'Segoe UI Emoji',
369-
'Segoe UI Symbol',
370-
'Noto Color Emoji'
364+
"ui-sans-serif",
365+
"system-ui",
366+
"sans-serif",
367+
"Apple Color Emoji",
368+
"Segoe UI Emoji",
369+
"Segoe UI Symbol",
370+
"Noto Color Emoji",
371371
],
372372
serif: [
373-
'ui-serif',
374-
'Georgia',
375-
'Cambria',
376-
'Times New Roman',
377-
'Times',
378-
'serif'
373+
"ui-serif",
374+
"Georgia",
375+
"Cambria",
376+
"Times New Roman",
377+
"Times",
378+
"serif",
379379
],
380380
mono: [
381-
'ui-monospace',
382-
'SFMono-Regular',
383-
'Menlo',
384-
'Monaco',
385-
'Consolas',
386-
'Liberation Mono',
387-
'Courier New',
388-
'monospace'
389-
]
381+
"ui-monospace",
382+
"SFMono-Regular",
383+
"Menlo",
384+
"Monaco",
385+
"Consolas",
386+
"Liberation Mono",
387+
"Courier New",
388+
"monospace",
389+
],
390+
};
391+
392+
const border = {
393+
0: "0",
394+
1: "1",
395+
2: "2",
396+
4: "4",
397+
8: "8",
398+
};
399+
400+
const ring = {
401+
0: "0",
402+
1: "1",
403+
2: "2",
404+
3: "3",
405+
4: "4",
406+
8: "8",
390407
};
391408

392409
export const config = {
@@ -400,4 +417,6 @@ export const config = {
400417
color,
401418
fontWeight,
402419
fontFamily,
420+
border,
421+
ring,
403422
};

packages/backend/src/tailwind/tailwindDefaultBuilder.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ export class TailwindDefaultBuilder {
114114

115115
border(): this {
116116
if ("strokes" in this.node) {
117-
this.addAttributes(tailwindBorderWidth(this.node));
118-
this.customColor(this.node.strokes, "border");
117+
const { isRing, property } = tailwindBorderWidth(this.node);
118+
this.addAttributes(property);
119+
this.customColor(this.node.strokes, isRing ? "ring" : "border");
119120
}
120121

121122
return this;
@@ -188,7 +189,11 @@ export class TailwindDefaultBuilder {
188189
// must be called before Position, because of the hasFixedSize attribute.
189190
size(): this {
190191
const { node, optimizeLayout, settings } = this;
191-
const { width, height, constraints } = tailwindSizePartial(node, optimizeLayout, settings);
192+
const { width, height, constraints } = tailwindSizePartial(
193+
node,
194+
optimizeLayout,
195+
settings,
196+
);
192197

193198
if (node.type === "TEXT") {
194199
switch (node.textAutoResize) {

packages/plugin-ui/src/components/Preview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ const Preview: React.FC<{
169169
{/* Footer with size info */}
170170
<div className="px-3 py-1.5 text-xs text-neutral-500 dark:text-neutral-400 flex items-center justify-between border-t border-neutral-200 dark:border-neutral-700">
171171
<span>
172-
{props.htmlPreview.size.width}×{props.htmlPreview.size.height}px
172+
{props.htmlPreview.size.width.toFixed(0)}×
173+
{props.htmlPreview.size.height.toFixed(0)}px
173174
</span>
174175
<div className="flex items-center gap-1.5">
175176
{viewMode === "mobile" ? (

packages/types/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export interface TailwindTextConversion {
189189
contrastBlack: number;
190190
}
191191

192-
export type TailwindColorType = "text" | "bg" | "border" | "solid";
192+
export type TailwindColorType = "text" | "bg" | "border" | "ring";
193193

194194
export type SwiftUIModifier = [
195195
string,

0 commit comments

Comments
 (0)