Files
wursor/packages/design-language/src/schema.ts
T
2026-04-26 09:09:55 +01:00

112 lines
4.6 KiB
TypeScript

import { z } from 'zod';
// ── Token schemas ─────────────────────────────────────────────────────────────
export const ColorTokenSchema = z.object({
value: z.string().regex(/^#[0-9A-Fa-f]{3,8}$|^rgba?\(|^hsl/, 'Must be a valid CSS color'),
/** Fluent 2 token name this maps to, e.g. "colorBrandBackground" */
fluentToken: z.string().optional(),
description: z.string().optional(),
});
export const TypographyTokenSchema = z.object({
fontFamily: z.string().optional(),
fontSize: z.union([z.string(), z.number()]).optional(),
fontWeight: z.union([z.string(), z.number()]).optional(),
lineHeight: z.union([z.string(), z.number()]).optional(),
letterSpacing: z.union([z.string(), z.number()]).optional(),
fluentToken: z.string().optional(),
});
export const SpacingTokenSchema = z.object({
value: z.union([z.string(), z.number()]),
fluentToken: z.string().optional(),
});
export const MotionTokenSchema = z.object({
duration: z.string().optional(),
easing: z.string().optional(),
fluentToken: z.string().optional(),
});
export const TokensSchema = z.object({
colors: z.record(ColorTokenSchema).optional(),
typography: z.record(TypographyTokenSchema).optional(),
spacing: z.record(SpacingTokenSchema).optional(),
motion: z.record(MotionTokenSchema).optional(),
});
// ── Component rules ───────────────────────────────────────────────────────────
export const PropRuleSchema = z.object({
allowed: z.array(z.unknown()).optional(),
forbidden: z.array(z.unknown()).optional(),
required: z.boolean().optional(),
type: z.string().optional(),
});
export const ComponentRuleSchema = z.object({
/** Allowed prop values, keyed by prop name */
props: z.record(PropRuleSchema).optional(),
/** Fluent 2 variants that are explicitly forbidden */
forbiddenVariants: z.array(z.string()).optional(),
/** ARIA attributes that are required on this component */
requiredAria: z.array(z.string()).optional(),
notes: z.string().optional(),
});
// ── Screen rules ──────────────────────────────────────────────────────────────
export const ScreenRuleSchema = z.object({
/** Components that are allowed to appear on this screen */
allowedComponents: z.array(z.string()).optional(),
/** Components that must be present on this screen */
requiredSections: z.array(z.string()).optional(),
layout: z.string().optional(),
notes: z.string().optional(),
});
// ── Voice / tone ──────────────────────────────────────────────────────────────
export const VoiceRuleSchema = z.object({
tone: z.string().optional(),
maxSentenceLength: z.number().optional(),
avoidWords: z.array(z.string()).optional(),
preferWords: z.array(z.string()).optional(),
examples: z.array(z.string()).optional(),
});
// ── Accessibility ─────────────────────────────────────────────────────────────
export const AccessibilitySchema = z.object({
wcagLevel: z.enum(['A', 'AA', 'AAA']).optional(),
contrastRatio: z.number().optional(),
customRules: z.array(z.string()).optional(),
});
// ── Design Language File ──────────────────────────────────────────────────────
export const DesignLanguageFileBodySchema = z.object({
/** Semantic version, e.g. "1.0.0" */
version: z.string().optional(),
/** Human-readable name, e.g. "Acme Design System" */
name: z.string().optional(),
tokens: TokensSchema.optional(),
/** Per-component rules, keyed by component display name */
components: z.record(ComponentRuleSchema).optional(),
/** Per-screen rules, keyed by screen name or route pattern */
screens: z.record(ScreenRuleSchema).optional(),
voice: VoiceRuleSchema.optional(),
accessibility: AccessibilitySchema.optional(),
});
export type DesignLanguageFileBody = z.infer<typeof DesignLanguageFileBodySchema>;
export type Tokens = z.infer<typeof TokensSchema>;
export type ComponentRule = z.infer<typeof ComponentRuleSchema>;
export type ScreenRule = z.infer<typeof ScreenRuleSchema>;