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.
164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
import type { LucideIcon } from "lucide-react";
|
|
import { Copy, Ruler, Trash2 } from "lucide-react";
|
|
import { cn } from "@/shared/ui/cn";
|
|
import {
|
|
MapActionRow,
|
|
MapModeButton,
|
|
MapPanelSection
|
|
} from "./control-panel";
|
|
import {
|
|
MAP_COMPACT_RADIUS_CLASS_NAME,
|
|
MAP_READABLE_RADIUS_CLASS_NAME,
|
|
MAP_READABLE_SURFACE_CLASS_NAME,
|
|
MAP_READABLE_SURFACE_STRONG_CLASS_NAME
|
|
} from "./map-control-styles";
|
|
|
|
export type MapMeasureMode = {
|
|
id: string;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
export type MapMeasureUnit = {
|
|
id: string;
|
|
label: string;
|
|
};
|
|
|
|
export type MapMeasureResultMetric = {
|
|
label: string;
|
|
value: string;
|
|
};
|
|
|
|
export type MapMeasureAction = {
|
|
id: string;
|
|
label: string;
|
|
description: string;
|
|
icon?: LucideIcon;
|
|
strong?: boolean;
|
|
variant?: "default" | "primary";
|
|
tone?: "default" | "danger";
|
|
disabled?: boolean;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
export type MapMeasurePanelProps = {
|
|
modes: MapMeasureMode[];
|
|
activeModeId: string;
|
|
resultLabel: string;
|
|
resultValue: string;
|
|
resultUnit: string;
|
|
metrics?: MapMeasureResultMetric[];
|
|
units: MapMeasureUnit[];
|
|
activeUnitId: string;
|
|
actions: MapMeasureAction[];
|
|
onSelectMode?: (id: string) => void;
|
|
onSelectUnit?: (id: string) => void;
|
|
};
|
|
|
|
export function MapMeasurePanel({
|
|
modes,
|
|
activeModeId,
|
|
resultLabel,
|
|
resultValue,
|
|
resultUnit,
|
|
metrics = [],
|
|
units,
|
|
activeUnitId,
|
|
actions,
|
|
onSelectMode,
|
|
onSelectUnit
|
|
}: MapMeasurePanelProps) {
|
|
return (
|
|
<div className="space-y-3">
|
|
<MapPanelSection title="模式">
|
|
<div className={cn("surface-well grid grid-cols-3 gap-1.5 border p-1.5", MAP_READABLE_RADIUS_CLASS_NAME)}>
|
|
{modes.map((mode) => (
|
|
<MapModeButton
|
|
key={mode.id}
|
|
icon={mode.icon}
|
|
label={mode.label}
|
|
active={mode.id === activeModeId}
|
|
disabled={mode.disabled}
|
|
onClick={() => onSelectMode?.(mode.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</MapPanelSection>
|
|
|
|
<MapPanelSection title="结果">
|
|
<div className={cn("border border-blue-100 bg-blue-50/95 p-2", MAP_READABLE_RADIUS_CLASS_NAME)}>
|
|
<div className={cn("p-3", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_STRONG_CLASS_NAME)}>
|
|
<div className="min-w-0 border-b border-slate-100 pb-3">
|
|
<div className="text-xs font-semibold text-blue-700">{resultLabel}</div>
|
|
<div className="mt-1 flex min-w-0 items-baseline gap-1.5 text-slate-950">
|
|
<span className="truncate text-2xl font-semibold leading-none">{resultValue}</span>
|
|
<span className="shrink-0 text-xs font-semibold text-slate-500">{resultUnit}</span>
|
|
</div>
|
|
</div>
|
|
{metrics.length > 0 ? (
|
|
<div className="mt-2 grid grid-cols-[repeat(auto-fit,minmax(92px,1fr))] gap-1.5">
|
|
{metrics.map((metric) => (
|
|
<div
|
|
key={metric.label}
|
|
className={cn("min-w-0 px-2.5 py-1.5 text-left", MAP_COMPACT_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME)}
|
|
>
|
|
<div className="truncate text-xs text-slate-500">{metric.label}</div>
|
|
<div className="mt-0.5 truncate text-sm font-semibold text-slate-900">{metric.value}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
<div className="mt-3 grid grid-cols-[repeat(auto-fit,minmax(0,1fr))] gap-1.5">
|
|
{units.map((unit) => (
|
|
<button
|
|
key={unit.id}
|
|
type="button"
|
|
onClick={() => onSelectUnit?.(unit.id)}
|
|
className={cn(
|
|
"h-10 text-xs font-semibold",
|
|
MAP_COMPACT_RADIUS_CLASS_NAME,
|
|
unit.id === activeUnitId
|
|
? "bg-blue-600 text-white"
|
|
: "surface-control text-slate-600 hover:text-blue-700"
|
|
)}
|
|
>
|
|
{unit.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</MapPanelSection>
|
|
|
|
<MapPanelSection title="操作">
|
|
{actions.map((action) => (
|
|
<MapActionRow
|
|
key={action.id}
|
|
icon={action.icon ?? getDefaultActionIcon(action.id)}
|
|
label={action.label}
|
|
description={action.description}
|
|
strong={action.strong}
|
|
variant={action.variant}
|
|
tone={action.tone}
|
|
disabled={action.disabled}
|
|
onClick={action.onClick}
|
|
/>
|
|
))}
|
|
</MapPanelSection>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function getDefaultActionIcon(actionId: string) {
|
|
if (actionId.includes("copy")) {
|
|
return Copy;
|
|
}
|
|
|
|
if (actionId.includes("clear") || actionId.includes("delete")) {
|
|
return Trash2;
|
|
}
|
|
|
|
return Ruler;
|
|
}
|