Catalog
Code
import * as React from "react";
import { Check, Home, Package, ShoppingCart, Truck } from "lucide-react";
import { cn } from "@/lib/utils";
export type OrderStep = { label: string; time?: string; icon?: React.ComponentType<{ className?: string }> };
const DEFAULT_STEPS: OrderStep[] = [
{ label: "Order placed", icon: ShoppingCart },
{ label: "Packed", icon: Package },
{ label: "Shipped", icon: Truck },
{ label: "Delivered", icon: Home },
];
export interface OrderTrackerProps {
/** Index of the current step (0-based). */
current: number;
steps?: OrderStep[];
orientation?: "horizontal" | "vertical";
className?: string;
}
export function OrderTracker({ current, steps = DEFAULT_STEPS, orientation = "horizontal", className }: OrderTrackerProps) {
const vertical = orientation === "vertical";
return (
<ol className={cn("flex w-full", vertical ? "flex-col" : "items-start", className)} aria-label="Order progress">
{steps.map((s, i) => {
const done = i < current;
const active = i === current;
const Icon = done ? Check : s.icon ?? DEFAULT_STEPS[i]?.icon ?? Package;
const last = i === steps.length - 1;
return (
<li key={s.label} className={cn("relative flex", vertical ? "gap-4 pb-8 last:pb-0" : "flex-1 flex-col items-center text-center")} aria-current={active ? "step" : undefined}>
{!last && (
<span
aria-hidden
className={cn("absolute bg-border", vertical ? "left-5 top-10 h-[calc(100%-2.5rem)] w-0.5" : "left-1/2 top-5 h-0.5 w-full")}
>
<span
className="block bg-primary transition-all duration-700"
style={vertical ? { height: done ? "100%" : "0%", width: "100%" } : { width: done ? "100%" : "0%", height: "100%" }}
/>
</span>
)}
<span
className={cn(
"relative z-10 grid size-10 shrink-0 place-items-center rounded-full border-2 bg-background transition",
done && "border-primary bg-primary text-primary-foreground",
active && "border-primary text-primary shadow-[0_0_0_6px] shadow-primary/15",
!done && !active && "text-muted-foreground",
)}
>
<Icon className="size-4" />
</span>
<div className={cn(vertical ? "pt-2" : "mt-3 px-1")}>
<p className={cn("text-sm font-medium", !done && !active && "text-muted-foreground")}>{s.label}</p>
{s.time && <p className="text-xs text-muted-foreground">{s.time}</p>}
</div>
</li>
);
})}
</ol>
);
}