{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "description": "A step-by-step process for users to navigate through a series of steps.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "@ondo-ui/utils"
  ],
  "files": [
    {
      "path": "components/ui/stepper.tsx",
      "content": "\"use client\"\n\nimport type { HTMLAttributes } from \"react\"\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\"\nimport { mergeProps } from \"@base-ui/react/merge-props\"\nimport { useRender } from \"@base-ui/react/use-render\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype StepperOrientation = \"horizontal\" | \"vertical\"\ntype StepState = \"active\" | \"completed\" | \"inactive\"\ntype StepperVariant = \"default\" | \"info\" | \"success\" | \"warning\" | \"destructive\"\n\ntype StepIndicators = {\n  active?: React.ReactNode\n  completed?: React.ReactNode\n  inactive?: React.ReactNode\n  loading?: React.ReactNode\n}\n\ninterface StepperContextValue {\n  activeStep: number\n  setActiveStep: (step: number) => void\n  orientation: StepperOrientation\n  variant: StepperVariant\n  activeVariant: StepperVariant\n  indicators: StepIndicators\n  baseId: string\n  registerTrigger: (node: HTMLButtonElement) => () => void\n  focusNext: (node: HTMLButtonElement) => void\n  focusPrev: (node: HTMLButtonElement) => void\n  focusFirst: () => void\n  focusLast: () => void\n}\n\ninterface StepItemContextValue {\n  step: number\n  state: StepState\n  isDisabled: boolean\n  isLoading: boolean\n}\n\nconst StepperContext = createContext<StepperContextValue | undefined>(undefined)\nconst StepItemContext = createContext<StepItemContextValue | undefined>(\n  undefined\n)\n\nfunction useStepper() {\n  const context = useContext(StepperContext)\n\n  if (!context) {\n    throw new Error(\"useStepper must be used within a Stepper\")\n  }\n\n  return context\n}\n\nfunction useStepItem() {\n  const context = useContext(StepItemContext)\n\n  if (!context) {\n    throw new Error(\"useStepItem must be used within a StepperItem\")\n  }\n\n  return context\n}\n\ninterface StepperProps extends HTMLAttributes<HTMLDivElement> {\n  defaultValue?: number\n  value?: number\n  onValueChange?: (value: number) => void\n  orientation?: StepperOrientation\n  variant?: StepperVariant\n  activeVariant?: StepperVariant\n  indicators?: StepIndicators\n}\n\nconst stepperIndicatorVariants = cva(\n  \"relative flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full border-background bg-accent text-xs text-accent-foreground data-[state=inactive]:text-muted-foreground\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"data-[state=completed]:border-primary data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground\",\n        info: \"data-[state=completed]:border-info data-[state=completed]:bg-info data-[state=completed]:text-white\",\n        success:\n          \"data-[state=completed]:border-success data-[state=completed]:bg-success data-[state=completed]:text-white\",\n        warning:\n          \"data-[state=completed]:border-warning data-[state=completed]:bg-warning data-[state=completed]:text-white\",\n        destructive:\n          \"data-[state=completed]:border-destructive data-[state=completed]:bg-destructive data-[state=completed]:text-white\",\n      },\n      activeVariant: {\n        default:\n          \"data-[state=active]:border-primary data-[state=active]:bg-primary data-[state=active]:text-primary-foreground\",\n        info: \"data-[state=active]:border-info data-[state=active]:bg-info data-[state=active]:text-white\",\n        success:\n          \"data-[state=active]:border-success data-[state=active]:bg-success data-[state=active]:text-white\",\n        warning:\n          \"data-[state=active]:border-warning data-[state=active]:bg-warning data-[state=active]:text-white\",\n        destructive:\n          \"data-[state=active]:border-destructive data-[state=active]:bg-destructive data-[state=active]:text-white\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      activeVariant: \"default\",\n    },\n  }\n)\n\nconst stepperSeparatorVariants = cva(\n  \"style-vega:rounded-sm style-nova:rounded-sm style-maia:rounded-full style-lyra:rounded-none style-mira:rounded-sm style-luma:rounded-full style-rhea:rounded-full style-sera:rounded-none m-0.5 bg-muted group-data-[orientation=horizontal]/stepper-nav:h-0.5 group-data-[orientation=horizontal]/stepper-nav:flex-1 group-data-[orientation=vertical]/stepper-nav:h-12 group-data-[orientation=vertical]/stepper-nav:w-0.5\",\n  {\n    variants: {\n      variant: {\n        default: \"group-data-[state=completed]/step:bg-primary\",\n        info: \"group-data-[state=completed]/step:bg-info\",\n        success: \"group-data-[state=completed]/step:bg-success\",\n        warning: \"group-data-[state=completed]/step:bg-warning\",\n        destructive: \"group-data-[state=completed]/step:bg-destructive\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Stepper({\n  defaultValue = 1,\n  value,\n  onValueChange,\n  orientation = \"horizontal\",\n  variant = \"default\",\n  activeVariant,\n  indicators = {},\n  className,\n  children,\n  ...props\n}: StepperProps) {\n  const [internalValue, setInternalValue] = useState(defaultValue)\n  const [triggerNodes, setTriggerNodes] = useState<HTMLButtonElement[]>([])\n  const generatedId = useId().replace(/:/g, \"\")\n  const baseId = `stepper-${generatedId}`\n  const activeStep = value ?? internalValue\n\n  const setActiveStep = useCallback(\n    (step: number) => {\n      if (value === undefined) {\n        setInternalValue(step)\n      }\n\n      onValueChange?.(step)\n    },\n    [onValueChange, value]\n  )\n\n  const registerTrigger = useCallback((node: HTMLButtonElement) => {\n    setTriggerNodes((nodes) =>\n      nodes.includes(node) ? nodes : [...nodes, node]\n    )\n\n    return () => {\n      setTriggerNodes((nodes) =>\n        nodes.filter((candidate) => candidate !== node)\n      )\n    }\n  }, [])\n\n  const getEnabledTriggers = useCallback(\n    () => triggerNodes.filter((node) => !node.disabled),\n    [triggerNodes]\n  )\n\n  const focusNext = useCallback(\n    (node: HTMLButtonElement) => {\n      const enabledTriggers = getEnabledTriggers()\n      const currentIndex = enabledTriggers.indexOf(node)\n\n      if (enabledTriggers.length === 0) return\n\n      enabledTriggers[\n        currentIndex < 0 ? 0 : (currentIndex + 1) % enabledTriggers.length\n      ]?.focus()\n    },\n    [getEnabledTriggers]\n  )\n\n  const focusPrev = useCallback(\n    (node: HTMLButtonElement) => {\n      const enabledTriggers = getEnabledTriggers()\n      const currentIndex = enabledTriggers.indexOf(node)\n\n      if (enabledTriggers.length === 0) return\n\n      enabledTriggers[\n        currentIndex < 0\n          ? enabledTriggers.length - 1\n          : (currentIndex - 1 + enabledTriggers.length) % enabledTriggers.length\n      ]?.focus()\n    },\n    [getEnabledTriggers]\n  )\n\n  const focusFirst = useCallback(() => {\n    getEnabledTriggers()[0]?.focus()\n  }, [getEnabledTriggers])\n\n  const focusLast = useCallback(() => {\n    getEnabledTriggers().at(-1)?.focus()\n  }, [getEnabledTriggers])\n\n  const contextValue = useMemo<StepperContextValue>(\n    () => ({\n      activeStep,\n      setActiveStep,\n      orientation,\n      variant,\n      activeVariant: activeVariant ?? variant,\n      indicators,\n      baseId,\n      registerTrigger,\n      focusNext,\n      focusPrev,\n      focusFirst,\n      focusLast,\n    }),\n    [\n      activeStep,\n      baseId,\n      activeVariant,\n      focusFirst,\n      focusLast,\n      focusNext,\n      focusPrev,\n      indicators,\n      orientation,\n      registerTrigger,\n      setActiveStep,\n      variant,\n    ]\n  )\n\n  return (\n    <StepperContext.Provider value={contextValue}>\n      <div\n        data-slot=\"stepper\"\n        data-orientation={orientation}\n        className={cn(\"w-full\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </StepperContext.Provider>\n  )\n}\n\ninterface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  step: number\n  completed?: boolean\n  disabled?: boolean\n  loading?: boolean\n}\n\nfunction StepperItem({\n  step,\n  completed = false,\n  disabled = false,\n  loading = false,\n  className,\n  children,\n  ...props\n}: StepperItemProps) {\n  const { activeStep } = useStepper()\n  const state: StepState =\n    completed || step < activeStep\n      ? \"completed\"\n      : step === activeStep\n        ? \"active\"\n        : \"inactive\"\n  const isLoading = loading && step === activeStep\n\n  return (\n    <StepItemContext.Provider\n      value={{ step, state, isDisabled: disabled, isLoading }}\n    >\n      <div\n        data-slot=\"stepper-item\"\n        data-state={state}\n        data-loading={isLoading || undefined}\n        className={cn(\n          \"group/step flex items-center justify-center not-last:flex-1 group-data-[orientation=horizontal]/stepper-nav:flex-row group-data-[orientation=vertical]/stepper-nav:flex-col\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </StepItemContext.Provider>\n  )\n}\n\ntype StepperTriggerProps = useRender.ComponentProps<\"button\">\n\nfunction StepperTrigger({\n  className,\n  children,\n  tabIndex,\n  render,\n  ...props\n}: StepperTriggerProps) {\n  const { state, isLoading, step, isDisabled } = useStepItem()\n  const {\n    activeStep,\n    baseId,\n    setActiveStep,\n    registerTrigger,\n    focusNext,\n    focusPrev,\n    focusFirst,\n    focusLast,\n  } = useStepper()\n  const isSelected = activeStep === step\n  const tabId = `${baseId}-tab-${step}`\n  const panelId = `${baseId}-panel-${step}`\n  const buttonRef = useRef<HTMLButtonElement>(null)\n\n  useEffect(() => {\n    const node = buttonRef.current\n\n    if (!node) return\n\n    return registerTrigger(node)\n  }, [registerTrigger])\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n    const node = event.currentTarget\n\n    switch (event.key) {\n      case \"ArrowRight\":\n      case \"ArrowDown\":\n        event.preventDefault()\n        focusNext(node)\n        break\n      case \"ArrowLeft\":\n      case \"ArrowUp\":\n        event.preventDefault()\n        focusPrev(node)\n        break\n      case \"Home\":\n        event.preventDefault()\n        focusFirst()\n        break\n      case \"End\":\n        event.preventDefault()\n        focusLast()\n        break\n      case \"Enter\":\n      case \" \":\n        event.preventDefault()\n        setActiveStep(step)\n        break\n    }\n  }\n\n  const defaultProps = {\n    type: \"button\" as const,\n    role: \"tab\",\n    id: tabId,\n    \"aria-selected\": isSelected,\n    \"aria-controls\": panelId,\n    tabIndex:\n      typeof tabIndex === \"number\"\n        ? tabIndex\n        : isDisabled\n          ? -1\n          : isSelected\n            ? 0\n            : -1,\n    \"data-slot\": \"stepper-trigger\",\n    \"data-state\": state,\n    \"data-loading\": isLoading || undefined,\n    className: cn(\n      \"inline-flex cursor-pointer items-center gap-2.5 rounded-full outline-none focus-visible:z-10 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-60\",\n      className\n    ),\n    onClick: () => setActiveStep(step),\n    onKeyDown: handleKeyDown,\n    disabled: isDisabled,\n    children,\n  }\n\n  return useRender({\n    defaultTagName: \"button\",\n    render,\n    ref: buttonRef,\n    props: mergeProps<\"button\">(defaultProps, props),\n  })\n}\n\nfunction StepperIndicator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { state, isLoading } = useStepItem()\n  const { indicators, variant, activeVariant } = useStepper()\n  let content = children\n\n  if (isLoading && indicators.loading !== undefined) {\n    content = indicators.loading\n  } else if (indicators[state] !== undefined) {\n    content = indicators[state]\n  }\n\n  return (\n    <div\n      data-slot=\"stepper-indicator\"\n      data-state={state}\n      className={cn(\n        stepperIndicatorVariants({ variant, activeVariant }),\n        className\n      )}\n      {...props}\n    >\n      <div className=\"absolute\">{content}</div>\n    </div>\n  )\n}\n\nfunction StepperSeparator({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { state } = useStepItem()\n  const { variant } = useStepper()\n\n  return (\n    <div\n      data-slot=\"stepper-separator\"\n      data-state={state}\n      className={cn(stepperSeparatorVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nfunction StepperTitle({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"h3\">) {\n  const { state } = useStepItem()\n\n  return (\n    <h3\n      data-slot=\"stepper-title\"\n      data-state={state}\n      className={cn(\"text-sm leading-none font-medium\", className)}\n      {...props}\n    >\n      {children}\n    </h3>\n  )\n}\n\nfunction StepperDescription({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { state } = useStepItem()\n\n  return (\n    <div\n      data-slot=\"stepper-description\"\n      data-state={state}\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nfunction StepperNav({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"nav\">) {\n  const { activeStep, orientation } = useStepper()\n\n  return (\n    <nav\n      role=\"tablist\"\n      aria-orientation={orientation}\n      data-slot=\"stepper-nav\"\n      data-state={activeStep}\n      data-orientation={orientation}\n      className={cn(\n        \"group/stepper-nav inline-flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </nav>\n  )\n}\n\nfunction StepperPanel({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { activeStep } = useStepper()\n\n  return (\n    <div\n      data-slot=\"stepper-panel\"\n      data-state={activeStep}\n      className={cn(\"w-full\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\ninterface StepperContentProps extends React.ComponentProps<\"div\"> {\n  value: number\n  forceMount?: boolean\n}\n\nfunction StepperContent({\n  value,\n  forceMount = false,\n  children,\n  className,\n  ...props\n}: StepperContentProps) {\n  const { activeStep, baseId } = useStepper()\n  const isActive = value === activeStep\n\n  if (!forceMount && !isActive) {\n    return null\n  }\n\n  return (\n    <div\n      role=\"tabpanel\"\n      id={`${baseId}-panel-${value}`}\n      aria-labelledby={`${baseId}-tab-${value}`}\n      data-slot=\"stepper-content\"\n      data-state={isActive ? \"active\" : \"inactive\"}\n      className={cn(\"w-full\", className, !isActive && forceMount && \"hidden\")}\n      hidden={!isActive && forceMount}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport {\n  Stepper,\n  StepperContent,\n  StepperDescription,\n  StepperIndicator,\n  StepperItem,\n  StepperNav,\n  StepperPanel,\n  StepperSeparator,\n  StepperTitle,\n  StepperTrigger,\n  useStepItem,\n  useStepper,\n  type StepIndicators,\n  type StepperVariant,\n  type StepperContentProps,\n  type StepperItemProps,\n  type StepperProps,\n  type StepperTriggerProps,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}