coporate color system: init
This commit is contained in:
78
pkgs/theme/src/config.ts
Normal file
78
pkgs/theme/src/config.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { AliasMap, BaseColors, HexString } from "./types.js";
|
||||
|
||||
export type PaletteConfig = {
|
||||
baseColors: BaseColors;
|
||||
tones: number[];
|
||||
aliases: AliasMap<"primary" | "secondary" | "error">;
|
||||
common: {
|
||||
// Black and white is always constant
|
||||
// We declare this on the type level
|
||||
white: "#ffffff";
|
||||
black: "#000000";
|
||||
// Some other color constants/reservation
|
||||
[id: string]: HexString;
|
||||
};
|
||||
};
|
||||
|
||||
export const config: PaletteConfig = {
|
||||
/** All color shades that are available
|
||||
* This colors are used as "key colors" to generate a tonal palette from 0 to 100
|
||||
* Steps are defined in 'tones'
|
||||
*/
|
||||
baseColors: {
|
||||
neutral: {
|
||||
keyColor: "#807788",
|
||||
tones: [98],
|
||||
},
|
||||
red: {
|
||||
keyColor: "#e82439",
|
||||
tones: [95],
|
||||
},
|
||||
green: {
|
||||
keyColor: "#7AC51B",
|
||||
tones: [98],
|
||||
},
|
||||
yellow: {
|
||||
keyColor: "#E0E01F",
|
||||
tones: [98],
|
||||
},
|
||||
purple: {
|
||||
keyColor: "#661bc5",
|
||||
tones: [],
|
||||
},
|
||||
blue: {
|
||||
keyColor: "#1B7AC5",
|
||||
tones: [95],
|
||||
},
|
||||
},
|
||||
|
||||
/** Common tones to generate out of all the baseColors
|
||||
* number equals to the amount of light present in the color (HCT Color Space)
|
||||
*/
|
||||
tones: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
|
||||
|
||||
/** create aliases from the color palette
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* primary: "blue"
|
||||
* ->
|
||||
* ...
|
||||
* primary40 -> blue40
|
||||
* primary50 -> blue50
|
||||
* ...
|
||||
*/
|
||||
aliases: {
|
||||
primary: "purple",
|
||||
secondary: "green",
|
||||
error: "red",
|
||||
},
|
||||
/** some color names are reserved
|
||||
* typically those colors do not change when switching theme
|
||||
* or are other types of constant in the UI
|
||||
*/
|
||||
common: {
|
||||
white: "#ffffff",
|
||||
black: "#000000",
|
||||
},
|
||||
};
|
||||
46
pkgs/theme/src/generate.ts
Normal file
46
pkgs/theme/src/generate.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { writeFile } from "fs";
|
||||
import palette from "./out.json" assert { type: "json" };
|
||||
import { config } from "./config.js";
|
||||
|
||||
type PaletteFile = typeof palette;
|
||||
|
||||
const html = (palette: PaletteFile): string => {
|
||||
const colors = Object.keys(config.baseColors).map((baseName) => {
|
||||
const colors = Object.entries(palette.ref.palette)
|
||||
.filter(([name, _]) => name.includes(baseName))
|
||||
.sort((a, b) => {
|
||||
return a[1].meta.color.shade - b[1].meta.color.shade;
|
||||
})
|
||||
.map(([key, color]) => {
|
||||
console.log({ key, color });
|
||||
return `<div style="background-color:${color.value}; color:${
|
||||
color.meta.color.shade < 48 ? "#fff" : "#000"
|
||||
}; height: 10rem; border:solid 1px grey; display:grid; place-items:end;">${key}</div>`;
|
||||
});
|
||||
return `<div style="display: grid; grid-template-columns: repeat(${13}, minmax(0, 1fr)); gap: 1rem; margin-bottom: 1rem">${colors.join(
|
||||
"\n",
|
||||
)}</div>`;
|
||||
});
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title>Page Title</title>
|
||||
<style>
|
||||
</style>
|
||||
<body>
|
||||
|
||||
${colors.join("\n")}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
writeFile("index.html", html(palette), (err) => {
|
||||
if (err) {
|
||||
console.error({ err });
|
||||
} else {
|
||||
console.log("Exported colors to html");
|
||||
}
|
||||
});
|
||||
182
pkgs/theme/src/main.ts
Normal file
182
pkgs/theme/src/main.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
#!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");
|
||||
}
|
||||
},
|
||||
);
|
||||
90
pkgs/theme/src/types.ts
Normal file
90
pkgs/theme/src/types.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Hct } from "@material/material-color-utilities";
|
||||
|
||||
export type BaseColors = {
|
||||
neutral: BaseColor;
|
||||
red: BaseColor;
|
||||
green: BaseColor;
|
||||
yellow: BaseColor;
|
||||
purple: BaseColor;
|
||||
blue: BaseColor;
|
||||
};
|
||||
|
||||
export type BaseColor = {
|
||||
keyColor: HexString;
|
||||
tones: number[];
|
||||
follows?: string;
|
||||
};
|
||||
|
||||
export type ColorSet = { [key: string]: ColorDesignToken };
|
||||
|
||||
/** The resolved alias tokens
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* primary: "blue"
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
*/
|
||||
export type AliasMap<T extends string> = {
|
||||
[alias in T]: keyof BaseColors;
|
||||
};
|
||||
|
||||
/** The resolved alias tokens
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* primary0: "blue40"
|
||||
* primary10: "blue40"
|
||||
* ...
|
||||
* primary100: "blue100"
|
||||
* }
|
||||
*
|
||||
* Unfortunately My Typescript skills lack the ability to express this type any narrower :/
|
||||
*/
|
||||
export type AliasTokenMap = {
|
||||
[alias: string]: { value: string };
|
||||
};
|
||||
|
||||
export type TonalPaletteConfig = {
|
||||
tones: number[];
|
||||
};
|
||||
|
||||
export type HexString = string;
|
||||
|
||||
export type TonalPaletteItem = {
|
||||
/**
|
||||
* @example
|
||||
* 20
|
||||
*/
|
||||
shade: number;
|
||||
/**
|
||||
* @example
|
||||
* "blue20"
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @example
|
||||
* "blue"
|
||||
*/
|
||||
baseName: string;
|
||||
value: Hct;
|
||||
};
|
||||
export type TonalPalette = TonalPaletteItem[];
|
||||
|
||||
export type ColorDesignToken = {
|
||||
type: "color";
|
||||
value: HexString;
|
||||
meta: {
|
||||
color: TonalPaletteItem;
|
||||
date: Date;
|
||||
};
|
||||
};
|
||||
|
||||
export type RefTokenSystem = {
|
||||
ref: {
|
||||
palette: ColorSet;
|
||||
common: ColorSet;
|
||||
alias: AliasTokenMap;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user