mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 10:23:41 +00:00
feature: Replace connection indicator with shadcn StatusIndicator
Replace the custom connection indicator dots with the shadcn StatusIndicator component for better visual consistency and state indication. Changes: - Install status-indicator component from 8starlabs shadcn registry - Add getStatusIndicatorState() helper to map MachineStateCategory to StatusIndicator states - Replace connection indicator divs with StatusIndicator component in AppHeader - Connection indicator now shows state-dependent colors: - Green (active): Connected and ready, sewing, or complete - Yellow (fixing): Connected and waiting for user action - Red (down): Connected but interrupted or in error state - Gray (idle): Disconnected - Remove unused color prop from StatusIndicator component The StatusIndicator provides animated visual feedback for different machine states, making it easier for users to understand the current system status at a glance. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4992c33bf1
commit
a077dea68e
3 changed files with 122 additions and 9 deletions
|
|
@ -3,7 +3,10 @@ import { useMachineStore } from "../stores/useMachineStore";
|
|||
import { useUIStore } from "../stores/useUIStore";
|
||||
import { WorkflowStepper } from "./WorkflowStepper";
|
||||
import { ErrorPopoverContent } from "./ErrorPopover";
|
||||
import { getStateVisualInfo } from "../utils/machineStateHelpers";
|
||||
import {
|
||||
getStateVisualInfo,
|
||||
getStatusIndicatorState,
|
||||
} from "../utils/machineStateHelpers";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
BoltIcon,
|
||||
|
|
@ -14,6 +17,7 @@ import {
|
|||
} from "@heroicons/react/24/solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import StatusIndicator from "@/components/ui/status-indicator";
|
||||
import { Popover, PopoverTrigger } from "@/components/ui/popover";
|
||||
import {
|
||||
Tooltip,
|
||||
|
|
@ -66,20 +70,18 @@ export function AppHeader() {
|
|||
};
|
||||
const StatusIcon = stateIcons[stateVisual.iconName];
|
||||
|
||||
// Get connection indicator state (idle when disconnected, state-dependent when connected)
|
||||
const connectionIndicatorState = isConnected
|
||||
? getStatusIndicatorState(machineStatus)
|
||||
: "idle";
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<header className="bg-gradient-to-r from-primary-600 via-primary-700 to-primary-800 dark:from-primary-700 dark:via-primary-800 dark:to-primary-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-primary-900/20 dark:border-primary-800/30 flex-shrink-0">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
|
||||
{/* Machine Connection Status - Responsive width column */}
|
||||
<div className="flex items-center gap-3 w-full lg:w-[280px]">
|
||||
<div
|
||||
className="w-2.5 h-2.5 bg-success-400 rounded-full animate-pulse shadow-lg shadow-success-400/50"
|
||||
style={{ visibility: isConnected ? "visible" : "hidden" }}
|
||||
></div>
|
||||
<div
|
||||
className="w-2.5 h-2.5 bg-gray-400 rounded-full -ml-2.5"
|
||||
style={{ visibility: !isConnected ? "visible" : "hidden" }}
|
||||
></div>
|
||||
<StatusIndicator state={connectionIndicatorState} size="sm" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-lg lg:text-xl font-bold text-white leading-tight">
|
||||
|
|
|
|||
84
src/components/ui/status-indicator.tsx
Normal file
84
src/components/ui/status-indicator.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface StatusIndicatorProps {
|
||||
state: "active" | "down" | "fixing" | "idle";
|
||||
label?: string;
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
const getStateColors = (state: StatusIndicatorProps["state"]) => {
|
||||
switch (state) {
|
||||
case "active":
|
||||
return { dot: "bg-green-500", ping: "bg-green-300" };
|
||||
case "down":
|
||||
return { dot: "bg-red-500", ping: "bg-red-300" };
|
||||
case "fixing":
|
||||
return { dot: "bg-yellow-500", ping: "bg-yellow-300" };
|
||||
case "idle":
|
||||
default:
|
||||
return { dot: "bg-slate-700", ping: "bg-slate-400" };
|
||||
}
|
||||
};
|
||||
|
||||
const getSizeClasses = (size: StatusIndicatorProps["size"]) => {
|
||||
switch (size) {
|
||||
case "sm":
|
||||
return { dot: "h-2 w-2", ping: "h-2 w-2" };
|
||||
case "lg":
|
||||
return { dot: "h-4 w-4", ping: "h-4 w-4" };
|
||||
case "md":
|
||||
default:
|
||||
return { dot: "h-3 w-3", ping: "h-3 w-3" };
|
||||
}
|
||||
};
|
||||
|
||||
const StatusIndicator: React.FC<StatusIndicatorProps> = ({
|
||||
state = "idle",
|
||||
label,
|
||||
className,
|
||||
size = "md",
|
||||
labelClassName,
|
||||
}) => {
|
||||
const shouldAnimate =
|
||||
state === "active" || state === "fixing" || state === "down";
|
||||
const colors = getStateColors(state);
|
||||
const sizeClasses = getSizeClasses(size);
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<div className="relative flex items-center">
|
||||
{shouldAnimate && (
|
||||
<span
|
||||
className={cn(
|
||||
"absolute inline-flex rounded-full opacity-75 animate-ping",
|
||||
sizeClasses.ping,
|
||||
colors.ping,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"relative inline-flex rounded-full",
|
||||
sizeClasses.dot,
|
||||
colors.dot,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{label && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm text-slate-700 dark:text-slate-300",
|
||||
labelClassName,
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusIndicator;
|
||||
|
|
@ -205,3 +205,30 @@ export function getStateVisualInfo(status: MachineStatus): StateVisualInfo {
|
|||
|
||||
return visualMap[category];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map machine state category to status indicator state.
|
||||
* Returns the appropriate state for the StatusIndicator component.
|
||||
*/
|
||||
export function getStatusIndicatorState(
|
||||
status: MachineStatus,
|
||||
): "active" | "down" | "fixing" | "idle" {
|
||||
const category = getMachineStateCategory(status);
|
||||
|
||||
switch (category) {
|
||||
case MachineStateCategory.IDLE:
|
||||
return "active"; // Gray, no animation
|
||||
case MachineStateCategory.ACTIVE:
|
||||
return "active"; // Green with animation
|
||||
case MachineStateCategory.WAITING:
|
||||
return "fixing"; // Yellow with animation
|
||||
case MachineStateCategory.COMPLETE:
|
||||
return "active"; // Green with animation
|
||||
case MachineStateCategory.INTERRUPTED:
|
||||
return "down"; // Red with animation
|
||||
case MachineStateCategory.ERROR:
|
||||
return "down"; // Red with animation
|
||||
default:
|
||||
return "idle";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue