Merge pull request #2150 from zed-industries/nate/system-colors

Add system color palette
This commit is contained in:
Nate Butler
2023-02-25 11:43:02 -05:00
committed by GitHub
10 changed files with 1103 additions and 342 deletions
-21
View File
@@ -43,24 +43,3 @@ export const sizes = {
lg: 8,
xl: 12,
};
// export const colors = {
// neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1
// rose: colorRamp("#F43F5EFF"),
// red: colorRamp("#EF4444FF"),
// orange: colorRamp("#F97316FF"),
// amber: colorRamp("#F59E0BFF"),
// yellow: colorRamp("#EAB308FF"),
// lime: colorRamp("#84CC16FF"),
// green: colorRamp("#22C55EFF"),
// emerald: colorRamp("#10B981FF"),
// teal: colorRamp("#14B8A6FF"),
// cyan: colorRamp("#06BBD4FF"),
// sky: colorRamp("#0EA5E9FF"),
// blue: colorRamp("#3B82F6FF"),
// indigo: colorRamp("#6366F1FF"),
// violet: colorRamp("#8B5CF6FF"),
// purple: colorRamp("#A855F7FF"),
// fuschia: colorRamp("#D946E4FF"),
// pink: colorRamp("#EC4899FF"),
// }
+11
View File
@@ -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
}
+26
View File
@@ -0,0 +1,26 @@
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]
)
}
+159
View File
@@ -0,0 +1,159 @@
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()
// 55 is a magic number. It's the lightness value at which we consider a color to be "light".
// It was picked by eye with some testing. We might want to use a more scientific approach in the future.
const isLight = lch[0] > 55
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
}
+445
View File
@@ -0,0 +1,445 @@
import { generateColorFamily } from "../lib/generate"
import { curve } from "./curves"
// These are the source colors for the color scales in the system.
// 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.
// Light Gray ======================================== //
export const lightgray = generateColorFamily({
name: "lightgray",
color: {
hue: {
start: 210,
end: 210,
curve: curve.linear,
},
saturation: {
start: 10,
end: 15,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 50,
curve: curve.linear,
},
},
})
// Light Dark ======================================== //
export const darkgray = generateColorFamily({
name: "darkgray",
color: {
hue: {
start: 210,
end: 210,
curve: curve.linear,
},
saturation: {
start: 15,
end: 20,
curve: curve.saturation,
},
lightness: {
start: 55,
end: 8,
curve: curve.linear,
},
},
})
// Red ======================================== //
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,
},
},
})
// Sunset ======================================== //
export const sunset = generateColorFamily({
name: "sunset",
color: {
hue: {
start: 15,
end: 15,
curve: curve.linear,
},
saturation: {
start: 100,
end: 90,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 25,
curve: curve.lightness,
},
},
})
// Orange ======================================== //
export const orange = generateColorFamily({
name: "orange",
color: {
hue: {
start: 25,
end: 25,
curve: curve.linear,
},
saturation: {
start: 100,
end: 95,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 20,
curve: curve.lightness,
},
},
})
// Amber ======================================== //
export const amber = generateColorFamily({
name: "amber",
color: {
hue: {
start: 38,
end: 38,
curve: curve.linear,
},
saturation: {
start: 100,
end: 100,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 18,
curve: curve.lightness,
},
},
})
// Yellow ======================================== //
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: 15,
curve: curve.lightness,
},
},
})
// Lemon ======================================== //
export const lemon = generateColorFamily({
name: "lemon",
color: {
hue: {
start: 55,
end: 55,
curve: curve.linear,
},
saturation: {
start: 85,
end: 95,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 15,
curve: curve.lightness,
},
},
})
// Citron ======================================== //
export const citron = generateColorFamily({
name: "citron",
color: {
hue: {
start: 70,
end: 70,
curve: curve.linear,
},
saturation: {
start: 85,
end: 90,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 15,
curve: curve.lightness,
},
},
})
// Lime ======================================== //
export const lime = generateColorFamily({
name: "lime",
color: {
hue: {
start: 85,
end: 85,
curve: curve.linear,
},
saturation: {
start: 85,
end: 80,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 18,
curve: curve.lightness,
},
},
})
// Green ======================================== //
export const green = generateColorFamily({
name: "green",
color: {
hue: {
start: 108,
end: 108,
curve: curve.linear,
},
saturation: {
start: 60,
end: 70,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 18,
curve: curve.lightness,
},
},
})
// Mint ======================================== //
export const mint = generateColorFamily({
name: "mint",
color: {
hue: {
start: 142,
end: 142,
curve: curve.linear,
},
saturation: {
start: 60,
end: 75,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 20,
curve: curve.lightness,
},
},
})
// Cyan ======================================== //
export const cyan = generateColorFamily({
name: "cyan",
color: {
hue: {
start: 179,
end: 179,
curve: curve.linear,
},
saturation: {
start: 70,
end: 80,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 20,
curve: curve.lightness,
},
},
})
// Sky ======================================== //
export const sky = generateColorFamily({
name: "sky",
color: {
hue: {
start: 195,
end: 205,
curve: curve.linear,
},
saturation: {
start: 85,
end: 90,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 15,
curve: curve.lightness,
},
},
})
// Blue ======================================== //
export const blue = generateColorFamily({
name: "blue",
color: {
hue: {
start: 218,
end: 218,
curve: curve.linear,
},
saturation: {
start: 85,
end: 70,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 15,
curve: curve.lightness,
},
},
})
// Indigo ======================================== //
export const indigo = generateColorFamily({
name: "indigo",
color: {
hue: {
start: 245,
end: 245,
curve: curve.linear,
},
saturation: {
start: 60,
end: 50,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 22,
curve: curve.lightness,
},
},
})
// Purple ======================================== //
export const purple = generateColorFamily({
name: "purple",
color: {
hue: {
start: 260,
end: 270,
curve: curve.linear,
},
saturation: {
start: 65,
end: 55,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 20,
curve: curve.lightness,
},
},
})
// Pink ======================================== //
export const pink = generateColorFamily({
name: "pink",
color: {
hue: {
start: 320,
end: 330,
curve: curve.linear,
},
saturation: {
start: 70,
end: 65,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 32,
curve: curve.lightness,
},
},
})
// Rose ======================================== //
export const rose = generateColorFamily({
name: "rose",
color: {
hue: {
start: 345,
end: 345,
curve: curve.linear,
},
saturation: {
start: 90,
end: 70,
curve: curve.saturation,
},
lightness: {
start: 97,
end: 32,
curve: curve.lightness,
},
},
})
+25
View File
@@ -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.75, 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],
},
}
+32
View File
@@ -0,0 +1,32 @@
import chroma from "chroma-js"
import * as colorFamily from "./ref/color"
const color = {
lightgray: chroma
.scale(colorFamily.lightgray.scale.values)
.mode("lch")
.colors(9),
darkgray: chroma
.scale(colorFamily.darkgray.scale.values)
.mode("lch")
.colors(9),
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),
lemon: chroma.scale(colorFamily.lemon.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 }
+66
View File
@@ -0,0 +1,66 @@
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[]
rgba: number[]
isLight: boolean
}
export interface ColorScale {
colors: Color[]
// An array of hex values for each color in the scale
values: string[]
}
export type ColorFamily = {
name: string
scale: ColorScale
invertedScale: ColorScale
}
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: {
hue: ColorFamilyHue
saturation: ColorFamilySaturation
lightness: ColorFamilyLightness
}
}