import StyleDictionary from 'style-dictionary'; // Custom format: CSS custom properties with RGB triplets for Tailwind v4 compatibility // Outputs `--background: 240 240 236;` format that existing components expect StyleDictionary.registerFormat({ name: 'css/rgb-variables', format: ({ dictionary, options }) => { const selector = options.selector || ':root'; const header = options.header || ''; function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!result) return null; return `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}`; } // Map semantic token paths to CSS variable names function getCssVarName(token) { const path = token.path; // color.semantic.X or color.dark.X → --X if (path[0] === 'color' && (path[1] === 'semantic' || path[1] === 'dark')) { const rest = path.slice(2); // Handle nested like chart.1, sidebar.background if (rest[0] === 'chart') return `--chart-${rest[1]}`; if (rest[0] === 'sidebar') { const subParts = rest.slice(1); if (subParts.length === 1 && subParts[0] === 'background') return '--sidebar'; return `--sidebar-${subParts.join('-')}`; } return `--${rest.join('-')}`; } return null; } const lines = []; dictionary.allTokens.forEach((token) => { const varName = getCssVarName(token); if (!varName) return; const value = token.value || token.$value; const rgb = hexToRgb(value); if (rgb) { const desc = token.$description || token.description; if (desc) lines.push(` /* ${desc} */`); lines.push(` ${varName}: ${rgb};`); } }); return `${header}\n${selector} {\n${lines.join('\n')}\n}`; }, }); // Custom format: TypeScript constants StyleDictionary.registerFormat({ name: 'typescript/constants', format: ({ dictionary }) => { const lines = [ '// Auto-generated by Style Dictionary — DO NOT EDIT', '// Source: tokens/*.json (W3C DTCG format)', '', ]; // Group tokens by top-level category const groups = {}; dictionary.allTokens.forEach((token) => { const group = token.path[0]; if (!groups[group]) groups[group] = []; groups[group].push(token); }); for (const [group, tokens] of Object.entries(groups)) { const constName = group.charAt(0).toUpperCase() + group.slice(1) + 'Tokens'; lines.push(`export const ${constName} = {`); tokens.forEach((token) => { const key = token.path.slice(1).join('.'); const value = token.value || token.$value; if (typeof value === 'string' || typeof value === 'number') { lines.push(` '${key}': '${value}',`); } }); lines.push('} as const;'); lines.push(''); } return lines.join('\n'); }, }); // Custom format: Markdown reference StyleDictionary.registerFormat({ name: 'markdown/reference', format: ({ dictionary }) => { const lines = [ '# Greyhaven Design Tokens Reference', '', '> Auto-generated by Style Dictionary — DO NOT EDIT', '> Source: `tokens/*.json` (W3C DTCG format)', '', ]; const groups = {}; dictionary.allTokens.forEach((token) => { const group = token.path[0]; if (!groups[group]) groups[group] = []; groups[group].push(token); }); for (const [group, tokens] of Object.entries(groups)) { lines.push(`## ${group.charAt(0).toUpperCase() + group.slice(1)}`); lines.push(''); lines.push('| Token | Value | Description |'); lines.push('|-------|-------|-------------|'); tokens.forEach((token) => { const name = token.path.join('.'); const value = token.value || token.$value; const desc = token.$description || token.description || ''; const displayValue = typeof value === 'object' ? JSON.stringify(value) : value; lines.push(`| \`${name}\` | \`${displayValue}\` | ${desc} |`); }); lines.push(''); } return lines.join('\n'); }, }); export default { source: ['tokens/**/*.json'], preprocessors: ['tokens-studio'], platforms: { // CSS custom properties for light theme (semantic tokens) cssLight: { transformGroup: 'css', buildPath: 'app/tokens/', files: [ { destination: 'tokens-light.css', format: 'css/rgb-variables', filter: (token) => { return token.path[0] === 'color' && token.path[1] === 'semantic'; }, options: { selector: ':root', header: '/* Greyhaven Design Tokens — Light Theme\n Auto-generated by Style Dictionary — DO NOT EDIT\n Source: tokens/color.json */\n', }, }, ], }, // CSS custom properties for dark theme cssDark: { transformGroup: 'css', buildPath: 'app/tokens/', files: [ { destination: 'tokens-dark.css', format: 'css/rgb-variables', filter: (token) => { return token.path[0] === 'color' && token.path[1] === 'dark'; }, options: { selector: '.dark', header: '/* Greyhaven Design Tokens — Dark Theme\n Auto-generated by Style Dictionary — DO NOT EDIT\n Source: tokens/color.json */\n', }, }, ], }, // TypeScript constants ts: { transformGroup: 'js', buildPath: 'app/tokens/', files: [ { destination: 'tokens.ts', format: 'typescript/constants', }, ], }, // Markdown reference docs: { transformGroup: 'css', buildPath: 'app/tokens/', files: [ { destination: 'TOKENS.md', format: 'markdown/reference', }, ], }, }, };