Skip to content

Commit d2439a1

Browse files
committed
Rewrite ring into outline
1 parent 4dd5448 commit d2439a1

7 files changed

Lines changed: 115 additions & 43 deletions

File tree

packages/backend/src/html/htmlDefaultBuilder.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,16 @@ export class HtmlDefaultBuilder {
163163

164164
const strokes = ("strokes" in node && node.strokes) || undefined;
165165
const color = htmlColorFromFills(strokes, settings);
166+
if (!color) {
167+
return this;
168+
}
166169
const borderStyle =
167170
"dashPattern" in node && node.dashPattern.length > 0 ? "dotted" : "solid";
168171

172+
const strokeAlign = "strokeAlign" in node ? node.strokeAlign : "INSIDE";
173+
const layoutMode = "layoutMode" in node ? node.layoutMode : "NONE";
174+
175+
// Function to create border value string
169176
const consolidateBorders = (border: number): string =>
170177
[`${numberToFixedString(border)}px`, color, borderStyle]
171178
.filter((d) => d)
@@ -176,10 +183,40 @@ export class HtmlDefaultBuilder {
176183
return this;
177184
}
178185
const weight = commonBorder.all;
179-
this.addStyles(
180-
formatWithJSX("border", this.isJSX, consolidateBorders(weight)),
181-
);
186+
187+
if (
188+
strokeAlign === "CENTER" ||
189+
strokeAlign === "OUTSIDE" ||
190+
(strokeAlign === "INSIDE" && layoutMode === "NONE")
191+
) {
192+
this.addStyles(
193+
formatWithJSX("outline", this.isJSX, consolidateBorders(weight)),
194+
);
195+
if (strokeAlign === "CENTER") {
196+
this.addStyles(
197+
formatWithJSX(
198+
"outline-offset",
199+
this.isJSX,
200+
`${numberToFixedString(-weight / 2)}px`,
201+
),
202+
);
203+
} else if (strokeAlign === "INSIDE") {
204+
this.addStyles(
205+
formatWithJSX(
206+
"outline-offset",
207+
this.isJSX,
208+
`${numberToFixedString(-weight)}px`,
209+
),
210+
);
211+
}
212+
} else {
213+
// Default: use regular border on autolayout + strokeAlign: inside
214+
this.addStyles(
215+
formatWithJSX("border", this.isJSX, consolidateBorders(weight)),
216+
);
217+
}
182218
} else {
219+
// For non-uniform borders, always use individual border properties
183220
if (commonBorder.left !== 0) {
184221
this.addStyles(
185222
formatWithJSX(
@@ -280,7 +317,12 @@ export class HtmlDefaultBuilder {
280317
}
281318

282319
buildBackgroundBlendModes(paintArray: ReadonlyArray<Paint>): string {
283-
if (paintArray.length === 0) {
320+
if (
321+
paintArray.length === 0 ||
322+
paintArray.every(
323+
(d) => d.blendMode === "NORMAL" || d.blendMode === "PASS_THROUGH",
324+
)
325+
) {
284326
return "";
285327
}
286328

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ export const tailwindBlendMode = (node: MinimalBlendMixin): string => {
6464
export const tailwindBackgroundBlendMode = (
6565
paintArray: ReadonlyArray<Paint>,
6666
): string => {
67-
if (paintArray.length === 0) {
67+
if (
68+
paintArray.length === 0 ||
69+
paintArray.every(
70+
(d) => d.blendMode === "NORMAL" || d.blendMode === "PASS_THROUGH",
71+
)
72+
) {
6873
return "";
6974
}
7075

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

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import { getCommonRadius } from "../../common/commonRadius";
22
import { commonStroke } from "../../common/commonStroke";
33
import {
4-
nearestValue,
54
pxToBorderRadius,
65
pxToBorderWidth,
7-
pxToRing,
6+
pxToOutline,
87
} from "../conversionTables";
98
import { numberToFixedString } from "../../common/numToAutoFixed";
109
import { addWarning } from "../../common/commonConversionWarnings";
1110

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-
}
11+
const getBorder = (
12+
weight: number,
13+
kind: string,
14+
useOutline: boolean = false,
15+
isBoxShadow: boolean = false,
16+
): string => {
17+
// For box-shadow (inside stroke on non-autolayout), return empty string as we'll handle separately
18+
if (isBoxShadow) {
19+
return "";
20+
}
1921

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`;
22+
// Use outline utilities for outside/center strokes
23+
if (useOutline) {
24+
const outlineWidth = pxToOutline(weight);
25+
if (outlineWidth === null) {
26+
return `outline-[${numberToFixedString(weight)}px]`;
2627
} else {
27-
return `ring-${ringWidth}`;
28+
return `outline-${outlineWidth}`;
2829
}
2930
}
3031

@@ -52,41 +53,66 @@ const getBorder = (weight: number, kind: string, isRing: boolean = false) => {
5253
export const tailwindBorderWidth = (
5354
node: SceneNode,
5455
): {
55-
isRing: boolean;
56+
isOutline: boolean;
5657
property: string;
58+
shadowProperty?: string; // This can be removed if not used elsewhere
5759
} => {
5860
const commonBorder = commonStroke(node);
5961
if (!commonBorder) {
6062
return {
61-
isRing: false,
63+
isOutline: false,
6264
property: "",
6365
};
6466
}
6567

66-
// Check if stroke is outside
67-
const isRing =
68-
"strokeAlign" in node &&
69-
(node.strokeAlign === "OUTSIDE" || node.strokeAlign === "CENTER");
68+
// Check stroke alignment and layout mode
69+
const strokeAlign = "strokeAlign" in node ? node.strokeAlign : "INSIDE";
70+
const layoutMode = "layoutMode" in node ? node.layoutMode : "NONE";
7071

7172
if ("all" in commonBorder) {
7273
if (commonBorder.all === 0) {
7374
return {
74-
isRing: false,
75+
isOutline: false,
7576
property: "",
7677
};
7778
}
78-
return {
79-
isRing,
80-
property: getBorder(commonBorder.all, "", isRing),
81-
};
79+
80+
const weight = commonBorder.all;
81+
82+
if (
83+
strokeAlign === "CENTER" ||
84+
strokeAlign === "OUTSIDE" ||
85+
(strokeAlign === "INSIDE" && layoutMode === "NONE")
86+
) {
87+
// For CENTER, OUTSIDE, or INSIDE+NONE, use outline
88+
const property = getBorder(weight, "", true);
89+
let offsetProperty = "";
90+
91+
if (strokeAlign === "CENTER") {
92+
offsetProperty = `outline-offset-[-${numberToFixedString(weight / 2)}px]`;
93+
} else if (strokeAlign === "INSIDE") {
94+
offsetProperty = `outline-offset-[-${numberToFixedString(weight)}px]`;
95+
}
96+
97+
return {
98+
isOutline: true,
99+
property: offsetProperty ? `${property} ${offsetProperty}` : property,
100+
};
101+
} else {
102+
// Default case: use normal border (for INSIDE + AUTO_LAYOUT)
103+
return {
104+
isOutline: false,
105+
property: getBorder(weight, "", false),
106+
};
107+
}
82108
} else {
109+
// For non-uniform borders, we only support border (not outline)
83110
addWarning(
84111
'Non-uniform borders are only supported with strokeAlign set to "inside". Will paint inside.',
85112
);
86113
}
87114

88-
// If borders are non-uniform, always use border utilities for better control
89-
// regardless of whether the stroke is outside or not
115+
// Handle non-uniform borders with individual border properties
90116
const comp = [];
91117
if (commonBorder.left !== 0) {
92118
comp.push(getBorder(commonBorder.left, "-l"));
@@ -102,7 +128,7 @@ export const tailwindBorderWidth = (
102128
}
103129

104130
return {
105-
isRing,
131+
isOutline: false,
106132
property: comp.join(" "),
107133
};
108134
};

packages/backend/src/tailwind/conversionTables.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ export const pxToBorderWidth = (value: number): string | null => {
112112
return pxToTailwind(value, config.border);
113113
};
114114

115-
export const pxToRing = (value: number): string | null => {
116-
return pxToTailwind(value, config.ring);
115+
export const pxToOutline = (value: number): string | null => {
116+
return pxToTailwind(value, config.outline);
117117
};
118118

119119
export const pxToBlur = (value: number): string | null => {

packages/backend/src/tailwind/tailwindConfig.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,10 @@ const border = {
397397
8: "8",
398398
};
399399

400-
const ring = {
400+
const outline = {
401401
0: "0",
402402
1: "1",
403403
2: "2",
404-
3: "3",
405404
4: "4",
406405
8: "8",
407406
};
@@ -418,5 +417,5 @@ export const config = {
418417
fontWeight,
419418
fontFamily,
420419
border,
421-
ring,
420+
outline,
422421
};

packages/backend/src/tailwind/tailwindDefaultBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ export class TailwindDefaultBuilder {
115115

116116
border(): this {
117117
if ("strokes" in this.node) {
118-
const { isRing, property } = tailwindBorderWidth(this.node);
118+
const { isOutline, property } = tailwindBorderWidth(this.node);
119119
this.addAttributes(property);
120-
this.customColor(this.node.strokes, isRing ? "ring" : "border");
120+
this.customColor(this.node.strokes, isOutline ? "outline" : "border");
121121
}
122122

123123
return this;

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" | "ring";
192+
export type TailwindColorType = "text" | "bg" | "border" | "outline";
193193

194194
export type SwiftUIModifier = [
195195
string,

0 commit comments

Comments
 (0)