Skip to content

Commit b35dc20

Browse files
authored
feat: add set custom primary colors method in initialize flow (#11)
1 parent 0c4c5da commit b35dc20

File tree

8 files changed

+195
-1
lines changed

8 files changed

+195
-1
lines changed

src/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ let config = {
206206
APP_ID: process.env.APP_ID,
207207
SUPPORT_URL: process.env.SUPPORT_URL,
208208
PARAGON_THEME_URLS: parseParagonThemeUrls(process.env.PARAGON_THEME_URLS),
209+
CUSTOM_PRIMARY_COLORS: process.env.CUSTOM_PRIMARY_COLORS || {},
209210
};
210211

211212
/**
@@ -360,4 +361,5 @@ export function ensureConfig(keys, requester = 'unspecified application code') {
360361
* @property {string} APP_ID
361362
* @property {string} SUPPORT_URL
362363
* @property {string} PARAGON_THEME_URLS
364+
* @property {Object} CUSTOM_PRIMARY_COLORS
363365
*/

src/constants.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,17 @@ export const APP_INIT_ERROR = `${APP_TOPIC}.INIT_ERROR`;
6464
export const CONFIG_TOPIC = 'CONFIG';
6565

6666
export const CONFIG_CHANGED = `${CONFIG_TOPIC}.CHANGED`;
67+
68+
export const PRIMARY_COLOR_DEFINITIONS = {
69+
'pgn-color-primary-100': { '#FFFFFF': 94 },
70+
'pgn-color-primary-200': { '#FFFFFF': 75 },
71+
'pgn-color-primary-300': { '#FFFFFF': 50 },
72+
'pgn-color-primary-400': { '#FFFFFF': 25 },
73+
'pgn-color-primary-500': { '#FFFFFF': 0 },
74+
'pgn-color-primary-600': { '#000000': 10 },
75+
'pgn-color-primary-700': { '#000000': 20 },
76+
'pgn-color-primary-800': { '#000000': 25 },
77+
'pgn-color-primary-900': { '#000000': 30 },
78+
'pgn-color-link-base': { '#FFFFFF': 35 },
79+
'pgn-color-link-hover': { '#FFFFFF': 0 },
80+
};

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
ensureDefinedConfig,
88
parseURL,
99
getPath,
10+
mix,
1011
} from './utils';
1112
export {
1213
APP_TOPIC,

src/initialize.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Note that the env.config.js file in frontend-platform's root directory is NOT us
5454
initialization code, it's just there for the test suite and example application.
5555
*/
5656
import envConfig from 'env.config'; // eslint-disable-line import/no-unresolved
57-
import { getPath } from './utils';
57+
import { getPath, mix } from './utils';
5858
import {
5959
publish,
6060
} from './pubSub';
@@ -87,6 +87,7 @@ import {
8787
APP_LOGGING_INITIALIZED,
8888
APP_ANALYTICS_INITIALIZED,
8989
APP_READY, APP_INIT_ERROR,
90+
PRIMARY_COLOR_DEFINITIONS,
9091
} from './constants';
9192
import configureCache from './auth/LocalForageCache';
9293

@@ -205,6 +206,42 @@ export function loadExternalScripts(externalScripts, data) {
205206
});
206207
}
207208

