Validate Eidos motion and icon primitives

active-uix
dev 5 months ago
parent 45d266b1c6
commit 110c5026b9

@ -28,10 +28,14 @@ Actualizacion Eidos ecosystem 2026-05-16:
CSS kebab-case antes de generar selectores o custom properties desde config.
- Claves authorables de tipografia (`families`, `weights`, `styles`) deben
generar sufijos CSS validos; `typography.sizes` permanece canonico.
- Motion e icon dejan de depender solo de tipos TS: sus key sets canonicos
(`DURATION_KEYS`, `EASE_KEYS`, `MOTION_DISTANCE_KEYS`,
`MOTION_SCALE_KEYS`, `ICON_STROKE_WIDTH_KEYS`) se exportan y validan en
runtime antes de generar CSS.
- Validado:
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 29 tests OK.
- `npx vitest run src/uix/eidos/active-eidos-config.test.ts` -> 30 tests OK.
- `npx vitest run src/uix/eidos/active-eidos.test.ts` -> 19 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 69 tests OK.
- `npx vitest run src/uix/eidos` -> 7 archivos, 70 tests OK.
- `npm run check` -> 0 errores, 0 warnings.
Actualizacion permutation runner 2026-05-16:

@ -497,6 +497,31 @@ describe('ActiveEidos config', () => {
).toBe(true)
})
it('validates motion and icon primitive keys', () => {
const cfg = structuredClone(THEME_BASE_OPTIONS)
delete (cfg.primitives.motion!.duration as Record<string, string>)['fast']
;(cfg.primitives.motion!.ease as Record<string, string>)['bounce'] = 'ease-out'
;(cfg.primitives.motion as { stagger: string }).stagger = ''
delete (cfg.primitives.icon!.size as Record<string, string>)['md']
;(cfg.primitives.icon!.strokeWidth as Record<string, number>)['hairline'] = 1
const report = createEidos(cfg).validate()
expect(report.ok).toBe(false)
expect(report.issues.some((issue) => issue.path === 'primitives.motion.duration.fast')).toBe(
true
)
expect(report.issues.some((issue) => issue.path === 'primitives.motion.ease.bounce')).toBe(
true
)
expect(report.issues.some((issue) => issue.path === 'primitives.motion.stagger')).toBe(true)
expect(report.issues.some((issue) => issue.path === 'primitives.icon.size.md')).toBe(true)
expect(
report.issues.some((issue) => issue.path === 'primitives.icon.strokeWidth.hairline')
).toBe(true)
})
it('validates recipe token keys and values', () => {
const cfg = structuredClone(THEME_BASE_OPTIONS)

@ -178,7 +178,12 @@ export {
CONTENT_WIDTH_KEYS,
DENSITIES,
DENSITY_KEYS,
DURATION_KEYS,
EASE_KEYS,
HIERARCHY_COLOR_ROLES,
ICON_STROKE_WIDTH_KEYS,
MOTION_DISTANCE_KEYS,
MOTION_SCALE_KEYS,
OPACITY_KEYS,
SHADOW_KEYS,
SHADOW_PHYSICAL_KEYS,

@ -199,10 +199,16 @@ export type RecipeTokenValue = string | number;
export type RecipeTokenMap = Readonly<Record<string, RecipeTokenValue>>;
export type RecipeTokenSet = Readonly<Record<string, RecipeTokenMap>>;
export type DurationKey = 'instant' | 'fast' | 'normal' | 'moderate' | 'slow';
export type EaseKey = 'default' | 'out' | 'in' | 'spring' | 'alert' | 'symmetric';
export type MotionDistanceKey = 'xs' | 'sm' | 'md' | 'lg';
export type MotionScaleKey = 'enter' | 'press';
export const DURATION_KEYS = ['instant', 'fast', 'normal', 'moderate', 'slow'] as const;
export const EASE_KEYS = ['default', 'out', 'in', 'spring', 'alert', 'symmetric'] as const;
export const MOTION_DISTANCE_KEYS = ['xs', 'sm', 'md', 'lg'] as const;
export const MOTION_SCALE_KEYS = ['enter', 'press'] as const;
export const ICON_STROKE_WIDTH_KEYS = ['sm', 'md', 'lg'] as const;
export type DurationKey = (typeof DURATION_KEYS)[number];
export type EaseKey = (typeof EASE_KEYS)[number];
export type MotionDistanceKey = (typeof MOTION_DISTANCE_KEYS)[number];
export type MotionScaleKey = (typeof MOTION_SCALE_KEYS)[number];
export interface MotionPrimitiveSet {
readonly duration: Record<DurationKey, string>;
@ -213,7 +219,7 @@ export interface MotionPrimitiveSet {
}
export type IconSizeKey = Exclude<Size, 'full'>;
export type IconStrokeWidthKey = 'sm' | 'md' | 'lg';
export type IconStrokeWidthKey = (typeof ICON_STROKE_WIDTH_KEYS)[number];
export interface IconPrimitiveSet {
readonly size: Record<IconSizeKey, string>;

@ -7,6 +7,11 @@ import {
CONTAINER_WIDTH_KEYS,
CONTENT_WIDTH_KEYS,
DENSITY_KEYS,
DURATION_KEYS,
EASE_KEYS,
ICON_STROKE_WIDTH_KEYS,
MOTION_DISTANCE_KEYS,
MOTION_SCALE_KEYS,
OPACITY_KEYS,
SHADOW_KEYS,
SIZE_PRIMITIVE_KEYS,
@ -45,6 +50,11 @@ const BORDER_STYLE_KEY_SET = new Set<string>(BORDER_STYLE_KEYS)
const CONTAINER_WIDTH_KEY_SET = new Set<string>(CONTAINER_WIDTH_KEYS)
const CONTENT_WIDTH_KEY_SET = new Set<string>(CONTENT_WIDTH_KEYS)
const DENSITY_KEY_SET = new Set<string>(DENSITY_KEYS)
const DURATION_KEY_SET = new Set<string>(DURATION_KEYS)
const EASE_KEY_SET = new Set<string>(EASE_KEYS)
const ICON_STROKE_WIDTH_KEY_SET = new Set<string>(ICON_STROKE_WIDTH_KEYS)
const MOTION_DISTANCE_KEY_SET = new Set<string>(MOTION_DISTANCE_KEYS)
const MOTION_SCALE_KEY_SET = new Set<string>(MOTION_SCALE_KEYS)
const OPACITY_KEY_SET = new Set<string>(OPACITY_KEYS)
const SHADOW_KEY_SET = new Set<string>(SHADOW_KEYS)
const SIZE_PRIMITIVE_KEY_SET = new Set<string>(SIZE_PRIMITIVE_KEYS)
@ -96,6 +106,8 @@ export function validateEidosConfig(options: EidosConfig): EidosValidationReport
validateBorderPrimitives(options, issues)
validateLayoutPrimitives(options, issues)
validateDensityPrimitives(options, issues)
validateMotionPrimitives(options, issues)
validateIconPrimitives(options, issues)
validateRecipeTokens(options, issues)
validateRecordPrimitive(
'primitives.opacity',
@ -629,6 +641,55 @@ function validateDensityPrimitives(options: EidosConfig, issues: EidosValidation
)
}
function validateMotionPrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void {
const motion = options.primitives.motion
if (!motion) return
validateRecordPrimitive(
'primitives.motion.duration',
motion.duration,
DURATION_KEYS,
DURATION_KEY_SET,
issues
)
validateRecordPrimitive('primitives.motion.ease', motion.ease, EASE_KEYS, EASE_KEY_SET, issues)
validateRecordPrimitive(
'primitives.motion.distance',
motion.distance,
MOTION_DISTANCE_KEYS,
MOTION_DISTANCE_KEY_SET,
issues
)
validateRecordPrimitive(
'primitives.motion.scale',
motion.scale,
MOTION_SCALE_KEYS,
MOTION_SCALE_KEY_SET,
issues
)
validateNonEmptyCssValue('primitives.motion.stagger', motion.stagger, issues)
}
function validateIconPrimitives(options: EidosConfig, issues: EidosValidationIssue[]): void {
const icon = options.primitives.icon
if (!icon) return
validateRecordPrimitive(
'primitives.icon.size',
icon.size,
SIZE_PRIMITIVE_KEYS,
SIZE_PRIMITIVE_KEY_SET,
issues
)
validateRecordPrimitive(
'primitives.icon.strokeWidth',
icon.strokeWidth,
ICON_STROKE_WIDTH_KEYS,
ICON_STROKE_WIDTH_KEY_SET,
issues
)
}
function validateRecipeTokens(options: EidosConfig, issues: EidosValidationIssue[]): void {
for (const [component, tokens] of Object.entries(options.recipes ?? {})) {
if (!isCssTokenSegment(component)) {

Loading…
Cancel
Save

Powered by TurnKey Linux.