algorithm.ts

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