'use client' import * as React from 'react' import { motion, AnimatePresence } from 'motion/react' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { ArrowLeft, ArrowRight, Check, Loader2 } from 'lucide-react' export interface WizardStep { id: string title: string description?: string isOptional?: boolean } interface FormWizardContextValue { currentStep: number totalSteps: number steps: WizardStep[] goToStep: (step: number) => void nextStep: () => void prevStep: () => void isFirstStep: boolean isLastStep: boolean canGoNext: boolean setCanGoNext: (can: boolean) => void } const FormWizardContext = React.createContext(null) export function useFormWizard() { const context = React.useContext(FormWizardContext) if (!context) { throw new Error('useFormWizard must be used within a FormWizard') } return context } interface FormWizardProps { steps: WizardStep[] children: React.ReactNode onComplete?: () => void | Promise isSubmitting?: boolean submitLabel?: string className?: string showStepIndicator?: boolean allowStepNavigation?: boolean } export function FormWizard({ steps, children, onComplete, isSubmitting = false, submitLabel = 'Submit', className, showStepIndicator = true, allowStepNavigation = false, }: FormWizardProps) { const [currentStep, setCurrentStep] = React.useState(0) const [canGoNext, setCanGoNext] = React.useState(true) const [direction, setDirection] = React.useState(0) // -1 for back, 1 for forward const totalSteps = steps.length const isFirstStep = currentStep === 0 const isLastStep = currentStep === totalSteps - 1 const goToStep = React.useCallback((step: number) => { if (step >= 0 && step < totalSteps) { setDirection(step > currentStep ? 1 : -1) setCurrentStep(step) } }, [currentStep, totalSteps]) const nextStep = React.useCallback(() => { if (currentStep < totalSteps - 1) { setDirection(1) setCurrentStep((prev) => prev + 1) } }, [currentStep, totalSteps]) const prevStep = React.useCallback(() => { if (currentStep > 0) { setDirection(-1) setCurrentStep((prev) => prev - 1) } }, [currentStep]) const handleNext = async () => { if (isLastStep && onComplete) { await onComplete() } else { nextStep() } } const contextValue: FormWizardContextValue = { currentStep, totalSteps, steps, goToStep, nextStep, prevStep, isFirstStep, isLastStep, canGoNext, setCanGoNext, } const childrenArray = React.Children.toArray(children) const currentChild = childrenArray[currentStep] const variants = { enter: (direction: number) => ({ x: direction > 0 ? 100 : -100, opacity: 0, }), center: { x: 0, opacity: 1, }, exit: (direction: number) => ({ x: direction < 0 ? 100 : -100, opacity: 0, }), } return (
{showStepIndicator && ( )}
{currentChild}
) } interface StepIndicatorProps { steps: WizardStep[] currentStep: number allowNavigation?: boolean onStepClick?: (step: number) => void } export function StepIndicator({ steps, currentStep, allowNavigation = false, onStepClick, }: StepIndicatorProps) { return (
{/* Progress bar */}
Step {currentStep + 1} of {steps.length} {Math.round(((currentStep + 1) / steps.length) * 100)}% complete
{/* Step indicators */}
{steps.map((step, index) => { const isCompleted = index < currentStep const isCurrent = index === currentStep const isClickable = allowNavigation && (isCompleted || isCurrent) return ( {index < steps.length - 1 && (
)} ) })}
) } interface WizardStepContentProps { children: React.ReactNode title?: string description?: string className?: string } export function WizardStepContent({ children, title, description, className, }: WizardStepContentProps) { return (
{(title || description) && (
{title && (

{title}

)} {description && (

{description}

)}
)}
{children}
) }