UNPKG

@weme-ui/colors

Version:
220 lines (215 loc) 7.02 kB
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 };