Files
next-tjwater-frontend/src/features/map/core/components/toolbar.tsx
T
jiang f4318a9b9c feat: refine workbench visuals and map controls
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.
2026-07-28 16:39:36 +08:00

124 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}