209+
/*
210+
* Set custom colors based on the config content.
211+
* This method allows to change primary colors and its levels on runtime,
212+
* if a specific level is already in the configuration that level will have
213+
* priority otherwise the level will be calculated based on primary color by
214+
* using the mix function.
215+
*/
216+
export function setCustomPrimaryColors() {
217+
const { CUSTOM_PRIMARY_COLORS } = getConfig();
218+
const { PARAGON_THEME_URLS } = getConfig();
219+
const primary = CUSTOM_PRIMARY_COLORS['pgn-color-primary-base'];
220+
221+
if (!primary || Object.keys(PARAGON_THEME_URLS).length > 0) {
222+
return;
223+
}
224+
document.documentElement.style.setProperty('--pgn-color-primary-base', primary);
225+
226+
Object.keys(PRIMARY_COLOR_DEFINITIONS).forEach((key) => {
227+
let color;
228+
229+
if (key in CUSTOM_PRIMARY_COLORS) {
230+
color = CUSTOM_PRIMARY_COLORS[key];
231+
} else {
232+
try {
233+
const [base, weight] = Object.entries(PRIMARY_COLOR_DEFINITIONS[key])[0];
234+
235+
color = mix(base, primary, weight);
236+
} catch (error) {
237+
// eslint-disable-next-line no-console
238+
console.error('Error setting custom colors', error.message);
239+
}
240+
}
241+
document.documentElement.style.setProperty('--'.concat(key), color);
242+
});
243+
}
244+
208245
/**
209246
* The default handler for the initialization lifecycle's `analytics` phase.
210247
*
@@ -306,6 +343,7 @@ export async function initialize({
306343
await handlers.config();
307344
await jsFileConfig();
308345
await runtimeConfig();
346+
setCustomPrimaryColors();
309347
publish(APP_CONFIG_INITIALIZED);
310348

311349
loadExternalScripts(externalScripts, {

src/initialize.test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,91 @@ describe('initialize', () => {
352352
expect(hydrateAuthenticatedUser).not.toHaveBeenCalled();
353353
expect(logError).not.toHaveBeenCalled();
354354
});
355+
356+
it('should not set any color', async () => {
357+
const messages = { i_am: 'a message' };
358+
await initialize({
359+
messages,
360+
});
361+
362+
// eslint-disable-next-line no-underscore-dangle
363+
expect(document.documentElement.style._values).toEqual({});
364+
expect(logError).not.toHaveBeenCalled();
365+
});
366+
367+
it('should set primary color and calculate its levels', async () => {
368+
config.CUSTOM_PRIMARY_COLORS = { 'pgn-color-primary-base': '#A000000' };
369+
const messages = { i_am: 'a message' };
370+
const expectedKeys = [
371+
'--pgn-color-primary-base',
372+
'--pgn-color-primary-100',
373+
'--pgn-color-primary-200',
374+
'--pgn-color-primary-300',
375+
'--pgn-color-primary-400',
376+
'--pgn-color-primary-500',
377+
'--pgn-color-primary-600',
378+
'--pgn-color-primary-700',
379+
'--pgn-color-primary-800',
380+
'--pgn-color-primary-900',
381+
'--pgn-color-link-base',
382+
'--pgn-color-link-hover',
383+
];
384+
385+
await initialize({
386+
messages,
387+
});
388+
389+
// eslint-disable-next-line no-underscore-dangle
390+
expect(Object.keys(document.documentElement.style._values)).toEqual(expectedKeys);
391+
expect(logError).not.toHaveBeenCalled();
392+
});
393+
394+
it('should set primary color and its levels from config', async () => {
395+
config.CUSTOM_PRIMARY_COLORS = {
396+
'pgn-color-primary-base': '#A000000',
397+
'pgn-color-primary-100': '#A001000',
398+
'pgn-color-primary-200': '#A000000',
399+
'pgn-color-primary-300': '#A045000',
400+
'pgn-color-primary-400': '#A07AB00',
401+
'pgn-color-primary-500': '#A000B12',
402+
'pgn-color-primary-600': '#A087400',
403+
'pgn-color-primary-700': '#A0abc00',
404+
'pgn-color-primary-800': '#AABCFA0',
405+
'pgn-color-primary-900': '#A014200',
406+
'pgn-color-link-base': '#FF0056',
407+
'pgn-color-link-hover': '#AFFCDA',
408+
};
409+
const messages = { i_am: 'a message' };
410+
411+
await initialize({
412+
messages,
413+
});
414+
415+
// eslint-disable-next-line no-underscore-dangle
416+
expect(Object.values(document.documentElement.style._values)).toEqual(Object.values(config.CUSTOM_PRIMARY_COLORS));
417+
expect(logError).not.toHaveBeenCalled();
418+
});
419+
it('should log error when color is invalid', async () => {
420+
// eslint-disable-next-line no-console
421+
console.error = jest.fn();
422+
configureCache.mockReturnValueOnce(Promise.resolve({
423+
get: (url) => {
424+
const params = new URL(url).search;
425+
const mfe = new URLSearchParams(params).get('mfe');
426+
return ({ data: { ...newConfig.common, ...newConfig[mfe] } });
427+
},
428+
}));
429+
config.CUSTOM_PRIMARY_COLORS = { 'pgn-color-primary-base': '#AB' };
430+
const messages = { i_am: 'a message' };
431+
432+
await initialize({
433+
messages,
434+
});
435+
436+
// eslint-disable-next-line no-console
437+
expect(console.error).toHaveBeenNthCalledWith(9, 'Error setting custom colors', 'Parameter color does not have format #RRGGBB');
438+
expect(logError).not.toHaveBeenCalled();
439+
});
355440
});
356441

357442
describe('history', () => {

src/react/AppProvider.test.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jest.mock('../config', () => ({
3333
ACCESS_TOKEN_COOKIE_NAME: 'access_token',
3434
CSRF_TOKEN_API_PATH: 'localhost:18000/csrf',
3535
PUBLIC_PATH: '/',
36+
CUSTOM_PRIMARY_COLORS: {},
3637
}),
3738
}));
3839

src/utils.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,37 @@ export function ensureDefinedConfig(object, requester) {
201201
}
202202
});
203203
}
204+
205+
/**
206+
* This function is the javascript version of SASS mix() function,
207+
* https://sass-lang.com/documentation/modules/color#mix
208+
*
209+
* @param {string} First color in hexadecimal.
210+
* @param {string} Second color in hexadecimal.
211+
* @param {number} Relative opacity of each color.
212+
* @returns {string} Returns a color that’s a mixture of color1 and color2.
213+
*/
214+
export function mix(color1, color2, weight = 50) {
215+
let color = '#';
216+
217+
function d2h(d) { return d.toString(16); } // convert a decimal value to hex
218+
function h2d(h) { return parseInt(h, 16); } // convert a hex value to decimal
219+
220+
if (color1.length < 6 || color2.length < 6) {
221+
throw new Error('Parameter color does not have format #RRGGBB');
222+
}
223+
224+
for (let i = 0; i <= 5; i += 2) { // loop through each of the 3 hex pairs—red, green, and blue
225+
const v1 = h2d(color1.replace('#', '').substr(i, 2));
226+
const v2 = h2d(color2.replace('#', '').substr(i, 2));
227+
let val = d2h(Math.round(v2 + (v1 - v2) * (weight / 100.0)));
228+
229+
while (val.length < 2) {
230+
val = '0'.concat(val);
231+
}
232+
233+
color += val;
234+
}
235+
236+
return color;
237+
}

src/utils.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
parseURL,
88
getPath,
99
getQueryParameters,
10+
mix,
1011
} from '.';
1112

1213
describe('modifyObjectKeys', () => {
@@ -208,3 +209,21 @@ describe('getPath', () => {
208209
expect(getPath(testURL)).toEqual('/learning/');
209210
});
210211
});
212+
213+
describe('mix', () => {
214+
it('should return rigth value', () => {
215+
const expected = '#546e88'; // This value was calculated in https://sass.js.org/ by using sass mix function
216+
217+
expect(mix('#FFFFFF', '#0A3055', 30)).toBe(expected);
218+
});
219+
220+
it('should thow error', () => {
221+
expect(() => mix('#FFFFFF', '#0A3')).toThrow('Parameter color does not have format #RRGGBB');
222+
});
223+
224+
it('should return rigth value without hash symbol on parameters', () => {
225+
const expected = '#8598aa'; // This value was calculated in https://sass.js.org/ by using sass mix function
226+
227+
expect(mix('FFFFFF', '0A3055')).toBe(expected);
228+
});
229+
});

0 commit comments

Comments
 (0)