@patreon/studio
Version:
Patreon Studio Design System
234 lines • 7.92 kB
JavaScript
import { Hct, argbFromHex, yFromLstar } from '@material/material-color-utilities';
/**
* Converts an ARGB color to a hex color.
*
* Note: We're defining our own function here because the
* material-color-utilities library `hexFromArgb` function
* reduces hexes to 3 and 4 character strings when possible,
* and we need to ensure we always return a 6 or 8 character
* hex string.
*
* @param argb - The ARGB color to convert.
* @returns The hex color.
*/
function argb2hex(argb) {
// eslint-disable-next-line no-bitwise
return ('#' + ((argb & 0xffffff) | 0x1000000).toString(16).slice(1));
}
/**
* Converts a number to a hex string
*
* @param value - The number to convert.
* @returns The hex string.
*/
function num2hexFragment(value) {
return Math.round(value).toString(16).padStart(2, '0');
}
/**
* Converts a hex fragment to a number
*
* @param hexFragment - The hex fragment to convert (e.g. "00" or "FF").
* @returns The number (0 - 255).
*/
function hexFragment2Number(hexFragment) {
return parseInt(hexFragment, 16);
}
/**
* Checks if a hex color is a 3 character hex string
*
* @param colorString - The hex color to check including the # symbol.
* @returns True if the hex color is a 3 character hex string, false otherwise.
*/
export function isValidHex3(colorString) {
return colorString.length === 4 && /(^#[\da-f]{3}$)/i.test(colorString);
}
/**
* Checks if a hex color is a 6 character hex string
*
* @param colorString - The hex color to check including the # symbol.
* @returns True if the hex color is a 6 character hex string, false otherwise.
*/
export function isValidHex6(colorString) {
return colorString.length === 7 && /(^#[\da-f]{6}$)/i.test(colorString);
}
/**
* Checks if a hex color is a 8 character hex string
*
* @param colorString - The hex color to check including the # symbol.
* @returns True if the hex color is a 8 character hex string, false otherwise.
*/
export function isValidHex8(colorString) {
return colorString.length === 9 && /(^#[\da-f]{8}$)/i.test(colorString);
}
/**
* Checks if a hex color is a valid hex color
*
* @param colorString - The hex color to check including the # symbol.
* @returns True if the hex color is a valid hex color, false otherwise.
*/
export function isValidHex(colorString) {
return isValidHex3(colorString) || isValidHex6(colorString) || isValidHex8(colorString);
}
/**
* Converts a hex color to an HCT instance
*
* @internal we don't intend to export this function because
* we don't want to fully expose the internals the HCT class.
*
* @param hex - The hex color to convert (e.g. "#000000")
* @returns An HCT color instance.
*/
export function hex2hctInstance(hex) {
if (!isValidHex(hex)) {
throw new Error('Invalid hex color');
}
return Hct.fromInt(argbFromHex(hex));
}
/**
* Converts an HCT instance to a hex color
*
* @internal we don't intend to export this function because
* we don't want to fully expose the internals the HCT class.
*
* @param hct - The HCT color instance to convert.
* @returns The hex color.
**/
export function hctInstance2hex(hct) {
return argb2hex(hct.toInt());
}
/**
* Converts an HCT instance to a HCTa object
*
* @param hct - The HCT color instance to convert.
* @returns A HCTa object.
*/
export function hctInstance2hcta(hct) {
return {
hue: hct.hue,
chroma: hct.chroma,
tone: hct.tone,
alpha: undefined,
};
}
/**
* Converts a hex color to an HCTa color object
*
* @param hex - The hex color to convert (e.g. "#000000")
* @returns An object with an HCT color instance and alpha value (0-1).
**/
export function hex2hcta(hex) {
const isSixDigit = isValidHex6(hex);
const isEightDigit = isValidHex8(hex);
if (!isSixDigit && !isEightDigit) {
throw new Error('Invalid hex color, expected 6 or 8 character hex string');
}
const instance = Hct.fromInt(argbFromHex(hex));
const hue = instance.hue;
const chroma = instance.chroma;
const tone = instance.tone;
const alpha = isEightDigit ? hexFragment2Number(hex.slice(7, 9)) / 255 : undefined;
return { hue, chroma, tone, alpha };
}
export function hcta2hex({ hue, chroma, tone, alpha }) {
const hctInstance = Hct.from(hue, chroma, tone);
if (alpha === undefined) {
return hctInstance2hex(hctInstance);
}
return `${hctInstance2hex(hctInstance)}${num2hexFragment(alpha * 255)}`;
}
/**
* hello linear interpolation function, we call you lerp?
*
* @param x - The start value.
* @param y - The end value.
* @param a - The amount to interpolate between the start and end values.
* @returns The interpolated value.
*/
export const lerp = (x, y, a) => x * (1 - a) + y * a;
/**
* Linearly interpolates between two colors in the HCT color space.
*
* @param startColor - The start color (6 or 8-digit hex string).
* @param endColor - The end color (6 or 8-digit hex string).
* @param a - Amount 0 - 1 between start and end.
* @returns The interpolated color (6 or 8-digit hex string).
*/
export function lerpColor(startColor, endColor, a) {
const startHcta = hex2hcta(startColor);
const endHcta = hex2hcta(endColor);
if (startHcta.alpha === undefined && endHcta.alpha === undefined) {
return hcta2hex({
hue: lerp(startHcta.hue, endHcta.hue, a),
chroma: lerp(startHcta.chroma, endHcta.chroma, a),
tone: lerp(startHcta.tone, endHcta.tone, a),
});
}
return hcta2hex({
hue: lerp(startHcta.hue, endHcta.hue, a),
chroma: lerp(startHcta.chroma, endHcta.chroma, a),
tone: lerp(startHcta.tone, endHcta.tone, a),
alpha: lerp(startHcta.alpha ?? 1, endHcta.alpha ?? 1, a),
});
}
/**
* Calculates the distance between two hex colors using the
* Hct color space.
*
* @param color1 - The first hex color.
* @param color2 - The second hex color.
* @returns The distance between the two colors.
*/
export function computeColorDistance(color1, color2) {
const hct1 = hex2hcta(color1);
const hct2 = hex2hcta(color2);
// compute the distance between the two colors in the
// HCT color space using the Euclidean distance formula
return Math.sqrt(
// compute the distance between hue
Math.pow(hct1.hue - hct2.hue, 2) +
// compute the distance between chroma
Math.pow(hct1.chroma - hct2.chroma, 2) +
// compute the distance between tone
Math.pow(hct1.tone - hct2.tone, 2));
}
/**
* Computes the contrast ratio between two hex colors using the
* HCT color space.
*
* Returns a value between 1 and 21, where:
* - 1:1 contrast ratio, no contrast
* - 3:1 contrast ratio, AA large text
* - 4.5:1 contrast ratio, AA small text / AAA large text
* - 7:1 contrast ratio, AAA small text
*
* @param color1 - The first hex color.
* @param color2 - The second hex color.
* @returns The contrast ratio between the two colors.
*/
export function computeContrastRatio(color1, color2) {
const { tone: tone1 } = hex2hcta(color1);
const { tone: tone2 } = hex2hcta(color2);
return computeContrastRatioFromTone(tone1, tone2);
}
/**
* Computes the contrast ratio between two tones using the
* Y value from the L*a*b* color space.
*
* Returns a value between 1 and 21, where:
* - 1:1 contrast ratio, no contrast
* - 3:1 contrast ratio, AA large text
* - 4.5:1 contrast ratio, AA small text / AAA large text
* - 7:1 contrast ratio, AAA small text
*
* @param tone1 - The first tone.
* @param tone2 - The second tone.
* @returns The contrast ratio between the two tones.
*/
export function computeContrastRatioFromTone(tone1, tone2) {
const y1 = yFromLstar(tone1);
const y2 = yFromLstar(tone2);
const lighter = y1 > y2 ? y1 : y2;
const darker = lighter === y2 ? y1 : y2;
return (lighter + 5.0) / (darker + 5.0);
}
//# sourceMappingURL=index.js.map