Skip to content

Commit

Permalink
feat: add set custom primary colors method in initialize flow
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-canon committed Nov 22, 2024
1 parent 55332c1 commit 44c2f7d
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ let config = {
MFE_CONFIG_API_URL: process.env.MFE_CONFIG_API_URL,
APP_ID: process.env.APP_ID,
SUPPORT_URL: process.env.SUPPORT_URL,
CUSTOM_PRIMARY_COLORS: process.env.CUSTOM_PRIMARY_COLORS || {},
};

/**
Expand Down Expand Up @@ -325,4 +326,5 @@ export function ensureConfig(keys, requester = 'unspecified application code') {
* @property {string} APP_ID
* @property {string} SUPPORT_URL
* @property {string} PARAGON_THEME_URLS
* @property {Object} CUSTOM_PRIMARY_COLORS
*/
14 changes: 14 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,17 @@ export const APP_INIT_ERROR = `${APP_TOPIC}.INIT_ERROR`;
export const CONFIG_TOPIC = 'CONFIG';

export const CONFIG_CHANGED = `${CONFIG_TOPIC}.CHANGED`;

export const PRIMARY_COLOR_DEFINITIONS = {
'pgn-color-primary-100': { '#FFFFFF': 94 },
'pgn-color-primary-200': { '#FFFFFF': 75 },
'pgn-color-primary-300': { '#FFFFFF': 50 },
'pgn-color-primary-400': { '#FFFFFF': 25 },
'pgn-color-primary-500': { '#FFFFFF': 0 },
'pgn-color-primary-600': { '#000000': 10 },
'pgn-color-primary-700': { '#000000': 20 },
'pgn-color-primary-800': { '#000000': 25 },
'pgn-color-primary-900': { '#000000': 30 },
'pgn-color-link-base': { '#FFFFFF': 35 },
'pgn-color-link-hover': { '#FFFFFF': 0 },
};
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
ensureDefinedConfig,
parseURL,
getPath,
mix,
} from './utils';
export {
APP_TOPIC,
Expand Down
39 changes: 38 additions & 1 deletion src/initialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Note that the env.config.js file in frontend-platform's root directory is NOT us
initialization code, it's just there for the test suite and example application.
*/
import envConfig from 'env.config'; // eslint-disable-line import/no-unresolved
import { getPath } from './utils';
import { getPath, mix } from './utils';
import {
publish,
} from './pubSub';
Expand Down Expand Up @@ -87,6 +87,7 @@ import {
APP_LOGGING_INITIALIZED,
APP_ANALYTICS_INITIALIZED,
APP_READY, APP_INIT_ERROR,
PRIMARY_COLOR_DEFINITIONS,
} from './constants';
import configureCache from './auth/LocalForageCache';

Expand Down Expand Up @@ -205,6 +206,41 @@ export function loadExternalScripts(externalScripts, data) {
});
}

/*
* Set custom colors based on the config content.
* This method allows to change primary colors and its levels on runtime,
* if a specific level is already in the configuration that level will have
* priority otherwise the level will be calculated based on primary color by
* using the mix function.
*/
export function setCustomPrimaryColors() {
const { CUSTOM_PRIMARY_COLORS } = getConfig();
const primary = CUSTOM_PRIMARY_COLORS['pgn-color-primary-base'];

if (!primary) {
return;
}
document.documentElement.style.setProperty('--pgn-color-primary-base', primary);

Object.keys(PRIMARY_COLOR_DEFINITIONS).forEach((key) => {
let color;

if (key in CUSTOM_PRIMARY_COLORS) {
color = CUSTOM_PRIMARY_COLORS[key];
} else {
try {
const [base, weight] = Object.entries(PRIMARY_COLOR_DEFINITIONS[key])[0];

color = mix(base, primary, weight);
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error setting custom colors', error.message);
}
}
document.documentElement.style.setProperty('--'.concat(key), color);
});
}

