braid-design-system
Version:
Themeable design system for the SEEK Group
123 lines (122 loc) • 4.24 kB
JavaScript
import { parseToHsl, getLuminance, toColorString, parseToRgb, hslToColorString, setLightness } from "polished";
const AA_TEXT_CONTRAST = 4.52;
function contrast(color1, color2) {
const L1 = getLuminance(color1) + 0.05;
const L2 = getLuminance(color2) + 0.05;
return L1 < L2 ? L2 / L1 : L1 / L2;
}
function findClosestAccessibleLighterColor(inputColor, fixedColor, contrastRatio = AA_TEXT_CONTRAST) {
const normalisedInput = toColorString(parseToRgb(inputColor));
const normalisedFixed = toColorString(parseToRgb(fixedColor));
const { hue, saturation, lightness } = parseToHsl(normalisedInput);
let minLightness = lightness;
let maxLightness = 0.98;
let maxColor = hslToColorString({ hue, saturation, lightness: maxLightness });
let minColor = hslToColorString({ hue, saturation, lightness });
if (contrast(normalisedInput, normalisedFixed) >= contrastRatio) {
return inputColor;
}
const actualRatio = contrast(maxColor, normalisedFixed);
if (actualRatio < contrastRatio) {
throw new Error(
`Desired contrast ratio cannot be achieved,
Foreground: ${inputColor}
Background:${fixedColor}
Desired Contrast:${contrastRatio}
Actual Contrast:${actualRatio}`
);
}
let lastMinColor;
let lastMaxColor;
let adjustedLightness;
let adjustedColor = normalisedInput;
while (minColor !== lastMinColor || maxColor !== lastMaxColor) {
lastMinColor = minColor;
lastMaxColor = maxColor;
adjustedLightness = (minLightness + maxLightness) / 2;
adjustedColor = setLightness(adjustedLightness, adjustedColor);
if (contrast(adjustedColor, normalisedFixed) < contrastRatio) {
minLightness = adjustedLightness;
minColor = adjustedColor;
} else {
maxLightness = adjustedLightness;
maxColor = adjustedColor;
}
}
return maxColor;
}
function findClosestAccessibleDarkerColor(inputColor, fixedColor, contrastRatio = AA_TEXT_CONTRAST) {
const normalisedInput = toColorString(parseToRgb(inputColor));
const normalisedFixed = toColorString(parseToRgb(fixedColor));
const { hue, saturation, lightness } = parseToHsl(normalisedInput);
let minLightness = 0.02;
let maxLightness = lightness;
let maxColor = hslToColorString({ hue, saturation, lightness: minLightness });
let minColor = hslToColorString({ hue, saturation, lightness });
if (contrast(normalisedInput, normalisedFixed) >= contrastRatio) {
return inputColor;
}
const actualRatio = contrast(maxColor, normalisedFixed);
if (actualRatio < contrastRatio) {
throw new Error(
`Desired contrast ratio cannot be achieved,
Foreground: ${inputColor}
Background:${fixedColor}
Desired Contrast:${contrastRatio}
Actual Contrast:${actualRatio}`
);
}
let lastMinColor;
let lastMaxColor;
let adjustedLightness;
let adjustedColor = normalisedInput;
while (minColor !== lastMinColor || maxColor !== lastMaxColor) {
lastMinColor = minColor;
lastMaxColor = maxColor;
adjustedLightness = (minLightness + maxLightness) / 2;
adjustedColor = setLightness(adjustedLightness, adjustedColor);
if (contrast(adjustedColor, normalisedFixed) < contrastRatio) {
maxLightness = adjustedLightness;
maxColor = adjustedColor;
} else {
minLightness = adjustedLightness;
minColor = adjustedColor;
}
}
return minColor;
}
const smoothSaturation = (saturation, luminance) => {
const isBright = luminance > 0.6;
if (isBright) {
return saturation * 0.8;
}
return saturation * 0.45;
};
const smoothLightness = (lightness, luminance) => {
const isBright = luminance > 0.6;
if (isBright) {
return 0.95 - lightness * 0.03;
}
return 0.95 - lightness * 0.06;
};
function getLightVariant(color) {
const { hue, saturation, lightness } = parseToHsl(color);
const luminance = getLuminance(color);
return toColorString({
hue,
saturation: smoothSaturation(saturation, luminance),
lightness: smoothLightness(lightness, luminance)
});
}
function getAccessibleVariant(color, background) {
return findClosestAccessibleDarkerColor(
color,
background ?? getLightVariant(color),
AA_TEXT_CONTRAST
);
}
export {
findClosestAccessibleLighterColor,
getAccessibleVariant,
getLightVariant
};