@johnmusans/arcadia-ui-style-engine
Version:
Shared style system logic and types for Arcadia UI components
150 lines (129 loc) • 3.73 kB
text/typescript
import {
convertColorValue,
contrast as getContrast,
BackgroundColor as LeonardoBgColor,
Color as LeonardoColor,
Theme as LeonardoTheme,
} from "@adobe/leonardo-contrast-colors";
import type { CssColor } from "@adobe/leonardo-contrast-colors";
import { RADIUS_TOKENS } from "@arcadia-ui/registry-definition/registry-tokens";
import { SCALE_NUMBERRS } from "../constants";
import type { ColorTokens, ModeDefinition, Radius, Theme } from "../types";
export interface ColorScale {
name: string;
values: {
name: string;
contrast: number;
value: string;
}[];
}
export const createRatiosObject = (
scaleId: string,
ratios: number[],
): Record<string, number> => {
return ratios.reduce(
(acc, ratio, index) => {
const scaleName = SCALE_NUMBERRS[index] ?? (index + 1) * 100;
acc[`${scaleId}-${scaleName}`] = ratio;
return acc;
},
{} as Record<string, number>,
);
};
export const createColorScales = (modeDefinition: ModeDefinition) => {
const neutralScale = modeDefinition.scales.neutral;
const neutral = new LeonardoBgColor({
name: "neutral",
colorKeys: neutralScale.colorKeys.map((color) => color.color) as CssColor[],
ratios: createRatiosObject("neutral", neutralScale.ratios),
});
const colors = Object.entries(modeDefinition.scales).map(
([scaleId, scale]) => {
const color = new LeonardoColor({
name: scaleId,
colorKeys: scale.colorKeys.map((color) => color.color) as CssColor[],
ratios: createRatiosObject(scaleId, scale.ratios),
smooth: scale.smooth,
});
return color;
},
);
const generatedTheme = new LeonardoTheme({
colors,
backgroundColor: neutral,
lightness: modeDefinition.lightness,
saturation: modeDefinition.saturation,
contrast: modeDefinition.contrast / 100,
output: "HEX",
});
const [_, ...scales] = generatedTheme.contrastColors;
return scales;
};
export const getContrastColor = (colorValue: string): string => {
const colorTuple = convertColorValue(colorValue, "RGB", true) as unknown as {
r: number;
g: number;
b: number;
};
const rgbArray: [number, number, number] = [
colorTuple.r,
colorTuple.g,
colorTuple.b,
];
const contrastWithBlack = getContrast(rgbArray, [0, 0, 0]);
const contrastWithWhite = getContrast(rgbArray, [255, 255, 255]);
return contrastWithBlack > contrastWithWhite
? "hsl(0 0% 0%)"
: "hsl(0 0% 100%)";
};
export const createModeCssVars = (
modeDefinition: ModeDefinition,
): Record<string, string> => {
const colorScales = createColorScales(modeDefinition);
const cssVars = colorScales
.flatMap((colorScale) =>
colorScale.values.flatMap((value) => [
[value.name, value.value] as const,
[`on-${value.name}`, getContrastColor(value.value)] as const,
]),
)
.reduce(
(acc, [key, value]) => {
acc[key] = value;
return acc;
},
{} as Record<string, string>,
);
return cssVars;
};
export const createColorThemeVars = (
tokensDefinition: ColorTokens,
): Record<string, string> => {
const cssThemeVars = tokensDefinition.reduce(
(acc, token) => {
acc[token.name] = token.value;
return acc;
},
{} as Record<string, string>,
);
return cssThemeVars;
};
export const createRadiusVars = (radius: Radius): Theme => {
const themeCssVars = RADIUS_TOKENS.reduce(
(acc, token) => {
acc[token.name] = token.defaultValue;
return acc;
},
{} as Record<string, string>,
);
return {
cssVars: {
light: {
"radius-factor": radius.toString(),
},
theme: {
...themeCssVars,
},
},
};
};