UNPKG

braid-design-system

Version:
123 lines (122 loc) 4.24 kB
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 };