Skip to content

Commit 325ad70

Browse files
committed
fix: make new strict custom validators for tailwind merge plugin
1 parent 58fdf2d commit 325ad70

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

packages/utopia-tailwind-merge/src/index.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,62 @@
11
import { mergeConfigs, validators, type Config } from "tailwind-merge";
22

3+
import {
4+
isArbitrarySpace,
5+
isArbitraryStep,
6+
isSpace,
7+
isStep,
8+
} from "./validators";
9+
10+
type ArrayElement<ArrayType extends readonly unknown[]> =
11+
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
12+
313
export function withUtopia(config: Config<string, string>) {
14+
type ClassGroupKeys = keyof typeof config.classGroups;
15+
type ClassGroup = (typeof config.classGroups)[ClassGroupKeys];
16+
type ClassDefinition = ArrayElement<ClassGroup>;
17+
interface ThemeGetter {
18+
(theme: (typeof config)["theme"]): ClassGroup;
19+
isThemeGetter: true;
20+
}
21+
22+
const isThemeGetter = (def: ClassDefinition): def is ThemeGetter =>
23+
typeof def === "function" && "isThemeGetter" in def && def.isThemeGetter;
24+
25+
const resolveThemeGetters = (
26+
group: ClassGroup,
27+
): Exclude<ClassDefinition, ThemeGetter>[] =>
28+
group.flatMap((def) =>
29+
isThemeGetter(def) ? resolveThemeGetters(def(config.theme)) : [def],
30+
);
31+
432
const classGroups = Object.entries(config.classGroups)
533
.filter(
634
([_name, group]) => group.length !== 0 && typeof group[0] !== "string",
735
)
36+
.filter(([_name, group]) => {
37+
const classGroup = Object.values(group[0])[0] as ClassGroup;
38+
39+
const resolvedClassGroup = resolveThemeGetters(classGroup);
40+
41+
return resolvedClassGroup.some(
42+
(def) =>
43+
def === validators.isLength || def === validators.isArbitraryLength,
44+
);
45+
})
846
.reduce((acc, [name, group]) => {
947
const groupName = Object.keys(group[0])[0];
10-
const def = [validators.isAny];
48+
49+
const classGroup =
50+
groupName === "text"
51+
? [{ [`~${groupName}`]: [isStep, isArbitraryStep] }]
52+
: [
53+
{ [`~${groupName}`]: [isSpace, isArbitrarySpace] },
54+
{ [`~-${groupName}`]: [isSpace, isArbitrarySpace] },
55+
];
1156

1257
return {
1358
...acc,
14-
[name]: [{ [`~${groupName}`]: def }, { [`~-${groupName}`]: def }],
59+
[name]: classGroup,
1560
};
1661
}, {});
1762

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { validators } from "tailwind-merge";
2+
3+
const tshirtUnitRegex = /^(\d*xs|xs|s|m|l|xl|\d*xl)$/;
4+
5+
function isRange(value: string) {
6+
if (!value.startsWith("[") || !value.endsWith("]")) return false;
7+
8+
value = value.slice(1, -1);
9+
10+
const parts = value.split("/");
11+
12+
if (parts.length < 1 || parts.length > 2) return false;
13+
14+
if (parts.length === 1) {
15+
return validators.isNumber(parts[0]);
16+
} else if (parts.length === 2) {
17+
const [a, b] = parts;
18+
19+
return (
20+
(a === "" || validators.isNumber(a)) &&
21+
(b === "" || validators.isNumber(b))
22+
);
23+
}
24+
25+
return false;
26+
}
27+
28+
function isStep(value: string) {
29+
return (
30+
value === "1" ||
31+
(value !== "x0" &&
32+
value.startsWith("x") &&
33+
validators.isNumber(value.slice(1)))
34+
);
35+
}
36+
37+
function isArbitraryStep(value: string) {
38+
return (
39+
(value.startsWith("[x") &&
40+
value.endsWith("]") &&
41+
validators.isNumber(value.slice(2, -1))) ||
42+
isRange(value)
43+
);
44+
}
45+
46+
function isSpace(value: string) {
47+
return tshirtUnitRegex.test(value);
48+
}
49+
50+
function isArbitrarySpace(value: string) {
51+
return isRange(value);
52+
}
53+
54+
export { isStep, isArbitraryStep, isSpace, isArbitrarySpace };

0 commit comments

Comments
 (0)