Skip to content

Commit ece1f40

Browse files
committed
feat: unify design tokens to use numeric keys throughout
Consolidate spacing and elevation tokens to use consistent numeric keys instead of mixed string/numeric types. This provides a cleaner, more intuitive API while maintaining full type safety. ## Breaking Changes - Spacing now uses numeric keys: `spacing[16]` instead of `spacing['space-16']` - Elevation uses numeric keys: `elevation[1]` instead of `elevation['1']` - Card component `elevationLevel` prop now accepts numbers: `elevationLevel={1}` ## Changes **Token System:** - Converted spacing.ts to use numeric keys (0-64) - Unified elevation.ts to numeric keys (0-5) - Simplified token-helpers.ts to directly re-export source tokens - Updated index.ts with single source of truth exports - Removed dual type system complexity **API Improvements:** - `spacing[16]` returns '32px' (instead of spacing['space-16']) - `elevation[1].shadow` (instead of elevation['1'].shadow) - `<Card elevationLevel={1} />` (instead of elevationLevel="1") - `cssVar.spacing(8)` returns 'var(--ai-spacing-8)' **Type Exports:** - `SpacingScale` - 0 | 1 | 2 | 4 | 6 | 8 | 10 | 12 | 16 | 20 | 24 | 32 | 64 - `ElevationLevel` - 0 | 1 | 2 | 3 | 4 | 5 **Documentation:** - Added TOKEN_USAGE.md with comprehensive examples - Added EXAMPLES.tsx with real-world component patterns - Updated README with numeric key examples - Updated all Storybook examples **Testing:** - Added 35 new tests for token helpers - All 548 tests passing - Full TypeScript type safety maintained ## Benefits - Cleaner API: `spacing[16]` vs `spacing['space-16']` - Single source of truth (no duplicate types) - Consistent numeric access for scalar tokens - Better developer experience - Simplified codebase (-50 lines of conversion code)
1 parent c049d33 commit ece1f40

30 files changed

+2108
-198
lines changed

README.md

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ import '@ainativekit/ui/styles';
102102

