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.
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
import type { LucideIcon } from "lucide-react";
|
||
import { cn } from "@/shared/ui/cn";
|
||
import {
|
||
Tooltip,
|
||
TooltipContent,
|
||
TooltipProvider,
|
||
TooltipTrigger
|
||
} from "@/shared/ui/tooltip";
|
||
import {
|
||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||
MAP_CONTROL_SURFACE_CLASS_NAME
|
||
} from "./map-control-styles";
|
||
|
||
export type MapToolbarItem = {
|
||
id: string;
|
||
label: string;
|
||
description?: string;
|
||
icon: LucideIcon;
|
||
active?: boolean;
|
||
disabled?: boolean;
|
||
badge?: string | number;
|
||
onClick?: () => void;
|
||
};
|
||
|
||
type MapToolbarProps = {
|
||
items: MapToolbarItem[];
|
||
label?: string;
|
||
orientation?: "vertical" | "horizontal";
|
||
className?: string;
|
||
};
|
||
|
||
export function MapToolbar({
|
||
items,
|
||
label = "地图工具",
|
||
orientation = "vertical",
|
||
className = ""
|
||
}: MapToolbarProps) {
|
||
const isVertical = orientation === "vertical";
|
||
|
||
return (
|
||
<TooltipProvider delayDuration={180}>
|
||
<nav
|
||
aria-label={label}
|
||
className={cn(
|
||
"pointer-events-auto p-1",
|
||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||
isVertical ? "flex w-12 flex-col" : "inline-flex",
|
||
className
|
||
)}
|
||
>
|
||
{items.map((tool, index) => {
|
||
const Icon = tool.icon;
|
||
const title = tool.description ? `${tool.label}:${tool.description}` : tool.label;
|
||
|
||
return (
|
||
<div key={tool.id} className={cn(isVertical ? "contents" : "flex items-center")}>
|
||
{index > 0 ? <ToolbarDivider vertical={isVertical} /> : null}
|
||
<Tooltip>
|
||
<TooltipTrigger asChild>
|
||
<button
|
||
type="button"
|
||
aria-label={title}
|
||
aria-pressed={tool.active}
|
||
disabled={tool.disabled}
|
||
onClick={tool.onClick}
|
||
className={cn(
|
||
"relative grid h-10 w-10 shrink-0 place-items-center border border-transparent text-slate-700",
|
||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||
tool.active
|
||
? "border-blue-600 bg-blue-600 text-white shadow-[var(--action-shadow-primary)]"
|
||
: MAP_CONTROL_HOVER_CLASS_NAME,
|
||
tool.disabled && "text-slate-400 opacity-45 hover:bg-transparent hover:text-slate-400 hover:ring-0"
|
||
)}
|
||
>
|
||
<Icon size={20} strokeWidth={2.15} aria-hidden="true" />
|
||
<span className="sr-only">{tool.label}</span>
|
||
{tool.badge ? (
|
||
<span className="status-tone-danger absolute -right-1 -top-1 grid min-w-4 place-items-center rounded-full bg-[var(--status-mark)] px-1 text-xs font-semibold leading-4 text-white ring-2 ring-white">
|
||
{tool.badge}
|
||
</span>
|
||
) : null}
|
||
</button>
|
||
</TooltipTrigger>
|
||
<TooltipContent
|
||
side={isVertical ? "left" : "top"}
|
||
sideOffset={8}
|
||
className="border border-slate-200 bg-white px-2.5 py-1.5 text-xs font-medium text-slate-800 shadow-lg"
|
||
>
|
||
{title}
|
||
</TooltipContent>
|
||
</Tooltip>
|
||
</div>
|
||
);
|
||
})}
|
||
</nav>
|
||
</TooltipProvider>
|
||
);
|
||
}
|
||
|
||
function ToolbarDivider({ vertical }: { vertical: boolean }) {
|
||
if (vertical) {
|
||
return (
|
||
<div
|
||
className="my-0.5 flex h-px w-full items-center justify-center"
|
||
aria-hidden="true"
|
||
>
|
||
<span className="block h-px w-3.5 bg-slate-300/70" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className="mx-0.5 flex h-full w-px items-center justify-center"
|
||
aria-hidden="true"
|
||
>
|
||
<span className="block h-3.5 w-px bg-slate-300/70" />
|
||
</div>
|
||
);
|
||
}
|