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 2994 additions and 264 deletions
Showing only changes of commit 129282d634 - Show all commits

3172
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,8 @@
"workspaces": [ "workspaces": [
"apps/backend", "apps/backend",
"apps/frontend", "apps/frontend",
"apps/docs" "apps/docs",
"packages/design-system"
], ],
"scripts": { "scripts": {
"dev:backend": "npm run start:dev -w apps/backend", "dev:backend": "npm run start:dev -w apps/backend",
@ -18,6 +19,12 @@
"format:check": "prettier --check \"**/*.{ts,tsx}\"", "format:check": "prettier --check \"**/*.{ts,tsx}\"",
"dev:docs": "npm run dev -w apps/docs", "dev:docs": "npm run dev -w apps/docs",
"build:docs": "npm run build -w apps/docs", "build:docs": "npm run build -w apps/docs",
"build:design-system": "npm run build -w packages/design-system",
"test:design-system": "npm run test -w packages/design-system",
"lint:design-system": "npm run lint -w packages/design-system",
"storybook": "npm run storybook -w packages/design-system",
"build:storybook": "npm run build-storybook -w packages/design-system",
"test:storybook": "npm run test:storybook -w packages/design-system",
"prepare": "husky" "prepare": "husky"
}, },
"lint-staged": { "lint-staged": {

View File

@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { resolveToken } from './resolveToken';
import type { TokenCollection } from './types';
describe('resolveToken', () => {
it('resolves an alias chain and preserves the declared type', () => {
const tokens = {
base: { $type: 'color' as const, $value: '#176747' },
semantic: { $type: 'color' as const, $value: '{base}' },
component: { $type: 'color' as const, $value: '{semantic}' },
} satisfies TokenCollection;
expect(resolveToken(tokens, 'component')).toEqual({ type: 'color', value: '#176747' });
});
it.each([
['missing alias', { a: { $type: 'color', $value: '{missing}' } }, /Unknown token/],
[
'cycle',
{ a: { $type: 'color', $value: '{b}' }, b: { $type: 'color', $value: '{a}' } },
/cycle/i,
],
[
'type mismatch',
{ a: { $type: 'color', $value: '#fff' }, b: { $type: 'dimension', $value: '{a}' } },
/type/i,
],
] as const)('rejects %s', (_name, tokens, error) => {
expect(() => resolveToken(tokens, 'b')).toThrow(error);
});
});

View File

@ -0,0 +1,35 @@
import type { TokenCollection, ResolvedToken } from './types';
const aliasPattern = /^\{([^}]+)\}$/;
export function resolveToken(
tokens: TokenCollection,
name: string,
visited = new Set<string>(),
): ResolvedToken {
if (visited.has(name)) {
throw new Error(`Cycle detected: ${name} has already been visited in this chain`);
}
const def = tokens[name];
if (!def) {
throw new Error(`Unknown token: ${name}`);
}
const match = aliasPattern.exec(String(def.$value));
if (!match) {
return { type: def.$type, value: def.$value };
}
const targetName = match[1];
visited.add(name);
const resolved = resolveToken(tokens, targetName, visited);
if (resolved.type !== def.$type) {
throw new Error(
`Type mismatch: token "${name}" has type "${def.$type}" but alias target "${targetName}" has type "${resolved.type}"`,
);
}
return resolved;
}

View File

@ -0,0 +1,12 @@
export type TokenType =
| 'color'
| 'dimension'
| 'fontFamily'
| 'fontWeight'
| 'duration'
| 'cubicBezier'
| 'shadow';
export type TokenValue = string | number | readonly number[] | readonly string[];
export type TokenDefinition = { readonly $type: TokenType; readonly $value: TokenValue };
export type TokenCollection = Readonly<Record<string, TokenDefinition>>;
export type ResolvedToken = { readonly type: TokenType; readonly value: TokenValue };