UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

304 lines (255 loc) • 8.32 kB
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', ); } }