/**
* The default handler for the initialization lifecycle's `analytics` phase.
*
Expand Down Expand Up @@ -306,6 +342,7 @@ export async function initialize({
await handlers.config();
await jsFileConfig();
await runtimeConfig();
setCustomPrimaryColors();
publish(APP_CONFIG_INITIALIZED);

loadExternalScripts(externalScripts, {
Expand Down
85 changes: 85 additions & 0 deletions src/initialize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,91 @@ describe('initialize', () => {
expect(hydrateAuthenticatedUser).not.toHaveBeenCalled();
expect(logError).not.toHaveBeenCalled();
});

it('should not set any color', async () => {
const messages = { i_am: 'a message' };
await initialize({
messages,
});

// eslint-disable-next-line no-underscore-dangle
expect(document.documentElement.style._values).toEqual({});
expect(logError).not.toHaveBeenCalled();
});

it('should set primary color and calculate its levels', async () => {
config.CUSTOM_PRIMARY_COLORS = { 'pgn-color-primary-base': '#A000000' };
const messages = { i_am: 'a message' };
const expectedKeys = [
'--pgn-color-primary-base',
'--pgn-color-primary-100',
'--pgn-color-primary-200',
'--pgn-color-primary-300',
'--pgn-color-primary-400',
'--pgn-color-primary-500',
'--pgn-color-primary-600',
'--pgn-color-primary-700',
'--pgn-color-primary-800',
'--pgn-color-primary-900',
'--pgn-color-link-base',
'--pgn-color-link-hover',
];

await initialize({
messages,
});

// eslint-disable-next-line no-underscore-dangle
expect(Object.keys(document.documentElement.style._values)).toEqual(expectedKeys);
expect(logError).not.toHaveBeenCalled();
});

it('should set primary color and its levels from config', async () => {
config.CUSTOM_PRIMARY_COLORS = {
'pgn-color-primary-base': '#A000000',
'pgn-color-primary-100': '#A001000',
'pgn-color-primary-200': '#A000000',
'pgn-color-primary-300': '#A045000',
'pgn-color-primary-400': '#A07AB00',
'pgn-color-primary-500': '#A000B12',
'pgn-color-primary-600': '#A087400',
'pgn-color-primary-700': '#A0abc00',
'pgn-color-primary-800': '#AABCFA0',
'pgn-color-primary-900': '#A014200',
'pgn-color-link-base': '#FF0056',
'pgn-color-link-hover': '#AFFCDA',
};
const messages = { i_am: 'a message' };

await initialize({
messages,
});

// eslint-disable-next-line no-underscore-dangle
expect(Object.values(document.documentElement.style._values)).toEqual(Object.values(config.CUSTOM_PRIMARY_COLORS));
expect(logError).not.toHaveBeenCalled();
});
it('should log error when color is invalid', async () => {
// eslint-disable-next-line no-console
console.error = jest.fn();
configureCache.mockReturnValueOnce(Promise.resolve({
get: (url) => {
const params = new URL(url).search;
const mfe = new URLSearchParams(params).get('mfe');
return ({ data: { ...newConfig.common, ...newConfig[mfe] } });
},
}));
config.CUSTOM_PRIMARY_COLORS = { 'pgn-color-primary-base': '#AB' };
const messages = { i_am: 'a message' };

await initialize({
messages,
});

// eslint-disable-next-line no-console
expect(console.error).toHaveBeenNthCalledWith(9, 'Error setting custom colors', 'Parameter color does not have format #RRGGBB');
expect(logError).not.toHaveBeenCalled();
});
});

describe('history', () => {
Expand Down
34 changes: 34 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,37 @@ export function ensureDefinedConfig(object, requester) {
}
});
}

/**
* This function is the javascript version of SASS mix() function,
* https://sass-lang.com/documentation/modules/color#mix
*
* @param {string} First color in hexadecimal.
* @param {string} Second color in hexadecimal.
* @param {number} Relative opacity of each color.
* @returns {string} Returns a color that’s a mixture of color1 and color2.
*/
export function mix(color1, color2, weight = 50) {
let color = '#';

function d2h(d) { return d.toString(16); } // convert a decimal value to hex
function h2d(h) { return parseInt(h, 16); } // convert a hex value to decimal

if (color1.length < 6 || color2.length < 6) {
throw new Error('Parameter color does not have format #RRGGBB');
}

for (let i = 0; i <= 5; i += 2) { // loop through each of the 3 hex pairs—red, green, and blue
const v1 = h2d(color1.replace('#', '').substr(i, 2));
const v2 = h2d(color2.replace('#', '').substr(i, 2));
let val = d2h(Math.round(v2 + (v1 - v2) * (weight / 100.0)));

while (val.length < 2) {
val = '0'.concat(val);
}

color += val;
}

return color;
}
19 changes: 19 additions & 0 deletions src/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
parseURL,
getPath,
getQueryParameters,
mix,
} from '.';

describe('modifyObjectKeys', () => {
Expand Down Expand Up @@ -207,3 +208,21 @@ describe('getPath', () => {
expect(getPath(testURL)).toEqual('/learning/');
});
});

describe('mix', () => {
it('should return rigth value', () => {
const expected = '#546e88'; // This value was calculated in https://sass.js.org/ by using sass mix function

expect(mix('#FFFFFF', '#0A3055', 30)).toBe(expected);
});

it('should thow error', () => {
expect(() => mix('#FFFFFF', '#0A3')).toThrow('Parameter color does not have format #RRGGBB');
});

it('should return rigth value without hash symbol on parameters', () => {
const expected = '#8598aa'; // This value was calculated in https://sass.js.org/ by using sass mix function

expect(mix('FFFFFF', '0A3055')).toBe(expected);
});
});

0 comments on commit 44c2f7d

Please sign in to comment.