Files
greyhaven-design-system/style-dictionary.config.mjs
2026-04-13 15:33:00 -05:00

191 lines
5.8 KiB
JavaScript

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',
},
],
},
},
};