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.
132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import type { Map as MapLibreMap } from "maplibre-gl";
|
|
import { Home, Minus, Plus, type LucideIcon } from "lucide-react";
|
|
import { type RefObject, useCallback } from "react";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger
|
|
} from "@/shared/ui/tooltip";
|
|
import { cn } from "@/shared/ui/cn";
|
|
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";
|
|
|
|
type MapZoomProps = {
|
|
mapRef: RefObject<MapLibreMap | null>;
|
|
mapReady: boolean;
|
|
onHome: () => void;
|
|
className?: string;
|
|
};
|
|
|
|
export function MapZoom({ mapRef, mapReady, onHome, className = "" }: MapZoomProps) {
|
|
const handleZoomIn = useCallback(() => {
|
|
mapRef.current?.zoomIn({ duration: 260 });
|
|
}, [mapRef]);
|
|
|
|
const handleZoomOut = useCallback(() => {
|
|
mapRef.current?.zoomOut({ duration: 260 });
|
|
}, [mapRef]);
|
|
|
|
return (
|
|
<TooltipProvider delayDuration={180}>
|
|
<div
|
|
role="group"
|
|
aria-label="地图缩放"
|
|
className={cn(
|
|
"pointer-events-auto flex w-12 flex-col gap-2",
|
|
className
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"flex flex-col p-1",
|
|
MAP_CONTROL_RADIUS_CLASS_NAME,
|
|
MAP_CONTROL_SURFACE_CLASS_NAME
|
|
)}
|
|
>
|
|
<ControlButton
|
|
icon={Plus}
|
|
label="放大"
|
|
disabled={!mapReady}
|
|
onClick={handleZoomIn}
|
|
/>
|
|
<Divider />
|
|
<ControlButton
|
|
icon={Minus}
|
|
label="缩小"
|
|
disabled={!mapReady}
|
|
onClick={handleZoomOut}
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
className={cn(
|
|
"p-1",
|
|
MAP_CONTROL_RADIUS_CLASS_NAME,
|
|
MAP_CONTROL_SURFACE_CLASS_NAME
|
|
)}
|
|
>
|
|
<ControlButton
|
|
icon={Home}
|
|
label="缩放到全局"
|
|
disabled={!mapReady}
|
|
onClick={onHome}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</TooltipProvider>
|
|
);
|
|
}
|
|
|
|
type ControlButtonProps = {
|
|
icon: LucideIcon;
|
|
label: string;
|
|
disabled: boolean;
|
|
onClick: () => void;
|
|
};
|
|
|
|
function ControlButton({ icon: Icon, label, disabled, onClick }: ControlButtonProps) {
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
type="button"
|
|
aria-label={label}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
className={cn(
|
|
"grid h-10 w-10 place-items-center border border-transparent",
|
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
"bg-transparent text-slate-700",
|
|
MAP_CONTROL_HOVER_CLASS_NAME,
|
|
disabled &&
|
|
"text-slate-400 opacity-45 hover:bg-transparent hover:text-slate-400"
|
|
)}
|
|
>
|
|
<Icon size={18} strokeWidth={2.2} aria-hidden="true" />
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent
|
|
side="left"
|
|
sideOffset={8}
|
|
className="border border-slate-200 bg-white px-2.5 py-1.5 text-xs font-medium text-slate-800 shadow-lg"
|
|
>
|
|
{label}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
function Divider() {
|
|
return (
|
|
<div
|
|
className="my-0.5 h-px w-3.5 self-center bg-slate-300/70"
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|