- Create tailwind.config.js merging production tokens with design system palettes from Style Dictionary - Extend Style Dictionary to output tailwind-tokens.js module - Add PostCSS config and Tailwind directive CSS - Import Tailwind CSS in Storybook preview - Production breakpoints, colours, typography, spacing preserved - Design system brand/sage/neutral/red/amber/green/blue palettes added alongside production's generic color-1..6 naming Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
/**
|
|
* Style Dictionary configuration for FA Design System (v4)
|
|
*
|
|
* Transforms W3C DTCG token JSON into:
|
|
* - CSS custom properties (for runtime theming)
|
|
* - JavaScript ES6 module (for MUI theme consumption)
|
|
* - Tailwind token module (for tailwind.config.js consumption)
|
|
* - JSON (for Penpot import)
|
|
*/
|
|
|
|
import StyleDictionary from 'style-dictionary';
|
|
import { readFileSync, writeFileSync } from 'fs';
|
|
|
|
const sd = new StyleDictionary({
|
|
source: ['tokens/**/*.json'],
|
|
usesDtcg: true,
|
|
|
|
platforms: {
|
|
css: {
|
|
transformGroup: 'css',
|
|
prefix: 'fa',
|
|
buildPath: 'src/theme/generated/',
|
|
files: [{
|
|
destination: 'tokens.css',
|
|
format: 'css/variables',
|
|
options: {
|
|
outputReferences: true,
|
|
},
|
|
}],
|
|
},
|
|
|
|
js: {
|
|
transformGroup: 'js',
|
|
buildPath: 'src/theme/generated/',
|
|
files: [{
|
|
destination: 'tokens.js',
|
|
format: 'javascript/es6',
|
|
}],
|
|
},
|
|
|
|
json: {
|
|
transformGroup: 'js',
|
|
buildPath: 'tokens/export/',
|
|
files: [{
|
|
destination: 'tokens-flat.json',
|
|
format: 'json/flat',
|
|
}],
|
|
},
|
|
},
|
|
});
|
|
|
|
await sd.buildAllPlatforms();
|
|
|
|
// Generate TypeScript declarations for the JS token output
|
|
const jsPath = 'src/theme/generated/tokens.js';
|
|
const dtsPath = 'src/theme/generated/tokens.d.ts';
|
|
|
|
const jsContent = readFileSync(jsPath, 'utf-8')
|
|
.replace(/=\n\s+/g, '= ');
|
|
|
|
const declarations = ['/**', ' * Do not edit directly, this file was auto-generated.', ' */'];
|
|
|
|
for (const line of jsContent.split('\n')) {
|
|
const m = line.match(/^export const (\w+)\s*=\s*(.+?)\s*;/);
|
|
if (!m) continue;
|
|
const [, name, val] = m;
|
|
const isNum = !val.startsWith('"') && !val.startsWith("'") && !isNaN(Number(val));
|
|
declarations.push(`export declare const ${name}: ${isNum ? 'number' : 'string'};`);
|
|
}
|
|
|
|
writeFileSync(dtsPath, declarations.join('\n') + '\n');
|
|
console.log(`✓ Generated ${declarations.length - 3} TypeScript declarations`);
|
|
|
|
// Generate Tailwind token module from the flat JSON export
|
|
// Keys are PascalCase: ColorBrand500, Spacing4, FontFamilyBody, etc.
|
|
const flatTokens = JSON.parse(readFileSync('tokens/export/tokens-flat.json', 'utf-8'));
|
|
|
|
const twColors = {};
|
|
const twSpacing = {};
|
|
const twBorderRadius = {};
|
|
const twFontFamily = {};
|
|
const twFontSize = {};
|
|
const twFontWeight = {};
|
|
const twLineHeight = {};
|
|
const twLetterSpacing = {};
|
|
|
|
// Helper: split PascalCase into parts, e.g. "ColorBrand500" → ["Color", "Brand", "500"]
|
|
function splitPascal(str) {
|
|
return str.match(/[A-Z][a-z]*|\d+/g) || [];
|
|
}
|
|
|
|
for (const [key, value] of Object.entries(flatTokens)) {
|
|
// Primitive colour tokens: ColorBrand500 → brand.500
|
|
const colorMatch = key.match(/^Color(Brand|Sage|Neutral|Red|Amber|Green|Blue)(\d+)$/);
|
|
if (colorMatch) {
|
|
const group = colorMatch[1].toLowerCase();
|
|
const shade = colorMatch[2];
|
|
if (!twColors[group]) twColors[group] = {};
|
|
twColors[group][shade] = value;
|
|
continue;
|
|
}
|
|
// Single-value colors: ColorWhite, ColorBlack
|
|
if (key === 'ColorWhite') { twColors['white'] = value; continue; }
|
|
if (key === 'ColorBlack') { twColors['black'] = value; continue; }
|
|
|
|
// Primitive spacing: Spacing1, Spacing0-5, Spacing16, etc.
|
|
const spacingMatch = key.match(/^Spacing(\d+)$/);
|
|
if (spacingMatch) {
|
|
twSpacing[spacingMatch[1]] = value;
|
|
continue;
|
|
}
|
|
if (key === 'Spacing05') { twSpacing['0.5'] = value; continue; }
|
|
|
|
// Primitive border radius: BorderRadiusSm, BorderRadiusMd, etc.
|
|
const radiusMatch = key.match(/^BorderRadius(\w+)$/);
|
|
if (radiusMatch) {
|
|
twBorderRadius[radiusMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
|
|
// Font family: FontFamilyBody, FontFamilyDisplay, FontFamilyMono
|
|
const fontFamilyMatch = key.match(/^FontFamily(\w+)$/);
|
|
if (fontFamilyMatch) {
|
|
twFontFamily[fontFamilyMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
|
|
// Primitive font size: FontSize2xs, FontSizeBase, etc. (skip mobile/display)
|
|
const fontSizeMatch = key.match(/^FontSize(2xs|Xs|Sm|Base|Md|Lg|Xl|2xl|3xl|4xl)$/i);
|
|
if (fontSizeMatch) {
|
|
twFontSize[fontSizeMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
|
|
// Font weight: FontWeightRegular, FontWeightMedium, etc.
|
|
const fontWeightMatch = key.match(/^FontWeight(\w+)$/);
|
|
if (fontWeightMatch) {
|
|
twFontWeight[fontWeightMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
|
|
// Line height: LineHeightTight, LineHeightNormal, etc.
|
|
const lineHeightMatch = key.match(/^LineHeight(\w+)$/);
|
|
if (lineHeightMatch) {
|
|
twLineHeight[lineHeightMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
|
|
// Letter spacing: LetterSpacingTight, LetterSpacingNormal, etc.
|
|
const letterSpacingMatch = key.match(/^LetterSpacing(\w+)$/);
|
|
if (letterSpacingMatch) {
|
|
twLetterSpacing[letterSpacingMatch[1].toLowerCase()] = value;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const tailwindTokens = {
|
|
colors: twColors,
|
|
spacing: twSpacing,
|
|
borderRadius: twBorderRadius,
|
|
fontFamily: twFontFamily,
|
|
fontSize: twFontSize,
|
|
fontWeight: twFontWeight,
|
|
lineHeight: twLineHeight,
|
|
letterSpacing: twLetterSpacing,
|
|
};
|
|
|
|
const twOutput = `/**\n * Auto-generated Tailwind tokens from Style Dictionary.\n * Do not edit directly — edit tokens/*.json and run npm run build:tokens.\n */\nmodule.exports = ${JSON.stringify(tailwindTokens, null, 2)};\n`;
|
|
|
|
writeFileSync('src/theme/generated/tailwind-tokens.js', twOutput);
|
|
console.log(`✓ Generated Tailwind token module`);
|