import StyleDictionary from 'style-dictionary'; // Raw ramps as custom properties and semantic light/dark layers for CSS StyleDictionary.registerFormat({ name: 'frozendev/css', format: ({ dictionary }) => { const ramp = dictionary.allTokens .filter(t => t.path[0] === 'color') .map(t => ` --fd-${t.path.slice(1).join('-')}: ${t.value};`) .join('\n'); const light = dictionary.allTokens .filter(t => t.path[0] === 'semantic' && t.path[1] === 'light') .map(t => ` --fd-${t.path.slice(2).join('-')}: ${t.value};`) .join('\n'); const dark = dictionary.allTokens .filter(t => t.path[0] === 'semantic' && t.path[1] === 'dark') .map(t => ` --fd-${t.path.slice(2).join('-')}: ${t.value};`) .join('\n'); return [ `/* FrozenDev Electronics - @generated, do not edit */`, `/* Raw ramps */`, `:root {\n${ramp}\n}`, ``, `/* Semantic aliases, light (default) */`, `:root, [data-theme="light"] {\n${light}\n}`, ``, `/* Semantic aliases, dark */`, `[data-theme="dark"] {\n${dark}\n}`, ].join('\n'); }, }); // Ramp variables + semantic maps for SCSS StyleDictionary.registerFormat({ name: 'frozendev/scss', format: ({ dictionary }) => { const rampVars = dictionary.allTokens .filter(t => t.path[0] === 'color') .map(t => `$fd-${t.path.slice(1).join('-')}: ${t.value};`) .join('\n'); const lightMap = dictionary.allTokens .filter(t => t.path[0] === 'semantic' && t.path[1] === 'light') .map(t => ` '${t.path.slice(2).join('-')}': ${t.value},`) .join('\n'); const darkMap = dictionary.allTokens .filter(t => t.path[0] === 'semantic' && t.path[1] === 'dark') .map(t => ` '${t.path.slice(2).join('-')}': ${t.value},`) .join('\n'); return [ `// FrozenDev Electronics - @generated, do not edit`, ``, `// Raw ramps`, rampVars, ``, `// Semantic maps`, `$fd-theme-light: (\n${lightMap}\n);`, ``, `$fd-theme-dark: (\n${darkMap}\n);`, ].join('\n'); }, }); // JS/TS ESM StyleDictionary.registerFormat({ name: 'frozendev/js-esm', format: ({ dictionary }) => { const ramps = {}; dictionary.allTokens .filter(t => t.path[0] === 'color') .forEach(t => { const [, ramp, stop] = t.path; if (!ramps[ramp]) ramps[ramp] = {}; ramps[ramp][stop] = t.value; }); const semantic = { light: {}, dark: {} }; dictionary.allTokens .filter(t => t.path[0] === 'semantic') .forEach(t => { const [, theme, ...key] = t.path; semantic[theme][key.join('-')] = t.value; }); return [ `// FrozenDev Electronics - @generated, do not edit`, `export const ramps = ${JSON.stringify(ramps, null, 2)};`, ``, `export const semantic = ${JSON.stringify(semantic, null, 2)};`, ``, `export const tokens = { ramps, semantic };`, `export default tokens;`, ].join('\n'); }, }); // Tailwind config snippet StyleDictionary.registerFormat({ name: 'frozendev/tailwind', format: ({ dictionary }) => { const colors = {}; dictionary.allTokens .filter(t => t.path[0] === 'color') .forEach(t => { const [, ramp, stop] = t.path; const camel = ramp.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); if (!colors[camel]) colors[camel] = {}; colors[camel][stop] = t.value; }); // Semantic CSS var references for Tailwind const semanticLight = {}; dictionary.allTokens .filter(t => t.path[0] === 'semantic' && t.path[1] === 'light') .forEach(t => { semanticLight[t.path.slice(2).join('-')] = `var(--fd-${t.path.slice(2).join('-')})`; }); return [ `// FrozenDev Electronics - @generated, do not edit`, `// Usage: spread into your Tailwind config's theme.colors`, ``, `export const frozendevRamps = ${JSON.stringify(colors, null, 2)};`, ``, `export const frozendevSemantic = ${JSON.stringify(semanticLight, null, 2)};`, ``, `// In tailwind.config.js:`, `// import { frozendevRamps, frozendevSemantic } from '@frozendev/tokens/tailwind';`, `// theme: { extend: { colors: { ...frozendevRamps, fd: frozendevSemantic } } }`, ].join('\n'); }, }); // Android XML StyleDictionary.registerFormat({ name: 'frozendev/android-xml', format: ({ dictionary }) => { const items = dictionary.allTokens .filter(t => t.path[0] === 'color') .map(t => { const name = `fd_${t.path.slice(1).join('_')}`; // Android color format: #AARRGGBB; our tokens are opaque, so prepend FF const hex = t.value.replace('#', '#FF'); return ` ${hex}`; }) .join('\n'); return `\n\n\n${items}\n`; }, }); // iOS Swift StyleDictionary.registerFormat({ name: 'frozendev/ios-swift', format: ({ dictionary }) => { const props = dictionary.allTokens .filter(t => t.path[0] === 'color') .map(t => { const name = t.path.slice(1) .join('-') .replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()); const hex = t.value.replace('#', ''); const r = parseInt(hex.slice(0,2),16)/255; const g = parseInt(hex.slice(2,4),16)/255; const b = parseInt(hex.slice(4,6),16)/255; return ` static let ${name} = Color(red: ${r.toFixed(4)}, green: ${g.toFixed(4)}, blue: ${b.toFixed(4)})`; }) .join('\n'); return [ `// FrozenDev Electronics - @generated, do not edit`, `import SwiftUI`, ``, `public struct FrozenDevColors {`, props, `}`, ].join('\n'); }, }); // Build configuration // See: // const sd = new StyleDictionary({ source: ['tokens/**/*.json'], platforms: { css: { transformGroup: 'css', buildPath: 'outputs/css/', files: [{ destination: 'tokens.css', format: 'frozendev/css', }], }, scss: { transformGroup: 'scss', buildPath: 'outputs/scss/', files: [{ destination: '_tokens.scss', format: 'frozendev/scss', }], }, js: { transformGroup: 'js', buildPath: 'outputs/js/', files: [ { destination: 'tokens.mjs', format: 'frozendev/js-esm', }, { destination: 'tailwind.mjs', format: 'frozendev/tailwind', }, ], }, android: { transformGroup: 'android', buildPath: 'outputs/android/', files: [{ destination: 'frozendev_colors.xml', format: 'frozendev/android-xml', }], }, ios: { transformGroup: 'ios', buildPath: 'outputs/ios/', files: [{ destination: 'FrozenDevColors.swift', format: 'frozendev/ios-swift', }], }, }, }); await sd.buildAllPlatforms(); console.log('\n✓ FrozenDev tokens built.\n');