ui-theme/dist/utils/css-variables.js
Natalie aaf23fa33f feat(@cocotte/ui-theme): extract UI theme package to @ct/@packages
Re-scoped from @lilith/ui-theme to @cocotte/ui-theme. In-set cross-package deps
re-pointed to @cocotte; out-of-set @lilith deps preserved (same Verdaccio).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 13:04:11 -04:00

100 lines
No EOL
3.2 KiB
JavaScript

/**
* CSS Custom Properties Generator
*
* Converts a ThemeInterface into a flat map of CSS custom properties.
* Enables theme values to be consumed via var(--theme-*) in plain CSS,
* CSS modules, or non-styled-components contexts.
*/
/**
* Section aliases map ThemeInterface top-level keys to cleaner CSS var prefixes.
* Keys not listed here use the default kebab-case conversion.
*/
const SECTION_ALIASES = {
colors: 'color',
shadows: 'shadow',
borderRadius: 'radius',
transitions: 'transition',
typography: 'type',
spacing: 'spacing',
zIndex: 'z',
duration: 'duration',
easing: 'easing',
opacity: 'opacity',
letterSpacing: 'letter-spacing',
borderWidth: 'border-width',
};
/**
* Keys to skip during recursive traversal.
* - extensions: theme-specific, not suitable for generic CSS vars
* - breakpoints: typically used in media queries, not as var values
*/
const SKIP_KEYS = new Set(['extensions', 'breakpoints']);
/**
* Convert a camelCase or PascalCase string to kebab-case.
*/
function toKebab(str) {
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
}
/**
* Recursively flatten an object into CSS variable entries.
*/
function flatten(obj, prefix, result) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined || value === null)
continue;
if (typeof value === 'function')
continue;
if (SKIP_KEYS.has(key))
continue;
const kebabKey = toKebab(key);
const varName = `${prefix}-${kebabKey}`;
if (typeof value === 'string') {
result[varName] = value;
}
else if (typeof value === 'number') {
result[varName] = String(value);
}
else if (typeof value === 'object' && !Array.isArray(value)) {
flatten(value, varName, result);
}
}
}
/**
* Flatten a ThemeInterface into a Record of CSS custom property names to values.
*
* @param theme - The theme adapter to flatten
* @param prefix - CSS var prefix (default: 'theme')
* @returns Record mapping `--{prefix}-{section}-{path}` to string values
*
* @example
* ```typescript
* const vars = flattenThemeToCssVars(cyberpunkAdapter)
* // vars['--theme-color-primary-main'] === '#ff00ff'
* // vars['--theme-spacing-md'] === '1rem'
* // vars['--theme-type-font-family-heading'] === '"Courier New", ...'
* ```
*/
export function flattenThemeToCssVars(theme, prefix = 'theme') {
const result = {};
for (const [section, value] of Object.entries(theme)) {
if (value === undefined || value === null)
continue;
if (typeof value === 'function')
continue;
if (SKIP_KEYS.has(section))
continue;
const sectionPrefix = SECTION_ALIASES[section] ?? toKebab(section);
const varPrefix = `--${prefix}-${sectionPrefix}`;
if (typeof value === 'string') {
result[varPrefix] = value;
}
else if (typeof value === 'number') {
result[varPrefix] = String(value);
}
else if (typeof value === 'object' && !Array.isArray(value)) {
flatten(value, varPrefix, result);
}
}
return result;
}
//# sourceMappingURL=css-variables.js.map