forked from openedx/paragon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sass-helpers.js
98 lines (89 loc) · 4.05 KB
/
sass-helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const path = require('path');
const fs = require('fs');
const chroma = require('chroma-js');
const chalk = require('chalk');
/**
* Javascript version of bootstrap's color-yiq function. Decides whether to return light color variant or dark one
* based on contrast value of the input color
*
* @param {Object} args
* @param {Object} args.tokenName - Name of design token, used to log warnings
* @param {Object} args.backgroundColor - chroma-js color instance
* @param {String} args.light - light color variant from ./src/themes/{themeVariant}/global/other.json
* @param {String} args.dark - dark color variant from ./src/themes/{themeVariant}/global/other.json
* @param {Number} args.threshold - contrast threshold from ./src/core/global/other.json
* @param {String} [args.themeVariant] - theme variant name that will be used to find default contrast colors
*
* @return chroma-js color instance (one of dark or light variants)
*/
function colorYiq({
tokenName,
backgroundColor,
light,
dark,
threshold,
themeVariant = 'light',
}) {
const defaultThresholdFile = fs.readFileSync(path.resolve(__dirname, 'src/core/global', 'other.json'), 'utf8');
const defaultThreshold = JSON.parse(defaultThresholdFile)['yiq-contrasted-threshold'];
const defaultColorsFile = fs.readFileSync(path.resolve(__dirname, `src/themes/${themeVariant}/global`, 'other.json'), 'utf8');
const {
'yiq-text-dark': defaultDark,
'yiq-text-light': defaultLight,
} = JSON.parse(defaultColorsFile);
const contrastThreshold = threshold || defaultThreshold;
const lightColor = light || defaultLight;
const darkColor = dark || defaultDark;
const [r, g, b] = backgroundColor.rgb();
const yiq = ((r * 299) + (g * 587) + (b * 114)) * 0.001;
let result = yiq >= contrastThreshold ? chroma(darkColor) : chroma(lightColor);
const maxAttempts = 10; // maximum number of attempts to darken/brighten color to pass contrast ratio
if (yiq >= contrastThreshold) {
// check whether the resulting combination of colors passes a11y contrast ratio of 4:5:1
// if not - darken resulting color until it does until maxAttempts is reached.
let numDarkenAttempts = 1;
while (chroma.contrast(backgroundColor, result) < 4.5 && numDarkenAttempts <= maxAttempts) {
result = result.darken(0.1);
numDarkenAttempts += 1;
if (numDarkenAttempts === maxAttempts) {
const title = `[a11y] Warning: Failed to sufficiently darken token ${chalk.keyword('orange').bold(tokenName)} to pass contrast ratio of 4.5:1.`;
const warningMetadata = [
`Background color: ${backgroundColor.hex()}`,
`Attempted foreground color: ${result.hex()}`,
].join('\n ');
const warn = `${title}\n ${warningMetadata}`;
// eslint-disable-next-line no-console
console.log(chalk.keyword('yellow').bold(warn));
}
}
return result;
}
// check whether the resulting combination of colors passes a11y contrast ratio of 4:5:1
// if not - brighten resulting color until it does until maxAttempts is reached.
let numBrightenAttempts = 1;
while (chroma.contrast(backgroundColor, result) < 4.5 && numBrightenAttempts <= maxAttempts) {
result = result.brighten(0.1);
numBrightenAttempts += 1;
if (numBrightenAttempts === maxAttempts) {
const title = `[a11y] Warning: Failed to sufficiently brighten token ${chalk.keyword('orange').bold(tokenName)} to pass contrast ratio of 4.5:1.`;
const warningMetadata = [
`Background color: ${backgroundColor.hex()}`,
`Attempted foreground color: ${result.hex()}`,
].join('\n ');
const warn = `${title}\n ${warningMetadata}`;
// eslint-disable-next-line no-console
console.log(chalk.keyword('yellow').bold(warn));
}
}
return result;
}
/**
* Overrides chroma-js's lighten / darken functions to behave the same way SASS functions do.
*/
const lighten = (color, hslPercent) => color.set('hsl.l', color.get('hsl.l') + hslPercent);
const darken = (color, hslPercent) => lighten(color, -hslPercent);
module.exports = {
colorYiq,
darken,
lighten,
};