codex/design-system-foundation #33
3172
package-lock.json
generated
3172
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": {
|
||||||
|
|||||||
30
packages/design-system/src/tokens/resolveToken.test.ts
Normal file
30
packages/design-system/src/tokens/resolveToken.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
35
packages/design-system/src/tokens/resolveToken.ts
Normal file
35
packages/design-system/src/tokens/resolveToken.ts
Normal 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;
|
||||||
|
}
|
||||||
12
packages/design-system/src/tokens/types.ts
Normal file
12
packages/design-system/src/tokens/types.ts
Normal 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 };
|
||||||
Loading…
x
Reference in New Issue
Block a user