101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
import * as React from "react";
|
|
import { cn } from "@/lib/utils";
|
|
import { Check } from "lucide-react";
|
|
|
|
interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
steps: string[];
|
|
currentStep: number; // 0-indexed
|
|
orientation?: "horizontal" | "vertical";
|
|
}
|
|
|
|
interface StepProps {
|
|
label: string;
|
|
stepNumber: number;
|
|
isActive: boolean;
|
|
isCompleted: boolean;
|
|
isLast: boolean;
|
|
orientation?: "horizontal" | "vertical";
|
|
}
|
|
|
|
const Step = ({ label, stepNumber, isActive, isCompleted, isLast, orientation = "horizontal" }: StepProps) => {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex items-center",
|
|
orientation === "vertical" ? "flex-col" : "flex-row",
|
|
orientation === "vertical" && "w-full"
|
|
)}
|
|
>
|
|
<div className="flex flex-col items-center">
|
|
<div
|
|
className={cn(
|
|
"flex items-center justify-center w-8 h-8 rounded-full border-2 transition-all duration-200",
|
|
isActive && "border-[var(--interactive-primary)] bg-[var(--interactive-primary)]/20 text-[var(--interactive-primary)]",
|
|
isCompleted && "border-[var(--color-success)] bg-[var(--color-success)]/20 text-[var(--color-success)]",
|
|
!isActive && !isCompleted && "border-[var(--border-default)] bg-[var(--bg-sunken)] text-[var(--text-muted)]"
|
|
)}
|
|
aria-current={isActive ? "step" : undefined}
|
|
>
|
|
{isCompleted ? (
|
|
<Check size={16} />
|
|
) : (
|
|
<span className="text-xs font-semibold">{stepNumber}</span>
|
|
)}
|
|
</div>
|
|
<span
|
|
className={cn(
|
|
"text-xs mt-1 font-medium",
|
|
isActive && "text-[var(--interactive-primary)]",
|
|
isCompleted && "text-[var(--color-success)]",
|
|
!isActive && !isCompleted && "text-[var(--text-muted)]"
|
|
)}
|
|
>
|
|
{label}
|
|
</span>
|
|
</div>
|
|
{!isLast && (
|
|
<div
|
|
className={cn(
|
|
"flex-1 mx-2",
|
|
orientation === "vertical" ? "h-8 w-px my-1" : "h-px",
|
|
isCompleted ? "bg-[var(--color-success)]" : "bg-[var(--border-default)]"
|
|
)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export function Stepper({ steps, currentStep, orientation = "horizontal", className, ...props }: StepperProps) {
|
|
return (
|
|
<div
|
|
data-slot="stepper"
|
|
className={cn(
|
|
"flex w-full",
|
|
orientation === "horizontal" ? "flex-row items-center" : "flex-col",
|
|
className
|
|
)}
|
|
role="list"
|
|
aria-label="Progress steps"
|
|
{...props}
|
|
>
|
|
{steps.map((step, index) => (
|
|
<div
|
|
key={step}
|
|
className={cn("flex items-center", orientation === "vertical" && "w-full")}
|
|
role="listitem"
|
|
>
|
|
<Step
|
|
label={step}
|
|
stepNumber={index + 1}
|
|
isActive={index === currentStep}
|
|
isCompleted={index < currentStep}
|
|
isLast={index === steps.length - 1}
|
|
orientation={orientation}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|