"use client"; import { CheckIcon, LoaderCircleIcon } from "lucide-react"; import { Slot } from "radix-ui"; import * as React from "react"; import { createContext, useContext } from "react"; import { cn } from "@/lib/utils"; // Types type StepperContextValue = { activeStep: number; setActiveStep: (step: number) => void; orientation: "horizontal" | "vertical"; }; type StepItemContextValue = { step: number; state: StepState; isDisabled: boolean; isLoading: boolean; }; type StepState = "active" | "completed" | "inactive" | "loading"; // Contexts const StepperContext = createContext( undefined, ); const StepItemContext = createContext( undefined, ); const useStepper = () => { const context = useContext(StepperContext); if (!context) { throw new Error("useStepper must be used within a Stepper"); } return context; }; const useStepItem = () => { const context = useContext(StepItemContext); if (!context) { throw new Error("useStepItem must be used within a StepperItem"); } return context; }; // Components interface StepperProps extends React.HTMLAttributes { defaultValue?: number; value?: number; onValueChange?: (value: number) => void; orientation?: "horizontal" | "vertical"; } function Stepper({ defaultValue = 0, value, onValueChange, orientation = "horizontal", className, ...props }: StepperProps) { const [activeStep, setInternalStep] = React.useState(defaultValue); const setActiveStep = React.useCallback( (step: number) => { if (value === undefined) { setInternalStep(step); } onValueChange?.(step); }, [value, onValueChange], ); const currentStep = value ?? activeStep; return (
); } // StepperItem interface StepperItemProps extends React.HTMLAttributes { step: number; completed?: boolean; disabled?: boolean; loading?: boolean; } function StepperItem({ step, completed = false, disabled = false, loading = false, className, children, ...props }: StepperItemProps) { const { activeStep } = useStepper(); const state: StepState = completed || step < activeStep ? "completed" : activeStep === step ? "active" : "inactive"; const isLoading = loading && step === activeStep; return (
{children}
); } // StepperTrigger interface StepperTriggerProps extends React.ButtonHTMLAttributes { asChild?: boolean; } function StepperTrigger({ asChild = false, className, children, ...props }: StepperTriggerProps) { const { setActiveStep } = useStepper(); const { step, isDisabled } = useStepItem(); if (asChild) { const Comp = asChild ? Slot.Root : "span"; return ( {children} ); } return ( ); } // StepperIndicator interface StepperIndicatorProps extends React.HTMLAttributes { asChild?: boolean; } function StepperIndicator({ asChild = false, className, children, ...props }: StepperIndicatorProps) { const { state, step, isLoading } = useStepItem(); return ( {asChild ? ( children ) : ( <> {step} ); } // StepperTitle function StepperTitle({ className, ...props }: React.HTMLAttributes) { return (

); } // StepperDescription function StepperDescription({ className, ...props }: React.HTMLAttributes) { return (

); } // StepperSeparator function StepperSeparator({ className, ...props }: React.HTMLAttributes) { return (

); } export { Stepper, StepperDescription, StepperIndicator, StepperItem, StepperSeparator, StepperTitle, StepperTrigger, };