@weme-ui/colors
Version:
Weme UI - Colors
220 lines (215 loc) • 7.02 kB
JavaScript
import * as RadixColors from "@radix-ui/colors";
import Color from "colorjs.io";
import BezierEasing from "bezier-easing";
//#region src/utils.ts
const SPECIAL_COLOR_NAMES = ["black", "white"];
const NEUTRAL_COLOR_NAMES = [
"gray",
"mauve",
"slate",
"sage",
"olive",
"sand"
];
const ACCENT_COLOR_NAMES = [
"tomato",
"red",
"ruby",
"crimson",
"pink",
"plum",
"purple",
"violet",
"iris",
"indigo",
"blue",
"cyan",
"teal",
"jade",
"green",
"grass",
"bronze",
"gold",
"brown",
"orange",
"amber",
"yellow",
"lime",
"mint",
"sky"
];
const COLOR_NAMES = [
...SPECIAL_COLOR_NAMES,
...NEUTRAL_COLOR_NAMES,
...ACCENT_COLOR_NAMES
];
function isRadixColorName(color) {
return COLOR_NAMES.includes(color);
}
function getColorNames(type) {
return !type ? COLOR_NAMES : type === "accent" ? ACCENT_COLOR_NAMES : NEUTRAL_COLOR_NAMES;
}
function getRadixColorName(name, appearance) {
return name === "black" ? "blackP3A" : name === "white" ? "whiteP3A" : `${name}${appearance === "light" ? "P3" : "DarkP3"}`;
}
function getRadixColor(appearance, name, stringify) {
const colorName = getRadixColorName(name, appearance);
return Object.values(RadixColors[colorName]).map((color) => stringify ? toColorString(color) : new Color(color).to("oklch"));
}
function toColorString(colorOrStr) {
const color = typeof colorOrStr === "string" ? new Color(colorOrStr) : colorOrStr;
return color.to("oklch").toString({ precision: 4 });
}
//#endregion
//#region src/colors.ts
const lightColors = Object.fromEntries(getColorNames().map((name) => [name, getRadixColor("light", name)]));
const darkColors = Object.fromEntries(getColorNames().map((name) => [name, getRadixColor("dark", name)]));
const lightNeutralColors = Object.fromEntries(getColorNames("neutral").map((name) => [name, getRadixColor("light", name)]));
const darkNeutralColors = Object.fromEntries(getColorNames("neutral").map((name) => [name, getRadixColor("dark", name)]));
function getColors(appearance, isNeutral) {
return isNeutral ? appearance === "light" ? lightNeutralColors : darkNeutralColors : appearance === "light" ? lightColors : darkColors;
}
//#endregion
//#region src/generator.ts
function generateColor(color, colors, background) {
const sourceColor = new Color(color).to("oklch");
const backgroundColor = new Color(background).to("oklch");
const scales = getScaleColors(sourceColor, colors, backgroundColor);
scales[8] = sourceColor;
scales[9] = getHoverColor(sourceColor, [scales]);
scales[10].coords[1] = Math.min(Math.max(scales[8].coords[1], scales[7].coords[1]), scales[10].coords[1]);
scales[11].coords[1] = Math.min(Math.max(scales[8].coords[1], scales[7].coords[1]), scales[11].coords[1]);
return scales.map(toColorString);
}
function getScaleColors(source, colors, backgroundColor) {
const allColors = [];
Object.entries(colors).forEach(([name, scale$1]) => {
for (const color of scale$1) {
const distance = source.deltaEOK(color);
allColors.push({
name,
distance,
color
});
}
});
allColors.sort((a$1, b$1) => a$1.distance - b$1.distance);
const closestColors = allColors.filter((color, i, arr) => i === arr.findIndex((value) => value.name === color.name));
const grayColorNames = getColorNames("neutral");
const allAreGrays = closestColors.every((color) => grayColorNames.includes(color.name));
if (!allAreGrays && grayColorNames.includes(closestColors[0].name)) while (grayColorNames.includes(closestColors[1].name)) closestColors.splice(1, 1);
const closestA = closestColors[0];
const closestB = closestColors[1];
const a = closestB.distance;
const b = closestA.distance;
const c = closestA.color.deltaEOK(closestB.color);
const cosA = (b ** 2 + c ** 2 - a ** 2) / (2 * b * c);
const radA = Math.acos(cosA);
const sinA = Math.sin(radA);
const cosB = (a ** 2 + c ** 2 - b ** 2) / (2 * a * c);
const radB = Math.acos(cosB);
const sinB = Math.sin(radB);
const tanC1 = cosA / sinA;
const tanC2 = cosB / sinB;
const ratio = Math.max(0, tanC1 / tanC2) * .5;
const colorA = colors[closestA.name];
const colorB = colors[closestB.name];
const scale = [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
].map((i) => new Color(Color.mix(colorA[i], colorB[i], ratio)).to("oklch"));
const baseColor = scale.slice().sort((a$1, b$1) => source.deltaEOK(a$1) - source.deltaEOK(b$1))[0];
const ratioC = source.coords[1] / baseColor.coords[1];
scale.forEach((color) => {
color.coords[1] = Math.min(source.coords[1] * 1.5, color.coords[1] * ratioC);
color.coords[2] = source.coords[2];
});
if (scale[0].coords[0] > .5) {
const lightnessScale$1 = scale.map(({ coords }) => coords[0]);
const backgroundL = Math.max(0, Math.min(1, backgroundColor.coords[0]));
const newLightnessScale$1 = transposeProgressionStart(backgroundL, [1, ...lightnessScale$1], [
0,
2,
0,
2
]);
newLightnessScale$1.shift();
newLightnessScale$1.forEach((lightness, i) => {
scale[i].coords[0] = lightness;
});
return scale;
}
const ease = [
1,
0,
1,
0
];
const referenceBackgroundColorL = scale[0].coords[0];
const backgroundColorL = Math.max(0, Math.min(1, backgroundColor.coords[0]));
const ratioL = backgroundColorL / referenceBackgroundColorL;
if (ratioL > 1) {
const maxRatio = 1.5;
for (let i = 0; i < ease.length; i++) {
const metaRatio = (ratioL - 1) * (maxRatio / (maxRatio - 1));
ease[i] = ratioL > maxRatio ? 0 : Math.max(0, ease[i] * (1 - metaRatio));
}
}
const lightnessScale = scale.map(({ coords }) => coords[0]);
const newLightnessScale = transposeProgressionStart(backgroundColorL, lightnessScale, ease);
newLightnessScale.forEach((lightness, i) => {
scale[i].coords[0] = lightness;
});
return scale;
}
function getHoverColor(source, scales) {
const [L, C, H] = source.coords;
const newL = L > .4 ? L - .03 / (L + .1) : L + .03 / (L + .1);
const newC = L > .4 && !Number.isNaN(H) ? C * .93 + 0 : C;
const hoverColor = new Color("oklch", [
newL,
newC,
H
]);
let closestColor = hoverColor;
let minDistance = Infinity;
scales.forEach((scale) => {
for (const color of scale) {
const distance = hoverColor.deltaEOK(color);
if (distance < minDistance) {
minDistance = distance;
closestColor = color;
}
}
});
hoverColor.coords[1] = closestColor.coords[1];
hoverColor.coords[2] = closestColor.coords[2];
return hoverColor;
}
function transposeProgressionStart(to, arr, curve) {
return arr.map((n, i, arr$1) => {
const lastIndex = arr$1.length - 1;
const diff = arr$1[0] - to;
const fn = BezierEasing(...curve);
return n - diff * fn(1 - i / lastIndex);
});
}
//#endregion
//#region src/transform.ts
function transformColor({ color, appearance = "light", isNeutral = false, background = "#fff" }) {
if (isRadixColorName(color)) return getRadixColor(appearance, color, true);
const colors = getColors(appearance, isNeutral);
return generateColor(color, colors, background);
}
//#endregion
export { getColorNames, getColors, toColorString, transformColor };