import type { Meta, StoryObj } from '@storybook/react-vite' import tokens from '../../public/tokens.json' // ─── helpers ──────────────────────────────────────────────────────────────── function resolveAlias(value: string): string { // e.g. "{color.blue.500}" → tokens.color.blue["500"].$value const match = value.match(/^\{(.+)\}$/) if (!match) return value const parts = match[1].split('.') // eslint-disable-next-line @typescript-eslint/no-explicit-any let node: any = tokens for (const p of parts) node = node?.[p] return node?.['$value'] ?? value } function isLight(hex: string): boolean { const c = hex.replace('#', '') const r = parseInt(c.slice(0, 2), 16) const g = parseInt(c.slice(2, 4), 16) const b = parseInt(c.slice(4, 6), 16) return (r * 299 + g * 587 + b * 114) / 1000 > 155 } // ─── sub-components ───────────────────────────────────────────────────────── function SectionTitle({ children }: { children: React.ReactNode }) { return (

{children}

) } function SubTitle({ children }: { children: React.ReactNode }) { return (

{children}

) } // ─── Color palette ─────────────────────────────────────────────────────────── // Derive color families and steps dynamically from tokens const COLOR_FAMILIES = Object.keys(tokens.color).filter(k => k !== 'semantic' && k !== 'black') const STEPS = Object.keys( // eslint-disable-next-line @typescript-eslint/no-explicit-any (tokens.color as any)[COLOR_FAMILIES[0]] ?? {} ).sort((a, b) => Number(a) - Number(b)) function ColorPalette() { return (
Color Palette
{COLOR_FAMILIES.map(family => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const familyTokens = (tokens.color as any)[family] return (
{family}
{STEPS.map(step => { const hex: string = familyTokens[step]?.['$value'] ?? '#ccc' const light = isLight(hex) return (
{step} {hex}
) })}
) })}
) } // ─── Semantic colors ───────────────────────────────────────────────────────── const SEMANTIC_LABELS: Record = { accent: 'ACCENT', positive: 'POSITIVE', destructive: 'NEGATIVE', secondary: 'SECONDARY', standard: 'STANDARD', } function SemanticColors() { return (
Semantic Colors
{Object.entries(tokens.color.semantic).map(([key, token]) => { const raw = token['$value'] as string const hex = resolveAlias(raw) const light = isLight(hex) // Extract alias path like "blue-500" from "{color.blue.500}" const aliasMatch = raw.match(/^\{color\.(.+)\}$/) const aliasLabel = aliasMatch ? aliasMatch[1].replace('.', '-') : raw return (
{hex}
{SEMANTIC_LABELS[key] ?? key}
{aliasLabel}
) })}
) } // ─── Gradients ─────────────────────────────────────────────────────────────── function resolveGradientStops(stops: Array<{ color: string; position: number }>): string { return stops .map(s => `${resolveAlias(s.color)} ${Math.round(s.position * 100)}%`) .join(', ') } function Gradients() { const gradientEntries = Object.entries(tokens.gradient) const cssStrings = gradientEntries.map(([, g]) => resolveGradientStops(g['$value'] as Array<{ color: string; position: number }>) ) // Find the bg and overlay gradients for the combined preview const bgIndex = gradientEntries.findIndex(([k]) => k === 'header-bg') const overlayIndex = gradientEntries.findIndex(([k]) => k === 'header-overlay') const bgCss = bgIndex >= 0 ? cssStrings[bgIndex] : cssStrings[0] const overlayCss = overlayIndex >= 0 ? cssStrings[overlayIndex] : cssStrings[1] return (
Gradients
{/* Left: individual gradient swatches (bg only) */}
{gradientEntries.map(([key, g], i) => key === 'header-overlay' ? null : (
{key}
{g['$description']}
) )}
{/* Right: combined as seen in ApplicationHeader */}
Header-BG with Overlay
{/* Overlay offset from top by 0.5rem, matching .application-header-gradient::before */}
--gradient-header-overlay
) } // ─── Typography ────────────────────────────────────────────────────────────── function Typography() { const { 'text-size': textSize } = tokens.typography return (
Typography Text Size
{Object.entries(textSize).map(([key, token]) => { const raw = token['$value'] const isAlias = typeof raw === 'string' && raw.startsWith('{') const resolvedValue = isAlias ? resolveAlias(raw) as unknown as { value: number; unit: string } : raw as { value: number; unit: string } const desc = token['$description'] as string const sailName = desc.split('—')[0].trim().replace(/^[^.]+\./, '') // rem values are relative to browser default 16px root const px = Math.round(resolvedValue.value * 16) const sizeLabel = isAlias ? `→ text-${raw.replace(/^\{typography\.text-size\./, '').replace(/\}$/, '')} (${resolvedValue.value}rem / ${px}px)` : `${resolvedValue.value}${resolvedValue.unit} / ${px}px` return (
text-{key} The quick brown fox {sizeLabel} {sailName}
) })}
) } // ─── Spacing ───────────────────────────────────────────────────────────────── function Spacing() { const { margin } = tokens.spacing return (
Spacing Margin and Padding Scale
{Object.entries(margin).map(([key, token]) => { const val = token['$value'] as { value: number; unit: string } const px = val.unit === 'rem' ? val.value * 16 : val.value const width = Math.max(px, 4) const desc = (token['$description'] as string).replace(/^[^.]+\./, '') return (
{key}
{val.value}{val.unit} ({px}px) — {desc}
) })}
) } // ─── Shape ──────────────────────────────────────────────────────────────────── function Shape() { const { radius } = tokens.spacing return (
Shape Border Radius
{Object.entries(radius).map(([key, token]) => { const val = token['$value'] as { value: number; unit: string } const px = val.value * 16 return (
{key}
{val.value}{val.unit} ({px}px)
) })}
) } // ─── Full page ──────────────────────────────────────────────────────────────── function DesignTokensPage() { return (

Design Tokens

Aurora color palette, semantic mappings, typography, spacing, and gradients — sourced from{' '} public/tokens.json


) } // ─── Story ──────────────────────────────────────────────────────────────────── const meta = { title: 'Foundation/Design Tokens', component: DesignTokensPage, parameters: { layout: 'fullscreen' }, } satisfies Meta export default meta type Story = StoryObj export const AllTokens: Story = {}