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.
127 lines
2.9 KiB
TypeScript
127 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|