Unify the Agent history extension with the header acrylic surface, preserve the full conversation body, and consolidate shared control and status styling. Restore map flow and SCADA controller behavior, remove obsolete rendering paths, and extend regression coverage. Button press coverage now releases outside the target so state assertions cannot accidentally toggle the control.
101 lines
3.4 KiB
TypeScript
101 lines
3.4 KiB
TypeScript
import type { LucideIcon } from "lucide-react";
|
|
import { cn } from "@/shared/ui/cn";
|
|
import { StatusDot } from "@/shared/ui/status";
|
|
import {
|
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
|
MAP_READABLE_RADIUS_CLASS_NAME,
|
|
MAP_READABLE_SURFACE_CLASS_NAME,
|
|
MAP_TOOL_PANEL_SURFACE_CLASS_NAME
|
|
} from "./map-control-styles";
|
|
|
|
export type MapLegendItem = {
|
|
id: string;
|
|
label: string;
|
|
description?: string;
|
|
color: string;
|
|
icon?: LucideIcon;
|
|
imageSrc?: string;
|
|
shape?: "line" | "dashed-line" | "dash-dot-line" | "dot" | "ring" | "square";
|
|
};
|
|
|
|
type MapLegendProps = {
|
|
title?: string;
|
|
items: MapLegendItem[];
|
|
className?: string;
|
|
};
|
|
|
|
export function MapLegend({ title = "图例", items, className = "" }: MapLegendProps) {
|
|
return (
|
|
<section
|
|
aria-label={title}
|
|
className={cn(
|
|
"pointer-events-auto w-[236px] p-3",
|
|
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
|
MAP_TOOL_PANEL_SURFACE_CLASS_NAME,
|
|
className
|
|
)}
|
|
>
|
|
<h2 className="px-1 text-sm font-semibold leading-tight text-slate-950">{title}</h2>
|
|
<p className="mt-0.5 px-1 text-xs text-slate-500">业务图层语义</p>
|
|
<MapLegendList items={items} className="mt-3" />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
type MapLegendListProps = {
|
|
items: MapLegendItem[];
|
|
className?: string;
|
|
showStatusDot?: boolean;
|
|
};
|
|
|
|
export function MapLegendList({ items, className = "", showStatusDot = false }: MapLegendListProps) {
|
|
return (
|
|
<ul className={cn("space-y-1.5", className)}>
|
|
{items.map((item) => {
|
|
const Icon = item.icon;
|
|
|
|
return (
|
|
<li
|
|
key={item.id}
|
|
className={cn("flex min-h-10 items-center gap-2.5 px-2.5 py-2 text-sm text-slate-700", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME)}
|
|
>
|
|
<span className={cn("grid h-7 w-7 shrink-0 place-items-center bg-slate-50", MAP_ICON_CELL_RADIUS_CLASS_NAME)} aria-hidden="true">
|
|
{item.imageSrc ? <img src={item.imageSrc} alt="" width={24} height={24} /> : Icon ? <Icon size={15} style={{ color: item.color }} /> : <MapLegendSwatch color={item.color} shape={item.shape} />}
|
|
</span>
|
|
<span className="min-w-0 flex-1">
|
|
<span className="block truncate font-semibold">{item.label}</span>
|
|
{item.description ? <span className="block truncate text-xs text-slate-500">{item.description}</span> : null}
|
|
</span>
|
|
{showStatusDot ? <StatusDot tone="success" size="md" /> : null}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
export function MapLegendSwatch({ color, shape = "dot" }: Pick<MapLegendItem, "color" | "shape">) {
|
|
if (shape === "line") {
|
|
return <span className="h-1 w-6 rounded-full" style={{ backgroundColor: color }} />;
|
|
}
|
|
|
|
if (shape === "dashed-line" || shape === "dash-dot-line") {
|
|
return (
|
|
<svg width="24" height="8" viewBox="0 0 24 8" aria-hidden="true">
|
|
<line x1="1" y1="4" x2="23" y2="4" stroke={color} strokeWidth="3" strokeDasharray={shape === "dashed-line" ? "5 3" : "8 3 2 3"} />
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
if (shape === "square") {
|
|
return <span className="h-4 w-4 rounded-sm" style={{ backgroundColor: color }} />;
|
|
}
|
|
|
|
if (shape === "ring") {
|
|
return <span className="h-4 w-4 rounded-full border-[3px] bg-white" style={{ borderColor: color }} />;
|
|
}
|
|
|
|
return <span className="h-4 w-4 rounded-full ring-2 ring-white" style={{ backgroundColor: color }} />;
|
|
}
|