codex/design-system-foundation #33

Merged
ksv741 merged 12 commits from codex/design-system-foundation into main 2026-06-21 16:44:32 +03:00
5 changed files with 335 additions and 0 deletions
Showing only changes of commit fdffe204e4 - Show all commits

View File

@ -0,0 +1,88 @@
import type { TokenCollection } from './types';
export const componentTokens = {
// Button
'button.font.weight': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' },
'button.radius': { $type: 'dimension' as const, $value: '{radius.md}' },
'button.padding.x': { $type: 'dimension' as const, $value: '{space.inset.lg}' },
'button.padding.y': { $type: 'dimension' as const, $value: '{space.inset.sm}' },
'button.primary.bg': { $type: 'color' as const, $value: '{color.action.primary}' },
'button.primary.text': { $type: 'color' as const, $value: '{color.text.inverse}' },
'button.primary.hover.bg': { $type: 'color' as const, $value: '{color.action.primaryHover}' },
'button.secondary.bg': { $type: 'color' as const, $value: '{color.surface.default}' },
'button.secondary.text': { $type: 'color' as const, $value: '{color.text.primary}' },
'button.secondary.border': { $type: 'color' as const, $value: '{color.border.default}' },
'button.danger.bg': { $type: 'color' as const, $value: '{color.action.danger}' },
'button.danger.text': { $type: 'color' as const, $value: '{color.text.inverse}' },
// IconButton
'iconButton.size.sm': { $type: 'dimension' as const, $value: '{size.control.sm}' },
'iconButton.size.md': { $type: 'dimension' as const, $value: '{size.control.md}' },
// Field
'field.radius': { $type: 'dimension' as const, $value: '{radius.md}' },
'field.border': { $type: 'color' as const, $value: '{color.border.default}' },
'field.border.focus': { $type: 'color' as const, $value: '{color.border.focus}' },
'field.border.error': { $type: 'color' as const, $value: '{color.feedback.error}' },
'field.label.weight': { $type: 'fontWeight' as const, $value: '{font.weight.medium}' },
// Checkbox
'checkbox.radius': { $type: 'dimension' as const, $value: '{radius.sm}' },
// Surface
'surface.radius': { $type: 'dimension' as const, $value: '{radius.lg}' },
'surface.padding': { $type: 'dimension' as const, $value: '{space.inset.lg}' },
// Card
'card.radius': { $type: 'dimension' as const, $value: '{radius.lg}' },
'card.shadow': { $type: 'shadow' as const, $value: '{shadow.sm}' },
'card.padding': { $type: 'dimension' as const, $value: '{space.inset.lg}' },
// Chip
'chip.radius': { $type: 'dimension' as const, $value: '{radius.pill}' },
'chip.height': { $type: 'dimension' as const, $value: '{size.control.sm}' },
// Badge
'badge.radius': { $type: 'dimension' as const, $value: '{radius.pill}' },
'badge.font.size': { $type: 'dimension' as const, $value: '{font.size.200}' },
// Alert
'alert.radius': { $type: 'dimension' as const, $value: '{radius.md}' },
'alert.info.bg': { $type: 'color' as const, $value: '{color.blue.50}' },
'alert.info.border': { $type: 'color' as const, $value: '{color.feedback.info}' },
'alert.success.bg': { $type: 'color' as const, $value: '{color.green.50}' },
'alert.success.border': { $type: 'color' as const, $value: '{color.feedback.success}' },
'alert.warning.bg': { $type: 'color' as const, $value: '{color.amber.50}' },
'alert.warning.border': { $type: 'color' as const, $value: '{color.feedback.warning}' },
'alert.error.bg': { $type: 'color' as const, $value: '{color.red.50}' },
'alert.error.border': { $type: 'color' as const, $value: '{color.feedback.error}' },
// Dialog
'dialog.radius': { $type: 'dimension' as const, $value: '{radius.lg}' },
'dialog.shadow': { $type: 'shadow' as const, $value: '{shadow.md}' },
'dialog.padding': { $type: 'dimension' as const, $value: '{space.6}' },
// Skeleton
'skeleton.radius': { $type: 'dimension' as const, $value: '{radius.sm}' },
// Progress
'progress.height': { $type: 'dimension' as const, $value: '{space.1}' },
'progress.radius': { $type: 'dimension' as const, $value: '{radius.pill}' },
'progress.bg': { $type: 'color' as const, $value: '{color.neutral.100}' },
'progress.fill': { $type: 'color' as const, $value: '{color.action.primary}' },
// Table
'table.header.weight': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' },
'table.cell.padding': { $type: 'dimension' as const, $value: '{space.inset.sm}' },
'table.row.hover.bg': { $type: 'color' as const, $value: '{color.surface.subtle}' },
// Metric
'metric.label.size': { $type: 'dimension' as const, $value: '{font.size.200}' },
'metric.label.weight': { $type: 'fontWeight' as const, $value: '{font.weight.medium}' },
'metric.value.weight': { $type: 'fontWeight' as const, $value: '{font.weight.bold}' },
'metric.gap': { $type: 'dimension' as const, $value: '{space.1}' },
// PageState
'pageState.icon.size': { $type: 'dimension' as const, $value: '{space.12}' },
'pageState.title.gap': { $type: 'dimension' as const, $value: '{space.4}' },
} as const satisfies TokenCollection;

