refactor: source shared design tokens from JSON

This commit is contained in:
2026-09-08 19:43:49 +03:00
parent d762ba5034
commit 0f50f15658
8 changed files with 791 additions and 93 deletions
+149
View File
@@ -0,0 +1,149 @@
import { z } from "zod";
const hexColor = z.string().regex(/^#(?:[\da-f]{3}|[\da-f]{6})$/i);
const finite = z.number().finite();
const positive = finite.positive();
const shade = z.union([
z.literal(0),
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4),
z.literal(5),
z.literal(6),
z.literal(7),
z.literal(8),
z.literal(9),
]);
const scale = <T extends z.ZodTypeAny>(value: T) =>
z.object({ xs: value, sm: value, md: value, lg: value, xl: value }).strict();
const role = z
.object({
color: z.string().min(1),
shade: shade.nullable(),
description: z.string().min(1),
})
.strict();
const heading = z.object({ fontSize: positive, lineHeight: positive }).strict();
const shadow = z
.object({
offsetX: finite,
offsetY: finite,
blur: finite.nonnegative(),
spread: finite,
color: z.enum(["white", "black"]),
opacity: finite.min(0).max(1),
})
.strict();
const designTokensSchema = z
.object({
formatVersion: z.literal(1),
description: z.string().min(1),
color: z
.object({
base: z.object({ white: hexColor, black: hexColor }).strict(),
palettes: z.record(
z
.string()
.regex(/^[a-z][a-z0-9-]*$/)
.refine(
(name) => name !== "dimmed" && name !== "bright",
"Palette name is reserved by Mantine",
),
z.array(hexColor).length(10),
),
primaryShade: z.object({ light: shade, dark: shade }).strict(),
roles: z.record(z.string().regex(/^[a-z][a-z0-9-]*$/), role),
})
.strict(),
typography: z
.object({
fontFamily: z.string().min(1),
fontFamilyMonospace: z.string().min(1),
unit: z.literal("px"),
fontSizes: scale(positive),
fontWeights: z
.object({
regular: finite.min(1).max(1000),
medium: finite.min(1).max(1000),
bold: finite.min(1).max(1000),
})
.strict(),
lineHeights: scale(positive),
headings: z
.object({
h1: heading,
h2: heading,
h3: heading,
h4: heading,
h5: heading,
h6: heading,
})
.strict(),
})
.strict(),
spacing: z
.object({ unit: z.literal("px"), values: scale(finite.nonnegative()) })
.strict(),
radius: z
.object({
unit: z.literal("px"),
default: z.enum(["xs", "sm", "md", "lg", "xl"]),
values: scale(finite.nonnegative()),
})
.strict(),
shadows: z
.object({ unit: z.literal("px"), values: scale(z.array(shadow).min(1)) })
.strict(),
breakpoints: z
.object({ unit: z.literal("em"), values: scale(positive) })
.strict(),
})
.strict()
.superRefine((tokens, context) => {
const { base, palettes, roles } = tokens.color;
for (const required of ["canvas", "text", "primary"]) {
if (!Object.hasOwn(roles, required)) {
context.addIssue({
code: "custom",
path: ["color", "roles", required],
message: "Required semantic role is missing",
});
}
}
for (const [name, value] of Object.entries(roles)) {
const isPalette = Object.hasOwn(palettes, value.color);
const isBase = Object.hasOwn(base, value.color);
if ((!isPalette && !isBase) || (isBase && value.shade !== null)) {
context.addIssue({
code: "custom",
path: ["color", "roles", name],
message:
"Role must reference an existing palette shade or an unshaded base color",
});
}
}
if (
roles.primary &&
(!Object.hasOwn(palettes, roles.primary.color) ||
roles.primary.shade !== null)
) {
context.addIssue({
code: "custom",
path: ["color", "roles", "primary"],
message: "Primary must reference a palette with the default shade",
});
}
for (const name of Object.keys(base)) {
if (Object.hasOwn(palettes, name)) {
context.addIssue({
code: "custom",
path: ["color", "palettes", name],
message: "Palette name conflicts with a base color",
});
}
}
});
export { designTokensSchema };