diff --git a/styles/src/system/algorithm.ts b/styles/src/system/algorithm.ts deleted file mode 100644 index 8cdeef461d..0000000000 --- a/styles/src/system/algorithm.ts +++ /dev/null @@ -1,170 +0,0 @@ -// Adapted from @k-vyn/coloralgorithm - -import bezier from "bezier-easing"; -import chroma, { Scale } from "chroma-js"; -import { Curve } from "./curves"; -import { ColorFamily, ColorProps, ColorSet } from "./types"; - -function validColor(color: string) { - if (chroma.valid(color)) { - return color; - } else { - throw new Error(`Invalid color: ${color}`); - } -} - -function assignColor(scale: Scale, steps: number, step: number) { - const color = scale(step / steps); - const lch = color.lch(); - const rgbaArray = color.rgba(); - const hex = color.hex(); - - // Roughly calculate if a color is dark or light - const isLight = lch[0] > 50; - - const result = { - step, - hex, - lch, - rgbaArray, - isLight, - }; - - return result; -} - -/** Outputs 101 colors (0-100) */ -export function generateColors(props: ColorProps, inverted: boolean) { - const steps = 101; - const colors: ColorSet = []; - - const { start, middle, end } = props.color; - - const startColor = typeof start === "string" ? validColor(start) : start; - const middleColor = typeof middle === "string" ? validColor(middle) : middle; - const endColor = typeof end === "string" ? validColor(end) : end; - - // TODO: Use curve when generating colors - - let scale: Scale; - - if (inverted) { - scale = chroma.scale([endColor, middleColor, startColor]).mode("lch"); - } else { - scale = chroma.scale([startColor, middleColor, endColor]).mode("lch"); - } - for (let i = 0; i < steps; i++) { - const color = assignColor(scale, steps, i); - colors.push(color); - } - return colors; -} - -export function generateColorsUsingCurve( - startColor: string, - endColor: string, - curve: number[] -) { - const NUM_STEPS = 101; - - const easing = bezier(curve[0], curve[1], curve[2], curve[3]); - const curveProgress = []; - for (let i = 0; i <= NUM_STEPS; i++) { - curveProgress.push(easing(i / NUM_STEPS)); - } - - const colors: chroma.Color[] = []; - for (let i = 0; i < NUM_STEPS; i++) { - // Use HSL as an input as it is easier to construct programatically - // const color = chroma.hsl(); - const color = chroma.mix(startColor, endColor, curveProgress[i], "lch"); - colors.push(color); - } - - return colors; -} - -export function generateColors2( - hue: { - start: number; - end: number; - curve: Curve; - }, - saturation: { - start: number; - end: number; - curve: Curve; - }, - lightness: { - start: number; - end: number; - curve: Curve; - } -) { - const NUM_STEPS = 9; - - const hueEasing = bezier( - hue.curve.value[0], - hue.curve.value[1], - hue.curve.value[2], - hue.curve.value[3] - ); - const saturationEasing = bezier( - saturation.curve.value[0], - saturation.curve.value[1], - saturation.curve.value[2], - saturation.curve.value[3] - ); - const lightnessEasing = bezier( - lightness.curve.value[0], - lightness.curve.value[1], - lightness.curve.value[2], - lightness.curve.value[3] - ); - - const colors: chroma.Color[] = []; - for (let i = 0; i < NUM_STEPS; i++) { - const hueValue = - hueEasing(i / NUM_STEPS) * (hue.end - hue.start) + hue.start; - const saturationValue = - saturationEasing(i / NUM_STEPS) * (saturation.end - saturation.start) + - saturation.start; - const lightnessValue = - lightnessEasing(i / NUM_STEPS) * (lightness.end - lightness.start) + - lightness.start; - - const color = chroma.hsl( - hueValue, - saturationValue / 100, - lightnessValue / 100 - ); - colors.push(color); - } - - const scale = chroma.scale(colors).mode("lch"); - return scale; -} - -/** Generates two color ramps: - * One for for light, and one for dark. - * By generating two ramps, rather than two default themes, we can use the same reference palette values for tokens in components. - * - * Each ramp has 101 colors (0-100) - */ -export function generateColorSet(props: ColorProps) { - const generatedColors = generateColors(props, false); - const generatedInvertedColors = generateColors(props, true); - - const colors = generatedColors.map((color) => color.hex); - const invertedColors = generatedInvertedColors.map((color) => color.hex); - - const result: ColorFamily = { - name: props.name, - colors: colors, - invertedColors: invertedColors, - colorsMeta: generatedColors, - invertedMeta: generatedInvertedColors, - }; - - return result; -} diff --git a/styles/src/system/curves.ts b/styles/src/system/curves.ts deleted file mode 100644 index 488c68df1e..0000000000 --- a/styles/src/system/curves.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Adapted from @k-vyn/coloralgorithm - -export interface Curve { - name: string; - formatted_name: string; - value: number[]; -} - -export interface Curves { - lightness: Curve; - saturation: Curve; - linear: Curve; - easeInCubic: Curve; - easeOutCubic: Curve; - easeInOutCubic: Curve; - easeInSine: Curve; - easeOutSine: Curve; - easeInOutSine: Curve; - easeInQuad: Curve; - easeOutQuad: Curve; - easeInOutQuad: Curve; - easeInQuart: Curve; - easeOutQuart: Curve; - easeInOutQuart: Curve; - easeInQuint: Curve; - easeOutQuint: Curve; - easeInOutQuint: Curve; - easeInExpo: Curve; - easeOutExpo: Curve; - easeInOutExpo: Curve; - easeInCirc: Curve; - easeOutCirc: Curve; - easeInOutCirc: Curve; - easeInBack: Curve; - easeOutBack: Curve; - easeInOutBack: Curve; -} - -export const curve: Curves = { - lightness: { - name: "nate", - formatted_name: "Nate", - value: [0.2, 0, 0.85, 1.1], - }, - saturation: { - name: "nate", - formatted_name: "Nate", - value: [0.67, 0.6, 0.55, 1.0], - }, - linear: { - name: "linear", - formatted_name: "Linear", - value: [0.5, 0.5, 0.5, 0.5], - }, - easeInCubic: { - name: "easeInCubic", - formatted_name: "Cubic - EaseIn", - value: [0.55, 0.055, 0.675, 0.19], - }, - easeOutCubic: { - name: "easeOutCubic", - formatted_name: "Cubic - EaseOut", - value: [0.215, 0.61, 0.355, 1], - }, - easeInOutCubic: { - name: "easeInOutCubic", - formatted_name: "Cubic - EaseInOut", - value: [0.645, 0.045, 0.355, 1], - }, - easeInSine: { - name: "easeInSine", - formatted_name: "Sine - EaseIn", - value: [0.47, 0, 0.745, 0.715], - }, - easeOutSine: { - name: "easeOutSine", - formatted_name: "Sine - EaseOut", - value: [0.39, 0.575, 0.565, 1], - }, - easeInOutSine: { - name: "easeInOutSine", - formatted_name: "Sine - EaseInOut", - value: [0.445, 0.05, 0.55, 0.95], - }, - easeInQuad: { - name: "easeInQuad", - formatted_name: "Quad - EaseIn", - value: [0.55, 0.085, 0.68, 0.53], - }, - easeOutQuad: { - name: "easeOutQuad", - formatted_name: "Quad - EaseOut", - value: [0.25, 0.46, 0.45, 0.94], - }, - easeInOutQuad: { - name: "easeInOutQuad", - formatted_name: "Quad - EaseInOut", - value: [0.455, 0.03, 0.515, 0.955], - }, - easeInQuart: { - name: "easeInQuart", - formatted_name: "Quart - EaseIn", - value: [0.895, 0.03, 0.685, 0.22], - }, - easeOutQuart: { - name: "easeOutQuart", - formatted_name: "Quart - EaseOut", - value: [0.165, 0.84, 0.44, 1], - }, - easeInOutQuart: { - name: "easeInOutQuart", - formatted_name: "Quart - EaseInOut", - value: [0.77, 0, 0.175, 1], - }, - easeInQuint: { - name: "easeInQuint", - formatted_name: "Quint - EaseIn", - value: [0.755, 0.05, 0.855, 0.06], - }, - easeOutQuint: { - name: "easeOutQuint", - formatted_name: "Quint - EaseOut", - value: [0.23, 1, 0.32, 1], - }, - easeInOutQuint: { - name: "easeInOutQuint", - formatted_name: "Quint - EaseInOut", - value: [0.86, 0, 0.07, 1], - }, - easeInCirc: { - name: "easeInCirc", - formatted_name: "Circ - EaseIn", - value: [0.6, 0.04, 0.98, 0.335], - }, - easeOutCirc: { - name: "easeOutCirc", - formatted_name: "Circ - EaseOut", - value: [0.075, 0.82, 0.165, 1], - }, - easeInOutCirc: { - name: "easeInOutCirc", - formatted_name: "Circ - EaseInOut", - value: [0.785, 0.135, 0.15, 0.86], - }, - easeInExpo: { - name: "easeInExpo", - formatted_name: "Expo - EaseIn", - value: [0.95, 0.05, 0.795, 0.035], - }, - easeOutExpo: { - name: "easeOutExpo", - formatted_name: "Expo - EaseOut", - value: [0.19, 1, 0.22, 1], - }, - easeInOutExpo: { - name: "easeInOutExpo", - formatted_name: "Expo - EaseInOut", - value: [1, 0, 0, 1], - }, - easeInBack: { - name: "easeInBack", - formatted_name: "Back - EaseIn", - value: [0.6, -0.28, 0.735, 0.045], - }, - easeOutBack: { - name: "easeOutBack", - formatted_name: "Back - EaseOut", - value: [0.175, 0.885, 0.32, 1.275], - }, - easeInOutBack: { - name: "easeInOutBack", - formatted_name: "Back - EaseInOut", - value: [0.68, -0.55, 0.265, 1.55], - }, -}; diff --git a/styles/src/system/lib/convert.ts b/styles/src/system/lib/convert.ts new file mode 100644 index 0000000000..ee7c0743dc --- /dev/null +++ b/styles/src/system/lib/convert.ts @@ -0,0 +1,11 @@ +/** Converts a percentage scale value (0-100) to normalized scale (0-1) value. */ +export function percentageToNormalized(value: number) { + const normalized = value / 100; + return normalized; +} + +/** Converts a normalized scale (0-1) value to a percentage scale (0-100) value. */ +export function normalizedToPercetage(value: number) { + const percentage = value * 100; + return percentage; +} diff --git a/styles/src/system/lib/curve.ts b/styles/src/system/lib/curve.ts new file mode 100644 index 0000000000..003dfd769f --- /dev/null +++ b/styles/src/system/lib/curve.ts @@ -0,0 +1,21 @@ +import bezier from "bezier-easing"; +import { Curve } from "../ref/curves"; + +/** + * Formats our Curve data structure into a bezier easing function. + * @param {Curve} curve - The curve to format. + * @param {Boolean} inverted - Whether or not to invert the curve. + * @returns {EasingFunction} The formatted easing function. + */ +export function curve(curve: Curve, inverted?: Boolean) { + if (inverted) { + return bezier( + curve.value[3], + curve.value[2], + curve.value[1], + curve.value[0] + ); + } + + return bezier(curve.value[0], curve.value[1], curve.value[2], curve.value[3]); +} diff --git a/styles/src/system/lib/generate.ts b/styles/src/system/lib/generate.ts new file mode 100644 index 0000000000..c77d93fff8 --- /dev/null +++ b/styles/src/system/lib/generate.ts @@ -0,0 +1,157 @@ +import bezier from "bezier-easing"; +import chroma from "chroma-js"; +import { Color, ColorFamily, ColorFamilyConfig, ColorScale } from "../types"; +import { percentageToNormalized } from "./convert"; +import { curve } from "./curve"; + +// Re-export interface in a more standard format +export type EasingFunction = bezier.EasingFunction; + +/** + * Generates a color, outputs it in multiple formats, and returns a variety of useful metadata. + * + * @param {EasingFunction} hueEasing - An easing function for the hue component of the color. + * @param {EasingFunction} saturationEasing - An easing function for the saturation component of the color. + * @param {EasingFunction} lightnessEasing - An easing function for the lightness component of the color. + * @param {ColorFamilyConfig} family - Configuration for the color family. + * @param {number} step - The current step. + * @param {number} steps - The total number of steps in the color scale. + * + * @returns {Color} The generated color, with its calculated contrast against black and white, as well as its LCH values, RGBA array, hexadecimal representation, and a flag indicating if it is light or dark. + */ +function generateColor( + hueEasing: EasingFunction, + saturationEasing: EasingFunction, + lightnessEasing: EasingFunction, + family: ColorFamilyConfig, + step: number, + steps: number +) { + const { hue, saturation, lightness } = family.color; + + const stepHue = hueEasing(step / steps) * (hue.end - hue.start) + hue.start; + const stepSaturation = + saturationEasing(step / steps) * (saturation.end - saturation.start) + + saturation.start; + const stepLightness = + lightnessEasing(step / steps) * (lightness.end - lightness.start) + + lightness.start; + + const color = chroma.hsl( + stepHue, + percentageToNormalized(stepSaturation), + percentageToNormalized(stepLightness) + ); + + const contrast = { + black: { + value: chroma.contrast(color, "black"), + aaPass: chroma.contrast(color, "black") >= 4.5, + aaaPass: chroma.contrast(color, "black") >= 7, + }, + white: { + value: chroma.contrast(color, "white"), + aaPass: chroma.contrast(color, "white") >= 4.5, + aaaPass: chroma.contrast(color, "white") >= 7, + }, + }; + + const lch = color.lch(); + const rgba = color.rgba(); + const hex = color.hex(); + + const isLight = lch[0] > 50; + + const result: Color = { + step, + lch, + hex, + rgba, + contrast, + isLight, + }; + + return result; +} + +/** + * Generates a color scale based on a color family configuration. + * + * @param {ColorFamilyConfig} config - The configuration for the color family. + * @param {Boolean} inverted - Specifies whether the color scale should be inverted or not. + * + * @returns {ColorScale} The generated color scale. + * + * @example + * ```ts + * const colorScale = generateColorScale({ + * name: "blue", + * color: { + * hue: { + * start: 210, + * end: 240, + * curve: "easeInOut" + * }, + * saturation: { + * start: 100, + * end: 100, + * curve: "easeInOut" + * }, + * lightness: { + * start: 50, + * end: 50, + * curve: "easeInOut" + * } + * } + * }); + * ``` + */ + +export function generateColorScale( + config: ColorFamilyConfig, + inverted: Boolean = false +) { + const { hue, saturation, lightness } = config.color; + + // 101 steps means we get values from 0-100 + const NUM_STEPS = 101; + + const hueEasing = curve(hue.curve, inverted); + const saturationEasing = curve(saturation.curve, inverted); + const lightnessEasing = curve(lightness.curve, inverted); + + let scale: ColorScale = { + colors: [], + values: [], + }; + + for (let i = 0; i < NUM_STEPS; i++) { + const color = generateColor( + hueEasing, + saturationEasing, + lightnessEasing, + config, + i, + NUM_STEPS + ); + + scale.colors.push(color); + scale.values.push(color.hex); + } + + return scale; +} + +/** Generates a color family with a scale and an inverted scale. */ +export function generateColorFamily(config: ColorFamilyConfig) { + const scale = generateColorScale(config, false); + const invertedScale = generateColorScale(config, true); + + const family: ColorFamily = { + name: config.name, + scale, + invertedScale, + }; + + return family; +} diff --git a/styles/src/system/ref/color.ts b/styles/src/system/ref/color.ts index 72ba747aa4..aa00169b39 100644 --- a/styles/src/system/ref/color.ts +++ b/styles/src/system/ref/color.ts @@ -1,336 +1,376 @@ -import chroma from "chroma-js"; -import { - generateColors2, - generateColorSet, - generateColorsUsingCurve, -} from "../algorithm"; -import { curve } from "../curves"; -import { ColorFamily } from "../types"; +import { generateColorFamily } from "../lib/generate"; +import { curve } from "./curves"; // These are the source colors for the color scales in the system. -// This should never directly be used in the system, or exported to be used in a component or theme -// As it will generate thousands of lines of code. +// These should never directly be used directly in components or themes as they generate thousands of lines of code. // Instead, use the outputs from the reference palette which exports a smaller subset of colors. -// Token or user-facing colors should use short, clear names -// and a 100-900 scale to match the font weight scale. +// Token or user-facing colors should use short, clear names and a 100-900 scale to match the font weight scale. // Red ======================================== // -export const red = generateColors2( - { - start: 0, - end: 0, - curve: curve.linear, +export const red = generateColorFamily({ + name: "red", + color: { + hue: { + start: 0, + end: 0, + curve: curve.linear, + }, + saturation: { + start: 95, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 95, - end: 75, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Sunset ======================================== // -export const sunset = generateColors2( - { - start: 12, - end: 12, - curve: curve.linear, +export const sunset = generateColorFamily({ + name: "sunset", + color: { + hue: { + start: 12, + end: 12, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 80, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 100, - end: 80, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Orange ======================================== // -export const orange = generateColors2( - { - start: 25, - end: 25, - curve: curve.linear, +export const orange = generateColorFamily({ + name: "orange", + color: { + hue: { + start: 25, + end: 25, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 100, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 100, - end: 100, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Amber ======================================== // -export const amber = generateColors2( - { - start: 34, - end: 34, - curve: curve.linear, +export const amber = generateColorFamily({ + name: "amber", + color: { + hue: { + start: 34, + end: 34, + curve: curve.linear, + }, + saturation: { + start: 100, + end: 100, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 100, - end: 100, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Yellow ======================================== // -export const yellow = generateColors2( - { - start: 48, - end: 48, - curve: curve.linear, +export const yellow = generateColorFamily({ + name: "yellow", + color: { + hue: { + start: 48, + end: 48, + curve: curve.linear, + }, + saturation: { + start: 90, + end: 100, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 28, + curve: curve.lightness, + }, }, - { - start: 90, - end: 100, - curve: curve.saturation, - }, - { - start: 97, - end: 32, - curve: curve.lightness, - } -); +}); // Citron ======================================== // -export const citron = generateColors2( - { - start: 65, - end: 65, - curve: curve.linear, +export const citron = generateColorFamily({ + name: "citron", + color: { + hue: { + start: 65, + end: 65, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 70, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 85, - end: 70, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Lime ======================================== // -export const lime = generateColors2( - { - start: 85, - end: 85, - curve: curve.linear, +export const lime = generateColorFamily({ + name: "lime", + color: { + hue: { + start: 85, + end: 85, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 70, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 85, - end: 70, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Green ======================================== // -export const green = generateColors2( - { - start: 108, - end: 108, - curve: curve.linear, +export const green = generateColorFamily({ + name: "green", + color: { + hue: { + start: 108, + end: 108, + curve: curve.linear, + }, + saturation: { + start: 60, + end: 50, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 25, + curve: curve.lightness, + }, }, - { - start: 60, - end: 50, - curve: curve.saturation, - }, - { - start: 97, - end: 25, - curve: curve.lightness, - } -); +}); // Mint ======================================== // -export const mint = generateColors2( - { - start: 142, - end: 142, - curve: curve.linear, +export const mint = generateColorFamily({ + name: "mint", + color: { + hue: { + start: 142, + end: 142, + curve: curve.linear, + }, + saturation: { + start: 60, + end: 50, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 60, - end: 50, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Cyan ======================================== // -export const cyan = generateColors2( - { - start: 179, - end: 179, - curve: curve.linear, +export const cyan = generateColorFamily({ + name: "cyan", + color: { + hue: { + start: 179, + end: 179, + curve: curve.linear, + }, + saturation: { + start: 70, + end: 60, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 70, - end: 60, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Sky ======================================== // -export const sky = generateColors2( - { - start: 190, - end: 190, - curve: curve.linear, +export const sky = generateColorFamily({ + name: "sky", + color: { + hue: { + start: 195, + end: 195, + curve: curve.linear, + }, + saturation: { + start: 85, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 85, - end: 75, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Blue ======================================== // -export const blue = generateColors2( - { - start: 210, - end: 210, - curve: curve.linear, +export const blue = generateColorFamily({ + name: "blue", + color: { + hue: { + start: 210, + end: 210, + curve: curve.linear, + }, + saturation: { + start: 90, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 90, - end: 60, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Indigo ======================================== // -export const indigo = generateColors2( - { - start: 240, - end: 240, - curve: curve.linear, +export const indigo = generateColorFamily({ + name: "indigo", + color: { + hue: { + start: 230, + end: 230, + curve: curve.linear, + }, + saturation: { + start: 80, + end: 50, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 80, - end: 40, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Purple ======================================== // -export const purple = generateColors2( - { - start: 260, - end: 265, - curve: curve.linear, +export const purple = generateColorFamily({ + name: "purple", + color: { + hue: { + start: 260, + end: 265, + curve: curve.linear, + }, + saturation: { + start: 80, + end: 50, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 80, - end: 50, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Pink ======================================== // -export const pink = generateColors2( - { - start: 310, - end: 310, - curve: curve.linear, +export const pink = generateColorFamily({ + name: "pink", + color: { + hue: { + start: 310, + end: 310, + curve: curve.linear, + }, + saturation: { + start: 80, + end: 75, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 80, - end: 70, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); // Rose ======================================== // -export const rose = generateColors2( - { - start: 345, - end: 345, - curve: curve.linear, +export const rose = generateColorFamily({ + name: "rose", + color: { + hue: { + start: 345, + end: 345, + curve: curve.linear, + }, + saturation: { + start: 90, + end: 65, + curve: curve.saturation, + }, + lightness: { + start: 97, + end: 20, + curve: curve.lightness, + }, }, - { - start: 90, - end: 65, - curve: curve.saturation, - }, - { - start: 97, - end: 20, - curve: curve.lightness, - } -); +}); diff --git a/styles/src/system/ref/curves.ts b/styles/src/system/ref/curves.ts new file mode 100644 index 0000000000..0eaa3d1678 --- /dev/null +++ b/styles/src/system/ref/curves.ts @@ -0,0 +1,25 @@ +export interface Curve { + name: string; + value: number[]; +} + +export interface Curves { + lightness: Curve; + saturation: Curve; + linear: Curve; +} + +export const curve: Curves = { + lightness: { + name: "lightnessCurve", + value: [0.2, 0, 0.85, 1.0], + }, + saturation: { + name: "saturationCurve", + value: [0.67, 0.6, 0.55, 1.0], + }, + linear: { + name: "linear", + value: [0.5, 0.5, 0.5, 0.5], + }, +}; diff --git a/styles/src/system/reference.ts b/styles/src/system/reference.ts deleted file mode 100644 index 47134915f0..0000000000 --- a/styles/src/system/reference.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as color from "./ref/color"; - -export { color }; diff --git a/styles/src/system/system.ts b/styles/src/system/system.ts new file mode 100644 index 0000000000..78bc7c5374 --- /dev/null +++ b/styles/src/system/system.ts @@ -0,0 +1,23 @@ +import chroma from "chroma-js"; +import * as colorFamily from "./ref/color"; + +const color = { + red: chroma.scale(colorFamily.red.scale.values).mode("lch").colors(9), + sunset: chroma.scale(colorFamily.sunset.scale.values).mode("lch").colors(9), + orange: chroma.scale(colorFamily.orange.scale.values).mode("lch").colors(9), + amber: chroma.scale(colorFamily.amber.scale.values).mode("lch").colors(9), + yellow: chroma.scale(colorFamily.yellow.scale.values).mode("lch").colors(9), + citron: chroma.scale(colorFamily.citron.scale.values).mode("lch").colors(9), + lime: chroma.scale(colorFamily.lime.scale.values).mode("lch").colors(9), + green: chroma.scale(colorFamily.green.scale.values).mode("lch").colors(9), + mint: chroma.scale(colorFamily.mint.scale.values).mode("lch").colors(9), + cyan: chroma.scale(colorFamily.cyan.scale.values).mode("lch").colors(9), + sky: chroma.scale(colorFamily.sky.scale.values).mode("lch").colors(9), + blue: chroma.scale(colorFamily.blue.scale.values).mode("lch").colors(9), + indigo: chroma.scale(colorFamily.indigo.scale.values).mode("lch").colors(9), + purple: chroma.scale(colorFamily.purple.scale.values).mode("lch").colors(9), + pink: chroma.scale(colorFamily.pink.scale.values).mode("lch").colors(9), + rose: chroma.scale(colorFamily.rose.scale.values).mode("lch").colors(9), +}; + +export { color }; diff --git a/styles/src/system/types.ts b/styles/src/system/types.ts index 260a204b02..487befef62 100644 --- a/styles/src/system/types.ts +++ b/styles/src/system/types.ts @@ -1,27 +1,67 @@ import { Color as ChromaColor } from "chroma-js"; +import { Curve } from "./ref/curves"; + +export interface ColorAccessiblityValue { + value: number; + aaPass: boolean; + aaaPass: boolean; +} + +/** + * Calculates the color contrast between a specified color and its corresponding background and foreground colors. + * + * @note This implementation is currently basic – Currently we only calculate contrasts against black and white, in the future will allow for dynamic color contrast calculation based on the colors present in a given palette. + * @note The goal is to align with WCAG3 accessibility standards as they become stabilized. See the [WCAG 3 Introduction](https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/) for more information. + */ +export interface ColorAccessiblity { + black: ColorAccessiblityValue; + white: ColorAccessiblityValue; +} export type Color = { step: number; + contrast: ColorAccessiblity; hex: string; lch: number[]; - rgbaArray: number[]; + rgba: number[]; + isLight: boolean; }; -export type ColorSet = Color[]; +export interface ColorScale { + colors: Color[]; + // An array of hex values for each color in the scale + values: string[]; +} export type ColorFamily = { name: string; - colors: string[]; - invertedColors: string[]; - colorsMeta: ColorSet; - invertedMeta: ColorSet; + scale: ColorScale; + invertedScale: ColorScale; }; -export interface ColorProps { +export interface ColorFamilyHue { + start: number; + end: number; + curve: Curve; +} + +export interface ColorFamilySaturation { + start: number; + end: number; + curve: Curve; +} + +export interface ColorFamilyLightness { + start: number; + end: number; + curve: Curve; +} + +export interface ColorFamilyConfig { name: string; color: { - start: string | ChromaColor; - middle: string | ChromaColor; - end: string | ChromaColor; + hue: ColorFamilyHue; + saturation: ColorFamilySaturation; + lightness: ColorFamilyLightness; }; } diff --git a/styles/theme-tool/app/page.tsx b/styles/theme-tool/app/page.tsx index 8a9a139480..cab19c59fd 100644 --- a/styles/theme-tool/app/page.tsx +++ b/styles/theme-tool/app/page.tsx @@ -1,12 +1,8 @@ /* eslint-disable import/no-relative-packages */ -import { Scale } from 'chroma-js'; - -import { color } from '../../src/system/reference'; +import { color } from '../../src/system/system'; import styles from './page.module.css'; -function ColorChips({ colorScale }: { colorScale: Scale }) { - const colors = colorScale.colors(11); - +function ColorChips({ colors }: { colors: string[] }) { return (