diff --git a/src/utils/color.ts b/src/utils/color.ts index 2db8e85..eb1cafc 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -2,6 +2,8 @@ import chroma from "chroma-js"; const style = getComputedStyle(document.body); +export const WCAG_TEXT_CONTRAST_RATIO = 4.5; +export const WCAG_NON_TEXT_CONTRAST_RATIO = 3.5; export const COLOR_NAMES = [ "red", "orange", @@ -12,86 +14,59 @@ export const COLOR_NAMES = [ "purple", "pink", ] as const; +export type ColorName = (typeof COLOR_NAMES)[number]; export function isColorName(color: string): color is ColorName { return COLOR_NAMES.includes(color as ColorName); } -export type ColorName = (typeof COLOR_NAMES)[number]; -export type RGB = [number, number, number]; - export class Color { constructor( - private readonly r: number, - private readonly g: number, - private readonly b: number, - private readonly a: number = 1 + private readonly _chroma: chroma.Color, + private readonly _alpha: number = 1 ) {} get hex(): string { - const rHex = this.r.toString(16).padStart(2, "0"); - const gHex = this.g.toString(16).padStart(2, "0"); - const bHex = this.b.toString(16).padStart(2, "0"); - - return `#${rHex}${gHex}${bHex}`; + return this.chroma.hex(); } get rgb(): string { - return `rgb(${this.r}, ${this.g}, ${this.b})`; + return this.chroma.css(); } get rgba(): string { - return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`; + return this.chroma.alpha(this._alpha).css(); + } + + get chroma(): chroma.Color { + return this._chroma; } get contrastColor(): Color { - return this.r * 0.299 + - this.g * 0.587 + - this.b * 0.114 + - (1 - this.a) * 255 > - 186 - ? new Color(0, 0, 0) - : new Color(255, 255, 255); + return chroma.contrast(this.chroma, "white") < WCAG_TEXT_CONTRAST_RATIO + ? new Color(chroma("black")) + : new Color(chroma("white")); } alpha(alpha: number): Color { - return new Color(this.r, this.g, this.b, alpha); + alpha = Math.max(0, Math.min(1, alpha)); + return new Color(this.chroma, alpha); } darken(amount?: number): Color { - return new Color( - ...chroma([this.r, this.g, this.b]).darken(amount).rgb() - ); + return new Color(this.chroma.darken(amount)); } lighten(amount?: number): Color { - return new Color( - ...chroma([this.r, this.g, this.b]).brighten(amount).rgb() - ); + return new Color(this.chroma.brighten(amount)); } static fromName(color: ColorName): Color { - const rawRgb = style - .getPropertyValue(`--color-${color}-rgb`) - .split(", "); - - return new Color( - parseInt(rawRgb[0], 10), - parseInt(rawRgb[1], 10), - parseInt(rawRgb[2], 10) - ); + return new Color(chroma(style.getPropertyValue(`--color-${color}`))); } static fromCSSColor(color: string): Color { - const ctx = document.createElement("canvas").getContext("2d")!; - ctx.fillStyle = color; - const hexColor = ctx.fillStyle; - - return new Color( - parseInt(hexColor.slice(1, 3), 16), - parseInt(hexColor.slice(3, 5), 16), - parseInt(hexColor.slice(5, 7), 16) - ); + return new Color(chroma(color)); } static getAll(): Color[] { @@ -106,7 +81,7 @@ export class Color { return chroma .scale(colors) .mode("lch") - .colors(n, "rgb") - .map(([r, g, b]) => new Color(r, g, b)); + .colors(n, null) + .map((color) => new Color(color)); } }