coporate color system: init

This commit is contained in:
Johannes Kirschbauer
2023-10-03 14:58:31 +02:00
parent 56325fc9aa
commit 6150baedb3
21 changed files with 1787 additions and 0 deletions

78
pkgs/theme/src/config.ts Normal file
View 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",
},
};

View 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
View 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
View 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;
};
};