1// Adapted from @k-vyn/coloralgorithm
2
3import chroma, { Scale } from "chroma-js";
4import { ColorFamily, ColorProps, ColorSet } from "./types";
5
6function validColor(color: string) {
7 if (chroma.valid(color)) {
8 return color;
9 } else {
10 throw new Error(`Invalid color: ${color}`);
11 }
12}
13
14function assignColor(scale: Scale, steps: number, step: number) {
15 const color = scale(step / steps);
16 const lch = color.lch();
17 const rgbaArray = color.rgba();
18 const hex = color.hex();
19
20 // Roughly calculate if a color is dark or light
21 const isLight = lch[0] > 50;
22
23 const result = {
24 step,
25 hex,
26 lch,
27 rgbaArray,
28 isLight,
29 };
30
31 return result;
32}
33
34/** Outputs 101 colors (0-100) */
35export function generateColors(props: ColorProps, inverted: boolean) {
36 const steps = 101;
37 const colors: ColorSet = [];
38
39 const { start, middle, end } = props.color;
40
41 const startColor = typeof start === "string" ? validColor(start) : start;
42 const middleColor = typeof middle === "string" ? validColor(middle) : middle;
43 const endColor = typeof end === "string" ? validColor(end) : end;
44
45 // TODO: Use curve when generating colors
46
47 let scale: Scale;
48
49 if (inverted) {
50 scale = chroma.scale([endColor, middleColor, startColor]).mode("lch");
51 } else {
52 scale = chroma.scale([startColor, middleColor, endColor]).mode("lch");
53 }
54 for (let i = 0; i < steps; i++) {
55 const color = assignColor(scale, steps, i);
56 colors.push(color);
57 }
58 return colors;
59}
60
61/** Generates two color ramps:
62 * One for for light, and one for dark.
63 * By generating two ramps, rather than two default themes, we can use the same reference palette values for tokens in components.
64 *
65 * Each ramp has 101 colors (0-100)
66 */
67export function generateColorSet(props: ColorProps) {
68 const generatedColors = generateColors(props, false);
69 const generatedInvertedColors = generateColors(props, true);
70
71 const colors = generatedColors.map((color) => color.hex);
72 const invertedColors = generatedInvertedColors.map((color) => color.hex);
73
74 const result: ColorFamily = {
75 name: props.name,
76 colors: colors,
77 invertedColors: invertedColors,
78 colorsMeta: generatedColors,
79 invertedMeta: generatedInvertedColors,
80 };
81
82 return result;
83}