97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { Check, X, Loader2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type StepState = 'waiting' | 'active' | 'complete' | 'failed';
|
|
|
|
export interface DeployStepIndicatorProps {
|
|
label: string;
|
|
state: StepState;
|
|
index: number;
|
|
isLast?: boolean;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function DeployStepIndicator({
|
|
label,
|
|
state,
|
|
index,
|
|
isLast = false,
|
|
}: DeployStepIndicatorProps) {
|
|
return (
|
|
<div className="flex items-center">
|
|
{/* Step circle */}
|
|
<div className="flex flex-col items-center">
|
|
<div
|
|
className={cn(
|
|
'relative flex h-10 w-10 items-center justify-center rounded-full border-2 text-sm font-semibold transition-all duration-500',
|
|
// Waiting
|
|
state === 'waiting' &&
|
|
'border-gray-600 bg-gray-800 text-gray-500',
|
|
// Active
|
|
state === 'active' &&
|
|
'border-indigo-500 bg-indigo-500/20 text-indigo-400 shadow-lg shadow-indigo-500/25',
|
|
// Complete
|
|
state === 'complete' &&
|
|
'border-emerald-500 bg-emerald-500/20 text-emerald-400 shadow-lg shadow-emerald-500/25',
|
|
// Failed
|
|
state === 'failed' &&
|
|
'border-red-500 bg-red-500/20 text-red-400 shadow-lg shadow-red-500/25',
|
|
)}
|
|
>
|
|
{/* Pulse ring for active */}
|
|
{state === 'active' && (
|
|
<span className="absolute inset-0 animate-ping rounded-full border-2 border-indigo-400 opacity-30" />
|
|
)}
|
|
|
|
{/* Icon */}
|
|
{state === 'complete' && <Check className="h-5 w-5" />}
|
|
{state === 'failed' && <X className="h-5 w-5" />}
|
|
{state === 'active' && (
|
|
<Loader2 className="h-5 w-5 animate-spin" />
|
|
)}
|
|
{state === 'waiting' && <span>{index + 1}</span>}
|
|
</div>
|
|
|
|
{/* Label */}
|
|
<span
|
|
className={cn(
|
|
'mt-2 text-xs font-medium transition-colors duration-300',
|
|
state === 'waiting' && 'text-gray-500',
|
|
state === 'active' && 'text-indigo-400',
|
|
state === 'complete' && 'text-emerald-400',
|
|
state === 'failed' && 'text-red-400',
|
|
)}
|
|
>
|
|
{label}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Connector line */}
|
|
{!isLast && (
|
|
<div className="mx-2 mb-6 h-0.5 w-12 sm:w-16 lg:w-20">
|
|
<div
|
|
className={cn(
|
|
'h-full rounded-full transition-all duration-700',
|
|
state === 'complete'
|
|
? 'bg-gradient-to-r from-emerald-500 to-emerald-500/50'
|
|
: state === 'active'
|
|
? 'bg-gradient-to-r from-indigo-500 to-gray-700 animate-pulse'
|
|
: 'bg-gray-700',
|
|
)}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|