@ucam/design-system
Version:
University of Cambridge Design System
78 lines (74 loc) • 11.4 kB
JavaScript
;
var core = require('@material-ui/core');
/**
* Calculates the rgba value that when overlaid on the "backgroundColor" forms the supplied "color".
* Note: There are many rgba values that may work, it picks the value with the lowest alpha (most transparent)
*
* This function only takes rgb strings eg: `"rgb(255,255,255)"`. Conversion from hex is possible using:
* hexToRgb from @material-ui/core.
*
* Justification: We're often given subtle, non-transparent colours by the UX team for use on borders and highlights.
* These colours provide adequate contrast against the default background colour, but do not guarantee to contrast
* sufficiently against other colours. A potential improvement (without changing the default case) is to use
* semi-transparent colours for the foreground colour. There is still no contrast guarantee, but it does produce nicer
* results when used with most unexpected background colours.
*
* For example:
* 1. A grey border colour on a white background has an adequate contrast ratio.
* 2. The same grey border colour on a grey background has a poor contrast ratio.
* 3. A black (but semi-transparent) border colour on a white background appears grey, an adequate contrast.
* 4. A black (but semi-transparent) border colour on a grey background appears **dark** grey, an adequate contrast.
*
* Both 1. and 3. produce the same border colour, but 4. produces a better result than 2.
*/
const equivalentRGBA = (colorRGB, backgroundColorRGB, preferredAlpha = 0, precision = 0.001) => {
console.assert(preferredAlpha >= 0 && preferredAlpha <= 1, 'preferredAlpha must be a number between 0 and 1');
console.assert(precision > 0, 'precision must be positive');
const rgbColor = core.decomposeColor(colorRGB);
const rgbBackgroundColor = core.decomposeColor(backgroundColorRGB);
if (rgbColor.type !== 'rgb') {
throw Error('Argument colorRGB must be an RGB color');
}
if (rgbBackgroundColor.type !== 'rgb') {
throw Error('Argument backgroundColorRGB must be an RGB color');
}
if (rgbColor.colorSpace !== rgbBackgroundColor.colorSpace) {
throw Error('Color spaces must match');
}
// Binary search for the minimum alpha that results in a valid rgba overlay color
let currentAlpha = preferredAlpha;
let delta = (1 - preferredAlpha) / 2;
let result = [
rgbColor.values[0],
rgbColor.values[1],
rgbColor.values[2],
1
];
while (delta > precision / 3 && currentAlpha + delta <= 1) {
const testAlpha = currentAlpha + delta;
// For the testAlpha calculate the rgb values for the overlayColor
const overlayColor = [
(rgbColor.values[0] - rgbBackgroundColor.values[0] * (1 - testAlpha)) / testAlpha,
(rgbColor.values[1] - rgbBackgroundColor.values[1] * (1 - testAlpha)) / testAlpha,
(rgbColor.values[2] - rgbBackgroundColor.values[2] * (1 - testAlpha)) / testAlpha
];
// Check that each of the overlayColor's RGB values are in the range 0-255
const isValid = Math.max(...overlayColor) <= 255 && Math.min(...overlayColor) >= 0;
if (isValid) {
// Found a valid color, now check for colors with a lower alpha
result = [...overlayColor, Math.round(testAlpha / precision) * precision];
delta /= 2;
}
else {
// Did not find a valid color, now check for colors with a higher alpha
currentAlpha += delta;
}
}
return core.recomposeColor({
type: 'rgba',
values: result,
colorSpace: rgbColor.colorSpace
});
};
module.exports = equivalentRGBA;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXF1aXZhbGVudFJHQkEuanMiLCJzb3VyY2VzIjpbIi9AdWNhbS9kZXNpZ24tc3lzdGVtL3NyYy91dGlscy9lcXVpdmFsZW50UkdCQS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBkZWNvbXBvc2VDb2xvciwgcmVjb21wb3NlQ29sb3IgfSBmcm9tICdAbWF0ZXJpYWwtdWkvY29yZSc7XG5cbi8qKlxuICogQ2FsY3VsYXRlcyB0aGUgcmdiYSB2YWx1ZSB0aGF0IHdoZW4gb3ZlcmxhaWQgb24gdGhlIFwiYmFja2dyb3VuZENvbG9yXCIgZm9ybXMgdGhlIHN1cHBsaWVkIFwiY29sb3JcIi5cbiAqIE5vdGU6IFRoZXJlIGFyZSBtYW55IHJnYmEgdmFsdWVzIHRoYXQgbWF5IHdvcmssIGl0IHBpY2tzIHRoZSB2YWx1ZSB3aXRoIHRoZSBsb3dlc3QgYWxwaGEgKG1vc3QgdHJhbnNwYXJlbnQpXG4gKlxuICogVGhpcyBmdW5jdGlvbiBvbmx5IHRha2VzIHJnYiBzdHJpbmdzIGVnOiBgXCJyZ2IoMjU1LDI1NSwyNTUpXCJgLiBDb252ZXJzaW9uIGZyb20gaGV4IGlzIHBvc3NpYmxlIHVzaW5nOlxuICogaGV4VG9SZ2IgZnJvbSBAbWF0ZXJpYWwtdWkvY29yZS5cbiAqXG4gKiBKdXN0aWZpY2F0aW9uOiBXZSdyZSBvZnRlbiBnaXZlbiBzdWJ0bGUsIG5vbi10cmFuc3BhcmVudCBjb2xvdXJzIGJ5IHRoZSBVWCB0ZWFtIGZvciB1c2Ugb24gYm9yZGVycyBhbmQgaGlnaGxpZ2h0cy5cbiAqIFRoZXNlIGNvbG91cnMgcHJvdmlkZSBhZGVxdWF0ZSBjb250cmFzdCBhZ2FpbnN0IHRoZSBkZWZhdWx0IGJhY2tncm91bmQgY29sb3VyLCBidXQgZG8gbm90IGd1YXJhbnRlZSB0byBjb250cmFzdFxuICogc3VmZmljaWVudGx5IGFnYWluc3Qgb3RoZXIgY29sb3Vycy4gQSBwb3RlbnRpYWwgaW1wcm92ZW1lbnQgKHdpdGhvdXQgY2hhbmdpbmcgdGhlIGRlZmF1bHQgY2FzZSkgaXMgdG8gdXNlXG4gKiBzZW1pLXRyYW5zcGFyZW50IGNvbG91cnMgZm9yIHRoZSBmb3JlZ3JvdW5kIGNvbG91ci4gVGhlcmUgaXMgc3RpbGwgbm8gY29udHJhc3QgZ3VhcmFudGVlLCBidXQgaXQgZG9lcyBwcm9kdWNlIG5pY2VyXG4gKiByZXN1bHRzIHdoZW4gdXNlZCB3aXRoIG1vc3QgdW5leHBlY3RlZCBiYWNrZ3JvdW5kIGNvbG91cnMuXG4gKlxuICogRm9yIGV4YW1wbGU6XG4gKiAxLiBBIGdyZXkgYm9yZGVyIGNvbG91ciBvbiBhIHdoaXRlIGJhY2tncm91bmQgaGFzIGFuIGFkZXF1YXRlIGNvbnRyYXN0IHJhdGlvLlxuICogMi4gVGhlIHNhbWUgZ3JleSBib3JkZXIgY29sb3VyIG9uIGEgZ3JleSBiYWNrZ3JvdW5kIGhhcyBhIHBvb3IgY29udHJhc3QgcmF0aW8uXG4gKiAzLiBBIGJsYWNrIChidXQgc2VtaS10cmFuc3BhcmVudCkgYm9yZGVyIGNvbG91ciBvbiBhIHdoaXRlIGJhY2tncm91bmQgYXBwZWFycyBncmV5LCBhbiBhZGVxdWF0ZSBjb250cmFzdC5cbiAqIDQuIEEgYmxhY2sgKGJ1dCBzZW1pLXRyYW5zcGFyZW50KSBib3JkZXIgY29sb3VyIG9uIGEgZ3JleSBiYWNrZ3JvdW5kIGFwcGVhcnMgKipkYXJrKiogZ3JleSwgYW4gYWRlcXVhdGUgY29udHJhc3QuXG4gKlxuICogQm90aCAxLiBhbmQgMy4gcHJvZHVjZSB0aGUgc2FtZSBib3JkZXIgY29sb3VyLCBidXQgNC4gcHJvZHVjZXMgYSBiZXR0ZXIgcmVzdWx0IHRoYW4gMi5cbiAqL1xuY29uc3QgZXF1aXZhbGVudFJHQkEgPSAoXG4gIGNvbG9yUkdCOiBzdHJpbmcsXG4gIGJhY2tncm91bmRDb2xvclJHQjogc3RyaW5nLFxuICBwcmVmZXJyZWRBbHBoYSA9IDAsXG4gIHByZWNpc2lvbiA9IDAuMDAxXG4pOiBzdHJpbmcgPT4ge1xuICBjb25zb2xlLmFzc2VydChcbiAgICBwcmVmZXJyZWRBbHBoYSA+PSAwICYmIHByZWZlcnJlZEFscGhhIDw9IDEsXG4gICAgJ3ByZWZlcnJlZEFscGhhIG11c3QgYmUgYSBudW1iZXIgYmV0d2VlbiAwIGFuZCAxJ1xuICApO1xuICBjb25zb2xlLmFzc2VydChwcmVjaXNpb24gPiAwLCAncHJlY2lzaW9uIG11c3QgYmUgcG9zaXRpdmUnKTtcblxuICBjb25zdCByZ2JDb2xvciA9IGRlY29tcG9zZUNvbG9yKGNvbG9yUkdCKTtcbiAgY29uc3QgcmdiQmFja2dyb3VuZENvbG9yID0gZGVjb21wb3NlQ29sb3IoYmFja2dyb3VuZENvbG9yUkdCKTtcblxuICBpZiAocmdiQ29sb3IudHlwZSAhPT0gJ3JnYicpIHtcbiAgICB0aHJvdyBFcnJvcignQXJndW1lbnQgY29sb3JSR0IgbXVzdCBiZSBhbiBSR0IgY29sb3InKTtcbiAgfVxuICBpZiAocmdiQmFja2dyb3VuZENvbG9yLnR5cGUgIT09ICdyZ2InKSB7XG4gICAgdGhyb3cgRXJyb3IoJ0FyZ3VtZW50IGJhY2tncm91bmRDb2xvclJHQiBtdXN0IGJlIGFuIFJHQiBjb2xvcicpO1xuICB9XG4gIGlmIChyZ2JDb2xvci5jb2xvclNwYWNlICE9PSByZ2JCYWNrZ3JvdW5kQ29sb3IuY29sb3JTcGFjZSkge1xuICAgIHRocm93IEVycm9yKCdDb2xvciBzcGFjZXMgbXVzdCBtYXRjaCcpO1xuICB9XG5cbiAgLy8gQmluYXJ5IHNlYXJjaCBmb3IgdGhlIG1pbmltdW0gYWxwaGEgdGhhdCByZXN1bHRzIGluIGEgdmFsaWQgcmdiYSBvdmVybGF5IGNvbG9yXG4gIGxldCBjdXJyZW50QWxwaGEgPSBwcmVmZXJyZWRBbHBoYTtcbiAgbGV0IGRlbHRhID0gKDEgLSBwcmVmZXJyZWRBbHBoYSkgLyAyO1xuICBsZXQgcmVzdWx0OiBbbnVtYmVyLCBudW1iZXIsIG51bWJlciwgbnVtYmVyXSA9IFtcbiAgICByZ2JDb2xvci52YWx1ZXNbMF0sXG4gICAgcmdiQ29sb3IudmFsdWVzWzFdLFxuICAgIHJnYkNvbG9yLnZhbHVlc1syXSxcbiAgICAxXG4gIF07XG4gIHdoaWxlIChkZWx0YSA+IHByZWNpc2lvbiAvIDMgJiYgY3VycmVudEFscGhhICsgZGVsdGEgPD0gMSkge1xuICAgIGNvbnN0IHRlc3RBbHBoYSA9IGN1cnJlbnRBbHBoYSArIGRlbHRhO1xuICAgIC8vIEZvciB0aGUgdGVzdEFscGhhIGNhbGN1bGF0ZSB0aGUgcmdiIHZhbHVlcyBmb3IgdGhlIG92ZXJsYXlDb2xvclxuICAgIGNvbnN0IG92ZXJsYXlDb2xvcjogW251bWJlciwgbnVtYmVyLCBudW1iZXJdID0gW1xuICAgICAgKHJnYkNvbG9yLnZhbHVlc1swXSAtIHJnYkJhY2tncm91bmRDb2xvci52YWx1ZXNbMF0gKiAoMSAtIHRlc3RBbHBoYSkpIC8gdGVzdEFscGhhLFxuICAgICAgKHJnYkNvbG9yLnZhbHVlc1sxXSAtIHJnYkJhY2tncm91bmRDb2xvci52YWx1ZXNbMV0gKiAoMSAtIHRlc3RBbHBoYSkpIC8gdGVzdEFscGhhLFxuICAgICAgKHJnYkNvbG9yLnZhbHVlc1syXSAtIHJnYkJhY2tncm91bmRDb2xvci52YWx1ZXNbMl0gKiAoMSAtIHRlc3RBbHBoYSkpIC8gdGVzdEFscGhhXG4gICAgXTtcbiAgICAvLyBDaGVjayB0aGF0IGVhY2ggb2YgdGhlIG92ZXJsYXlDb2xvcidzIFJHQiB2YWx1ZXMgYXJlIGluIHRoZSByYW5nZSAwLTI1NVxuICAgIGNvbnN0IGlzVmFsaWQgPSBNYXRoLm1heCguLi5vdmVybGF5Q29sb3IpIDw9IDI1NSAmJiBNYXRoLm1pbiguLi5vdmVybGF5Q29sb3IpID49IDA7XG4gICAgaWYgKGlzVmFsaWQpIHtcbiAgICAgIC8vIEZvdW5kIGEgdmFsaWQgY29sb3IsIG5vdyBjaGVjayBmb3IgY29sb3JzIHdpdGggYSBsb3dlciBhbHBoYVxuICAgICAgcmVzdWx0ID0gWy4uLm92ZXJsYXlDb2xvciwgTWF0aC5yb3VuZCh0ZXN0QWxwaGEgLyBwcmVjaXNpb24pICogcHJlY2lzaW9uXTtcbiAgICAgIGRlbHRhIC89IDI7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIERpZCBub3QgZmluZCBhIHZhbGlkIGNvbG9yLCBub3cgY2hlY2sgZm9yIGNvbG9ycyB3aXRoIGEgaGlnaGVyIGFscGhhXG4gICAgICBjdXJyZW50QWxwaGEgKz0gZGVsdGE7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHJlY29tcG9zZUNvbG9yKHtcbiAgICB0eXBlOiAncmdiYScsXG4gICAgdmFsdWVzOiByZXN1bHQsXG4gICAgY29sb3JTcGFjZTogcmdiQ29sb3IuY29sb3JTcGFjZVxuICB9KTtcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGVxdWl2YWxlbnRSR0JBO1xuIl0sIm5hbWVzIjpbImRlY29tcG9zZUNvbG9yIiwicmVjb21wb3NlQ29sb3IiXSwibWFwcGluZ3MiOiI7Ozs7QUFFQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O01BcUJNLGNBQWMsR0FBRyxDQUNyQixRQUFnQixFQUNoQixrQkFBMEIsRUFDMUIsY0FBYyxHQUFHLENBQUMsRUFDbEIsU0FBUyxHQUFHLEtBQUs7SUFFakIsT0FBTyxDQUFDLE1BQU0sQ0FDWixjQUFjLElBQUksQ0FBQyxJQUFJLGNBQWMsSUFBSSxDQUFDLEVBQzFDLGlEQUFpRCxDQUNsRCxDQUFDO0lBQ0YsT0FBTyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEdBQUcsQ0FBQyxFQUFFLDRCQUE0QixDQUFDLENBQUM7SUFFNUQsTUFBTSxRQUFRLEdBQUdBLG1CQUFjLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDMUMsTUFBTSxrQkFBa0IsR0FBR0EsbUJBQWMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBRTlELElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxLQUFLLEVBQUU7UUFDM0IsTUFBTSxLQUFLLENBQUMsd0NBQXdDLENBQUMsQ0FBQztLQUN2RDtJQUNELElBQUksa0JBQWtCLENBQUMsSUFBSSxLQUFLLEtBQUssRUFBRTtRQUNyQyxNQUFNLEtBQUssQ0FBQyxrREFBa0QsQ0FBQyxDQUFDO0tBQ2pFO0lBQ0QsSUFBSSxRQUFRLENBQUMsVUFBVSxLQUFLLGtCQUFrQixDQUFDLFVBQVUsRUFBRTtRQUN6RCxNQUFNLEtBQUssQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO0tBQ3hDOztJQUdELElBQUksWUFBWSxHQUFHLGNBQWMsQ0FBQztJQUNsQyxJQUFJLEtBQUssR0FBRyxDQUFDLENBQUMsR0FBRyxjQUFjLElBQUksQ0FBQyxDQUFDO0lBQ3JDLElBQUksTUFBTSxHQUFxQztRQUM3QyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUNsQixRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUNsQixRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUNsQixDQUFDO0tBQ0YsQ0FBQztJQUNGLE9BQU8sS0FBSyxHQUFHLFNBQVMsR0FBRyxDQUFDLElBQUksWUFBWSxHQUFHLEtBQUssSUFBSSxDQUFDLEVBQUU7UUFDekQsTUFBTSxTQUFTLEdBQUcsWUFBWSxHQUFHLEtBQUssQ0FBQzs7UUFFdkMsTUFBTSxZQUFZLEdBQTZCO1lBQzdDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLFNBQVMsQ0FBQyxJQUFJLFNBQVM7WUFDakYsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsU0FBUyxDQUFDLElBQUksU0FBUztZQUNqRixDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsa0JBQWtCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxTQUFTLENBQUMsSUFBSSxTQUFTO1NBQ2xGLENBQUM7O1FBRUYsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLFlBQVksQ0FBQyxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ25GLElBQUksT0FBTyxFQUFFOztZQUVYLE1BQU0sR0FBRyxDQUFDLEdBQUcsWUFBWSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQyxHQUFHLFNBQVMsQ0FBQyxDQUFDO1lBQzFFLEtBQUssSUFBSSxDQUFDLENBQUM7U0FDWjthQUFNOztZQUVMLFlBQVksSUFBSSxLQUFLLENBQUM7U0FDdkI7S0FDRjtJQUVELE9BQU9DLG1CQUFjLENBQUM7UUFDcEIsSUFBSSxFQUFFLE1BQU07UUFDWixNQUFNLEVBQUUsTUFBTTtRQUNkLFVBQVUsRUFBRSxRQUFRLENBQUMsVUFBVTtLQUNoQyxDQUFDLENBQUM7QUFDTDs7OzsifQ==