Files
nextjs-python-web-template/pkgs/theme/src/main.ts
2023-10-03 14:58:31 +02:00

183 lines
4.8 KiB
JavaScript

#!usr/bin/node
import * as fs from "fs";
import {
argbFromHex,
Hct,
hexFromArgb,
} from "@material/material-color-utilities";
import {
AliasTokenMap,
ColorDesignToken,
ColorSet,
HexString,
RefTokenSystem,
TonalPalette,
TonalPaletteConfig,
TonalPaletteItem,
} from "./types.js";
import { config } from "./config.js";
const { baseColors, tones, aliases, common } = config;
/** Takes a color, tone and name
* If a tone is given adjust the lightning level accordingly
*
* @returns TonalPaletteItem (meta wrapper around HCT)
*/
const getTonalPaletteItem = (
value: HexString,
name: string,
tone?: number,
): TonalPaletteItem => {
const aRGB = argbFromHex(value);
const color = Hct.fromInt(aRGB);
if (tone !== undefined) {
color.tone = tone;
}
return {
shade: color.tone,
name: `${name || color.chroma}${Math.round(color.tone)}`,
baseName: name,
value: color,
};
};
/** create a flat list of the cross product from all colors and all tones.
*
* every color is mapped in the range from 0 to 100
* with the steps configure in `config.tones'
* additionally the key color is added unmodified
* lightning levels are rounded to the next natural number to form the 'name'
* Example:
*
* "blue" x [20.1, 30.3]
* ->
* [blue20, blue30]
*/
const mkTonalPalette =
(config: TonalPaletteConfig) =>
(name: string) =>
(keyTone: HexString): TonalPalette => {
const { tones } = config;
const aRGB = argbFromHex(keyTone);
const HctColor = Hct.fromInt(aRGB);
const roundedTone = Math.round(HctColor.tone * 100) / 100;
const localTones = [...tones, roundedTone];
return localTones.map((t) => getTonalPaletteItem(keyTone, name, t));
};
/**
* Converts a PaletteItem into a hex color. (Wrapped)
* Adding meta attributes which avoids any information loss.
*/
const toDesignTokenContent = (color: TonalPaletteItem): ColorDesignToken => {
const { value } = color;
return {
type: "color",
value: hexFromArgb(value.toInt()),
meta: {
color,
date: new Date(),
},
};
};
const color: ColorSet = Object.entries(baseColors)
.map(([name, baseColor]) => ({
name,
baseColor,
tones: mkTonalPalette({
tones: [...tones, ...baseColor.tones].sort((a, b) => a - b),
})(name)(baseColor.keyColor),
}))
.reduce((acc, curr) => {
let currTones = curr.tones.reduce(
(o, v) => ({
...o,
[v.name]: toDesignTokenContent(v),
}),
{},
);
return {
...acc,
...currTones,
};
}, {});
/** Generate a set of tokens from a given alias mapping
*
* @param alias A string e.g. Primary -> Blue (Primary is the alias)
* @param name A string; Basename of the referenced value (e.g. Blue)
* @param colors A set of colors
* @returns All aliases from the given color set
*/
function resolveAlias(
alias: string,
name: string,
colors: ColorSet,
): AliasTokenMap {
// All colors from the color map belonging to that single alias
// Example:
// Primary -> "blue"
// =>
// [ (blue0) , (blue10) , ..., (blue100) ]
const all = Object.values(colors)
.filter((n) => n.meta.color.name.includes(name))
.filter((n) => !n.meta.color.name.includes("."));
const tokens = all
.map((shade) => {
const shadeNumber = shade.meta.color.shade;
return {
name: `${alias}${Math.round(shadeNumber)}`,
value: { value: `{ref.palette.${shade.meta.color.name}}` },
// propagate the meta attribute of the actual value
meta: shade.meta,
};
})
// sort by tone
.sort((a, b) => a.meta.color.value.tone - b.meta.color.value.tone)
.reduce((acc, { name, value }) => ({ ...acc, [name]: value }), {});
return tokens;
}
const aliasMap = Object.entries(aliases).reduce(
(prev, [key, value]) => ({
...prev,
...resolveAlias(key, value, color),
}),
{},
);
const commonColors = Object.entries(common)
.map(([name, value]) =>
toDesignTokenContent(getTonalPaletteItem(value, name)),
)
.reduce(
(acc, val) => ({ ...acc, [val.meta.color.baseName]: val }),
{},
) as ColorSet;
const toPaletteToken = (color: ColorSet): RefTokenSystem => ({
ref: {
palette: color,
alias: aliasMap,
common: commonColors,
},
});
// Dump tokens to json file
fs.writeFile(
"colors.json",
JSON.stringify(toPaletteToken(color), null, 2),
(err) => {
if (err) {
console.error({ err });
} else {
console.log("tokens successfully exported");
}
},
);