Skip to content

Commit 320a8cf

Browse files
committed
feat: add fontSize and spacing support to runtime config
Extend RuntimeConfig to support theme.extend.fontSize and theme.extend.spacing: - Parse fontSize values from numbers or strings ("18px", "18") - Parse spacing values from numbers, px, or rem ("2rem" -> 32px) - Clear cache when config changes - Add comprehensive tests for all theme extensions
1 parent 41bb375 commit 320a8cf

File tree

2 files changed

+202
-1
lines changed

2 files changed

+202
-1
lines changed

src/runtime.test.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,155 @@ describe("runtime", () => {
209209
backgroundColor: "#007AFF",
210210
});
211211
});
212+
213+
it("should set custom fontFamily", () => {
214+
setConfig({
215+
theme: {
216+
extend: {
217+
fontFamily: {
218+
display: "Inter",
219+
body: ["Roboto", "sans-serif"], // Array format - takes first value
220+
},
221+
},
222+
},
223+
});
224+
225+
const theme = getCustomTheme();
226+
expect(theme.fontFamily).toEqual({
227+
display: "Inter",
228+
body: "Roboto",
229+
});
230+
});
231+
232+
it("should set custom fontSize", () => {
233+
setConfig({
234+
theme: {
235+
extend: {
236+
fontSize: {
237+
tiny: 10,
238+
small: "12px",
239+
medium: 16,
240+
large: "24",
241+
},
242+
},
243+
},
244+
});
245+
246+
const theme = getCustomTheme();
247+
expect(theme.fontSize).toEqual({
248+
tiny: 10,
249+
small: 12,
250+
medium: 16,
251+
large: 24,
252+
});
253+
});
254+
255+
it("should set custom spacing", () => {
256+
setConfig({
257+
theme: {
258+
extend: {
259+
spacing: {
260+
xs: 4,
261+
sm: "8px",
262+
md: 16,
263+
lg: "2rem", // 2rem = 32px
264+
},
265+
},
266+
},
267+
});
268+
269+
const theme = getCustomTheme();
270+
expect(theme.spacing).toEqual({
271+
xs: 4,
272+
sm: 8,
273+
md: 16,
274+
lg: 32,
275+
});
276+
});
277+
278+
it("should use custom fontSize in parsing", () => {
279+
setConfig({
280+
theme: {
281+
extend: {
282+
fontSize: {
283+
tiny: 10,
284+
},
285+
},
286+
},
287+
});
288+
289+
const result = tw`text-tiny`;
290+
expect(result?.style).toEqual({
291+
fontSize: 10,
292+
});
293+
});
294+
295+
it("should use custom spacing in parsing", () => {
296+
setConfig({
297+
theme: {
298+
extend: {
299+
spacing: {
300+
xs: 4,
301+
},
302+
},
303+
},
304+
});
305+
306+
const result = tw`m-xs p-xs`;
307+
expect(result?.style).toEqual({
308+
margin: 4,
309+
padding: 4,
310+
});
311+
});
312+
313+
it("should reset fontSize and spacing when config is cleared", () => {
314+
setConfig({
315+
theme: {
316+
extend: {
317+
fontSize: { tiny: 10 },
318+
spacing: { xs: 4 },
319+
},
320+
},
321+
});
322+
323+
let theme = getCustomTheme();
324+
expect(theme.fontSize).toEqual({ tiny: 10 });
325+
expect(theme.spacing).toEqual({ xs: 4 });
326+
327+
setConfig({});
328+
329+
theme = getCustomTheme();
330+
expect(theme.fontSize).toEqual({});
331+
expect(theme.spacing).toEqual({});
332+
});
333+
334+
it("should handle all theme extensions together", () => {
335+
setConfig({
336+
theme: {
337+
extend: {
338+
colors: { primary: "#007AFF" },
339+
fontFamily: { display: "Inter" },
340+
fontSize: { tiny: 10 },
341+
spacing: { xs: 4 },
342+
},
343+
},
344+
});
345+
346+
const theme = getCustomTheme();
347+
expect(theme.colors).toEqual({ primary: "#007AFF" });
348+
expect(theme.fontFamily).toEqual({ display: "Inter" });
349+
expect(theme.fontSize).toEqual({ tiny: 10 });
350+
expect(theme.spacing).toEqual({ xs: 4 });
351+
352+
// Test that parsing uses all of them
353+
const result = tw`bg-primary font-display text-tiny m-xs`;
354+
expect(result?.style).toEqual({
355+
backgroundColor: "#007AFF",
356+
fontFamily: "Inter",
357+
fontSize: 10,
358+
margin: 4,
359+
});
360+
});
212361
});
213362

214363
describe("cache", () => {

src/runtime.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@ export type RuntimeConfig = {
1212
extend?: {
1313
colors?: Record<string, string | Record<string, string>>;
1414
fontFamily?: Record<string, string | string[]>;
15+
fontSize?: Record<string, string | number>;
16+
spacing?: Record<string, string | number>;
1517
};
1618
};
1719
};
1820

1921
// Global custom theme configuration
20-
const globalCustomTheme: CustomTheme = { colors: {}, fontFamily: {} };
22+
const globalCustomTheme: CustomTheme = {
23+
colors: {},
24+
fontFamily: {},
25+
fontSize: {},
26+
spacing: {},
27+
};
2128

2229
// Simple memoization cache
2330
const styleCache = new Map<string, TwStyle>();
@@ -72,6 +79,51 @@ export function setConfig(config: RuntimeConfig): void {
7279
globalCustomTheme.fontFamily = {};
7380
}
7481

82+
// Extract custom fontSize
83+
if (config.theme?.extend?.fontSize) {
84+
const fontSizeResult: Record<string, number> = {};
85+
for (const [key, value] of Object.entries(config.theme.extend.fontSize)) {
86+
if (typeof value === "number") {
87+
fontSizeResult[key] = value;
88+
} else if (typeof value === "string") {
89+
// Parse string values like "18px" or "18" to number
90+
const parsed = parseFloat(value.replace(/px$/, ""));
91+
if (!isNaN(parsed)) {
92+
fontSizeResult[key] = parsed;
93+
}
94+
}
95+
}
96+
globalCustomTheme.fontSize = fontSizeResult;
97+
} else {
98+
globalCustomTheme.fontSize = {};
99+
}
100+
101+
// Extract custom spacing
102+
if (config.theme?.extend?.spacing) {
103+
const spacingResult: Record<string, number> = {};
104+
for (const [key, value] of Object.entries(config.theme.extend.spacing)) {
105+
if (typeof value === "number") {
106+
spacingResult[key] = value;
107+
} else if (typeof value === "string") {
108+
// Parse string values: "18rem" -> 288, "16px" -> 16, "16" -> 16
109+
let parsed: number;
110+
if (value.endsWith("rem")) {
111+
// Convert rem to px (1rem = 16px)
112+
parsed = parseFloat(value.replace(/rem$/, "")) * 16;
113+
} else {
114+
// Parse px or unitless values
115+
parsed = parseFloat(value.replace(/px$/, ""));
116+
}
117+
if (!isNaN(parsed)) {
118+
spacingResult[key] = parsed;
119+
}
120+
}
121+
}
122+
globalCustomTheme.spacing = spacingResult;
123+
} else {
124+
globalCustomTheme.spacing = {};
125+
}
126+
75127
// Clear cache when config changes
76128
styleCache.clear();
77129
}

0 commit comments

Comments
 (0)