node-colors.ts4.0 KBView on GitHub /**
* The closed rendering vocabulary for a node's background tint.
*
* Exactly the shape `BOARD_OPTION_ICONS` is, and for the same reason: the schema says
* `{ champion: 'green' }`, so Cedar knows what `green` looks like and never learns what
* `champion` means. Which field paints is `view.colorField`; what each of its values is worth
* is `field.optionColors`, set by whoever authored the schema — because "which of my values
* means good news" is a judgement about the values, and the graph is the only thing that knows.
*
* ── An unrecognised name renders as NO tint ──
*
* Never a fallback colour. The whole value of the tint is that it is read at a glance without
* checking the legend, so a wrong-but-present colour is worse than none: an uncoloured node
* reads as "not categorised", a mis-coloured one reads as a fact.
*
* ── Why these are SOLID, not a wash ──
*
* They were `bg-<hue>-500/12` — a 12% tint over whatever was behind the card. On a canvas that
* is what it sounds like: the node reads as transparent, the dot grid shows through it, and the
* category is a suggestion rather than a colour. A node is an OBJECT on the canvas and has to
* look like one, so each tint is an opaque surface: a pale shade in light mode, a deep one in
* dark, both chosen to leave `text-foreground` legible without a per-tint foreground colour.
*
* The border is the SAME hue only a step or two off the fill — barely a seam. It was several
* steps stronger, which drew a hard ring around every card and made a chart of eight people
* read as eight competing objects rather than as one diagram. The fill already carries the
* category; the border only has to stop the card dissolving into the canvas behind it.
*/
export interface NodeColor {
/**
* The card's opaque background. Kept SEPARATE from the border so a selected node can take the
* primary border without fighting this one — `border-primary` and `dark:border-green-800` are
* different variants, so tailwind-merge cannot resolve them against each other and the tint
* would win back in dark mode.
*/
surfaceClassName: string;
/** The same hue, stronger. Applied only when the node is not selected. */
borderClassName: string;
/** The legend's swatch — the same hue, solid, because a 12% chip is invisible at 12px. */
swatchClassName: string;
/** What a reader would call it. */
label: string;
}
export const GRAPH_NODE_COLORS: Record<string, NodeColor> = {
green: {
surfaceClassName: 'bg-green-100 dark:bg-green-950',
borderClassName: 'border-green-200 dark:border-green-900',
swatchClassName: 'bg-green-500',
label: 'Green',
},
blue: {
surfaceClassName: 'bg-blue-100 dark:bg-blue-950',
borderClassName: 'border-blue-200 dark:border-blue-900',
swatchClassName: 'bg-blue-500',
label: 'Blue',
},
violet: {
surfaceClassName: 'bg-violet-100 dark:bg-violet-950',
borderClassName: 'border-violet-200 dark:border-violet-900',
swatchClassName: 'bg-violet-500',
label: 'Violet',
},
cyan: {
surfaceClassName: 'bg-cyan-100 dark:bg-cyan-950',
borderClassName: 'border-cyan-200 dark:border-cyan-900',
swatchClassName: 'bg-cyan-500',
label: 'Cyan',
},
amber: {
surfaceClassName: 'bg-amber-100 dark:bg-amber-950',
borderClassName: 'border-amber-200 dark:border-amber-900',
swatchClassName: 'bg-amber-500',
label: 'Amber',
},
red: {
surfaceClassName: 'bg-red-100 dark:bg-red-950',
borderClassName: 'border-red-200 dark:border-red-900',
swatchClassName: 'bg-red-500',
label: 'Red',
},
slate: {
surfaceClassName: 'bg-slate-100 dark:bg-slate-900',
borderClassName: 'border-slate-200 dark:border-slate-800',
swatchClassName: 'bg-slate-500',
label: 'Slate',
},
};
export const GRAPH_NODE_COLOR_NAMES = Object.keys(GRAPH_NODE_COLORS);
/** The tint for a colour name, or `undefined` for one Cedar does not know. */
export function nodeColorFor(name: string | undefined): NodeColor | undefined {
return name ? GRAPH_NODE_COLORS[name] : undefined;
}