89 lines
4.4 KiB
TypeScript
89 lines
4.4 KiB
TypeScript
import type { Tone } from '../lib/format';
|
||
|
||
/* Icons live here and nowhere else. Each is the `d` of one stroked path on a 24×24 grid,
|
||
so a page never carries SVG markup of its own and two screens showing the same idea
|
||
cannot draw it two ways. An unknown name draws nothing rather than a broken box — a
|
||
mark is decoration, and a typo in one must never be what an operator notices about a
|
||
page. */
|
||
|
||
export const icons = {
|
||
overview: 'M4 13h6V4H4zm0 7h6v-4H4zm10 0h6v-9h-6zm0-16v4h6V4z',
|
||
library: 'M3 5h18v14H3zM7 5v14M17 5v14M3 9.5h4M3 14.5h4M17 9.5h4M17 14.5h4',
|
||
people:
|
||
'M15 19v-1.2a3.3 3.3 0 0 0-3.3-3.3H6.8A3.3 3.3 0 0 0 3.5 17.8V19M9.2 11a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4ZM17 10.6a3 3 0 0 0-1.4-5.7M20.5 19v-1.2a3.3 3.3 0 0 0-2.4-3.2',
|
||
person: 'M18 20v-1.5a4 4 0 0 0-4-4h-4a4 4 0 0 0-4 4V20M12 10.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z',
|
||
tv: 'M4 5h16v10H4zM9 19h6M12 15v4M8 2.5 12 5l4-2.5',
|
||
sliders: 'M4 7h10M18 7h2M4 17h2m4 0h10M14 4v6M6 14v6',
|
||
clock: 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM12 7.2V12l3 1.8',
|
||
pulse: 'M3 12h3.5L9 19l5-14 2.5 7H21',
|
||
chart: 'M4 19V9m5 10V5m5 14v-7m5 7V3',
|
||
chip: 'M8 8h8v8H8zM4.5 4.5h15v15h-15zM9 2v2.5M15 2v2.5M9 19.5V22M15 19.5V22M2 9h2.5M2 15h2.5M19.5 9H22M19.5 15H22',
|
||
database:
|
||
'M12 8.2c4.4 0 8-1.2 8-2.6S16.4 3 12 3 4 4.2 4 5.6s3.6 2.6 8 2.6ZM4 5.6v12.8C4 19.8 7.6 21 12 21s8-1.2 8-2.6V5.6M4 12c0 1.4 3.6 2.6 8 2.6s8-1.2 8-2.6',
|
||
download: 'M12 3.5v10m0 0 4-4m-4 4-4-4M4.5 18h15',
|
||
upload: 'M12 16V4m0 0L8 8m4-4 4 4M5 13v6h14v-6',
|
||
sync: 'M3.5 12a8.5 8.5 0 0 1 14.6-6M20.5 12a8.5 8.5 0 0 1-14.6 6M18 2.5V6h-3.5M6 21.5V18h3.5',
|
||
search: 'M10.5 17a6.5 6.5 0 1 1 0-13 6.5 6.5 0 0 1 0 13Zm4.6-1.9L20 20',
|
||
star: 'm12 3.2 2.6 5.4 5.9.8-4.3 4.1 1 5.9-5.2-2.8-5.2 2.8 1-5.9L3.5 9.4l5.9-.8L12 3.2Z',
|
||
sparkle:
|
||
'm10 3 1.5 4.3L16 8.8l-4.5 1.5L10 14.6 8.5 10.3 4 8.8l4.5-1.5L10 3ZM17.5 14l.9 2.4 2.6.9-2.6.9-.9 2.4-.9-2.4-2.6-.9 2.6-.9.9-2.4Z',
|
||
bell: 'M6.2 9.5a5.8 5.8 0 1 1 11.6 0c0 4.6 2.2 5.9 2.2 5.9H4s2.2-1.3 2.2-5.9M10 19.5a2 2 0 0 0 4 0',
|
||
shield: 'm12 3 7.5 3v5.4c0 5-3.2 8.2-7.5 9.6-4.3-1.4-7.5-4.6-7.5-9.6V6L12 3Zm-2.6 8.7 1.9 1.9 3.6-3.6',
|
||
wrench: 'm14.5 6.5 3-3 3 3-3 3M9 15l-5.5 5.5M13 4a5 5 0 0 0 6.5 6.5L10 20l-6-6 9.5-9.5Z',
|
||
play: 'M8 5.2v13.6L19 12 8 5.2ZM4 5v14',
|
||
list: 'M4 7h16M4 12h16M4 17h10',
|
||
inbox: 'M4 7h16v13H4zM8 4h8v3M8 12h8M8 16h5',
|
||
history: 'M3.5 12a8.5 8.5 0 1 0 2.8-6.3M3.5 4v4h4M12 7.5V12l3 1.8',
|
||
check: 'm5 12.5 4.5 4.5L19 7.5',
|
||
alert: 'M12 8.5v5m0 3.2h.01M10.3 4.4 2.7 17.5a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 4.4a2 2 0 0 0-3.4 0Z',
|
||
power: 'M12 3v9M7.5 6.2a7.5 7.5 0 1 0 9 0',
|
||
key: 'M14.5 3a6.5 6.5 0 1 0 3.4 12L19 14h2v-2h2V9.5l-2.5-2.5A6.5 6.5 0 0 0 14.5 3Zm-2.6 4.6a1.6 1.6 0 1 1-2.3 2.3 1.6 1.6 0 0 1 2.3-2.3Z',
|
||
captions: 'M4 5.5h16v13H4zM7 15h5m3 0h2M7 11h3m2 0h5',
|
||
journey: 'M4 6h5v5h6v7h5M7 3 4 6l3 3m10 6 3 3-3 3',
|
||
plug: 'M9 3v6M15 3v6M6.5 9h11v3.5a5.5 5.5 0 0 1-11 0zM12 18v3',
|
||
calendar: 'M4 6h16v15H4zM8 3v5M16 3v5M4 11h16',
|
||
trash: 'M4 7h16M9 7V4.5h6V7M6.5 7l1 13h9l1-13M10 11v5M14 11v5',
|
||
plus: 'M12 5v14M5 12h14',
|
||
close: 'M6 6l12 12M18 6 6 18',
|
||
caret: 'm6 9 6 6 6-6',
|
||
external: 'M14 4h6v6M20 4l-9 9M18 14v5.5H4.5V6H10',
|
||
refresh: 'M3.5 12a8.5 8.5 0 0 1 14.6-6M20.5 12a8.5 8.5 0 0 1-14.6 6M18 2.5V6h-3.5M6 21.5V18h3.5',
|
||
filter: 'M3.5 5.5h17l-6.5 7.5V20l-4-2v-5L3.5 5.5Z',
|
||
menu: 'M4 7h16M4 12h16M4 17h16',
|
||
globe: 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM3.5 9h17M3.5 15h17M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18Z',
|
||
logout: 'M15 17l5-5-5-5M20 12H9M12 4H5v16h7',
|
||
} as const;
|
||
|
||
export type IconName = keyof typeof icons;
|
||
|
||
export function Icon({ name, className }: { name: IconName; className?: string }) {
|
||
const path = icons[name];
|
||
if (!path) return null;
|
||
return (
|
||
<svg
|
||
className={className ?? 'ico'}
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
preserveAspectRatio="xMidYMid meet"
|
||
aria-hidden="true"
|
||
>
|
||
<path d={path} />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
/** Glyph is an icon in a tinted plate — the tile and card-heading mark.
|
||
*
|
||
* The tone is what says which area a thing belongs to, so it is passed rather than
|
||
* derived: the same idea wears the same colour on every page it appears on, which is
|
||
* most of what makes two dozen screens read as one console. */
|
||
export function Glyph({ name, tone }: { name: IconName; tone?: Tone }) {
|
||
if (!icons[name]) return null;
|
||
return (
|
||
<span className="glyph" data-tone={tone}>
|
||
<Icon name={name} />
|
||
</span>
|
||
);
|
||
}
|