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.
This commit is contained in:
@@ -158,7 +158,7 @@ export function MapActionRow({
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left transition duration-150 active:translate-y-px active:scale-[0.99]",
|
||||
"flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
isPrimary
|
||||
? "border-blue-600 bg-blue-600 text-white shadow-lg shadow-blue-600/25 hover:bg-blue-700"
|
||||
@@ -172,7 +172,7 @@ export function MapActionRow({
|
||||
(isPrimary
|
||||
? "cursor-not-allowed opacity-55 hover:border-blue-600 hover:bg-blue-600 hover:text-white"
|
||||
: "cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-white/95 hover:text-slate-700"),
|
||||
disabled && "active:translate-y-0 active:scale-100"
|
||||
disabled && "shadow-none"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -53,7 +53,7 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
"flex min-h-10 items-center gap-2 border px-2.5 py-2 text-left text-xs font-semibold transition duration-150",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
item.active
|
||||
? "border-blue-600 bg-blue-600 text-white shadow-md shadow-blue-600/20"
|
||||
? "border-blue-600 bg-blue-600 text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "text-slate-700 hover:border-blue-100 hover:bg-white hover:text-blue-700",
|
||||
!item.active && MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
isDanger && "hover:border-red-100 hover:text-red-700",
|
||||
@@ -85,7 +85,7 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
role="toolbar"
|
||||
aria-label={label}
|
||||
className={cn(
|
||||
"pointer-events-auto inline-flex p-1",
|
||||
"pointer-events-auto inline-flex gap-1 p-1",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||||
className
|
||||
@@ -105,14 +105,13 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
disabled={item.disabled}
|
||||
onClick={item.onClick}
|
||||
className={cn(
|
||||
"grid h-8 w-8 shrink-0 place-items-center text-slate-600 transition duration-150",
|
||||
"grid h-10 w-10 shrink-0 place-items-center border border-transparent text-slate-600",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-900 focus-visible:outline-offset-2",
|
||||
item.active
|
||||
? "bg-blue-600 text-white shadow-xs"
|
||||
? "border-blue-600 bg-blue-600 text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "hover:bg-blue-50 hover:text-blue-700",
|
||||
isDanger && "hover:bg-red-50 hover:text-red-700",
|
||||
item.disabled && "cursor-not-allowed opacity-45 hover:bg-transparent hover:text-slate-600"
|
||||
item.disabled && "opacity-45 hover:bg-transparent hover:text-slate-600"
|
||||
)}
|
||||
>
|
||||
<Icon size={16} aria-hidden="true" />
|
||||
|
||||
@@ -61,7 +61,7 @@ function LayerVisibilitySection({ items, onToggleLayer }: LayerVisibilitySection
|
||||
title={item.description}
|
||||
onClick={() => onToggleLayer(item.id, !item.visible)}
|
||||
className={cn(
|
||||
"relative flex min-h-[76px] w-full flex-col items-center justify-center gap-1.5 border px-1.5 py-2 text-center transition duration-150 active:translate-y-px active:scale-[0.98]",
|
||||
"relative flex min-h-[76px] w-full flex-col items-center justify-center gap-1.5 border px-1.5 py-2 text-center",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
item.visible
|
||||
? "border-blue-200 bg-blue-50/90 text-blue-950 shadow-xs shadow-blue-950/5"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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,
|
||||
@@ -66,7 +67,7 @@ export function MapLegendList({ items, className = "", showStatusDot = false }:
|
||||
<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 ? <span className="h-2 w-2 rounded-full bg-emerald-500" aria-hidden="true" /> : null}
|
||||
{showStatusDot ? <StatusDot tone="success" size="md" /> : null}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { buttonInteractionVariants } from "@/shared/ui/button-interaction";
|
||||
|
||||
export const MAP_MAJOR_PANEL_RADIUS_CLASS_NAME = "rounded-2xl";
|
||||
|
||||
export const MAP_CONTROL_RADIUS_CLASS_NAME = "rounded-xl";
|
||||
@@ -24,4 +26,4 @@ export const MAP_READABLE_SURFACE_STRONG_CLASS_NAME =
|
||||
"surface-control border";
|
||||
|
||||
export const MAP_CONTROL_HOVER_CLASS_NAME =
|
||||
"hover:bg-blue-100/70 hover:text-blue-700 hover:ring-1 hover:ring-blue-200/80 active:bg-blue-100";
|
||||
buttonInteractionVariants({ tone: "neutral" });
|
||||
|
||||
@@ -117,7 +117,7 @@ export function MapMeasurePanel({
|
||||
type="button"
|
||||
onClick={() => onSelectUnit?.(unit.id)}
|
||||
className={cn(
|
||||
"h-7 text-xs font-semibold transition",
|
||||
"h-10 text-xs font-semibold",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
unit.id === activeUnitId
|
||||
? "bg-blue-600 text-white"
|
||||
|
||||
@@ -44,7 +44,7 @@ export function showMapNotice(options: MapNoticeOptions) {
|
||||
: undefined,
|
||||
classNames: {
|
||||
cancelButton: cn(
|
||||
"absolute right-2 top-2 z-10 grid h-7 w-7 place-items-center border border-transparent bg-transparent p-0 text-slate-400 transition hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600/25",
|
||||
"absolute right-2 top-2 z-10 grid h-8 w-8 place-items-center border border-transparent bg-transparent p-0 text-slate-400 after:absolute after:-inset-1 after:content-[''] hover:border-slate-200 hover:bg-slate-50 hover:text-slate-700",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
StatusIcon as SemanticStatusIcon,
|
||||
type StatusTone
|
||||
} from "@/shared/ui/status";
|
||||
import {
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
@@ -8,37 +13,13 @@ import {
|
||||
} from "./map-control-styles";
|
||||
import type { MapNoticeOptions, MapNoticeTone } from "./notice-types";
|
||||
|
||||
const toneClassNames: Record<MapNoticeTone, string> = {
|
||||
info: "glass-transient text-slate-900",
|
||||
success: "glass-transient text-slate-900",
|
||||
warning: "glass-transient text-slate-900",
|
||||
error: "glass-transient text-slate-900",
|
||||
loading: "glass-transient text-slate-900"
|
||||
};
|
||||
|
||||
const iconClassNames: Record<MapNoticeTone, string> = {
|
||||
info: "text-blue-600",
|
||||
success: "text-green-600",
|
||||
warning: "text-orange-500",
|
||||
error: "text-red-500",
|
||||
loading: "text-blue-600"
|
||||
};
|
||||
|
||||
const noticeAccentClassNames: Record<MapNoticeTone, string> = {
|
||||
info: "bg-blue-600",
|
||||
success: "bg-green-500",
|
||||
warning: "bg-orange-500",
|
||||
error: "bg-red-500",
|
||||
loading: "bg-blue-600"
|
||||
};
|
||||
|
||||
const noticeIconSurfaceClassNames: Record<MapNoticeTone, string> = {
|
||||
info: "border-blue-100 bg-blue-50",
|
||||
success: "border-green-100 bg-green-50",
|
||||
warning: "border-orange-100 bg-orange-50",
|
||||
error: "border-red-100 bg-red-50",
|
||||
loading: "border-blue-100 bg-blue-50"
|
||||
};
|
||||
const noticeTones = {
|
||||
info: "info",
|
||||
success: "success",
|
||||
warning: "warning",
|
||||
error: "danger",
|
||||
loading: "info"
|
||||
} satisfies Record<MapNoticeTone, StatusTone>;
|
||||
|
||||
export function MapNoticeContent({
|
||||
title,
|
||||
@@ -79,15 +60,10 @@ function MapNoticeFrame({ tone, children }: { tone: MapNoticeTone; children: Rea
|
||||
<div
|
||||
role={tone === "error" || tone === "warning" ? "alert" : "status"}
|
||||
className={cn(
|
||||
"relative flex w-full items-start gap-2.5 overflow-hidden border py-2.5 pl-3 pr-10 text-sm",
|
||||
"glass-transient relative flex w-full items-start gap-2.5 overflow-hidden border py-2.5 pl-3 pr-10 text-sm text-slate-900",
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
toneClassNames[tone]
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("absolute bottom-0 left-0 top-0 w-1", noticeAccentClassNames[tone])}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -101,20 +77,14 @@ function MapNoticeIcon({
|
||||
icon: ReturnType<typeof getNoticeIcon>;
|
||||
}) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"mt-0.5 grid h-7 w-7 shrink-0 place-items-center border",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
iconClassNames[tone],
|
||||
noticeIconSurfaceClassNames[tone]
|
||||
)}
|
||||
<SemanticStatusIcon
|
||||
tone={noticeTones[tone]}
|
||||
activity={tone === "loading" ? "loading" : "static"}
|
||||
size="md"
|
||||
className={cn("mt-0.5", MAP_ICON_CELL_RADIUS_CLASS_NAME)}
|
||||
>
|
||||
<Icon
|
||||
size={17}
|
||||
aria-hidden="true"
|
||||
className={tone === "loading" ? "animate-spin" : undefined}
|
||||
/>
|
||||
</span>
|
||||
<Icon size={17} />
|
||||
</SemanticStatusIcon>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,16 +112,15 @@ function MapNoticeBody({
|
||||
|
||||
function MapNoticeAction({ label, onClick }: { label: string; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"mt-2 inline-flex h-7 items-center justify-center border border-blue-100 bg-blue-50 px-2.5 text-xs font-semibold text-blue-700 transition hover:bg-blue-100",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME
|
||||
)}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={cn("mt-2 border-blue-100 bg-blue-50 text-blue-700", MAP_COMPACT_RADIUS_CLASS_NAME)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function MapToaster() {
|
||||
classNames: {
|
||||
toast: "group pointer-events-auto",
|
||||
closeButton:
|
||||
"absolute right-2 top-2 grid h-6 w-6 place-items-center rounded-lg border border-transparent text-slate-400 transition hover:bg-white/70 hover:text-slate-700"
|
||||
"absolute right-2 top-2 grid h-8 w-8 place-items-center rounded-lg border border-transparent text-slate-400 after:absolute after:-inset-1 after:content-[''] hover:bg-white/70 hover:text-slate-700"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -44,10 +44,10 @@ export function MapToolbar({
|
||||
<nav
|
||||
aria-label={label}
|
||||
className={cn(
|
||||
"pointer-events-auto p-[3px]",
|
||||
"pointer-events-auto p-1",
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||||
isVertical ? "flex w-[46px] flex-col" : "inline-flex",
|
||||
isVertical ? "flex w-12 flex-col" : "inline-flex",
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -67,19 +67,18 @@ export function MapToolbar({
|
||||
disabled={tool.disabled}
|
||||
onClick={tool.onClick}
|
||||
className={cn(
|
||||
"relative grid h-[38px] w-[38px] shrink-0 place-items-center text-slate-700 transition duration-150",
|
||||
"relative grid h-10 w-10 shrink-0 place-items-center border border-transparent text-slate-700",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
"focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-900 focus-visible:outline-offset-2",
|
||||
tool.active
|
||||
? "bg-blue-50 text-blue-800 ring-1 ring-blue-300/70 hover:bg-blue-100 hover:text-blue-900"
|
||||
? "border-blue-600 bg-blue-600 text-white shadow-[var(--action-shadow-primary)]"
|
||||
: MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
tool.disabled && "cursor-not-allowed text-slate-400 opacity-60 hover:bg-transparent hover:text-slate-400 hover:ring-0 active:bg-transparent"
|
||||
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="absolute -right-1 -top-1 grid min-w-4 place-items-center rounded-full bg-red-500 px-1 text-xs font-semibold leading-4 text-white ring-2 ring-white">
|
||||
<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}
|
||||
|
||||
@@ -37,13 +37,13 @@ export function MapZoom({ mapRef, mapReady, onHome, className = "" }: MapZoomPro
|
||||
role="group"
|
||||
aria-label="地图缩放"
|
||||
className={cn(
|
||||
"pointer-events-auto flex w-[46px] flex-col gap-2",
|
||||
"pointer-events-auto flex w-12 flex-col gap-2",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col p-[3px]",
|
||||
"flex flex-col p-1",
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME
|
||||
)}
|
||||
@@ -65,7 +65,7 @@ export function MapZoom({ mapRef, mapReady, onHome, className = "" }: MapZoomPro
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"p-[3px]",
|
||||
"p-1",
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME
|
||||
)}
|
||||
@@ -99,13 +99,12 @@ function ControlButton({ icon: Icon, label, disabled, onClick }: ControlButtonPr
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"grid h-[38px] w-[38px] place-items-center transition duration-150",
|
||||
"grid h-10 w-10 place-items-center border border-transparent",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
"focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-900 focus-visible:outline-offset-2",
|
||||
"bg-transparent text-slate-700",
|
||||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
disabled &&
|
||||
"cursor-not-allowed text-slate-400 opacity-55 hover:bg-transparent hover:text-slate-400 active:bg-transparent"
|
||||
"text-slate-400 opacity-45 hover:bg-transparent hover:text-slate-400"
|
||||
)}
|
||||
>
|
||||
<Icon size={18} strokeWidth={2.2} aria-hidden="true" />
|
||||
|
||||
Reference in New Issue
Block a user