{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "multi-step-form",
  "title": "Multi Step Form",
  "description": "Dependency-free multi-step form state layer with per-step validation, designed to compose with Stepper",
  "dependencies": [],
  "registryDependencies": [
    "@boldkit/utils",
    "stepper",
    "button",
    "input",
    "label"
  ],
  "files": [
    {
      "path": "registry/default/ui/multi-step-form.tsx",
      "content": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from 'react'\n\n/**\n * Multi-step form: a dependency-free form-state layer designed to sit on top of\n * the <Stepper /> UI. It owns values, per-field errors, touched state, and the\n * active step, and gates forward navigation on the current step's validator.\n * Stepper renders the indicator; this drives its `activeStep`.\n */\n\nexport type StepValidator<V> = (values: V) => Record<string, string> | null\n\nexport interface MultiStepFormStep<V> {\n  id: string\n  validate?: StepValidator<V>\n}\n\ninterface MultiStepFormContextValue<V> {\n  values: V\n  setValue: <K extends keyof V>(name: K, value: V[K]) => void\n  errors: Record<string, string>\n  touched: Record<string, boolean>\n  activeStep: number\n  next: () => void\n  back: () => void\n  goTo: (step: number) => void\n  canGoNext: boolean\n  isStepValid: boolean\n  isFirstStep: boolean\n  isLastStep: boolean\n  submit: () => void\n}\n\n// One context object reused for every value type; consumers read it through the\n// typed useMultiStepForm<V>() hook below.\nconst MultiStepFormContext = React.createContext<MultiStepFormContextValue<unknown> | null>(null)\n\nexport interface MultiStepFormProps<V> {\n  steps: MultiStepFormStep<V>[]\n  initialValues: V\n  onSubmit: (values: V) => void\n  children: React.ReactNode\n}\n\nexport function MultiStepForm<V>({\n  steps,\n  initialValues,\n  onSubmit,\n  children,\n}: MultiStepFormProps<V>) {\n  const [values, setValues] = React.useState<V>(initialValues)\n  const [errors, setErrors] = React.useState<Record<string, string>>({})\n  const [touched, setTouched] = React.useState<Record<string, boolean>>({})\n  const [activeStep, setActiveStep] = React.useState(0)\n\n  const totalSteps = steps.length\n  const isFirstStep = activeStep === 0\n  const isLastStep = activeStep === totalSteps - 1\n\n  const setValue = React.useCallback(<K extends keyof V>(name: K, value: V[K]) => {\n    setValues((prev) => ({ ...prev, [name]: value }))\n    setTouched((prev) => ({ ...prev, [name as string]: true }))\n    setErrors((prev) => {\n      if (!(name as string in prev)) return prev\n      const next = { ...prev }\n      delete next[name as string]\n      return next\n    })\n  }, [])\n\n  // Validate the current step against the latest values. Returns true when the\n  // step has no validator or the validator returns null; otherwise publishes\n  // the errors and returns false.\n  const validateStep = React.useCallback(\n    (step: number): boolean => {\n      const validator = steps[step]?.validate\n      if (!validator) {\n        setErrors({})\n        return true\n      }\n      const result = validator(values)\n      setErrors(result ?? {})\n      return result === null\n    },\n    [steps, values]\n  )\n\n  const isStepValid = React.useMemo(() => {\n    const validator = steps[activeStep]?.validate\n    return validator ? validator(values) === null : true\n  }, [steps, activeStep, values])\n\n  const goTo = React.useCallback(\n    (step: number) => {\n      if (step < 0 || step >= totalSteps) return\n      // Backward navigation is unguarded; forward runs the current validator.\n      if (step > activeStep) {\n        if (!validateStep(activeStep)) return\n      } else {\n        setErrors({})\n      }\n      setActiveStep(step)\n    },\n    [activeStep, totalSteps, validateStep]\n  )\n\n  const next = React.useCallback(() => {\n    if (isLastStep) return\n    if (!validateStep(activeStep)) return\n    setActiveStep((s) => Math.min(s + 1, totalSteps - 1))\n  }, [activeStep, isLastStep, totalSteps, validateStep])\n\n  const back = React.useCallback(() => {\n    setErrors({})\n    setActiveStep((s) => Math.max(s - 1, 0))\n  }, [])\n\n  const submit = React.useCallback(() => {\n    if (!isLastStep) return\n    if (!validateStep(activeStep)) return\n    onSubmit(values)\n  }, [activeStep, isLastStep, onSubmit, validateStep, values])\n\n  const ctx: MultiStepFormContextValue<V> = {\n    values,\n    setValue,\n    errors,\n    touched,\n    activeStep,\n    next,\n    back,\n    goTo,\n    canGoNext: isStepValid && !isLastStep,\n    isStepValid,\n    isFirstStep,\n    isLastStep,\n    submit,\n  }\n\n  return (\n    <MultiStepFormContext.Provider value={ctx as MultiStepFormContextValue<unknown>}>\n      {children}\n    </MultiStepFormContext.Provider>\n  )\n}\n\nexport function useMultiStepForm<V>(): MultiStepFormContextValue<V> {\n  const ctx = React.useContext(MultiStepFormContext)\n  if (!ctx) {\n    throw new Error('useMultiStepForm must be used within a <MultiStepForm />')\n  }\n  return ctx as MultiStepFormContextValue<V>\n}\n",
      "type": "registry:ui",
      "target": "components/ui/multi-step-form.tsx"
    }
  ],
  "type": "registry:ui"
}