Skip to content

Commit 9d949f3

Browse files
committed
feat: added logic to define tokens
1 parent f09e4c3 commit 9d949f3

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createTokens } from './tokens';
3+
4+
describe('createTokens', () => {
5+
it('should create styles function', () => {
6+
const { styles } = createTokens({
7+
colors: {
8+
primary: '#000000',
9+
secondary: '#ffffff',
10+
},
11+
spacing: {
12+
sm: 8,
13+
md: 16,
14+
},
15+
});
16+
17+
expect(typeof styles).toBe('function');
18+
});
19+
20+
it('should resolve token references in styles', () => {
21+
const { styles } = createTokens({
22+
colors: {
23+
primary: '#000000',
24+
},
25+
spacing: {
26+
sm: 8,
27+
},
28+
});
29+
30+
const result = styles({
31+
base: {
32+
backgroundColor: '$colors.primary',
33+
padding: '$spacing.sm',
34+
},
35+
variants: {},
36+
});
37+
38+
expect(result()).toEqual({
39+
backgroundColor: '#000000',
40+
padding: 8,
41+
});
42+
});
43+
44+
it('should resolve token references in variants', () => {
45+
const { styles } = createTokens({
46+
colors: {
47+
primary: '#000000',
48+
},
49+
borderStyles: {
50+
default: 'solid',
51+
},
52+
});
53+
54+
const result = styles({
55+
base: {},
56+
variants: {
57+
type: {
58+
primary: {
59+
backgroundColor: '$colors.primary',
60+
borderStyle: '$borderStyles.default',
61+
},
62+
},
63+
},
64+
});
65+
66+
expect(result({ type: 'primary' })).toEqual({
67+
backgroundColor: '#000000',
68+
borderStyle: 'solid',
69+
});
70+
});
71+
72+
it('should handle compound variants', () => {
73+
const { styles } = createTokens({
74+
colors: {
75+
primary: '#000000',
76+
},
77+
spacing: {
78+
sm: 8,
79+
},
80+
});
81+
82+
const result = styles({
83+
base: {},
84+
variants: {
85+
size: { small: {} },
86+
type: { primary: {} },
87+
},
88+
compoundVariants: [{
89+
variants: { size: 'small' as const, type: 'primary' as const },
90+
style: {
91+
backgroundColor: '$colors.primary',
92+
padding: '$spacing.sm',
93+
},
94+
}],
95+
});
96+
97+
expect(result({ size: 'small', type: 'primary' })).toEqual({
98+
backgroundColor: '#000000',
99+
padding: 8,
100+
});
101+
});
102+
103+
it('should ignore missing token references', () => {
104+
const { styles } = createTokens({
105+
colors: {
106+
primary: '#000000',
107+
},
108+
});
109+
110+
const result = styles({
111+
base: {
112+
backgroundColor: '$colors.nonexistent',
113+
color: '$colors.primary',
114+
},
115+
variants: {},
116+
});
117+
118+
expect(result()).toEqual({
119+
color: '#000000',
120+
});
121+
});
122+
123+
it('should support all allowed token categories', () => {
124+
const { styles } = createTokens({
125+
colors: { primary: '#000000' },
126+
spacing: { sm: 8 },
127+
fontSizes: { base: 16 },
128+
fonts: { body: 'Arial' },
129+
lineHeight: { normal: 1.5 },
130+
borderWidth: { thin: 1 },
131+
borderStyles: { default: 'solid' },
132+
});
133+
134+
const result = styles({
135+
base: {
136+
color: '$colors.primary',
137+
padding: '$spacing.sm',
138+
fontSize: '$fontSizes.base',
139+
fontFamily: '$fonts.body',
140+
lineHeight: '$lineHeight.normal',
141+
borderWidth: '$borderWidth.thin',
142+
borderStyle: '$borderStyles.default',
143+
},
144+
variants: {},
145+
});
146+
147+
expect(result()).toEqual({
148+
color: '#000000',
149+
padding: 8,
150+
fontSize: 16,
151+
fontFamily: 'Arial',
152+
lineHeight: 1.5,
153+
borderWidth: 1,
154+
borderStyle: 'solid',
155+
});
156+
});
157+
});

packages/jacaranda/src/tokens.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { styles as baseStyles } from './index';
2+
import type { StyleObject } from './index';
3+
4+
// Define allowed token categories and their value types
5+
interface AllowedTokenCategories {
6+
colors: string;
7+
spacing: number;
8+
fontSizes: number;
9+
fonts: string;
10+
lineHeight: number;
11+
borderWidth: number;
12+
borderStyles: 'solid' | 'dashed' | 'dotted' | 'none';
13+
}
14+
15+
// Update TokenConfig to only allow specific categories
16+
type TokenConfig = {
17+
[K in keyof AllowedTokenCategories]?: Record<string, AllowedTokenCategories[K]>;
18+
};
19+
20+
interface CreateTokensReturn {
21+
styles: typeof baseStyles;
22+
}
23+
24+
// Helper to resolve token references in style objects
25+
function resolveTokens(style: StyleObject, tokens: TokenConfig): StyleObject {
26+
return Object.entries(style).reduce<Record<string, any>>((acc, [key, value]) => {
27+
if (typeof value !== 'string' || !value.startsWith('$')) {
28+
acc[key] = value;
29+
return acc;
30+
}
31+
32+
const tokenPath = value.slice(1).split('.');
33+
const [category, token] = tokenPath;
34+
35+
const tokenValue = tokens[category as keyof TokenConfig]?.[token];
36+
if (tokenValue !== undefined) {
37+
acc[key] = tokenValue;
38+
}
39+
40+
return acc;
41+
}, {}) as StyleObject;
42+
}
43+
44+
/**
45+
* Creates a token system and returns the styles function
46+
*/
47+
export function createTokens<T extends TokenConfig>(tokenConfig: T): CreateTokensReturn {
48+
// Create the tokens object
49+
const tokens = Object.entries(tokenConfig).reduce((acc, [category, values]) => {
50+
return {
51+
...acc,
52+
[category]: values,
53+
};
54+
}, {} as any);
55+
56+
// Create a wrapped styles function that resolves token references
57+
const wrappedStyles: typeof baseStyles = (config: any) => {
58+
// Resolve tokens in base styles
59+
const resolvedBase = config.base ? resolveTokens(config.base, tokens) : config.base;
60+
61+
// Resolve tokens in variants
62+
const resolvedVariants = config.variants ? Object.entries(config.variants).reduce((acc, [key, variantGroup]: [string, any]) => {
63+
const resolvedGroup = Object.entries(variantGroup).reduce((groupAcc, [variantKey, styles]: [string, any]) => {
64+
return {
65+
...groupAcc,
66+
[variantKey]: resolveTokens(styles, tokens),
67+
};
68+
}, {});
69+
return { ...acc, [key]: resolvedGroup };
70+
}, {}) : {};
71+
72+
// Resolve tokens in compound variants
73+
const resolvedCompoundVariants = config.compoundVariants?.map((compound: any) => ({
74+
...compound,
75+
style: resolveTokens(compound.style, tokens),
76+
}));
77+
78+
// Return the styles function with resolved tokens
79+
return baseStyles({
80+
...config,
81+
base: resolvedBase,
82+
variants: resolvedVariants,
83+
compoundVariants: resolvedCompoundVariants,
84+
});
85+
};
86+
87+
return {
88+
styles: wrappedStyles,
89+
};
90+
}

0 commit comments

Comments
 (0)