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>
176 lines
No EOL
6.2 KiB
JavaScript
176 lines
No EOL
6.2 KiB
JavaScript
import { createColorScale, createSemanticColor, createBorderColor } from '../types/ThemeInterface';
|
|
/**
|
|
* Keys on ThemeInterface.colors that are ColorScale branded types.
|
|
*/
|
|
const COLOR_SCALE_KEYS = new Set(['primary', 'secondary', 'accent']);
|
|
/**
|
|
* Keys on ThemeInterface.colors that are SemanticColor branded types.
|
|
*/
|
|
const SEMANTIC_COLOR_KEYS = new Set(['success', 'warning', 'error', 'info']);
|
|
/**
|
|
* Check if a value looks like a ColorScale override (has main/dark/light).
|
|
*/
|
|
function isColorScaleOverride(value) {
|
|
return typeof value === 'object' && value !== null && 'main' in value;
|
|
}
|
|
/**
|
|
* Check if a value looks like a SemanticColor override (has main + background/border/text).
|
|
*/
|
|
function isSemanticColorOverride(value) {
|
|
return typeof value === 'object' && value !== null && 'main' in value;
|
|
}
|
|
/**
|
|
* Merge a partial ColorScale override onto a base ColorScale,
|
|
* reconstructing the branded type with toString().
|
|
*/
|
|
function mergeColorScale(base, override) {
|
|
return createColorScale({
|
|
main: override.main ?? base.main,
|
|
dark: override.dark ?? base.dark,
|
|
light: override.light ?? base.light,
|
|
numeric: {
|
|
50: override[50] ?? base[50],
|
|
100: override[100] ?? base[100],
|
|
200: override[200] ?? base[200],
|
|
300: override[300] ?? base[300],
|
|
400: override[400] ?? base[400],
|
|
500: override[500] ?? override.main ?? base[500],
|
|
600: override[600] ?? base[600],
|
|
700: override[700] ?? base[700],
|
|
800: override[800] ?? base[800],
|
|
900: override[900] ?? base[900],
|
|
950: override[950] ?? base[950],
|
|
},
|
|
});
|
|
}
|
|
/**
|
|
* Merge a partial SemanticColor override onto a base SemanticColor,
|
|
* reconstructing the branded type with toString().
|
|
*/
|
|
function mergeSemanticColor(base, override) {
|
|
return createSemanticColor({
|
|
main: override.main ?? base.main,
|
|
background: override.background ?? base.background,
|
|
border: override.border ?? base.border,
|
|
text: override.text ?? base.text,
|
|
numeric: {
|
|
50: override[50] ?? base[50],
|
|
100: override[100] ?? base[100],
|
|
200: override[200] ?? base[200],
|
|
300: override[300] ?? base[300],
|
|
400: override[400] ?? base[400],
|
|
500: override[500] ?? override.main ?? base[500],
|
|
600: override[600] ?? base[600],
|
|
700: override[700] ?? base[700],
|
|
800: override[800] ?? base[800],
|
|
900: override[900] ?? base[900],
|
|
950: override[950] ?? base[950],
|
|
},
|
|
});
|
|
}
|
|
/**
|
|
* Merge a partial BorderColor override onto a base BorderColor.
|
|
*/
|
|
function mergeBorderColor(base, override) {
|
|
return createBorderColor(override.default ?? base.default, override.hover ?? base.hover);
|
|
}
|
|
/**
|
|
* Generic deep merge for plain objects (non-branded theme sections).
|
|
*/
|
|
function deepMerge(base, override) {
|
|
const result = { ...base };
|
|
for (const key of Object.keys(override)) {
|
|
const baseVal = base[key];
|
|
const overrideVal = override[key];
|
|
if (overrideVal === undefined)
|
|
continue;
|
|
if (typeof baseVal === 'object' && baseVal !== null && !Array.isArray(baseVal) &&
|
|
typeof overrideVal === 'object' && overrideVal !== null && !Array.isArray(overrideVal)) {
|
|
result[key] = deepMerge(baseVal, overrideVal);
|
|
}
|
|
else {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Merge the `colors` section with awareness of branded types.
|
|
*/
|
|
function mergeColors(base, override) {
|
|
const result = { ...base };
|
|
for (const key of Object.keys(override)) {
|
|
const overrideVal = override[key];
|
|
if (overrideVal === undefined)
|
|
continue;
|
|
if (COLOR_SCALE_KEYS.has(key) && isColorScaleOverride(overrideVal)) {
|
|
result[key] = mergeColorScale(base[key], overrideVal);
|
|
}
|
|
else if (SEMANTIC_COLOR_KEYS.has(key) && isSemanticColorOverride(overrideVal)) {
|
|
result[key] = mergeSemanticColor(base[key], overrideVal);
|
|
}
|
|
else if (key === 'border' && typeof overrideVal === 'object') {
|
|
result.border = mergeBorderColor(base.border, overrideVal);
|
|
}
|
|
else if (typeof overrideVal === 'object' && overrideVal !== null) {
|
|
const baseSection = base[key];
|
|
if (typeof baseSection === 'object' && baseSection !== null) {
|
|
result[key] = deepMerge(baseSection, overrideVal);
|
|
}
|
|
else {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
else {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Create a custom theme by deep-merging overrides onto a base theme.
|
|
*
|
|
* Handles branded types correctly:
|
|
* - ColorScale (primary, secondary, accent) — reconstructed via createColorScale()
|
|
* - SemanticColor (success, warning, error, info) — reconstructed via createSemanticColor()
|
|
* - BorderColor — reconstructed via createBorderColor()
|
|
*
|
|
* Plain object sections (spacing, typography, shadows, etc.) are deep-merged normally.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* import { cyberpunkAdapter, createCustomTheme } from '@cocotte/ui-theme';
|
|
*
|
|
* const myTheme = createCustomTheme(cyberpunkAdapter, {
|
|
* colors: {
|
|
* primary: { main: '#00ff9f', dark: '#00cc7f', light: '#66ffbf' },
|
|
* accent: { main: '#ff00ff', dark: '#cc00cc', light: '#ff66ff' },
|
|
* },
|
|
* });
|
|
* ```
|
|
*/
|
|
export function createCustomTheme(base, overrides) {
|
|
const result = { ...base };
|
|
for (const key of Object.keys(overrides)) {
|
|
const overrideVal = overrides[key];
|
|
if (overrideVal === undefined)
|
|
continue;
|
|
if (key === 'colors') {
|
|
result.colors = mergeColors(base.colors, overrideVal);
|
|
}
|
|
else if (typeof overrideVal === 'object' && overrideVal !== null) {
|
|
const baseSection = base[key];
|
|
if (typeof baseSection === 'object' && baseSection !== null) {
|
|
result[key] = deepMerge(baseSection, overrideVal);
|
|
}
|
|
else {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
else {
|
|
result[key] = overrideVal;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
//# sourceMappingURL=merge-theme.js.map
|