@shopify/polaris
Version:
Shopify’s admin product component library
304 lines (255 loc) • 8.32 kB
text/typescript
import {clamp} from './clamp';
import type {
RGBColor,
RGBAColor,
HSBColor,
HSBAColor,
HSLColor,
HSLAColor,
HSBLAColor,
} from './color-types';
import {roundNumberToDecimalPlaces} from './roundNumberToDecimalPlaces';
export function rgbString(color: RGBColor | RGBAColor) {
const {red, green, blue} = color;
if ('alpha' in color) {
return `rgba(${red}, ${green}, ${blue}, ${color.alpha})`;
} else {
return `rgb(${red}, ${green}, ${blue})`;
}
}
export const rgbaString = rgbString;
export function rgbToHex({red, green, blue}: RGBColor) {
return `#${componentToHex(red)}${componentToHex(green)}${componentToHex(
blue,
)}`;
}
function componentToHex(component: number) {
const hex = component.toString(16);
return hex.length === 1 ? `0${hex}` : hex;
}
export function hsbToHex(color: HSBColor) {
return rgbToHex(hsbToRgb(color));
}
function rgbFromHueAndChroma(hue: number, chroma: number) {
const huePrime = hue / 60;
const hueDelta = 1 - Math.abs((huePrime % 2) - 1);
const intermediateValue = chroma * hueDelta;
let red = 0;
let green = 0;
let blue = 0;
if (huePrime >= 0 && huePrime <= 1) {
red = chroma;
green = intermediateValue;
blue = 0;
}
if (huePrime >= 1 && huePrime <= 2) {
red = intermediateValue;
green = chroma;
blue = 0;
}
if (huePrime >= 2 && huePrime <= 3) {
red = 0;
green = chroma;
blue = intermediateValue;
}
if (huePrime >= 3 && huePrime <= 4) {
red = 0;
green = intermediateValue;
blue = chroma;
}
if (huePrime >= 4 && huePrime <= 5) {
red = intermediateValue;
green = 0;
blue = chroma;
}
if (huePrime >= 5 && huePrime <= 6) {
red = chroma;
green = 0;
blue = intermediateValue;
}
return {red, green, blue};
}
// implements https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
export function hsbToRgb(color: HSBColor): RGBColor;
export function hsbToRgb(color: HSBAColor): RGBAColor;
export function hsbToRgb(color: HSBAColor): RGBAColor {
const {hue, saturation, brightness, alpha = 1} = color;
const chroma = brightness * saturation;
let {red, green, blue} = rgbFromHueAndChroma(hue, chroma);
const chromaBrightnessDelta = brightness - chroma;
red += chromaBrightnessDelta;
green += chromaBrightnessDelta;
blue += chromaBrightnessDelta;
return {
red: Math.round(red * 255),
green: Math.round(green * 255),
blue: Math.round(blue * 255),
alpha,
};
}
// implements https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
export function hslToRgb(color: HSLColor): RGBColor;
export function hslToRgb(color: HSLAColor): RGBAColor;
export function hslToRgb(color: HSLAColor): RGBAColor {
const {hue, saturation, lightness, alpha = 1} = color;
const chroma = (1 - Math.abs(2 * (lightness / 100) - 1)) * (saturation / 100);
let {red, green, blue} = rgbFromHueAndChroma(hue, chroma);
const lightnessVal = lightness / 100 - chroma / 2;
red += lightnessVal;
green += lightnessVal;
blue += lightnessVal;
return {
red: Math.round(red * 255),
green: Math.round(green * 255),
blue: Math.round(blue * 255),
alpha,
};
}
// ref https://en.wikipedia.org/wiki/HSL_and_HSV
function rgbToHsbl(color: RGBAColor, type: 'b' | 'l' = 'b'): HSBLAColor {
const {alpha = 1} = color;
const red = color.red / 255;
const green = color.green / 255;
const blue = color.blue / 255;
const largestComponent = Math.max(red, green, blue);
const smallestComponent = Math.min(red, green, blue);
const delta = largestComponent - smallestComponent;
const lightness = (largestComponent + smallestComponent) / 2;
let saturation = 0;
if (largestComponent === 0) {
saturation = 0;
} else if (type === 'b') {
saturation = delta / largestComponent;
} else if (type === 'l') {
const baseSaturation =
lightness > 0.5
? delta / (2 - largestComponent - smallestComponent)
: delta / (largestComponent + smallestComponent);
saturation = isNaN(baseSaturation) ? 0 : baseSaturation;
}
let huePercentage = 0;
switch (largestComponent) {
case red:
huePercentage = (green - blue) / delta + (green < blue ? 6 : 0);
break;
case green:
huePercentage = (blue - red) / delta + 2;
break;
case blue:
huePercentage = (red - green) / delta + 4;
}
const hue = (huePercentage / 6) * 360;
const clampedHue = clamp(hue, 0, 360);
return {
hue: clampedHue ? roundNumberToDecimalPlaces(clampedHue, 2) : 0,
saturation: roundNumberToDecimalPlaces(clamp(saturation, 0, 1), 4),
brightness: roundNumberToDecimalPlaces(clamp(largestComponent, 0, 1), 4),
lightness: roundNumberToDecimalPlaces(lightness, 4),
alpha: roundNumberToDecimalPlaces(alpha, 4),
};
}
export function rgbToHsb(color: RGBColor): HSBColor;
export function rgbToHsb(color: RGBAColor): HSBAColor {
const {hue, saturation, brightness, alpha = 1} = rgbToHsbl(color, 'b');
return {hue, saturation, brightness, alpha};
}
export function rgbToHsl(color: RGBColor): HSLAColor;
export function rgbToHsl(color: RGBAColor): HSLAColor {
const {
hue,
saturation: rawSaturation,
lightness: rawLightness,
alpha = 1,
} = rgbToHsbl(color, 'l');
const saturation = roundNumberToDecimalPlaces(rawSaturation * 100, 2);
const lightness = roundNumberToDecimalPlaces(rawLightness * 100, 2);
return {hue, saturation, lightness, alpha};
}
export function hexToRgb(color: string) {
if (color.length === 4) {
const repeatHex = (hex1: number, hex2: number) =>
color.slice(hex1, hex2).repeat(2);
const red = parseInt(repeatHex(1, 2), 16);
const green = parseInt(repeatHex(2, 3), 16);
const blue = parseInt(repeatHex(3, 4), 16);
return {red, green, blue};
}
const red = parseInt(color.slice(1, 3), 16);
const green = parseInt(color.slice(3, 5), 16);
const blue = parseInt(color.slice(5, 7), 16);
return {red, green, blue};
}
type ColorType = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'default';
function getColorType(color: string): ColorType {
if (color.includes('#')) {
return 'hex';
} else if (color.includes('rgb')) {
return 'rgb';
} else if (color.includes('rgba')) {
return 'rgba';
} else if (color.includes('hsl')) {
return 'hsl';
} else if (color.includes('hsla')) {
return 'hsla';
} else {
if (process.env.NODE_ENV === 'development') {
/* eslint-disable-next-line no-console */
console.warn('Accepted colors formats are: hex, rgb, rgba, hsl and hsla');
}
return 'default';
}
}
function rgbToObject(color: string): RGBAColor {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const colorMatch = color.match(/\(([^)]+)\)/);
if (!colorMatch) {
return {red: 0, green: 0, blue: 0, alpha: 0};
}
const [red, green, blue, alpha] = colorMatch[1].split(',');
const objColor = {
red: parseInt(red, 10),
green: parseInt(green, 10),
blue: parseInt(blue, 10),
alpha: parseInt(alpha, 10) || 1,
};
return objColor;
}
function hexToHsla(color: string): HSLAColor {
return rgbToHsl(hexToRgb(color));
}
function rbgStringToHsla(color: string): HSLAColor {
return rgbToHsl(rgbToObject(color));
}
function hslToObject(color: string): HSLAColor {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const colorMatch = color.match(/\(([^)]+)\)/);
if (!colorMatch) {
return {hue: 0, saturation: 0, lightness: 0, alpha: 0};
}
const [hue, saturation, lightness, alpha] = colorMatch[1].split(',');
const objColor = {
hue: roundNumberToDecimalPlaces(parseFloat(hue), 2),
saturation: roundNumberToDecimalPlaces(parseFloat(saturation), 2),
lightness: roundNumberToDecimalPlaces(parseFloat(lightness), 2),
alpha: roundNumberToDecimalPlaces(parseFloat(alpha), 2) || 1,
};
return objColor;
}
export function colorToHsla(color: string): HSLAColor {
const type = getColorType(color);
switch (type) {
case 'hex':
return hexToHsla(color);
case 'rgb':
case 'rgba':
return rbgStringToHsla(color);
case 'hsl':
case 'hsla':
return hslToObject(color);
case 'default':
default:
throw new Error(
'Accepted color formats are: hex, rgb, rgba, hsl and hsla',
);
}
}