Rewrite components/ui/* (Button, Card, Badge, PageHeader, EmptyState, Skeleton) against the chunk-1 tokens instead of hardcoded gray-9xx classes, and drop the remaining font-mono from label/heading text. Add the three colour-blindness-validated encoding components from UI_REDESIGN.md §2.3: - components/marks/SeverityMark.tsx — glyph (filled triangle / outline triangle / outline circle) + optional spine + optional label, from the four-level severity ladder. Colour is never the only channel. - components/marks/TypeGlyph.tsx — five stroked SVG glyphs (fire, police, ems, collision, other) in currentColor. Incident type is now shape, not hue, since five hues can't clear an all-pairs CVD gate. - components/marks/NodeMark.tsx — diamond at four weights (filled+ring / filled / hollow / hollow-dashed). Green is gone from node state entirely. lib/severity.tsx now renders through SeverityMark; SEVERITY_COLORS reads the validated sev-moderate/sev-major tokens with routine/minor neutral. IncidentBadges.tsx's TypeBadge is reimplemented on TypeGlyph instead of a coloured pill. Per UI_REDESIGN.md chunk 2.
94 lines
3.0 KiB
TypeScript
94 lines
3.0 KiB
TypeScript
/**
|
|
* The severity ladder's visual encoding — see UI_REDESIGN.md §2.3.
|
|
*
|
|
* Colour is never the only channel: severity is triple-encoded so it
|
|
* survives both grayscale and colour-blindness.
|
|
* major — filled triangle, colour, 5px spine
|
|
* moderate — outline triangle, colour, 5px spine (thinner fill)
|
|
* minor — outline circle, neutral ink, 3px spine
|
|
* routine — faint outline circle, neutral ink, 2px spine
|
|
*/
|
|
import type { Severity } from "@/lib/severity";
|
|
import { SEVERITY_COLORS, SEVERITY_LABEL } from "@/lib/severity";
|
|
|
|
const SPINE_WIDTH: Record<Severity, number> = { major: 5, moderate: 5, minor: 3, routine: 2 };
|
|
const GLYPH_SIZE: Record<"sm" | "md", number> = { sm: 12, md: 16 };
|
|
|
|
function Glyph({ severity, size }: { severity: Severity; size: number }) {
|
|
const color = SEVERITY_COLORS[severity];
|
|
if (severity === "major") {
|
|
// filled triangle
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 16 16" aria-hidden>
|
|
<polygon points="8,1.5 14.5,14.5 1.5,14.5" fill={color} />
|
|
</svg>
|
|
);
|
|
}
|
|
if (severity === "moderate") {
|
|
// outline triangle
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 16 16" aria-hidden>
|
|
<polygon points="8,1.5 14.5,14.5 1.5,14.5" fill="none" stroke={color} strokeWidth={1.75} strokeLinejoin="round" />
|
|
</svg>
|
|
);
|
|
}
|
|
if (severity === "minor") {
|
|
// outline circle, neutral
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 16 16" aria-hidden>
|
|
<circle cx="8" cy="8" r="6" fill="none" stroke={color} strokeWidth={1.75} />
|
|
</svg>
|
|
);
|
|
}
|
|
// routine — faint outline circle
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 16 16" aria-hidden style={{ opacity: 0.6 }}>
|
|
<circle cx="8" cy="8" r="6" fill="none" stroke={color} strokeWidth={1.25} />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export function SeverityMark({
|
|
severity,
|
|
showLabel = false,
|
|
spine = false,
|
|
size = "sm",
|
|
className,
|
|
}: {
|
|
severity: Severity;
|
|
showLabel?: boolean;
|
|
spine?: boolean;
|
|
size?: "sm" | "md";
|
|
className?: string;
|
|
}) {
|
|
const color = SEVERITY_COLORS[severity];
|
|
return (
|
|
<span className={`inline-flex items-center gap-1.5 ${className ?? ""}`}>
|
|
{spine && (
|
|
<span
|
|
aria-hidden
|
|
className="inline-block rounded-full self-stretch"
|
|
style={{ width: SPINE_WIDTH[severity], background: color, minHeight: "0.9em" }}
|
|
/>
|
|
)}
|
|
<Glyph severity={severity} size={GLYPH_SIZE[size]} />
|
|
{showLabel && (
|
|
<span className="text-xs font-medium" style={{ color }}>
|
|
{SEVERITY_LABEL[severity]}
|
|
</span>
|
|
)}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
/** Standalone spine bar — for list rows that render the mark and spine in separate flex slots. */
|
|
export function SeveritySpine({ severity, className }: { severity: Severity; className?: string }) {
|
|
return (
|
|
<span
|
|
aria-hidden
|
|
className={`inline-block rounded-full self-stretch ${className ?? ""}`}
|
|
style={{ width: SPINE_WIDTH[severity], background: SEVERITY_COLORS[severity], minHeight: "100%" }}
|
|
/>
|
|
);
|
|
}
|