import { useState, useCallback } from 'react'; // ───────────────────────────────────────────────────────────────────────────── // Types // ───────────────────────────────────────────────────────────────────────────── export interface UseStepperProps { /** Total number of steps. */ totalSteps: number; /** Initial active step (1-indexed). @default 1 */ initialStep?: number; /** * Controlled current step (1-indexed). * When provided, step state is managed externally via `onStepChange`. */ step?: number; /** Called whenever the active step changes. */ onStepChange?: (step: number) => void; /** * Optional async guard called before advancing to the next step. * Return `true` to allow the transition, `false` to block it. * Useful for running form validation before moving forward. */ onBeforeNext?: (currentStep: number) => boolean | Promise; } export interface UseStepperReturn { // ── State ───────────────────────────────────────────────────────────────── /** The currently active step (1-indexed). */ currentStep: number; /** Total number of steps. */ totalSteps: number; // ── Flags ───────────────────────────────────────────────────────────────── /** Whether the current step is the first step. */ isFirstStep: boolean; /** Whether the current step is the last step. */ isLastStep: boolean; /** Whether navigation to the previous step is possible. */ canGoPrev: boolean; /** Whether navigation to the next step is possible. */ canGoNext: boolean; // ── Handlers ────────────────────────────────────────────────────────────── /** * Advance to the next step. * If `onBeforeNext` is provided, it is awaited first; the step only * advances when the guard returns `true`. */ next: () => Promise; /** Go back to the previous step (no-op if already on step 1). */ prev: () => void; /** Jump directly to a specific step (1-indexed, clamped to valid range). */ goTo: (step: number) => void; /** Jump to the first step. */ reset: () => void; } // ───────────────────────────────────────────────────────────────────────────── // Hook // ───────────────────────────────────────────────────────────────────────────── /** * Headless hook for multi-step wizard / stepper logic. * * @description * Manages the active step state, navigation guards, and derived flags for * multi-step flows. Supports both controlled and uncontrolled modes. * Pair with the `` / `` visual components or any custom UI. * * @example * ```tsx * const { currentStep, totalSteps, next, prev, isFirstStep, isLastStep } = * useStepper({ totalSteps: 3, onBeforeNext: () => form.trigger() }); * ``` */ export function useStepper({ totalSteps, initialStep = 1, step: controlledStep, onStepChange, onBeforeNext, }: UseStepperProps): UseStepperReturn { const [internalStep, setInternalStep] = useState(() => Math.min(Math.max(1, initialStep), totalSteps) ); const isControlled = controlledStep !== undefined; const currentStep = isControlled ? controlledStep! : internalStep; const setStep = useCallback( (s: number) => { const clamped = Math.min(Math.max(1, s), totalSteps); if (!isControlled) setInternalStep(clamped); onStepChange?.(clamped); }, [isControlled, onStepChange, totalSteps] ); // ── Derived ─────────────────────────────────────────────────────────────── const isFirstStep = currentStep === 1; const isLastStep = currentStep === totalSteps; const canGoPrev = currentStep > 1; const canGoNext = currentStep < totalSteps; // ── Handlers ────────────────────────────────────────────────────────────── const next = useCallback(async () => { if (!canGoNext) return; if (onBeforeNext) { const allowed = await onBeforeNext(currentStep); if (!allowed) return; } setStep(currentStep + 1); }, [canGoNext, currentStep, onBeforeNext, setStep]); const prev = useCallback(() => { if (!canGoPrev) return; setStep(currentStep - 1); }, [canGoPrev, currentStep, setStep]); const goTo = useCallback((s: number) => setStep(s), [setStep]); const reset = useCallback(() => setStep(1), [setStep]); return { currentStep, totalSteps, isFirstStep, isLastStep, canGoPrev, canGoNext, next, prev, goTo, reset, }; }