183 lines
4.8 KiB
JavaScript
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");
|
|
}
|
|
},
|
|
);
|