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:
2026-07-28 16:39:36 +08:00
parent ade88b4fbf
commit f4318a9b9c
96 changed files with 5480 additions and 1875 deletions
+126
View File
@@ -0,0 +1,126 @@
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/shared/ui/cn";
export type StatusTone = "neutral" | "info" | "success" | "warning" | "danger";
export type StatusActivity = "static" | "live" | "loading";
const toneClassNames: Record<StatusTone, string> = {
neutral: "status-tone-neutral",
info: "status-tone-info",
success: "status-tone-success",
warning: "status-tone-warning",
danger: "status-tone-danger"
};
type StatusBadgeProps = ComponentProps<"span"> & {
tone: StatusTone;
activity?: StatusActivity;
icon?: ReactNode;
size?: "sm" | "md";
};
export function StatusBadge({
tone,
activity = "static",
icon,
size = "sm",
className,
children,
...props
}: StatusBadgeProps) {
return (
<span
className={cn(
"status-badge inline-flex shrink-0 items-center rounded-full border font-semibold",
size === "sm" ? "min-h-5 gap-1 px-2 py-0.5 text-xs leading-4" : "min-h-6 gap-1.5 px-2.5 py-0.5 text-xs leading-5",
toneClassNames[tone],
className
)}
data-status-tone={tone}
{...props}
>
{icon ? (
<span
className="status-glyph grid shrink-0 place-items-center"
data-activity={activity}
aria-hidden="true"
>
{icon}
</span>
) : null}
{children}
</span>
);
}
type StatusDotProps = Omit<ComponentProps<"span">, "children"> & {
tone: StatusTone;
activity?: StatusActivity;
label?: string;
size?: "sm" | "md";
};
export function StatusDot({
tone,
activity = "static",
label,
size = "sm",
className,
...props
}: StatusDotProps) {
return (
<span
className={cn(
"status-dot inline-block shrink-0 rounded-full",
size === "sm" ? "h-1.5 w-1.5" : "h-2 w-2",
toneClassNames[tone],
className
)}
data-activity={activity}
data-status-tone={tone}
role={label ? "status" : undefined}
aria-label={label}
aria-hidden={label ? undefined : true}
{...props}
/>
);
}
type StatusIconProps = ComponentProps<"span"> & {
tone: StatusTone;
activity?: StatusActivity;
shape?: "rounded" | "circle";
size?: "xs" | "sm" | "md";
};
export function StatusIcon({
tone,
activity = "static",
shape = "rounded",
size = "sm",
className,
children,
...props
}: StatusIconProps) {
return (
<span
className={cn(
"status-icon inline-grid shrink-0 place-items-center border",
shape === "circle" ? "rounded-full" : "rounded-lg",
size === "xs" ? "h-3.5 w-3.5" : size === "sm" ? "h-6 w-6" : "h-7 w-7",
toneClassNames[tone],
className
)}
data-status-tone={tone}
{...props}
>
<span
className="status-glyph grid place-items-center"
data-activity={activity}
aria-hidden="true"
>
{children}
</span>
</span>
);
}