From 42b5fa1fa3380b341c4221be335e179cc72e7b87 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 9 Feb 2023 12:37:05 -0500 Subject: [PATCH] WIP: Use algorithm to generate reference color palette Adapted from @k-vyn/coloralgorithm Generate colors for our reference palette. --- styles/package-lock.json | 29 +++++ styles/package.json | 36 +++--- styles/src/system/algorithm.ts | 103 ++++++++++++++++++ styles/src/system/lib/curves.ts | 163 ++++++++++++++++++++++++++++ styles/src/system/ref/color.ts | 135 +++++++++++++++++------ styles/src/system/reference.ts | 10 +- styles/theme-tool/app/page.tsx | 30 ++++- styles/theme-tool/package-lock.json | 29 +++++ styles/theme-tool/package.json | 1 + 9 files changed, 474 insertions(+), 62 deletions(-) create mode 100644 styles/src/system/algorithm.ts create mode 100644 styles/src/system/lib/curves.ts diff --git a/styles/package-lock.json b/styles/package-lock.json index b0a904b11d..e5ea7b8d18 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@k-vyn/coloralgorithm": "^1.0.0", "@types/chroma-js": "^2.1.3", "@types/node": "^17.0.23", "case-anything": "^2.1.10", @@ -36,6 +37,15 @@ "node": ">=12" } }, + "node_modules/@k-vyn/coloralgorithm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@k-vyn/coloralgorithm/-/coloralgorithm-1.0.0.tgz", + "integrity": "sha512-a9aAOXxQ+c2Mw5sMC39elT0wYkPa3qktFjtxVkfY3mQEFBr7NMQEczCARVdkmIKo1dIrgNSx3z12sTXohzSZDg==", + "dependencies": { + "bezier-easing": "^2.1.0", + "chroma-js": "^2.1.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -90,6 +100,11 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "node_modules/case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", @@ -212,6 +227,15 @@ "@cspotcode/source-map-consumer": "0.8.0" } }, + "@k-vyn/coloralgorithm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@k-vyn/coloralgorithm/-/coloralgorithm-1.0.0.tgz", + "integrity": "sha512-a9aAOXxQ+c2Mw5sMC39elT0wYkPa3qktFjtxVkfY3mQEFBr7NMQEczCARVdkmIKo1dIrgNSx3z12sTXohzSZDg==", + "requires": { + "bezier-easing": "^2.1.0", + "chroma-js": "^2.1.0" + } + }, "@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -257,6 +281,11 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, + "bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "case-anything": { "version": "2.1.10", "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", diff --git a/styles/package.json b/styles/package.json index 118269bc81..24122f56d6 100644 --- a/styles/package.json +++ b/styles/package.json @@ -1,20 +1,20 @@ { - "name": "styles", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "build": "ts-node ./src/buildThemes.ts", - "build-licenses": "ts-node ./src/buildLicenses.ts" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@types/chroma-js": "^2.1.3", - "@types/node": "^17.0.23", - "case-anything": "^2.1.10", - "chroma-js": "^2.4.2", - "toml": "^3.0.0", - "ts-node": "^10.7.0" - } + "name": "styles", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "ts-node ./src/buildThemes.ts", + "build-licenses": "ts-node ./src/buildLicenses.ts" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@types/chroma-js": "^2.1.3", + "@types/node": "^17.0.23", + "case-anything": "^2.1.10", + "chroma-js": "^2.4.2", + "toml": "^3.0.0", + "ts-node": "^10.7.0" + } } diff --git a/styles/src/system/algorithm.ts b/styles/src/system/algorithm.ts new file mode 100644 index 0000000000..74a01a5ab8 --- /dev/null +++ b/styles/src/system/algorithm.ts @@ -0,0 +1,103 @@ +// Adapted from @k-vyn/coloralgorithm + +import chroma, { Scale } from "chroma-js"; + +export type Color = { + step: number; + hex: string; + lch: number[]; + rgbaArray: number[]; +}; + +export type ColorSet = Color[]; +export type ColorFamily = { + name: string; + colors: string[]; + invertedColors: string[]; + colorsMeta: ColorSet; + invertedMeta: ColorSet; +}; + +export interface ColorProps { + name: string; + color: { + start: string; + middle: string; + end: string; + }; +} + +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(); + + const result = { + step, + hex, + lch, + rgbaArray, + }; + + 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 = validColor(start); + const middleColor = validColor(middle); + const endColor = validColor(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; +} + +/** 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/lib/curves.ts b/styles/src/system/lib/curves.ts new file mode 100644 index 0000000000..432963a370 --- /dev/null +++ b/styles/src/system/lib/curves.ts @@ -0,0 +1,163 @@ +// Adapted from @k-vyn/coloralgorithm + +export interface Curve { + name: string; + formatted_name: string; + value: number[]; +} + +export interface Curves { + 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 = { + 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/ref/color.ts b/styles/src/system/ref/color.ts index 4532bad619..73307ecf21 100644 --- a/styles/src/system/ref/color.ts +++ b/styles/src/system/ref/color.ts @@ -1,39 +1,110 @@ import * as chroma from "chroma-js"; +import { ColorFamily, generateColorSet } from "../algorithm"; // Colors should use the LCH color space. // https://www.w3.org/TR/css-color-4/#lch-colors -const base = { - black: chroma.lch(0, 0, 0), - white: chroma.lch(150, 0, 0), - gray: { - light: chroma.lch(96, 0, 0), - mid: chroma.lch(55, 0, 0), - dark: chroma.lch(10, 0, 0), - }, - rose: { - light: chroma.lch(96, 5, 14), - mid: chroma.lch(56, 74, 21), - dark: chroma.lch(10, 24, 21), - }, - red: { - light: chroma.lch(96, 4, 31), - mid: chroma.lch(55, 77, 31), - dark: chroma.lch(10, 24, 31), - }, -}; +export const black = chroma.lch(0, 0, 0); -export const black = base.black; -export const white = base.white; +export const white = chroma.lch(150, 0, 0); -export const gray = chroma.scale([ - base.gray.light, - base.gray.mid, - base.gray.dark, -]); -export const rose = chroma.scale([ - base.rose.light, - base.rose.mid, - base.rose.dark, -]); -export const red = chroma.scale([base.red.light, base.red.mid, base.red.dark]); +// Gray ======================================== // + +const gray: ColorFamily = generateColorSet({ + name: "gray", + color: { + start: "#F0F0F0", + middle: "#787878", + end: "#0F0F0F", + }, +}); + +export const grayLight = chroma.scale(gray.colors).mode("lch"); +export const grayDark = chroma.scale(gray.invertedColors).mode("lch"); + +// Rose ======================================== // + +const rose: ColorFamily = generateColorSet({ + name: "rose", + color: { + start: "#FFF1F2", + middle: "#F43F5E", + end: "#881337", + }, +}); + +export const roseLight = chroma.scale(rose.colors).mode("lch"); +export const roseDark = chroma.scale(rose.invertedColors).mode("lch"); + +// Red ======================================== // + +const red: ColorFamily = generateColorSet({ + name: "red", + color: { + start: "#FEF2F2", + middle: "#EF4444", + end: "#7F1D1D", + }, +}); + +export const redLight = chroma.scale(red.colors).mode("lch"); +export const redDark = chroma.scale(red.invertedColors).mode("lch"); + +// Orange ======================================== // + +const orange: ColorFamily = generateColorSet({ + name: "orange", + color: { + start: "#FFF7ED", + middle: "#F97316", + end: "#7C2D12", + }, +}); + +export const orangeLight = chroma.scale(orange.colors).mode("lch"); +export const orangeDark = chroma.scale(orange.invertedColors).mode("lch"); + +// Amber ======================================== // + +const amber: ColorFamily = generateColorSet({ + name: "amber", + color: { + start: "#FFFBEB", + middle: "#F59E0B", + end: "#78350F", + }, +}); + +export const amberLight = chroma.scale(amber.colors).mode("lch"); +export const amberDark = chroma.scale(amber.invertedColors).mode("lch"); + +// TODO: Add the rest of the colors. +// Source: https://www.figma.com/file/YEZ9jsC1uc9o6hgbv4kfxq/Core-color-library?node-id=48%3A816&t=Ae6tY1cVb2fm5xaM-1 + +// Teal ======================================== // + +const teal: ColorFamily = generateColorSet({ + name: "teal", + color: { + start: "#E6FFFA", + middle: "#14B8A6", + end: "#134E4A", + }, +}); + +export const tealLight = chroma.scale(teal.colors).mode("lch"); +export const tealDark = chroma.scale(teal.invertedColors).mode("lch"); + +const cyan = generateColorSet({ + name: "cyan", + color: { + start: "#F0FDFA", + middle: "#06BBD4", + end: "#164E63", + }, +}); + +export const cyanLight = chroma.scale(cyan.colors).mode("lch"); +export const cyanDark = chroma.scale(cyan.colors).mode("lch"); + +console.log(JSON.stringify(teal, null, 2)); diff --git a/styles/src/system/reference.ts b/styles/src/system/reference.ts index 5b9c285f72..47134915f0 100644 --- a/styles/src/system/reference.ts +++ b/styles/src/system/reference.ts @@ -1,9 +1,3 @@ -import { black, gray, rose, red, white } from "./ref/color"; +import * as color from "./ref/color"; -export const color = { - white, - black, - gray, - rose, - red, -}; +export { color }; diff --git a/styles/theme-tool/app/page.tsx b/styles/theme-tool/app/page.tsx index 4706ad2c18..98a951a7f1 100644 --- a/styles/theme-tool/app/page.tsx +++ b/styles/theme-tool/app/page.tsx @@ -33,13 +33,35 @@ function ColorChips({ colorScale }: { colorScale: Scale }) { } export default function Home() { - const { red, gray, rose } = color; + const { + grayLight, + grayDark, + roseDark, + roseLight, + redDark, + redLight, + orangeDark, + orangeLight, + amberDark, + amberLight, + } = color; return (
- - - + + + + + + + + + + + + + +
); diff --git a/styles/theme-tool/package-lock.json b/styles/theme-tool/package-lock.json index a2a0849410..59a2af7881 100644 --- a/styles/theme-tool/package-lock.json +++ b/styles/theme-tool/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^3.7.1", + "@k-vyn/coloralgorithm": "^1.0.0", "@next/font": "13.1.6", "@types/chroma-js": "^2.1.5", "@types/node": "18.13.0", @@ -568,6 +569,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@k-vyn/coloralgorithm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@k-vyn/coloralgorithm/-/coloralgorithm-1.0.0.tgz", + "integrity": "sha512-a9aAOXxQ+c2Mw5sMC39elT0wYkPa3qktFjtxVkfY3mQEFBr7NMQEczCARVdkmIKo1dIrgNSx3z12sTXohzSZDg==", + "dependencies": { + "bezier-easing": "^2.1.0", + "chroma-js": "^2.1.0" + } + }, "node_modules/@next/env": { "version": "13.1.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.6.tgz", @@ -1276,6 +1286,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4530,6 +4545,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@k-vyn/coloralgorithm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@k-vyn/coloralgorithm/-/coloralgorithm-1.0.0.tgz", + "integrity": "sha512-a9aAOXxQ+c2Mw5sMC39elT0wYkPa3qktFjtxVkfY3mQEFBr7NMQEczCARVdkmIKo1dIrgNSx3z12sTXohzSZDg==", + "requires": { + "bezier-easing": "^2.1.0", + "chroma-js": "^2.1.0" + } + }, "@next/env": { "version": "13.1.6", "resolved": "https://registry.npmjs.org/@next/env/-/env-13.1.6.tgz", @@ -4962,6 +4986,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/styles/theme-tool/package.json b/styles/theme-tool/package.json index c3c5bb2ac0..3d10b48fd2 100644 --- a/styles/theme-tool/package.json +++ b/styles/theme-tool/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^3.7.1", + "@k-vyn/coloralgorithm": "^1.0.0", "@next/font": "13.1.6", "@types/chroma-js": "^2.1.5", "@types/node": "18.13.0",