- Add Plus Jakarta Sans as display font for headings - Add subtle noise texture overlay + indigo radial gradient for depth - New keyframe animations: glow-pulse, fade-in-up, scale-in, slide-in-right - Card: interactive hover-lift + selected ring variants - Button: scale micro-interactions, destructive glow, transition-all - Header: logo upgrade with wordmark, animated nav indicator bar, glass search button, gradient shadow depth - StatusDot: glow halos per status variant (active/success/error/warning/urgent) - HealthDot: glow effects for connected/disconnected/reconnecting states - Card hover-lift and status glow CSS utilities
114 lines
2.7 KiB
TypeScript
114 lines
2.7 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
|
|
export type StatusVariant = "active" | "success" | "warning" | "error" | "neutral" | "urgent";
|
|
|
|
const dotColors: Record<StatusVariant, string> = {
|
|
active: "bg-status-active-dot",
|
|
success: "bg-status-success-dot",
|
|
warning: "bg-status-warning-dot",
|
|
error: "bg-status-error-dot",
|
|
neutral: "bg-status-neutral-dot",
|
|
urgent: "bg-status-urgent-dot",
|
|
};
|
|
|
|
const glowMap: Record<StatusVariant, string | undefined> = {
|
|
active: "0 0 6px 1px hsl(var(--status-active-dot) / 0.5)",
|
|
success: "0 0 6px 1px hsl(var(--status-success-dot) / 0.4)",
|
|
warning: "0 0 6px 1px hsl(var(--status-warning-dot) / 0.4)",
|
|
error: "0 0 6px 1px hsl(var(--status-error-dot) / 0.5)",
|
|
neutral: undefined,
|
|
urgent: "0 0 6px 1px hsl(var(--status-urgent-dot) / 0.4)",
|
|
};
|
|
|
|
/** Maps raw entity status strings to semantic StatusVariant tokens. */
|
|
export function mapEntityStatus(rawStatus: string): StatusVariant {
|
|
switch (rawStatus) {
|
|
// Active / in-progress
|
|
case "running":
|
|
case "in_progress":
|
|
case "active":
|
|
case "building":
|
|
case "responded":
|
|
return "active";
|
|
|
|
// Success / completed
|
|
case "completed":
|
|
case "read":
|
|
case "low":
|
|
return "success";
|
|
|
|
// Warning / needs attention
|
|
case "waiting_for_input":
|
|
case "pending_approval":
|
|
case "pending_review":
|
|
case "approved":
|
|
case "exhausted":
|
|
case "medium":
|
|
return "warning";
|
|
|
|
// Error / failed
|
|
case "crashed":
|
|
case "blocked":
|
|
case "failed":
|
|
case "high":
|
|
return "error";
|
|
|
|
// Urgent
|
|
// (reserved for future use, no current raw statuses map here)
|
|
|
|
// Neutral / idle
|
|
case "pending":
|
|
case "idle":
|
|
case "stopped":
|
|
case "exited":
|
|
case "archived":
|
|
default:
|
|
return "neutral";
|
|
}
|
|
}
|
|
|
|
interface StatusDotProps {
|
|
status: string;
|
|
/** Override the auto-mapped variant with an explicit one. */
|
|
variant?: StatusVariant;
|
|
size?: "sm" | "md" | "lg";
|
|
pulse?: boolean;
|
|
label?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function StatusDot({
|
|
status,
|
|
variant: variantOverride,
|
|
size = "md",
|
|
pulse = false,
|
|
label,
|
|
className,
|
|
}: StatusDotProps) {
|
|
const sizeClasses = {
|
|
sm: "h-2 w-2",
|
|
md: "h-3 w-3",
|
|
lg: "h-4 w-4",
|
|
};
|
|
|
|
const variant = variantOverride ?? mapEntityStatus(status);
|
|
const color = dotColors[variant];
|
|
const glow = glowMap[variant];
|
|
const displayLabel = label ?? status.replace(/_/g, " ").toLowerCase();
|
|
|
|
return (
|
|
<span
|
|
role="status"
|
|
aria-label={displayLabel}
|
|
className={cn(
|
|
"inline-block shrink-0 rounded-full",
|
|
sizeClasses[size],
|
|
color,
|
|
pulse && "animate-status-pulse transition-transform",
|
|
className,
|
|
)}
|
|
style={glow ? { boxShadow: glow } : undefined}
|
|
/>
|
|
);
|
|
}
|