Switch color class to use chroma color as underlying data storage

This commit is contained in:
Evan Fiordeliso 2025-07-04 23:06:41 -04:00
parent 86ffa3753f
commit b5b9a3fd31
1 changed files with 23 additions and 48 deletions

View File

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