{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wizard-stepper",
  "type": "registry:ui",
  "title": "Wizard Stepper",
  "description": "A full-featured wizard stepper with titles and descriptions. Perfect for multi-step forms, checkout flows, and onboarding sequences.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/wizard-stepper.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion } from \"motion/react\";\nimport { CheckIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\ntype WizardStepState = \"active\" | \"completed\" | \"pending\";\n\ninterface WizardStepData {\n  id: string | number;\n  title: string;\n  description?: string;\n}\n\ninterface WizardContextValue {\n  currentStep: number;\n  setCurrentStep: (step: number) => void;\n  totalSteps: number;\n  isLoading: boolean;\n}\n\n// ── Context ─────────────────────────────────────────────────────────────────\n\nconst WizardContext = React.createContext<WizardContextValue | undefined>(\n  undefined,\n);\n\nfunction useWizard() {\n  const context = React.useContext(WizardContext);\n  if (!context) {\n    throw new Error(\"useWizard must be used within a WizardStepper\");\n  }\n  return context;\n}\n\n// ── WizardStepper ───────────────────────────────────────────────────────────\n\ninterface WizardStepperProps extends React.HTMLAttributes<HTMLDivElement> {\n  steps: WizardStepData[];\n  defaultStep?: number;\n  currentStep?: number;\n  onStepChange?: (step: number) => void;\n  loading?: boolean;\n  orientation?: \"horizontal\" | \"vertical\";\n}\n\nfunction WizardStepper({\n  steps,\n  defaultStep = 0,\n  currentStep: controlledStep,\n  onStepChange,\n  loading = false,\n  orientation = \"horizontal\",\n  className,\n  ...props\n}: WizardStepperProps) {\n  const [internalStep, setInternalStep] = React.useState(defaultStep);\n  const currentStep = controlledStep ?? internalStep;\n\n  const setCurrentStep = React.useCallback(\n    (step: number) => {\n      if (controlledStep === undefined) setInternalStep(step);\n      onStepChange?.(step);\n    },\n    [controlledStep, onStepChange],\n  );\n\n  return (\n    <WizardContext.Provider\n      value={{\n        currentStep,\n        setCurrentStep,\n        totalSteps: steps.length,\n        isLoading: loading,\n      }}\n    >\n      <div className={cn(\"w-full\", className)} {...props}>\n        <div\n          className={cn(\n            orientation === \"horizontal\"\n              ? \"flex items-start\"\n              : \"flex flex-col gap-0\",\n          )}\n        >\n          {steps.map((step, index) => {\n            const state: WizardStepState =\n              index < currentStep\n                ? \"completed\"\n                : index === currentStep\n                  ? \"active\"\n                  : \"pending\";\n\n            return (\n              <React.Fragment key={step.id}>\n                <WizardStepItem\n                  step={index}\n                  state={state}\n                  title={step.title}\n                  description={step.description}\n                  isLast={index === steps.length - 1}\n                  orientation={orientation}\n                  isLoading={loading && index === currentStep}\n                  onClick={() => setCurrentStep(index)}\n                />\n                {index < steps.length - 1 && orientation === \"horizontal\" && (\n                  <WizardConnector state={state} />\n                )}\n              </React.Fragment>\n            );\n          })}\n        </div>\n      </div>\n    </WizardContext.Provider>\n  );\n}\n\n// ── WizardStepItem ──────────────────────────────────────────────────────────\n\ninterface WizardStepItemProps {\n  step: number;\n  state: WizardStepState;\n  title: string;\n  description?: string;\n  isLast: boolean;\n  orientation: \"horizontal\" | \"vertical\";\n  isLoading: boolean;\n  onClick: () => void;\n}\n\nfunction WizardStepItem({\n  step,\n  state,\n  title,\n  description,\n  isLast,\n  orientation,\n  isLoading,\n  onClick,\n}: WizardStepItemProps) {\n  return (\n    <div\n      className={cn(\n        \"group flex cursor-pointer\",\n        orientation === \"horizontal\"\n          ? \"flex-col items-center text-center\"\n          : \"flex-row items-start gap-3\",\n      )}\n      onClick={onClick}\n    >\n      <div className=\"flex items-center\">\n        {orientation === \"vertical\" && (\n          <div className=\"flex flex-col items-center\">\n            <WizardCircle step={step + 1} state={state} isLoading={isLoading} />\n            {!isLast && (\n              <motion.div\n                className={cn(\n                  \"mt-2 w-px\",\n                  state === \"completed\"\n                    ? \"bg-foreground/20\"\n                    : \"bg-foreground/[0.06]\",\n                )}\n                initial={false}\n                animate={{ height: 36 }}\n                transition={{ type: \"spring\", stiffness: 300, damping: 25 }}\n              />\n            )}\n          </div>\n        )}\n        {orientation === \"horizontal\" && (\n          <WizardCircle step={step + 1} state={state} isLoading={isLoading} />\n        )}\n      </div>\n\n      <div\n        className={cn(\n          orientation === \"horizontal\" ? \"mt-2.5 max-w-[100px]\" : \"pt-0.5 pb-8\",\n        )}\n      >\n        <p\n          className={cn(\n            \"text-[13px] font-medium transition-colors duration-200\",\n            state === \"active\" && \"text-foreground\",\n            state === \"completed\" && \"text-foreground/55\",\n            state === \"pending\" && \"text-foreground/25\",\n          )}\n        >\n          {title}\n        </p>\n        {description && (\n          <p\n            className={cn(\n              \"mt-0.5 text-[11px] transition-colors duration-200\",\n              state === \"pending\" ? \"text-foreground/15\" : \"text-foreground/35\",\n            )}\n          >\n            {description}\n          </p>\n        )}\n      </div>\n    </div>\n  );\n}\n\n// ── WizardCircle ────────────────────────────────────────────────────────────\n\ninterface WizardCircleProps {\n  step: number;\n  state: WizardStepState;\n  isLoading: boolean;\n}\n\nfunction WizardCircle({ step, state, isLoading }: WizardCircleProps) {\n  return (\n    <div className=\"relative\">\n      {state === \"active\" && (\n        <motion.div\n          className=\"absolute -inset-1 rounded-full border border-foreground/10\"\n          initial={{ scale: 0.8, opacity: 0 }}\n          animate={{ scale: 1, opacity: 1 }}\n          transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n        />\n      )}\n      <motion.span\n        className={cn(\n          \"relative flex size-7 items-center justify-center rounded-full text-[11px] font-semibold\",\n          state === \"completed\" && \"bg-foreground text-background\",\n          state === \"active\" && \"bg-foreground text-background\",\n          state === \"pending\" && \"bg-foreground/[0.04] text-foreground/25\",\n        )}\n        whileTap={{ scale: 0.9 }}\n        transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n      >\n        {isLoading ? (\n          <motion.div\n            className=\"size-3 rounded-full border-[1.5px] border-background/30 border-t-background\"\n            animate={{ rotate: 360 }}\n            transition={{ repeat: Infinity, duration: 0.6, ease: \"linear\" }}\n          />\n        ) : state === \"completed\" ? (\n          <CheckIcon className=\"size-3.5\" />\n        ) : (\n          step\n        )}\n      </motion.span>\n    </div>\n  );\n}\n\n// ── WizardConnector ─────────────────────────────────────────────────────────\n\ninterface WizardConnectorProps {\n  state: WizardStepState;\n}\n\nfunction WizardConnector({ state }: WizardConnectorProps) {\n  return (\n    <div className=\"relative mx-2 mt-3.5 h-px flex-1 overflow-hidden bg-foreground/[0.06]\">\n      <motion.div\n        className=\"absolute inset-y-0 left-0 bg-foreground/25\"\n        initial={false}\n        animate={{ width: state === \"completed\" ? \"100%\" : \"0%\" }}\n        transition={{ type: \"spring\", stiffness: 300, damping: 30 }}\n      />\n    </div>\n  );\n}\n\n// ── Exports ─────────────────────────────────────────────────────────────────\n\nexport { WizardStepper, useWizard };\nexport type { WizardStepData, WizardStepState };\n",
      "type": "registry:ui",
      "target": "components/ruixen/wizard-stepper.tsx"
    }
  ]
}