/** * W3C Design Token Community Group (DTCG) format types and Zod schemas. * * Implements the W3C DTCG specification for design token files. * @see https://www.designtokens.org/tr/2025.10/format/ */ import { z } from 'zod'; // --------------------------------------------------------------------------- // Token types per DTCG spec // --------------------------------------------------------------------------- export type DTCGTokenType = | 'color' | 'dimension' | 'fontFamily' | 'fontWeight' | 'duration' | 'cubicBezier' | 'number' | 'shadow' | 'border' | 'strokeStyle' | 'transition' | 'gradient' | 'typography'; // --------------------------------------------------------------------------- // Value types per DTCG spec // --------------------------------------------------------------------------- /** Color value (sRGB) */ export interface DTCGColorValue { colorSpace: string; components: number[]; hex?: string; alpha?: number; } /** Dimension value */ export interface DTCGDimensionValue { value: number; unit: 'px' | 'rem'; } /** Shadow value (single layer) */ export interface DTCGShadowValue { color: string | DTCGColorValue; offsetX: string | DTCGDimensionValue; offsetY: string | DTCGDimensionValue; blur: string | DTCGDimensionValue; spread: string | DTCGDimensionValue; inset?: boolean; } /** Border value */ export interface DTCGBorderValue { color: string | DTCGColorValue; width: string | DTCGDimensionValue; style: string; } /** Typography value */ export interface DTCGTypographyValue { fontFamily: string | string[]; fontSize: string | DTCGDimensionValue; fontWeight: number | string; letterSpacing?: string | DTCGDimensionValue; lineHeight?: string | number; } /** Transition value */ export interface DTCGTransitionValue { duration: string; delay?: string; timingFunction: number[]; } /** Gradient stop */ export interface DTCGGradientStop { color: string | DTCGColorValue; position: number; } /** Cubic bezier value */ export type DTCGCubicBezierValue = [number, number, number, number]; // --------------------------------------------------------------------------- // Token and group structures // --------------------------------------------------------------------------- /** A single design token */ export interface DTCGToken { $value: unknown; $type?: DTCGTokenType; $description?: string; $deprecated?: boolean | string; $extensions?: Record; } /** A group (recursive container for tokens and sub-groups) */ export interface DTCGGroup { $type?: DTCGTokenType; $description?: string; $deprecated?: boolean | string; $extensions?: Record; $extends?: string; [key: string]: DTCGToken | DTCGGroup | unknown; } /** Root file structure — a top-level group */ export type DTCGTokenFile = DTCGGroup; // --------------------------------------------------------------------------- // Resolved token (after parsing) // --------------------------------------------------------------------------- export interface ResolvedDTCGToken { /** Dot-separated path (e.g., "color.brand.primary") */ path: string; /** Resolved type (inherited from group if not set on token) */ type: DTCGTokenType; /** Raw $value from the token */ rawValue: unknown; /** CSS-compatible string value */ cssValue: string; /** Description */ description?: string; /** Whether token is deprecated */ deprecated?: boolean | string; /** Extensions data */ extensions?: Record; } // --------------------------------------------------------------------------- // Zod schemas for runtime validation // --------------------------------------------------------------------------- const dtcgTokenTypeSchema = z.enum([ 'color', 'dimension', 'fontFamily', 'fontWeight', 'duration', 'cubicBezier', 'number', 'shadow', 'border', 'strokeStyle', 'transition', 'gradient', 'typography', ]); const dtcgColorValueSchema = z.union([ z.string(), z.object({ colorSpace: z.string().default('srgb'), components: z.array(z.number()), hex: z.string().optional(), alpha: z.number().optional(), }), ]); const dtcgDimensionValueSchema = z.union([ z.string(), z.object({ value: z.number(), unit: z.enum(['px', 'rem']), }), ]); const dtcgShadowValueSchema = z.object({ color: dtcgColorValueSchema, offsetX: dtcgDimensionValueSchema, offsetY: dtcgDimensionValueSchema, blur: dtcgDimensionValueSchema, spread: dtcgDimensionValueSchema.optional(), inset: z.boolean().optional(), }); const dtcgBorderValueSchema = z.object({ color: dtcgColorValueSchema, width: dtcgDimensionValueSchema, style: z.string(), }); const dtcgTypographyValueSchema = z.object({ fontFamily: z.union([z.string(), z.array(z.string())]), fontSize: dtcgDimensionValueSchema, fontWeight: z.union([z.number(), z.string()]), letterSpacing: dtcgDimensionValueSchema.optional(), lineHeight: z.union([z.string(), z.number()]).optional(), }); const dtcgTransitionValueSchema = z.object({ duration: z.string(), delay: z.string().optional(), timingFunction: z.array(z.number()).length(4), }); const dtcgGradientStopSchema = z.object({ color: dtcgColorValueSchema, position: z.number(), }); const dtcgCubicBezierValueSchema = z.tuple([ z.number(), z.number(), z.number(), z.number(), ]); /** Schema for a single token node (has $value) */ const dtcgTokenSchema = z.object({ $value: z.unknown(), $type: dtcgTokenTypeSchema.optional(), $description: z.string().optional(), $deprecated: z.union([z.boolean(), z.string()]).optional(), $extensions: z.record(z.string(), z.unknown()).optional(), }).passthrough(); /** * Top-level schema for a DTCG token file. * Validates the root structure — individual tokens are validated by type * during parsing since the tree is recursive. */ export const dtcgTokenFileSchema = z.record(z.string(), z.unknown()).refine( (data) => { // A valid DTCG file should not be empty and should not be an array return data !== null && typeof data === 'object' && !Array.isArray(data); }, { message: 'DTCG token file must be a JSON object' }, ); // Export individual schemas for consumers export { dtcgTokenTypeSchema, dtcgTokenSchema, dtcgColorValueSchema, dtcgDimensionValueSchema, dtcgShadowValueSchema, dtcgBorderValueSchema, dtcgTypographyValueSchema, dtcgTransitionValueSchema, dtcgGradientStopSchema, dtcgCubicBezierValueSchema, };