View File

@ -0,0 +1,11 @@
export { primitiveTokens } from './primitives';
export { semanticLightTokens } from './semantic.light';
export { componentTokens } from './components';
export { resolveToken } from './resolveToken';
export type {
TokenType,
TokenValue,
TokenDefinition,
TokenCollection,
ResolvedToken,
} from './types';

View File

@ -0,0 +1,103 @@
import type { TokenCollection } from './types';
export const primitiveTokens = {
// Colors — neutral
'color.neutral.0': { $type: 'color' as const, $value: '#ffffff' },
'color.neutral.50': { $type: 'color' as const, $value: '#f5f5f5' },
'color.neutral.100': { $type: 'color' as const, $value: '#e8e8e8' },
'color.neutral.200': { $type: 'color' as const, $value: '#d1d1d1' },
'color.neutral.400': { $type: 'color' as const, $value: '#a3a3a3' },
'color.neutral.600': { $type: 'color' as const, $value: '#666666' },
'color.neutral.800': { $type: 'color' as const, $value: '#333333' },
'color.neutral.900': { $type: 'color' as const, $value: '#1a1a1a' },
// Colors — green (positive)
'color.green.50': { $type: 'color' as const, $value: '#e8f5e9' },
'color.green.100': { $type: 'color' as const, $value: '#c8e6c9' },
'color.green.600': { $type: 'color' as const, $value: '#2e7d32' },
'color.green.700': { $type: 'color' as const, $value: '#176747' },
'color.green.800': { $type: 'color' as const, $value: '#1b5e20' },
// Colors — red (negative)
'color.red.50': { $type: 'color' as const, $value: '#ffebee' },
'color.red.600': { $type: 'color' as const, $value: '#e53935' },
'color.red.700': { $type: 'color' as const, $value: '#c62828' },
// Colors — amber (warning)
'color.amber.50': { $type: 'color' as const, $value: '#fff8e1' },
'color.amber.600': { $type: 'color' as const, $value: '#ffb300' },
// Colors — blue (info)
'color.blue.50': { $type: 'color' as const, $value: '#e3f2fd' },
'color.blue.600': { $type: 'color' as const, $value: '#1e88e5' },
// Spacing (4px grid)
'space.0': { $type: 'dimension' as const, $value: '0px' },
'space.1': { $type: 'dimension' as const, $value: '4px' },
'space.2': { $type: 'dimension' as const, $value: '8px' },
'space.3': { $type: 'dimension' as const, $value: '12px' },
'space.4': { $type: 'dimension' as const, $value: '16px' },
'space.5': { $type: 'dimension' as const, $value: '20px' },
'space.6': { $type: 'dimension' as const, $value: '24px' },
'space.8': { $type: 'dimension' as const, $value: '32px' },
'space.10': { $type: 'dimension' as const, $value: '40px' },
'space.12': { $type: 'dimension' as const, $value: '48px' },
// Border radius
'radius.none': { $type: 'dimension' as const, $value: '0px' },
'radius.sm': { $type: 'dimension' as const, $value: '4px' },
'radius.md': { $type: 'dimension' as const, $value: '8px' },
'radius.lg': { $type: 'dimension' as const, $value: '12px' },
'radius.pill': { $type: 'dimension' as const, $value: '9999px' },
// Font family
'font.family.sans': {
$type: 'fontFamily' as const,
$value: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
},
'font.family.mono': {
$type: 'fontFamily' as const,
$value: ['SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
},
// Font size (100 = 10px, 700 = 48px scale)
'font.size.100': { $type: 'dimension' as const, $value: '10px' },
'font.size.200': { $type: 'dimension' as const, $value: '12px' },
'font.size.300': { $type: 'dimension' as const, $value: '14px' },
'font.size.400': { $type: 'dimension' as const, $value: '16px' },
'font.size.500': { $type: 'dimension' as const, $value: '20px' },
'font.size.600': { $type: 'dimension' as const, $value: '24px' },
'font.size.700': { $type: 'dimension' as const, $value: '32px' },
// Font weight
'font.weight.regular': { $type: 'fontWeight' as const, $value: 400 },
'font.weight.medium': { $type: 'fontWeight' as const, $value: 500 },
'font.weight.semibold': { $type: 'fontWeight' as const, $value: 600 },
'font.weight.bold': { $type: 'fontWeight' as const, $value: 700 },
// Line height
'lineHeight.tight': { $type: 'dimension' as const, $value: '1.25' },
'lineHeight.normal': { $type: 'dimension' as const, $value: '1.5' },
'lineHeight.relaxed': { $type: 'dimension' as const, $value: '1.75' },
// Shadows
'shadow.none': { $type: 'shadow' as const, $value: 'none' },
'shadow.sm': { $type: 'shadow' as const, $value: '0px 1px 2px 0px rgba(0,0,0,0.08)' },
'shadow.md': { $type: 'shadow' as const, $value: '0px 4px 8px 0px rgba(0,0,0,0.12)' },
// Duration
'duration.instant': { $type: 'duration' as const, $value: 0 },
'duration.fast': { $type: 'duration' as const, $value: 150 },
'duration.normal': { $type: 'duration' as const, $value: 250 },
// Easing
'easing.standard': { $type: 'cubicBezier' as const, $value: [0.4, 0, 0.2, 1] as const },
// Control sizes
'size.control.sm': { $type: 'dimension' as const, $value: '32px' },
'size.control.md': { $type: 'dimension' as const, $value: '40px' },
// Focus ring
'size.focus.ring': { $type: 'dimension' as const, $value: '2px' },
'size.focus.offset': { $type: 'dimension' as const, $value: '2px' },
} as const satisfies TokenCollection;

View File

@ -0,0 +1,61 @@
import type { TokenCollection } from './types';
export const semanticLightTokens = {
// Canvas
'color.canvas': { $type: 'color' as const, $value: '{color.neutral.50}' },
// Surface
'color.surface.default': { $type: 'color' as const, $value: '{color.neutral.0}' },
'color.surface.subtle': { $type: 'color' as const, $value: '{color.neutral.50}' },
'color.surface.raised': { $type: 'color' as const, $value: '{color.neutral.0}' },
// Text
'color.text.primary': { $type: 'color' as const, $value: '{color.neutral.900}' },
'color.text.secondary': { $type: 'color' as const, $value: '{color.neutral.600}' },
'color.text.disabled': { $type: 'color' as const, $value: '{color.neutral.400}' },
'color.text.inverse': { $type: 'color' as const, $value: '{color.neutral.0}' },
// Border
'color.border.subtle': { $type: 'color' as const, $value: '{color.neutral.100}' },
'color.border.default': { $type: 'color' as const, $value: '{color.neutral.200}' },
'color.border.strong': { $type: 'color' as const, $value: '{color.neutral.400}' },
'color.border.focus': { $type: 'color' as const, $value: '{color.blue.600}' },
// Action
'color.action.primary': { $type: 'color' as const, $value: '{color.green.700}' },
'color.action.primaryHover': { $type: 'color' as const, $value: '{color.green.800}' },
'color.action.secondary': { $type: 'color' as const, $value: '{color.neutral.600}' },
'color.action.danger': { $type: 'color' as const, $value: '{color.red.600}' },
// Feedback
'color.feedback.info': { $type: 'color' as const, $value: '{color.blue.600}' },
'color.feedback.success': { $type: 'color' as const, $value: '{color.green.600}' },
'color.feedback.warning': { $type: 'color' as const, $value: '{color.amber.600}' },
'color.feedback.error': { $type: 'color' as const, $value: '{color.red.600}' },
// Finance
'color.finance.positive': { $type: 'color' as const, $value: '{color.green.700}' },
'color.finance.negative': { $type: 'color' as const, $value: '{color.red.700}' },
'color.finance.neutral': { $type: 'color' as const, $value: '{color.neutral.600}' },
// Typography
'font.family.body': { $type: 'fontFamily' as const, $value: '{font.family.sans}' },
'font.weight.body': { $type: 'fontWeight' as const, $value: '{font.weight.regular}' },
'font.weight.heading': { $type: 'fontWeight' as const, $value: '{font.weight.semibold}' },
'font.weight.strong': { $type: 'fontWeight' as const, $value: '{font.weight.bold}' },
// Spacing
'space.inset.sm': { $type: 'dimension' as const, $value: '{space.2}' },
'space.inset.md': { $type: 'dimension' as const, $value: '{space.3}' },
'space.inset.lg': { $type: 'dimension' as const, $value: '{space.4}' },
'space.stack.sm': { $type: 'dimension' as const, $value: '{space.1}' },
'space.stack.md': { $type: 'dimension' as const, $value: '{space.2}' },
'space.stack.lg': { $type: 'dimension' as const, $value: '{space.4}' },
'space.inline.sm': { $type: 'dimension' as const, $value: '{space.1}' },
'space.inline.md': { $type: 'dimension' as const, $value: '{space.2}' },
'space.inline.lg': { $type: 'dimension' as const, $value: '{space.3}' },
// Focus ring
'focus.ring.width': { $type: 'dimension' as const, $value: '{size.focus.ring}' },
'focus.ring.offset': { $type: 'dimension' as const, $value: '{size.focus.offset}' },
'focus.ring.color': { $type: 'color' as const, $value: '{color.blue.600}' },
} as const satisfies TokenCollection;

View File

@ -0,0 +1,72 @@
import { describe, it, expect } from 'vitest';
import { primitiveTokens } from './primitives';
import { semanticLightTokens } from './semantic.light';
import { componentTokens } from './components';
import { resolveToken } from './resolveToken';
const aliasPattern = /^\{([^}]+)\}$/;
const isAlias = (value: string | number | readonly number[] | readonly string[]): boolean =>
typeof value === 'string' && aliasPattern.test(value);
const allTokens = {
...primitiveTokens,
...semanticLightTokens,
...componentTokens,
} as const;
describe('token integrity', () => {
it('primitives contain no aliases', () => {
for (const [name, def] of Object.entries(primitiveTokens)) {
expect(isAlias(def.$value), `${name} should not be an alias`).toBe(false);
}
});
it('semantic tokens only use aliases', () => {
for (const [name, def] of Object.entries(semanticLightTokens)) {
expect(isAlias(def.$value), `${name} should be an alias`).toBe(true);
}
});
it('component tokens only use aliases', () => {
for (const [name, def] of Object.entries(componentTokens)) {
expect(isAlias(def.$value), `${name} should be an alias`).toBe(true);
}
});
it('all tokens resolve without errors', () => {
for (const name of Object.keys(allTokens)) {
expect(() => resolveToken(allTokens, name), `Failed to resolve ${name}`).not.toThrow();
}
});
it('has unique names across all collections', () => {
const allNames = [
...Object.keys(primitiveTokens),
...Object.keys(semanticLightTokens),
...Object.keys(componentTokens),
];
const uniqueNames = new Set(allNames);
expect(uniqueNames.size).toBe(allNames.length);
});
it('semantic tokens reference only primitive tokens', () => {
const primitiveNames = new Set(Object.keys(primitiveTokens));
for (const [name, def] of Object.entries(semanticLightTokens)) {
if (typeof def.$value === 'string' && aliasPattern.test(def.$value)) {
const target = def.$value.match(aliasPattern)![1];
expect(primitiveNames.has(target), `${name} references non-primitive "${target}"`).toBe(
true,
);
}
}
});
it('component tokens avoid direct primitive value literals', () => {
const literalPattern = /(px|#|rgba|rgb)/;
for (const [name, def] of Object.entries(componentTokens)) {
if (typeof def.$value === 'string') {
expect(literalPattern.test(def.$value), `${name} contains a literal value`).toBe(false);
}
}
});
});