Files
edr-platform/apps/edr-passenger-web/portal/src/components/ProgressIndicator.tsx
2026-07-09 17:20:00 +03:00

91 lines
3.4 KiB
TypeScript

'use client';
import { Check } from 'lucide-react';
import { useEffect, useRef } from 'react';
// Shared source of truth for the booking flow's step order/labels — also
// consumed by AppSidebar's vertical step list on desktop.
export const BOOKING_STEPS = [
{ id: 'search', name: 'Search' },
{ id: 'results', name: 'Results' },
{ id: 'passengers', name: 'Passengers' },
{ id: 'seats', name: 'Seats' },
{ id: 'review', name: 'Review' },
{ id: 'payment', name: 'Payment' },
{ id: 'confirmation', name: 'Done' },
];
const steps = BOOKING_STEPS;
export function ProgressIndicator({ currentStep }: { currentStep: string }) {
const currentIndex = steps.findIndex((s) => s.id === currentStep);
const scrollRef = useRef<HTMLDivElement>(null);
const activeRef = useRef<HTMLLIElement>(null);
// Auto-scroll active step to centre on mobile
useEffect(() => {
const container = scrollRef.current;
const active = activeRef.current;
if (!container || !active) return;
const offset = active.offsetLeft + active.offsetWidth / 2 - container.clientWidth / 2;
container.scrollTo({ left: offset, behavior: 'smooth' });
}, [currentIndex]);
const stepItem = (step: typeof steps[0], index: number) => {
const isComplete = index < currentIndex;
const isCurrent = index === currentIndex;
return (
<li
key={step.id}
ref={isCurrent ? activeRef : undefined}
className="flex flex-col items-center flex-shrink-0 w-[4.5rem] md:flex-1"
>
{/* Line + Circle row */}
<div className="flex items-center w-full">
{/* Left connector */}
<div className={`flex-1 h-0.5 ${index === 0 ? 'invisible' : isComplete ? 'bg-primary' : 'bg-gray-200 dark:bg-gray-700'}`} />
{/* Circle */}
<div className={`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-all duration-300 ${
isComplete ? 'bg-primary shadow-sm' :
isCurrent ? 'border-[3px] border-primary bg-white dark:bg-gray-800 shadow-sm' :
'border-2 border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800'
}`}>
{isComplete
? <Check className="h-4 w-4 text-white" />
: <span className={`text-xs font-bold ${isCurrent ? 'text-primary' : 'text-gray-400 dark:text-gray-500'}`}>{index + 1}</span>
}
</div>
{/* Right connector */}
<div className={`flex-1 h-0.5 ${index === steps.length - 1 ? 'invisible' : isComplete ? 'bg-primary' : 'bg-gray-200 dark:bg-gray-700'}`} />
</div>
{/* Label */}
<p className={`mt-1.5 text-[10px] md:text-xs font-medium text-center leading-tight whitespace-nowrap ${
isCurrent ? 'text-primary font-bold' :
isComplete ? 'text-gray-600 dark:text-gray-300' :
'text-gray-400 dark:text-gray-500'
}`}>
{step.name}
</p>
</li>
);
};
// Desktop shows the vertical step list embedded in AppSidebar instead —
// this component is mobile-only now (the sidebar doesn't exist below lg).
return (
<nav aria-label="Progress" className="lg:hidden">
<div
ref={scrollRef}
className="overflow-x-auto scrollbar-hide px-4 py-3"
>
<ol className="flex items-start min-w-full">
{steps.map((s, i) => stepItem(s, i))}
</ol>
</div>
</nav>
);
}