{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "author": "AlgorizeTech",
  "description": "A horizontal step indicator with icons and GSAP transitions between steps. Use it to show progress through a known, ordered sequence.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://avaraui.com/r/select.json",
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/StepperBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Check } from \"lucide-react\";\nimport React, { useEffect, useRef } from \"react\";\n\n// ==========================================\n// TYPES & INTERFACES\n// ==========================================\nexport interface Step {\n  id: string;\n  title: string;\n  description?: string;\n  icon?: React.ElementType;\n  content?: React.ReactNode;\n}\n\nexport interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n  steps: Step[];\n  currentStep: number;\n  onStepChange?: (step: number) => void;\n  /** Any CSS color (hex, rgb/hsl/oklch, var(--token)) or a Tailwind bg class. */\n  color?: string;\n}\n\n/**\n * A raw CSS color is applied inline; anything else is assumed to be a Tailwind\n * class the consumer already has in their build. Inline is the richer path —\n * the pulse ring needs a border color, and a `border-*` class derived at\n * runtime would never be seen by Tailwind's scanner.\n */\nconst isCssColorValue = (color: string) =>\n  /^(#|rgb|hsl|oklch|lab|lch|color\\(|var\\()/.test(color.trim());\n\n// ==========================================\n// MOTION CURVES (CSS easings)\n// ==========================================\n/** power3.inOut, sampled into a CSS `linear()` curve. */\nconst EASE_POWER3_IN_OUT =\n  \"linear(0, 0, 0, .0001, .0002, .0004, .0008, .0015, .0025, .0041, .0062, .009, .0128, .0176, .0237, .0313, .0405, .0516, .0648, .0804, .0988, .12, .1446, .1727, .2048, .2411, .2821, .3281, .3794, .4366, .5, .5634, .6206, .672, .7179, .7589, .7952, .8273, .8554, .88, .9012, .9196, .9352, .9484, .9595, .9688, .9763, .9824, .9872, .991, .9938, .996, .9975, .9985, .9992, .9996, .9998, 1, 1, 1, 1)\";\n/** power3.out, sampled into a CSS `linear()` curve. */\nconst EASE_POWER3_OUT =\n  \"linear(0, .0963, .1855, .2679, .3439, .4138, .478, .5367, .5904, .6392, .6836, .7237, .7599, .7924, .8215, .8474, .8704, .8907, .9085, .924, .9375, .9491, .959, .9674, .9744, .9802, .985, .9888, .9919, .9943, .9961, .9974, .9984, .9991, .9995, .9998, .9999, 1, 1, 1, 1)\";\n/** back.out(2.5) is a cubic, so this bezier is exact. */\nconst EASE_BACK_OUT = \"cubic-bezier(0.3333, 1.8333, 0.6667, 1)\";\n/** power2.out, exact. */\nconst EASE_POWER2_OUT = \"cubic-bezier(0.3333, 1, 0.6667, 1)\";\n\n// ==========================================\n// STEPPER NAVIGATION HEADER (CSS transitions)\n// ==========================================\nexport const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n  (\n    {\n      steps,\n      currentStep,\n      onStepChange,\n      color = \"var(--primary)\",\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const progressRef = useRef<HTMLDivElement>(null);\n    const progressPlaced = useRef(false);\n    // A single step has no line to fill (and would divide by zero).\n    const progress = steps.length > 1 ? currentStep / (steps.length - 1) : 0;\n    const isCssColor = isCssColorValue(color);\n\n    // Progress Line Animation — grows from the `scale-x-0` rest state on\n    // mount, then from wherever it is to each new step.\n    useEffect(() => {\n      const el = progressRef.current;\n      if (!el) return;\n      // Settle the starting style first so the very first change transitions.\n      if (!progressPlaced.current) {\n        void el.offsetWidth;\n        progressPlaced.current = true;\n      }\n      el.style.transition = `scale 0.5s ${EASE_POWER3_IN_OUT}`;\n      el.style.scale = `${progress} 1`;\n    }, [progress]);\n\n    return (\n      <div ref={ref} className={cn(\"relative w-full\", className)} {...props}>\n        <style>{`\n          @keyframes stepper-pulse-ring {\n            from { transform: scale(1); opacity: 0.8; }\n            to { transform: scale(1.45); opacity: 0; }\n          }\n        `}</style>\n\n        {/* Background Track Line — sits on the circle centre (16px on mobile, 20px from sm up) */}\n        <div className=\"absolute top-4 sm:top-5 left-0 w-full h-[2px] bg-muted -translate-y-1/2 z-0 rounded-full overflow-hidden\">\n          {/* Animated Foreground Progress Line */}\n          <div\n            ref={progressRef}\n            className={cn(\n              \"absolute top-0 left-0 w-full h-full origin-left scale-x-0\",\n              !isCssColor && color,\n            )}\n            style={{ backgroundColor: isCssColor ? color : undefined }}\n          />\n        </div>\n\n        {/* Step Indicators */}\n        <div className=\"relative z-10 flex justify-between w-full\">\n          {steps.map((step, index) => {\n            const isCompleted = currentStep > index;\n            const isActive = currentStep === index;\n\n            return (\n              <div\n                key={step.id}\n                className=\"flex flex-col items-center group relative min-w-0 px-1 sm:px-1.5\"\n                style={{ width: `${100 / steps.length}%` }}\n              >\n                {/* Clickable Area (Now unlocked for ALL steps) */}\n                <button\n                  type=\"button\"\n                  onClick={() => onStepChange?.(index)}\n                  disabled={!onStepChange}\n                  className={cn(\n                    \"relative flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 shrink-0 rounded-full border-2 transition-colors duration-300 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n                    isCompleted || isActive\n                      ? cn(\"border-transparent text-primary-foreground\", !isCssColor && color)\n                      : \"border-border bg-card text-muted-foreground\",\n                    onStepChange\n                      ? \"cursor-pointer hover:scale-110 active:scale-95\"\n                      : \"cursor-default\",\n                  )}\n                  style={{\n                    backgroundColor:\n                      (isCompleted || isActive) && isCssColor\n                        ? color\n                        : undefined,\n                  }}\n                >\n                  {/* Dynamic Inner Content with Pop */}\n                  <StepIndicatorContent\n                    isCompleted={isCompleted}\n                    isActive={isActive}\n                    index={index}\n                    icon={step.icon}\n                  />\n\n                  {/* Active Ring Pulse Effect (CSS keyframes) */}\n                  {isActive && isCssColor && <PulseRing color={color} />}\n                </button>\n\n                {/* Step Text */}\n                <div className=\"mt-1.5 sm:mt-3 text-center flex flex-col items-center w-full min-w-0\">\n                  <span\n                    className={cn(\n                      \"text-[11px] sm:text-sm font-semibold leading-tight break-words hyphens-auto transition-colors duration-300\",\n                      isActive || isCompleted\n                        ? \"text-foreground\"\n                        : \"text-muted-foreground\",\n                    )}\n                  >\n                    {step.title}\n                  </span>\n                  {step.description && (\n                    <span className=\"text-[11px] font-medium text-muted-foreground hidden sm:block mt-0.5 max-w-[120px] leading-tight\">\n                      {step.description}\n                    </span>\n                  )}\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    );\n  },\n);\nStepper.displayName = \"Stepper\";\n\n// ==========================================\n// HELPER: STEP ICON POP ANIMATION\n// ==========================================\ninterface StepIndicatorContentProps {\n  isCompleted: boolean;\n  isActive: boolean;\n  index: number;\n  icon?: React.ElementType;\n}\n\nconst StepIndicatorContent = ({\n  isCompleted,\n  isActive,\n  index,\n  icon: Icon,\n}: StepIndicatorContentProps) => {\n  const contentRef = useRef<HTMLDivElement>(null);\n  const popRef = useRef<Animation | null>(null);\n\n  useEffect(() => {\n    const el = contentRef.current;\n    if (!el) return;\n    popRef.current?.cancel();\n    popRef.current = el.animate(\n      [\n        { transform: \"scale(0.3)\", opacity: 0 },\n        { transform: \"scale(1)\", opacity: 1 },\n      ],\n      { duration: 400, easing: EASE_BACK_OUT },\n    );\n  }, [isCompleted, isActive]);\n\n  if (isCompleted) {\n    return (\n      <div ref={contentRef} className=\"flex items-center justify-center\">\n        <Check className=\"w-4 h-4 sm:w-5 sm:h-5 stroke-[3]\" />\n      </div>\n    );\n  }\n\n  return (\n    <div\n      ref={contentRef}\n      className=\"font-semibold text-xs sm:text-sm flex items-center justify-center\"\n    >\n      {Icon ? <Icon className=\"w-4 h-4 sm:w-5 sm:h-5\" /> : index + 1}\n    </div>\n  );\n};\n\n// ==========================================\n// HELPER: CONTINUOUS RING PULSE\n// ==========================================\nconst PulseRing = ({ color }: { color: string }) => {\n  return (\n    <div\n      className=\"absolute inset-0 rounded-full border-2 pointer-events-none\"\n      style={{\n        borderColor: color,\n        animation: `stepper-pulse-ring 1.5s ${EASE_POWER2_OUT} infinite`,\n      }}\n    />\n  );\n};\n\n// ==========================================\n// STEPPER CONTENT VIEW (Sliding Transitions)\n// ==========================================\nexport interface StepperContentProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  steps: Step[];\n  currentStep: number;\n}\n\nexport const StepperContent = React.forwardRef<\n  HTMLDivElement,\n  StepperContentProps\n>(({ steps, currentStep, className, ...props }, ref) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const prevStepRef = useRef(currentStep);\n\n  React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);\n\n  useEffect(() => {\n    if (!containerRef.current) return;\n    const dir = currentStep >= prevStepRef.current ? 1 : -1;\n    prevStepRef.current = currentStep;\n\n    // Animate slide-in\n    const animations = Array.from(containerRef.current.children, (child) =>\n      child.animate(\n        [\n          { transform: `translateX(${45 * dir}px) scale(0.98)`, opacity: 0 },\n          { transform: \"translateX(0px) scale(1)\", opacity: 1 },\n        ],\n        { duration: 450, easing: EASE_POWER3_OUT },\n      ),\n    );\n    return () => animations.forEach((animation) => animation.cancel());\n  }, [currentStep]);\n\n  return (\n    <div\n      ref={containerRef}\n      // overflow-x-clip, not overflow-hidden: the clip is only there to hide\n      // the 45px sideways slide-in. Hidden on both axes also cropped anything\n      // a step hangs below itself — a select's menu, an autocomplete list.\n      className={cn(\"relative w-full overflow-x-clip\", className)}\n      {...props}\n    >\n      {/* Key change triggers React to swap DOM nodes cleanly for the slide-in */}\n      <div key={currentStep} className=\"w-full\">\n        {steps[currentStep]?.content}\n      </div>\n    </div>\n  );\n});\nStepperContent.displayName = \"StepperContent\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/StepperBlock.tsx"
    }
  ],
  "docs": "Named exports: Stepper, StepperContent. `steps` take { id, title, description?, icon?, content? }. Controlled via `currentStep` and `onStepChange` — the component displays progress but does not own it. For the full state machine, use `wizard`.",
  "categories": [
    "navigation",
    "forms"
  ],
  "type": "registry:component"
}