103103
export function DocumentCard() {
104104
return (
105-
<Card elevationLevel="1" interactive>
105+
<Card elevationLevel={1} interactive>
106106
<Card.Header>
107107
<Card.ChipGroup>
108108
<Card.Chip variant="neutral" size="sm">AINativeKit UI</Card.Chip>
@@ -155,19 +155,65 @@ export function DocumentCard() {
155155

156156
## 🎨 Design System
157157

158-
Use consistent **colors**, **typography**, **spacing**, and **elevation** derived from OpenAI’s Figma system.
158+
Use consistent **colors**, **typography**, **spacing**, and **elevation** derived from OpenAI's Figma system with **full type safety** and **autocomplete**.
159+
160+
### Type-Safe Design Tokens
161+
162+
Get autocomplete and compile-time type checking for all design tokens:
159163

160164
```tsx
161-
import { colors, typography, spacing, elevation } from '@ainativekit/ui';
165+
import { cssVar } from '@ainativekit/ui/tokens';
166+
import { spacing, colors, typography } from '@ainativekit/ui/tokens';
167+
168+
// ✅ Method 1: Direct token values (clean numeric/string access)
169+
<div style={{
170+
gap: spacing[8], // '16px' - Numeric key!
171+
color: colors.light.text.primary, // '#0D0D0D' - Full autocomplete!
172+
...typography.heading1, // Complete typography style
173+
}} />
174+
175+
// ✅ Method 2: CSS variables for theme-aware styling (recommended for dynamic themes)
176+
<div style={{
177+
gap: cssVar.spacing(8), // 'var(--ai-spacing-8)'
178+
color: cssVar.color('text-primary'), // Adapts to light/dark theme
179+
borderRadius: cssVar.radius('xl'),
180+
}} />
181+
```
162182

163-
const style = {
164-
backgroundColor: colors.light.background.primary,
165-
padding: spacing['space-16'],
166-
fontSize: typography.body.fontSize,
167-
boxShadow: elevation.low,
168-
};
183+
**Before vs After:**
184+
185+
```tsx
186+
// ❌ Before: String literals, no autocomplete, typo-prone
187+
<div style={{
188+
gap: 'var(--ai-spacing-8)', // Could typo
189+
fontSize: 'var(--ai-font-size-sm)' // No autocomplete
190+
}} />
191+
192+
// ✅ After: Type-safe with autocomplete
193+
import { cssVar, spacing, typography } from '@ainativekit/ui/tokens';
194+
195+
<div style={{
196+
gap: cssVar.spacing(8), // Autocomplete suggests scale values!
197+
...typography.bodySmall // Complete font styles
198+
}} />
199+
200+
// Or use direct values:
201+
<div style={{
202+
gap: spacing[8], // Numeric key access
203+
...typography.bodySmall
204+
}} />
169205
```
170206

207+
**Available Token Categories:**
208+
- 🎯 **Spacing** - 13 scale values (0-64)
209+
- 🎨 **Colors** - Light/dark themes, semantic colors
210+
- 📝 **Typography** - 11 complete text styles
211+
- 🔲 **Border Radius** - 7 radius scales
212+
-**Elevation** - 6 shadow levels
213+
- 👁️ **Opacity** - 4 opacity presets
214+
215+
📖 **[Complete Token Usage Guide](./packages/ui/src/tokens/TOKEN_USAGE.md)** | **[Live Examples](./packages/ui/src/tokens/EXAMPLES.tsx)**
216+
171217
**Icons:**
172218

173219
```tsx

packages/ui/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ import '@ainativekit/ui/styles';
103103

104104
export function DocumentCard() {
105105
return (
106-
<Card elevationLevel="1" interactive>
106+
<Card elevationLevel={1} interactive>
107107
<Card.Header>
108108
<Card.ChipGroup>
109109
<Card.Chip variant="neutral" size="sm">AINativeKit UI</Card.Chip>
@@ -163,9 +163,9 @@ import { colors, typography, spacing, elevation } from '@ainativekit/ui';
163163

164164
const style = {
165165
backgroundColor: colors.light.background.primary,
166-
padding: spacing['space-16'],
166+
padding: spacing[16],
167167
fontSize: typography.body.fontSize,
168-
boxShadow: elevation.low,
168+
boxShadow: elevation[1].shadow,
169169
};
170170
```
171171

packages/ui/src/Introduction.stories.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ const IntroductionPage = () => {
489489
<div style={{ ...contentSectionStyle, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '24px' }}>
490490
{/* Restaurant from JSON - Real-World Example */}
491491
<SummaryCard
492-
elevationLevel="1"
492+
elevationLevel={1}
493493
interactive
494494
images={[
495495
{ src: 'https://persistent.oaistatic.com/pizzaz/pizzaz-1.png', alt: 'Signature Pizza' },
@@ -512,7 +512,7 @@ const IntroductionPage = () => {
512512

513513
{/* Article from JSON */}
514514
<SummaryCard
515-
elevationLevel="1"
515+
elevationLevel={1}
516516
interactive
517517
images="https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=400&h=200&fit=crop"
518518
title="Building AI-Native UIs"
@@ -708,7 +708,7 @@ const IntroductionPage = () => {
708708
<div style={{ ...contentSectionStyle, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '24px', maxWidth: '360px' }}>
709709
{/* Article Card using SummaryCard - shows what it looks like */}
710710
<SummaryCard
711-
elevationLevel="1"
711+
elevationLevel={1}
712712
interactive
713713
images="https://images.unsplash.com/photo-1499750310107-5fef28a66643?w=400&h=200&fit=crop"
714714
title="Building AI-Native UIs"
@@ -733,7 +733,7 @@ const IntroductionPage = () => {
733733
Need more control? Use compound Card components for rich customization. Same data, but with custom tags, advanced metadata, and full styling control:
734734
</div>
735735
<div style={codeBlockStyle}>
736-
{'<Card elevationLevel="1" interactive>'}<br />
736+
{'<Card elevationLevel={1} interactive>'}<br />
737737
&nbsp;&nbsp;{'<Card.Header>'}<br />
738738
&nbsp;&nbsp;&nbsp;&nbsp;{'<Card.ChipGroup>'}<br />
739739
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{'<Card.Chip variant="neutral" size="sm">ChatGPT Apps SDK</Card.Chip>'}<br />
@@ -762,7 +762,7 @@ const IntroductionPage = () => {
762762
</div>
763763
<div style={{ ...contentSectionStyle, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '24px', maxWidth: '360px' }}>
764764
{/* Documentation Card Example - shows what it looks like */}
765-
<Card elevationLevel="1" interactive>
765+
<Card elevationLevel={1} interactive>
766766
<Card.Header>
767767
<Card.ChipGroup>
768768
<Card.Chip variant="neutral" size="sm">ChatGPT Apps SDK</Card.Chip>

packages/ui/src/components/Alert/Alert.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ const AlertShowcaseComponent: React.FC = () => {
239239
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
240240
gap: '24px'
241241
}}>
242-
<Card elevationLevel="1">
242+
<Card elevationLevel={1}>
243243
<Alert
244244
variant="error"
245245
layout="card"
@@ -248,7 +248,7 @@ const AlertShowcaseComponent: React.FC = () => {
248248
/>
249249
</Card>
250250

251-
<Card elevationLevel="1">
251+
<Card elevationLevel={1}>
252252
<Alert
253253
variant="warning"
254254
layout="card"
@@ -259,7 +259,7 @@ const AlertShowcaseComponent: React.FC = () => {
259259
/>
260260
</Card>
261261

262-
<Card elevationLevel="1">
262+
<Card elevationLevel={1}>
263263
<Alert
264264
variant="success"
265265
layout="card"

packages/ui/src/components/Badge/Badge.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const BadgeCard: React.FC<{
3434

3535
return (
3636
<Card
37-
elevationLevel="1"
37+
elevationLevel={1}
3838
interactive
3939
onClick={handleCopy}
4040
style={{

packages/ui/src/components/Button/Button.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const ButtonCard: React.FC<{
4141

4242
return (
4343
<Card
44-
elevationLevel="1"
44+
elevationLevel={1}
4545
interactive
4646
onClick={handleCopy}
4747
style={{

packages/ui/src/components/Card/Card.stories.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const CardsComponent: React.FC = () => {
4040
</header>
4141

4242
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '24px', marginBottom: '32px', alignItems: 'start' }}>
43-
<Card elevationLevel="1">
43+
<Card elevationLevel={1}>
4444
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
4545
<span style={{ fontWeight: 600 }}>Default Card</span>
4646
<span style={{ color: 'var(--ai-color-text-secondary)', fontSize: '14px' }}>
@@ -49,7 +49,7 @@ const CardsComponent: React.FC = () => {
4949
</div>
5050
</Card>
5151

52-
<Card elevationLevel="1" interactive>
52+
<Card elevationLevel={1} interactive>
5353
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
5454
<span style={{ fontWeight: 600 }}>Interactive Card</span>
5555
<span style={{ color: 'var(--ai-color-text-secondary)', fontSize: '14px' }}>
@@ -58,7 +58,7 @@ const CardsComponent: React.FC = () => {
5858
</div>
5959
</Card>
6060

61-
<Card elevationLevel="2">
61+
<Card elevationLevel={2}>
6262
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
6363
<span style={{ fontWeight: 600 }}>Higher Elevation</span>
6464
<span style={{ color: 'var(--ai-color-text-secondary)', fontSize: '14px' }}>
@@ -71,7 +71,7 @@ const CardsComponent: React.FC = () => {
7171
<details style={{ marginTop: '16px', cursor: 'pointer' }}>
7272
<summary style={{ fontWeight: 600, marginBottom: '12px' }}>Show all elevation levels</summary>
7373
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '16px', marginTop: '16px', alignItems: 'start' }}>
74-
{(['0', '1', '2', '3', '4', '5'] as const).map(level => (
74+
{([0, 1, 2, 3, 4, 5] as const).map(level => (
7575
<Card
7676
key={level}
7777
elevationLevel={level}
@@ -86,7 +86,7 @@ const CardsComponent: React.FC = () => {
8686
>
8787
<strong>Elevation {level}</strong>
8888
<div style={{ color: 'var(--ai-color-text-secondary)', fontSize: '13px' }}>
89-
{level === '0' ? 'No shadow' : `Shadow level ${level}`}
89+
{level === 0 ? 'No shadow' : `Shadow level ${level}`}
9090
</div>
9191
</Card>
9292
))}
@@ -222,7 +222,7 @@ const CardsComponent: React.FC = () => {
222222
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '24px', alignItems: 'start' }}>
223223
<div>
224224
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Default Loading</h3>
225-
<Card elevationLevel="1" loading style={{ minHeight: '280px' }}>
225+
<Card elevationLevel={1} loading style={{ minHeight: '280px' }}>
226226
<p>This content won't show while loading</p>
227227
</Card>
228228
<p style={{ fontSize: '12px', color: 'var(--ai-color-text-secondary)', marginTop: '8px' }}>
@@ -232,7 +232,7 @@ const CardsComponent: React.FC = () => {
232232

233233
<div>
234234
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Custom Skeleton</h3>
235-
<Card elevationLevel="1" loading skeleton={
235+
<Card elevationLevel={1} loading skeleton={
236236
<>
237237
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
238238
<Skeleton variant="circular" width={48} height={48} />
@@ -255,7 +255,7 @@ const CardsComponent: React.FC = () => {
255255

256256
<div>
257257
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Not Loading</h3>
258-
<Card elevationLevel="1">
258+
<Card elevationLevel={1}>
259259
<Card.Header>
260260
<Card.Title>Normal Content</Card.Title>
261261
<Card.Description>This card shows content immediately</Card.Description>
@@ -285,7 +285,7 @@ const CardsComponent: React.FC = () => {
285285
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '24px', alignItems: 'start' }}>
286286
<div>
287287
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Default Error</h3>
288-
<Card elevationLevel="1" error style={{ minHeight: '280px' }}>
288+
<Card elevationLevel={1} error style={{ minHeight: '280px' }}>
289289
<p>This content won't show when error state is active</p>
290290
</Card>
291291
<p style={{ fontSize: '12px', color: 'var(--ai-color-text-secondary)', marginTop: '8px' }}>
@@ -296,7 +296,7 @@ const CardsComponent: React.FC = () => {
296296
<div>
297297
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Custom Error Message</h3>
298298
<Card
299-
elevationLevel="1"
299+
elevationLevel={1}
300300
error
301301
errorTitle="Failed to load"
302302
errorMessage="Unable to fetch the requested data. Please try again."
@@ -312,7 +312,7 @@ const CardsComponent: React.FC = () => {
312312
<div>
313313
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>With Retry Action</h3>
314314
<Card
315-
elevationLevel="1"
315+
elevationLevel={1}
316316
error
317317
errorTitle="Connection failed"
318318
errorMessage="Could not reach the server"
@@ -329,7 +329,7 @@ const CardsComponent: React.FC = () => {
329329
<div>
330330
<h3 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '12px', color: 'var(--ai-color-text-primary)' }}>Custom Error Content</h3>
331331
<Card
332-
elevationLevel="1"
332+
elevationLevel={1}
333333
error
334334
errorContent={
335335
<div style={{ padding: '48px 24px', textAlign: 'center' }}>
@@ -360,7 +360,7 @@ const CardsComponent: React.FC = () => {
360360

361361
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: '24px', alignItems: 'start' }}>
362362
{/* Product Card */}
363-
<Card elevationLevel="1" interactive>
363+
<Card elevationLevel={1} interactive>
364364
<Card.Header>
365365
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
366366
<Card.Title>Premium Headphones</Card.Title>
@@ -385,7 +385,7 @@ const CardsComponent: React.FC = () => {
385385
</Card>
386386

387387
{/* Article Card - Documentation Example */}
388-
<Card elevationLevel="1" interactive>
388+
<Card elevationLevel={1} interactive>
389389
<Card.Header>
390390
<Card.ChipGroup>
391391
<Card.Chip variant="neutral" size="sm">ChatGPT Apps</Card.Chip>
@@ -417,7 +417,7 @@ const CardsComponent: React.FC = () => {
417417
</Card>
418418

419419
{/* User Profile Card */}
420-
<Card elevationLevel="2">
420+
<Card elevationLevel={2}>
421421
<Card.Header>
422422
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
423423
<img
@@ -450,7 +450,7 @@ const CardsComponent: React.FC = () => {
450450
</Card>
451451

452452
{/* Venue/Restaurant Card with Features Meta */}
453-
<Card elevationLevel="1" interactive>
453+
<Card elevationLevel={1} interactive>
454454
<Card.Image
455455
src="https://images.unsplash.com/photo-1686836715835-65af22ea5cd4?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=400&h=200"
456456
alt="Artisan Bistro"
@@ -478,7 +478,7 @@ const CardsComponent: React.FC = () => {
478478
</Card>
479479

480480
{/* Project/Case Study Card */}
481-
<Card elevationLevel="1" interactive>
481+
<Card elevationLevel={1} interactive>
482482
<Card.Image
483483
src="https://images.unsplash.com/photo-1552664730-d307ca884978?w=400&h=200&fit=crop"
484484
alt="Project showcase"
@@ -509,7 +509,7 @@ const CardsComponent: React.FC = () => {
509509
</Card>
510510

511511
{/* Team Member Card */}
512-
<Card elevationLevel="1" interactive>
512+
<Card elevationLevel={1} interactive>
513513
<Card.Header>
514514
<div style={{ display: 'flex', gap: '16px', alignItems: 'flex-start' }}>
515515
<img
@@ -556,7 +556,7 @@ const CardsComponent: React.FC = () => {
556556

557557
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(360px, 1fr))', gap: '24px', alignItems: 'start' }}>
558558
{/* E-commerce Product */}
559-
<Card elevationLevel="2" interactive>
559+
<Card elevationLevel={2} interactive>
560560
<Card.Header>
561561
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start' }}>
562562
<div>
@@ -694,7 +694,7 @@ const CardsComponent: React.FC = () => {
694694
For basic content, use Card as a simple container. Best for custom layouts or simple content.
695695
</p>
696696
<pre style={codeBlockStyles.primary}>
697-
<code>{`<Card elevationLevel="1">
697+
<code>{`<Card elevationLevel={1}>
698698
<h3>Title</h3>
699699
<p>Content goes here</p>
700700
</Card>`}</code>
@@ -707,7 +707,7 @@ const CardsComponent: React.FC = () => {
707707
For structured layouts, use compound components. Provides semantic HTML and consistent styling.
708708
</p>
709709
<pre style={codeBlockStyles.primary}>
710-
<code>{`<Card elevationLevel="1" interactive>
710+
<code>{`<Card elevationLevel={1} interactive>
711711
<Card.Header>
712712
<Card.Title>Product Name</Card.Title>
713713
<Card.Chip variant="success">New</Card.Chip>
@@ -871,7 +871,7 @@ const CardsComponent: React.FC = () => {
871871
rows={[
872872
{
873873
name: 'elevationLevel',
874-
description: 'Elevation level for shadow and overlay. Options: "0" | "1" | "2" | "3" | "4" | "5". Default: "1"',
874+
description: 'Elevation level for shadow and overlay. Options: 0 | 1 | 2 | 3 | 4 | 5. Default: 1',
875875
},
876876
{
877877
name: 'border',

0 commit comments

Comments
 (0)