refactor: source shared design tokens from JSON
This commit is contained in:
@@ -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 };
|
||||
Reference in New Issue
Block a user