{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Stepper-TS-TW",
	"title": "Stepper",
	"description": "Animated multi-step progress indicator with active state transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Stepper/Stepper.tsx",
			"content": "import React, { useState, Children, useRef, useLayoutEffect, type HTMLAttributes, type ReactNode } from 'react';\nimport { motion, AnimatePresence, type Variants } from 'motion/react';\n\ninterface StepperProps extends HTMLAttributes<HTMLDivElement> {\n  children: ReactNode;\n  initialStep?: number;\n  onStepChange?: (step: number) => void;\n  onFinalStepCompleted?: () => void;\n  stepCircleContainerClassName?: string;\n  stepContainerClassName?: string;\n  contentClassName?: string;\n  footerClassName?: string;\n  backButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;\n  nextButtonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;\n  backButtonText?: string;\n  nextButtonText?: string;\n  disableStepIndicators?: boolean;\n  renderStepIndicator?: (props: {\n    step: number;\n    currentStep: number;\n    onStepClick: (clicked: number) => void;\n  }) => ReactNode;\n}\n\nexport default function Stepper({\n  children,\n  initialStep = 1,\n  onStepChange = () => {},\n  onFinalStepCompleted = () => {},\n  stepCircleContainerClassName = '',\n  stepContainerClassName = '',\n  contentClassName = '',\n  footerClassName = '',\n  backButtonProps = {},\n  nextButtonProps = {},\n  backButtonText = 'Back',\n  nextButtonText = 'Continue',\n  disableStepIndicators = false,\n  renderStepIndicator,\n  ...rest\n}: StepperProps) {\n  const [currentStep, setCurrentStep] = useState<number>(initialStep);\n  const [direction, setDirection] = useState<number>(0);\n  const stepsArray = Children.toArray(children);\n  const totalSteps = stepsArray.length;\n  const isCompleted = currentStep > totalSteps;\n  const isLastStep = currentStep === totalSteps;\n\n  const updateStep = (newStep: number) => {\n    setCurrentStep(newStep);\n    if (newStep > totalSteps) {\n      onFinalStepCompleted();\n    } else {\n      onStepChange(newStep);\n    }\n  };\n\n  const handleBack = () => {\n    if (currentStep > 1) {\n      setDirection(-1);\n      updateStep(currentStep - 1);\n    }\n  };\n\n  const handleNext = () => {\n    if (!isLastStep) {\n      setDirection(1);\n      updateStep(currentStep + 1);\n    }\n  };\n\n  const handleComplete = () => {\n    setDirection(1);\n    updateStep(totalSteps + 1);\n  };\n\n  return (\n    <div\n      className=\"flex min-h-full flex-1 flex-col items-center justify-center p-4 sm:aspect-[4/3] md:aspect-[2/1]\"\n      {...rest}\n    >\n      <div\n        className={`mx-auto w-full max-w-md rounded-4xl shadow-xl ${stepCircleContainerClassName}`}\n        style={{ border: '1px solid var(--border-primary, #222)' }}\n      >\n        <div className={`${stepContainerClassName} flex w-full items-center p-8`}>\n          {stepsArray.map((_, index) => {\n            const stepNumber = index + 1;\n            const isNotLastStep = index < totalSteps - 1;\n            return (\n              <React.Fragment key={stepNumber}>\n                {renderStepIndicator ? (\n                  renderStepIndicator({\n                    step: stepNumber,\n                    currentStep,\n                    onStepClick: clicked => {\n                      setDirection(clicked > currentStep ? 1 : -1);\n                      updateStep(clicked);\n                    }\n                  })\n                ) : (\n                  <StepIndicator\n                    step={stepNumber}\n                    disableStepIndicators={disableStepIndicators}\n                    currentStep={currentStep}\n                    onClickStep={clicked => {\n                      setDirection(clicked > currentStep ? 1 : -1);\n                      updateStep(clicked);\n                    }}\n                  />\n                )}\n                {isNotLastStep && <StepConnector isComplete={currentStep > stepNumber} />}\n              </React.Fragment>\n            );\n          })}\n        </div>\n\n        <StepContentWrapper\n          isCompleted={isCompleted}\n          currentStep={currentStep}\n          direction={direction}\n          className={`space-y-2 px-8 ${contentClassName}`}\n        >\n          {stepsArray[currentStep - 1]}\n        </StepContentWrapper>\n\n        {!isCompleted && (\n          <div className={`px-8 pb-8 ${footerClassName}`}>\n            <div className={`mt-10 flex ${currentStep !== 1 ? 'justify-between' : 'justify-end'}`}>\n              {currentStep !== 1 && (\n                <button\n                  onClick={handleBack}\n                  className={`duration-350 rounded px-2 py-1 transition ${\n                    currentStep === 1\n                      ? 'pointer-events-none opacity-50 text-neutral-400'\n                      : 'text-neutral-400 hover:text-neutral-700'\n                  }`}\n                  {...backButtonProps}\n                >\n                  {backButtonText}\n                </button>\n              )}\n              <button\n                onClick={isLastStep ? handleComplete : handleNext}\n                className=\"duration-350 flex items-center justify-center rounded-full bg-green-500 py-1.5 px-3.5 font-medium tracking-tight text-white transition hover:bg-green-600 active:bg-green-700\"\n                {...nextButtonProps}\n              >\n                {isLastStep ? 'Complete' : nextButtonText}\n              </button>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\ninterface StepContentWrapperProps {\n  isCompleted: boolean;\n  currentStep: number;\n  direction: number;\n  children: ReactNode;\n  className?: string;\n}\n\nfunction StepContentWrapper({\n  isCompleted,\n  currentStep,\n  direction,\n  children,\n  className = ''\n}: StepContentWrapperProps) {\n  const [parentHeight, setParentHeight] = useState<number>(0);\n\n  return (\n    <motion.div\n      style={{ position: 'relative', overflow: 'hidden' }}\n      animate={{ height: isCompleted ? 0 : parentHeight }}\n      transition={{ type: 'spring', duration: 0.4 }}\n      className={className}\n    >\n      <AnimatePresence initial={false} mode=\"sync\" custom={direction}>\n        {!isCompleted && (\n          <SlideTransition key={currentStep} direction={direction} onHeightReady={h => setParentHeight(h)}>\n            {children}\n          </SlideTransition>\n        )}\n      </AnimatePresence>\n    </motion.div>\n  );\n}\n\ninterface SlideTransitionProps {\n  children: ReactNode;\n  direction: number;\n  onHeightReady: (height: number) => void;\n}\n\nfunction SlideTransition({ children, direction, onHeightReady }: SlideTransitionProps) {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n\n  useLayoutEffect(() => {\n    if (containerRef.current) {\n      onHeightReady(containerRef.current.offsetHeight);\n    }\n  }, [children, onHeightReady]);\n\n  return (\n    <motion.div\n      ref={containerRef}\n      custom={direction}\n      variants={stepVariants}\n      initial=\"enter\"\n      animate=\"center\"\n      exit=\"exit\"\n      transition={{ duration: 0.4 }}\n      style={{ position: 'absolute', left: 0, right: 0, top: 0 }}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nconst stepVariants: Variants = {\n  enter: (dir: number) => ({\n    x: dir >= 0 ? '-100%' : '100%',\n    opacity: 0\n  }),\n  center: {\n    x: '0%',\n    opacity: 1\n  },\n  exit: (dir: number) => ({\n    x: dir >= 0 ? '50%' : '-50%',\n    opacity: 0\n  })\n};\n\ninterface StepProps {\n  children: ReactNode;\n}\n\nexport function Step({ children }: StepProps) {\n  return <div className=\"px-8\">{children}</div>;\n}\n\ninterface StepIndicatorProps {\n  step: number;\n  currentStep: number;\n  onClickStep: (clicked: number) => void;\n  disableStepIndicators?: boolean;\n}\n\nfunction StepIndicator({ step, currentStep, onClickStep, disableStepIndicators = false }: StepIndicatorProps) {\n  const status = currentStep === step ? 'active' : currentStep < step ? 'inactive' : 'complete';\n\n  const handleClick = () => {\n    if (step !== currentStep && !disableStepIndicators) {\n      onClickStep(step);\n    }\n  };\n\n  return (\n    <motion.div\n      onClick={handleClick}\n      className={`relative outline-none focus:outline-none ${disableStepIndicators ? 'pointer-events-none opacity-50' : 'cursor-pointer'}`}\n      animate={status}\n      initial={false}\n    >\n      <motion.div\n        variants={{\n          inactive: { scale: 1, backgroundColor: '#222', color: '#a3a3a3' },\n          active: { scale: 1, backgroundColor: '#5227FF', color: '#5227FF' },\n          complete: { scale: 1, backgroundColor: '#5227FF', color: '#3b82f6' }\n        }}\n        transition={{ duration: 0.3 }}\n        className=\"flex h-8 w-8 items-center justify-center rounded-full font-semibold\"\n      >\n        {status === 'complete' ? (\n          <CheckIcon className=\"h-4 w-4 text-black\" />\n        ) : status === 'active' ? (\n          <div className=\"h-3 w-3 rounded-full bg-[#120F17]\" />\n        ) : (\n          <span className=\"text-sm\">{step}</span>\n        )}\n      </motion.div>\n    </motion.div>\n  );\n}\n\ninterface StepConnectorProps {\n  isComplete: boolean;\n}\n\nfunction StepConnector({ isComplete }: StepConnectorProps) {\n  const lineVariants: Variants = {\n    incomplete: { width: 0, backgroundColor: 'transparent' },\n    complete: { width: '100%', backgroundColor: '#5227FF' }\n  };\n\n  return (\n    <div className=\"relative mx-2 h-0.5 flex-1 overflow-hidden rounded bg-neutral-600\">\n      <motion.div\n        className=\"absolute left-0 top-0 h-full\"\n        variants={lineVariants}\n        initial={false}\n        animate={isComplete ? 'complete' : 'incomplete'}\n        transition={{ duration: 0.4 }}\n      />\n    </div>\n  );\n}\n\ninterface CheckIconProps extends React.SVGProps<SVGSVGElement> {}\n\nfunction CheckIcon(props: CheckIconProps) {\n  return (\n    <svg {...props} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} viewBox=\"0 0 24 24\">\n      <motion.path\n        initial={{ pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={{\n          delay: 0.1,\n          type: 'tween',\n          ease: 'easeOut',\n          duration: 0.3\n        }}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M5 13l4 4L19 7\"\n      />\n    </svg>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}