Files
jitsi-meet/react/features/base/ui/utils.ts
Avram Tudor 04dc27b9e8 fix(dynamic-branding): custom palette colors not applied to semantic tokens (#17252)
Fixes a regression introduced by semantic tokens, released in 6626 where components switched from using base tokens (e.g., action01) to semantic tokens (e.g., prejoinActionButtonPrimary).

This caused custom branding palette overrides to stop working because semantic tokens were resolved BEFORE the custom palette was merged.
2026-03-31 16:00:40 +03:00

84 lines
2.9 KiB
TypeScript

import { merge } from 'lodash-es';
import * as jitsiTokens from './jitsiTokens.json';
import * as tokens from './tokens.json';
import { IPalette } from './types';
/**
* Creates the color tokens based on the color theme and the association map.
*
* @param {Object} colorMap - A map between the token name and the actual color value.
* @param {Object} customTokens - Optional custom token overrides to merge before resolution.
* This allows custom branding colors to propagate through semantic token references.
* For example, if customTokens contains { action01: '#custom' }, then any semantic token
* that references 'action01' (like 'prejoinActionButtonPrimary') will resolve to '#custom'.
* @returns {Object}
*/
export function createColorTokens(colorMap: Object, customTokens?: Partial<IPalette>): any {
const allTokens = merge({}, tokens, jitsiTokens, customTokens || {});
const result: any = {};
// First pass: resolve tokens that reference allTokens directly
Object.entries(colorMap).forEach(([ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
result[token] = color;
});
// Second pass: resolve semantic tokens that reference other colorMap entries
// Recursively resolve until we get actual color values
const resolveToken = (value: string, depth = 0): string => {
// Prevent infinite loops
if (depth > 10) {
return value;
}
// If it's already a color (starts with # or rgb/rgba), return it
if (value.startsWith('#') || value.startsWith('rgb')) {
return value;
}
// Look up in the result map first (for colorMap token references)
if (result[value]) {
return resolveToken(result[value], depth + 1);
}
// Then look up in allTokens
const resolved = allTokens[value as keyof typeof allTokens];
if (resolved && resolved !== value && typeof resolved === 'string') {
return resolveToken(resolved, depth + 1);
}
return value;
};
// Third pass: recursively resolve all values
Object.entries(result).forEach(([ token, value ]) => {
result[token] = resolveToken(String(value));
});
return result;
}
/**
* Create the typography tokens based on the typography theme and the association map.
*
* @param {Object} typography - A map between the token name and the actual typography value.
* @returns {Object}
*/
export function createTypographyTokens(typography: Object): any {
const allTokens = merge({}, tokens, jitsiTokens);
return Object.entries(typography)
.reduce((result, [ token, value ]: [any, any]) => {
let typographyValue = value;
if (typeof value === 'string') {
typographyValue = allTokens[value as keyof typeof allTokens] || value;
}
return Object.assign(result, { [token]: typographyValue });
}, {});
}