{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "description": "Multi-step form wizard with progress indicators, compound components pattern",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "@boldkit/utils"
  ],
  "files": [
    {
      "path": "registry/default/ui/stepper.tsx",
      "content": "/* eslint-disable react-refresh/only-export-components */\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport { Check } from 'lucide-react'\n\n// Context\ninterface StepperContextValue {\n  activeStep: number\n  setActiveStep: (step: number) => void\n  totalSteps: number\n  orientation: 'horizontal' | 'vertical'\n}\n\nconst StepperContext = React.createContext<StepperContextValue | null>(null)\n\nfunction useStepperContext() {\n  const context = React.useContext(StepperContext)\n  if (!context) {\n    throw new Error('Stepper components must be used within a <Stepper />')\n  }\n  return context\n}\n\n// Step variants\nconst stepVariants = cva(\n  'flex items-center justify-center border-3 border-foreground font-bold transition duration-200',\n  {\n    variants: {\n      state: {\n        completed: 'bg-success text-success-foreground shadow-[4px_4px_0px_hsl(var(--shadow-color))]',\n        active: 'bg-primary text-primary-foreground shadow-[4px_4px_0px_hsl(var(--shadow-color))] scale-110',\n        upcoming: 'bg-muted text-muted-foreground',\n      },\n      size: {\n        sm: 'h-8 w-8 text-sm',\n        md: 'h-10 w-10',\n        lg: 'h-12 w-12 text-lg',\n      },\n    },\n    defaultVariants: {\n      state: 'upcoming',\n      size: 'md',\n    },\n  }\n)\n\n/**\n * Depth-first count of <StepperItem> descendants.\n *\n * Items are normally nested inside <StepperList>, so scanning only the root's\n * direct children finds zero of them — which made `totalSteps` 0 and left\n * `isLast` permanently false, so a wizard could never be completed.\n */\nfunction countStepperItems(children: React.ReactNode): number {\n  let count = 0\n  React.Children.forEach(children, (child) => {\n    if (!React.isValidElement(child)) return\n    if ((child.type as { _isBoldKitStepperItem?: boolean })._isBoldKitStepperItem === true) {\n      count++\n      return\n    }\n    count += countStepperItems((child.props as { children?: React.ReactNode }).children)\n  })\n  return count\n}\n\n// Stepper Root\nexport interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n  activeStep?: number\n  onStepChange?: (step: number) => void\n  orientation?: 'horizontal' | 'vertical'\n  /** Overrides the automatic <StepperItem> count — for dynamic or virtualized steps. */\n  totalSteps?: number\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n  (\n    {\n      activeStep: controlledActiveStep,\n      onStepChange,\n      orientation = 'horizontal',\n      totalSteps: totalStepsProp,\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const [uncontrolledActiveStep, setUncontrolledActiveStep] = React.useState(0)\n\n    const isControlled = controlledActiveStep !== undefined\n    const activeStep = isControlled ? controlledActiveStep : uncontrolledActiveStep\n\n    const setActiveStep = React.useCallback(\n      (step: number) => {\n        if (!isControlled) {\n          setUncontrolledActiveStep(step)\n        }\n        onStepChange?.(step)\n      },\n      [isControlled, onStepChange]\n    )\n\n    // Count total steps from children, at any nesting depth.\n    const totalSteps = totalStepsProp ?? countStepperItems(children)\n\n    return (\n      <StepperContext.Provider value={{ activeStep, setActiveStep, totalSteps, orientation }}>\n        <div\n          ref={ref}\n          className={cn(\n            'flex',\n            orientation === 'horizontal' ? 'flex-row' : 'flex-col',\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </StepperContext.Provider>\n    )\n  }\n)\nStepper.displayName = 'Stepper'\n\n// Stepper List (container for triggers)\nexport type StepperListProps = React.HTMLAttributes<HTMLDivElement>\n\nconst StepperList = React.forwardRef<HTMLDivElement, StepperListProps>(\n  ({ className, children, ...props }, ref) => {\n    const { orientation } = useStepperContext()\n\n    return (\n      <div\n        ref={ref}\n        role=\"group\"\n        aria-label=\"Progress\"\n        className={cn(\n          'flex items-center',\n          orientation === 'horizontal' ? 'flex-row' : 'flex-col items-start',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    )\n  }\n)\nStepperList.displayName = 'StepperList'\n\n// Stepper Item (wrapper for a single step)\ninterface StepperItemContextValue {\n  index: number\n  triggerId: string\n}\n\nconst StepperItemContext = React.createContext<StepperItemContextValue | null>(null)\n\nfunction useStepperItemContext() {\n  const context = React.useContext(StepperItemContext)\n  if (!context) {\n    throw new Error('StepperItem components must be used within a <StepperItem />')\n  }\n  return context\n}\n\nexport interface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  index: number\n}\n\nconst StepperItem = React.forwardRef<HTMLDivElement, StepperItemProps>(\n  ({ index, className, children, ...props }, ref) => {\n    const { orientation } = useStepperContext()\n\n    const triggerId = `stepper-trigger-${index}`\n\n    return (\n      <StepperItemContext.Provider value={{ index, triggerId }}>\n        <div\n          ref={ref}\n          className={cn(\n            'flex items-center',\n            orientation === 'horizontal' ? 'flex-row' : 'flex-col',\n            className\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </StepperItemContext.Provider>\n    )\n  }\n)\nStepperItem.displayName = 'StepperItem'\n;(StepperItem as typeof StepperItem & { _isBoldKitStepperItem: boolean })._isBoldKitStepperItem = true\n\n// Stepper Trigger (the clickable step indicator)\nexport interface StepperTriggerProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof stepVariants> {\n  showStepNumber?: boolean\n}\n\nconst StepperTrigger = React.forwardRef<HTMLButtonElement, StepperTriggerProps>(\n  ({ size, showStepNumber = true, className, children, ...props }, ref) => {\n    const { activeStep, setActiveStep } = useStepperContext()\n    const { index, triggerId } = useStepperItemContext()\n\n    const state: 'completed' | 'active' | 'upcoming' =\n      index < activeStep ? 'completed' : index === activeStep ? 'active' : 'upcoming'\n\n    return (\n      <button\n        ref={ref}\n        id={triggerId}\n        type=\"button\"\n        aria-current={state === 'active' ? 'step' : undefined}\n        aria-controls={`stepper-panel-${index}`}\n        onClick={() => setActiveStep(index)}\n        className={cn(stepVariants({ state, size }), className)}\n        {...props}\n      >\n        {state === 'completed' ? (\n          <Check className=\"h-5 w-5\" />\n        ) : showStepNumber ? (\n          index + 1\n        ) : (\n          children\n        )}\n      </button>\n    )\n  }\n)\nStepperTrigger.displayName = 'StepperTrigger'\n\n// Stepper Separator (line between steps)\nexport type StepperSeparatorProps = React.HTMLAttributes<HTMLDivElement>\n\nconst StepperSeparator = React.forwardRef<HTMLDivElement, StepperSeparatorProps>(\n  ({ className, ...props }, ref) => {\n    const { activeStep, orientation } = useStepperContext()\n    // Optional on purpose: a separator is just as valid *between* items inside\n    // <StepperList> as it is inside one, and that's the composition the docs\n    // show. Outside an item there's no index to compare, so it renders in the\n    // un-completed style rather than throwing.\n    const itemContext = React.useContext(StepperItemContext)\n    const isCompleted = itemContext != null && itemContext.index < activeStep\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          'transition duration-200',\n          orientation === 'horizontal'\n            ? 'h-[3px] flex-1 min-w-8 mx-2'\n            : 'w-[3px] min-h-8 my-2 ml-5',\n          isCompleted\n            ? 'bg-foreground'\n            : 'bg-foreground/30 border-foreground/30',\n          !isCompleted && 'border-dashed border-2 bg-transparent',\n          className\n        )}\n        {...props}\n      />\n    )\n  }\n)\nStepperSeparator.displayName = 'StepperSeparator'\n\n// Stepper Content (panel for each step)\nexport interface StepperContentProps extends React.HTMLAttributes<HTMLDivElement> {\n  index: number\n}\n\nconst StepperContent = React.forwardRef<HTMLDivElement, StepperContentProps>(\n  ({ index, className, children, ...props }, ref) => {\n    const { activeStep } = useStepperContext()\n\n    if (index !== activeStep) {\n      return null\n    }\n\n    return (\n      <div\n        ref={ref}\n        id={`stepper-panel-${index}`}\n        role=\"group\"\n        aria-labelledby={`stepper-trigger-${index}`}\n        className={cn(\n          'mt-4 animate-[slide-in-from-bottom_200ms_ease-out]',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    )\n  }\n)\nStepperContent.displayName = 'StepperContent'\n\n// Stepper Actions (prev/next buttons helper)\nexport interface StepperActionsProps extends React.HTMLAttributes<HTMLDivElement> {\n  onComplete?: () => void\n  prevLabel?: string\n  nextLabel?: string\n  completeLabel?: string\n}\n\nconst StepperActions = React.forwardRef<HTMLDivElement, StepperActionsProps>(\n  (\n    {\n      onComplete,\n      prevLabel = 'Previous',\n      nextLabel = 'Next',\n      completeLabel = 'Complete',\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const { activeStep, setActiveStep, totalSteps } = useStepperContext()\n\n    const isFirst = activeStep === 0\n    const isLast = activeStep === totalSteps - 1\n\n    const handlePrev = () => {\n      if (!isFirst) {\n        setActiveStep(activeStep - 1)\n      }\n    }\n\n    const handleNext = () => {\n      if (isLast) {\n        onComplete?.()\n      } else {\n        setActiveStep(activeStep + 1)\n      }\n    }\n\n    return (\n      <div\n        ref={ref}\n        className={cn('flex items-center gap-2 mt-4', className)}\n        {...props}\n      >\n        {children || (\n          <>\n            <button\n              type=\"button\"\n              onClick={handlePrev}\n              disabled={isFirst}\n              className={cn(\n                'px-4 py-2 border-3 border-foreground font-bold uppercase text-sm',\n                'bg-muted shadow-[4px_4px_0px_hsl(var(--shadow-color))]',\n                'hover:translate-x-[4px] hover:translate-y-[4px] hover:shadow-none',\n                'disabled:opacity-50 disabled:pointer-events-none',\n                'transition duration-200'\n              )}\n            >\n              {prevLabel}\n            </button>\n            <button\n              type=\"button\"\n              onClick={handleNext}\n              className={cn(\n                'px-4 py-2 border-3 border-foreground font-bold uppercase text-sm',\n                'bg-primary text-primary-foreground shadow-[4px_4px_0px_hsl(var(--shadow-color))]',\n                'hover:translate-x-[4px] hover:translate-y-[4px] hover:shadow-none',\n                'transition duration-200'\n              )}\n            >\n              {isLast ? completeLabel : nextLabel}\n            </button>\n          </>\n        )}\n      </div>\n    )\n  }\n)\nStepperActions.displayName = 'StepperActions'\n\nexport {\n  Stepper,\n  StepperList,\n  StepperItem,\n  StepperTrigger,\n  StepperSeparator,\n  StepperContent,\n  StepperActions,\n  stepVariants,\n  useStepperContext,\n}\n",
      "type": "registry:ui",
      "target": "components/ui/stepper.tsx"
    }
  ],
  "type": "registry:ui"
}