{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "description": "Stepper from the Get IT Done design system.",
  "dependencies": [
    "class-variance-authority@^0.7.0",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/button.json",
    "https://zero-design.itdone.de/r/collapsible.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/stepper/context.tsx",
      "content": "'use client';\n\nimport { ReactNode, createContext, useState } from 'react';\nimport type { StepperProps } from './types';\n\ninterface StepperContextValue extends StepperProps {\n  clickable?: boolean;\n  isError?: boolean;\n  isLoading?: boolean;\n  isVertical?: boolean;\n  stepCount?: number;\n  expandVerticalSteps?: boolean;\n  activeStep: number;\n  initialStep: number;\n}\n\ntype StepperContextProviderProps = {\n  value: Omit<StepperContextValue, 'activeStep'> & { activeStep?: number };\n  children: ReactNode;\n};\n\nconst StepperContext = createContext<\n  StepperContextValue & {\n    nextStep: () => void;\n    prevStep: () => void;\n    resetSteps: () => void;\n    setStep: (step: number) => void;\n  }\n>({\n  steps: [],\n  activeStep: 0,\n  initialStep: 0,\n  nextStep: () => {},\n  prevStep: () => {},\n  resetSteps: () => {},\n  setStep: () => {},\n});\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n  const isError = value.state === 'error';\n  const isLoading = value.state === 'loading';\n\n  const [innerStep, setInnerStep] = useState(value.initialStep);\n  const controlled = value.activeStep !== undefined;\n  const activeStep = controlled ? (value.activeStep as number) : innerStep;\n\n  const setStep = (step: number) => {\n    if (!controlled) setInnerStep(step);\n    value.onStepChange?.(step);\n  };\n\n  const nextStep = () => setStep(activeStep + 1);\n\n  const prevStep = () => setStep(activeStep - 1);\n\n  const resetSteps = () => setStep(value.initialStep);\n\n  return (\n    <StepperContext.Provider\n      value={{\n        ...value,\n        isError,\n        isLoading,\n        activeStep,\n        nextStep,\n        prevStep,\n        resetSteps,\n        setStep,\n      }}\n    >\n      {children}\n    </StepperContext.Provider>\n  );\n};\n\nexport { StepperContext, StepperProvider };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/horizontal-step.tsx",
      "content": "'use client';\n\nimport { forwardRef } from 'react';\nimport { cn } from '@/lib/utils';\nimport { StepButtonContainer } from './step-button-container';\nimport { StepIcon } from './step-icon';\nimport { StepLabel } from './step-label';\nimport type { StepSharedProps } from './types';\nimport { useStepper } from './use-stepper';\n\nconst HorizontalStep = forwardRef<HTMLDivElement, StepSharedProps>((props, ref) => {\n  const {\n    isError,\n    isLoading,\n    onClickStep,\n    variant,\n    clickable,\n    checkIcon: checkIconContext,\n    errorIcon: errorIconContext,\n    styles,\n    steps,\n    setStep,\n  } = useStepper();\n\n  const {\n    index,\n    isCompletedStep,\n    isCurrentStep,\n    hasVisited,\n    icon,\n    label,\n    description,\n    isKeepError,\n    state,\n    checkIcon: checkIconProp,\n    errorIcon: errorIconProp,\n  } = props;\n\n  const localIsLoading = isLoading || state === 'loading';\n  const localIsError = isError || state === 'error';\n\n\n  const active = variant === 'line' ? isCompletedStep || isCurrentStep : isCompletedStep;\n\n  const checkIcon = checkIconProp || checkIconContext;\n  const errorIcon = errorIconProp || errorIconContext;\n\n  return (\n    <div\n      aria-disabled={!hasVisited}\n      className={cn(\n        'stepper__horizontal-step',\n        'flex items-center relative transition-all duration-200',\n        '[&:not(:last-child)]:flex-1',\n        '[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200',\n        \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-button-border-outline-default\",\n        'data-[completed=true]:[&:not(:last-child)]:after:bg-primary',\n        'data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive',\n        variant === 'circle-alt' &&\n          'justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]',\n        variant === 'circle' &&\n          '[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]',\n        variant === 'line' && 'flex-col flex-1 border-t-[3px] border-solid data-[active=true]:border-primary',\n        styles?.['horizontal-step'],\n      )}\n      data-optional={steps[index || 0]?.optional}\n      data-completed={isCompletedStep}\n      data-active={active}\n      data-invalid={localIsError}\n      data-clickable={clickable}\n      onClick={() => onClickStep?.(index || 0, setStep)}\n      ref={ref}\n    >\n      <div\n        className={cn(\n          'stepper__horizontal-step-container',\n          'flex items-center',\n          variant === 'circle-alt' && 'flex-col justify-center gap-1',\n          variant === 'line' && 'w-full',\n          styles?.['horizontal-step-container'],\n        )}\n      >\n        <StepButtonContainer {...{ ...props, isError: localIsError, isLoading: localIsLoading }}>\n          <StepIcon\n            {...{\n              index,\n              isCompletedStep,\n              isCurrentStep,\n              isError: localIsError,\n              isKeepError,\n              isLoading: localIsLoading,\n            }}\n            icon={icon}\n            checkIcon={checkIcon}\n            errorIcon={errorIcon}\n          />\n        </StepButtonContainer>\n        <StepLabel label={label} description={description} {...{ isCurrentStep, hasVisited }} />\n      </div>\n    </div>\n  );\n});\n\nexport { HorizontalStep };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/index.tsx",
      "content": "'use client';\n\nimport { CSSProperties, Children, ReactElement, ReactNode, Ref, cloneElement, forwardRef, isValidElement } from 'react';\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\nimport { StepperProvider } from './context';\nimport { Step } from './step';\nimport type { StepItem, StepProps, StepperProps } from './types';\nimport { useMediaQuery } from './use-media-query';\nimport { useStepper } from './use-stepper';\n\nconst VARIABLE_SIZES = {\n  sm: '36px',\n  md: '40px',\n  lg: '44px',\n};\n\nconst Stepper = forwardRef<HTMLDivElement, StepperProps>((props, ref: Ref<HTMLDivElement>) => {\n  const {\n    className,\n    children,\n    orientation: orientationProp,\n    state,\n    responsive,\n    checkIcon,\n    errorIcon,\n    onClickStep,\n    mobileBreakpoint,\n    expandVerticalSteps = false,\n    initialStep = 0,\n    size,\n    theme,\n    iconSize,\n    steps,\n    variant,\n    styles,\n    variables,\n    scrollTracking = false,\n    activeStep: activeStepProp,\n    onStepChange,\n    ...rest\n  } = props;\n\n  const zero = usePreset() === 'zero';\n  const childArr = Children.toArray(children);\n\n  const items = [] as ReactElement[];\n\n  const footer = childArr.map((child, _index) => {\n    if (!isValidElement(child)) {\n      throw new Error('Stepper children must be valid React elements.');\n    }\n    if (child.type === Step) {\n      items.push(child);\n      return null;\n    }\n\n    return child;\n  });\n\n  const stepCount = items.length;\n\n  const isMobile = useMediaQuery(`(max-width: ${mobileBreakpoint || '768px'})`);\n\n  const clickable = !!onClickStep;\n\n  const orientation = isMobile && responsive ? 'vertical' : orientationProp;\n\n  const isVertical = orientation === 'vertical';\n\n  return (\n    <StepperProvider\n      value={{\n        initialStep,\n        activeStep: activeStepProp,\n        onStepChange,\n        orientation,\n        state,\n        size,\n        theme,\n        iconSize,\n        responsive,\n        checkIcon,\n        errorIcon,\n        onClickStep,\n        clickable,\n        stepCount,\n        isVertical,\n        variant: variant || 'circle',\n        expandVerticalSteps,\n        steps,\n        scrollTracking,\n        styles,\n      }}\n    >\n      <div\n        ref={ref}\n        className={cn(\n          'stepper__main-container',\n          'flex w-full flex-wrap',\n          stepCount === 1 ? 'justify-end' : 'justify-between',\n          orientation === 'vertical' ? 'flex-col' : 'flex-row',\n          variant === 'line' && orientation === 'horizontal' && 'gap-4',\n          className,\n          styles?.['main-container'],\n        )}\n        style={\n          {\n            '--step-icon-size': variables?.['--step-icon-size'] || (zero ? 'var(--size-stepper-marker)' : `${VARIABLE_SIZES[size || 'md']}`),\n            '--step-gap': variables?.['--step-gap'] || '8px',\n          } as CSSProperties\n        }\n        {...rest}\n      >\n        <VerticalContent>{items}</VerticalContent>\n      </div>\n      {orientation === 'horizontal' && <HorizontalContent>{items}</HorizontalContent>}\n      {footer}\n    </StepperProvider>\n  );\n});\n\nStepper.defaultProps = {\n  size: 'md',\n  orientation: 'horizontal',\n  responsive: true,\n};\n\nconst VerticalContent = ({ children }: { children: ReactNode }) => {\n  const { activeStep } = useStepper();\n\n  const childArr = Children.toArray(children);\n  const stepCount = childArr.length;\n\n  return (\n    <>\n      {Children.map(children, (child, i) => {\n        const isCompletedStep = (isValidElement(child) && (child.props as any).isCompletedStep) ?? i < activeStep;\n        const isLastStep = i === stepCount - 1;\n        const isCurrentStep = i === activeStep;\n\n        const stepProps = {\n          index: i,\n          isCompletedStep,\n          isCurrentStep,\n          isLastStep,\n        };\n\n        if (isValidElement(child)) {\n          return cloneElement(child, stepProps);\n        }\n        return null;\n      })}\n    </>\n  );\n};\n\nconst HorizontalContent = ({ children }: { children: ReactNode }) => {\n  const { activeStep } = useStepper();\n  const childArr = Children.toArray(children);\n\n  if (activeStep > childArr.length) {\n    return null;\n  }\n\n  return (\n    <>\n      {Children.map(childArr[activeStep], (node) => {\n        if (!isValidElement(node)) {\n          return null;\n        }\n        return Children.map(node.props.children, (childNode) => childNode);\n      })}\n    </>\n  );\n};\n\nexport { Step, Stepper, useStepper };\nexport type { StepItem, StepProps, StepperProps };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/step-button-container.tsx",
      "content": "'use client';\n\nimport { ReactNode } from 'react';\nimport { Button } from '@/components/ui/button';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\nimport { cn } from '@/lib/utils';\nimport type { StepSharedProps } from './types';\nimport { useStepper } from './use-stepper';\n\ntype StepButtonContainerProps = StepSharedProps & {\n  children?: ReactNode;\n};\n\nconst themeClasses = {\n  light:\n    'border-transparent bg-transparent data-[active=true]:border-transparent data-[active=true]:bg-transparent data-[active=true]:text-primary data-[current=true]:border-transparent data-[current=true]:bg-transparent data-[current=true]:text-primary',\n  mid: 'bg-transparent data-[active=true]:bg-transparent data-[active=true]:text-primary data-[current=true]:bg-transparent data-[current=true]:text-primary',\n  dark: 'data-[current=true]:bg-primary data-[current=true]:text-primary-foreground',\n} as const;\n\nconst StepButtonContainer = ({\n  isCurrentStep,\n  isCompletedStep,\n  children,\n  isError,\n  isLoading: isLoadingProp,\n  onClickStep,\n  label,\n  index,\n}: StepButtonContainerProps) => {\n  const { clickable, isLoading: isLoadingContext, variant, styles, theme } = useStepper();\n  const l = useLabels();\n  const zero = usePreset() === 'zero';\n\n  const currentStepClickable = clickable || !!onClickStep;\n\n  const isLoading = isLoadingProp || isLoadingContext;\n\n  if (variant === 'line') {\n    return null;\n  }\n\n  // Accessible name: \"Step 2: Account, completed\". The status words follow the same rules as the visual marker.\n  const status = isError && (isCurrentStep || isCompletedStep) ? l.stepFailed : isCompletedStep ? l.stepCompleted : isCurrentStep ? l.stepCurrent : undefined;\n  const stepName = l.stepName((index ?? 0) + 1, typeof label === 'string' ? label : undefined);\n  const accessibleName = status ? `${stepName}, ${status}` : stepName;\n\n  return (\n    <Button\n      variant=\"ghost\"\n      type=\"button\"\n      tabIndex={currentStepClickable ? 0 : -1}\n      className={cn(\n        'stepper__step-button-container',\n        'pointer-events-none rounded-full p-0',\n        'h-[var(--step-icon-size)] w-[var(--step-icon-size)]',\n        'flex items-center justify-center rounded-full border-2 border-solid',\n        'data-[clickable=true]:pointer-events-auto',\n        'data-[active=true]:text-white data-[active=true]:border-primary data-[active=true]:bg-primary',\n        'data-[current=true]:bg-secondary data-[current=true]:border-primary',\n        'data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive data-[invalid=true]:text-destructive-foreground',\n        // Zero: the marker is the 22px circle with a soft brand halo on the current step.\n        zero && 'data-[current=true]:ring-halo data-[current=true]:ring-ring/ring-halo',\n        theme && themeClasses[theme],\n        styles?.['step-button-container'],\n      )}\n      data-theme={theme}\n      aria-label={accessibleName}\n      aria-current={isCurrentStep ? 'step' : undefined}\n      data-current={isCurrentStep}\n      data-invalid={isError && (isCurrentStep || isCompletedStep)}\n      data-active={isCompletedStep}\n      data-clickable={currentStepClickable}\n      data-loading={isLoading && (isCurrentStep || isCompletedStep)}\n    >\n      {children}\n    </Button>\n  );\n};\n\nexport { StepButtonContainer };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/step-icon.tsx",
      "content": "'use client';\n\nimport { cva } from 'class-variance-authority';\nimport { CheckIcon, Loader2, X } from 'lucide-react';\n\nimport { forwardRef, useMemo } from 'react';\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\nimport type { IconType } from './types';\nimport { useStepper } from './use-stepper';\n\ninterface StepIconProps {\n  isCompletedStep?: boolean;\n  isCurrentStep?: boolean;\n  isError?: boolean;\n  isLoading?: boolean;\n  isKeepError?: boolean;\n  icon?: IconType;\n  index?: number;\n  checkIcon?: IconType;\n  errorIcon?: IconType;\n}\n\nconst iconVariants = cva('', {\n  variants: {\n    size: {\n      sm: 'size-4',\n      md: 'size-4',\n      lg: 'size-5',\n    },\n  },\n  defaultVariants: {\n    size: 'md',\n  },\n});\n\nconst figmaIconSizes = { sm: 'h-icon-sm w-icon-sm', md: 'h-icon-sm w-icon-sm', lg: 'h-icon-md w-icon-md' } as const;\n\nconst StepIcon = forwardRef<HTMLDivElement, StepIconProps>((props, ref) => {\n  const { size, iconSize } = useStepper();\n  const zero = usePreset() === 'zero';\n  const figma = iconSize === 'figma';\n  // Zero: the marker is 22px, so glyphs use the extra small icon and the number the 11/600 label role.\n  const iconClass = zero ? 'h-icon-xs w-icon-xs' : figma ? figmaIconSizes[size ?? 'md'] : cn(iconVariants({ size }));\n\n  const {\n    isCompletedStep,\n    isCurrentStep,\n    isError,\n    isLoading,\n    isKeepError,\n    icon: CustomIcon,\n    index,\n    checkIcon: CustomCheckIcon,\n    errorIcon: CustomErrorIcon,\n  } = props;\n\n  const Icon = useMemo(() => (CustomIcon ? CustomIcon : null), [CustomIcon]);\n\n  const ErrorIcon = useMemo(() => (CustomErrorIcon ? CustomErrorIcon : null), [CustomErrorIcon]);\n\n  const Check = useMemo(() => (CustomCheckIcon ? CustomCheckIcon : CheckIcon), [CustomCheckIcon]);\n\n  return useMemo(() => {\n    if (isCompletedStep) {\n      if (isError && isKeepError) {\n        return (\n          <div key=\"icon\">\n            <X className={iconClass} />\n          </div>\n        );\n      }\n      return (\n        <div key=\"check-icon\">\n          <Check className={cn(iconClass, '[stroke-width:var(--size-icon-stroke-check,3)]')} />\n        </div>\n      );\n    }\n    if (isCurrentStep) {\n      if (isError && ErrorIcon) {\n        return (\n          <div key=\"error-icon\">\n            <ErrorIcon className={iconClass} />\n          </div>\n        );\n      }\n      if (isError) {\n        return (\n          <div key=\"icon\">\n            <X className={iconClass} />\n          </div>\n        );\n      }\n      if (isLoading) {\n        return <Loader2 className={cn(iconClass, 'animate-spin')} />;\n      }\n    }\n    if (Icon) {\n      return (\n        <div key=\"step-icon\">\n          <Icon className={iconClass} />\n        </div>\n      );\n    }\n    return (\n      <span ref={ref} key=\"label\" className={cn(zero ? 'typography-kpi-label-strong' : figma ? 'typography-ui-medium' : 'text-md', 'text-center font-medium')}>\n        {(index || 0) + 1}\n      </span>\n    );\n  }, [isCompletedStep, isCurrentStep, isError, isLoading, Icon, index, Check, ErrorIcon, isKeepError, ref, iconClass, figma, zero]);\n});\n\nexport { StepIcon };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/step-label.tsx",
      "content": "'use client';\n\nimport { cva } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\nimport { usePreset } from '@/components/ui/labels-context';\nimport { useStepper } from './use-stepper';\n\ninterface StepLabelProps {\n  isCurrentStep?: boolean;\n  // false for a step that is still ahead: the label uses the subtle text role at full opacity\n  hasVisited?: boolean;\n  label?: string | React.ReactNode;\n  description?: string | null;\n}\n\nconst labelVariants = cva('', {\n  variants: {\n    size: {\n      sm: 'text-sm',\n      md: 'text-sm',\n      lg: 'text-base',\n    },\n  },\n  defaultVariants: {\n    size: 'md',\n  },\n});\n\nconst descriptionVariants = cva('', {\n  variants: {\n    size: {\n      sm: 'text-xs',\n      md: 'text-xs',\n      lg: 'text-sm',\n    },\n  },\n  defaultVariants: {\n    size: 'md',\n  },\n});\n\nconst StepLabel = ({ isCurrentStep, hasVisited = true, label, description }: StepLabelProps) => {\n  const { variant, styles, size, orientation } = useStepper();\n  const zero = usePreset() === 'zero';\n  const shouldRender = !!label || !!description;\n\n  return shouldRender ? (\n    <div\n      aria-current={isCurrentStep ? 'step' : undefined}\n      className={cn(\n        'stepper__step-label-container',\n        'flex flex-col',\n        variant !== 'line' ? 'ms-2' : orientation === 'horizontal' && 'my-2',\n        variant === 'circle-alt' && 'text-center',\n        variant === 'circle-alt' && orientation === 'horizontal' && 'ms-0',\n        variant === 'circle-alt' && orientation === 'vertical' && 'text-start',\n        !hasVisited && 'text-text-subtle',\n        styles?.['step-label-container'],\n      )}\n    >\n      {!!label && (\n        <span className={cn('stepper__step-label', zero ? (isCurrentStep ? 'typography-ui-medium' : 'typography-ui') : labelVariants({ size }), styles?.['step-label'])}>{label}</span>\n      )}\n      {!!description && (\n        <span\n          className={cn(\n            'stepper__step-description',\n            'text-muted-foreground',\n            zero ? 'typography-caption' : descriptionVariants({ size }),\n            styles?.['step-description'],\n          )}\n        >\n          {description}\n        </span>\n      )}\n    </div>\n  ) : null;\n};\n\nexport { StepLabel };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/step.tsx",
      "content": "'use client';\n\nimport { forwardRef, Ref } from 'react';\nimport { HorizontalStep } from './horizontal-step';\nimport type { StepProps } from './types';\nimport { useStepper } from './use-stepper';\nimport { VerticalStep } from './vertical-step';\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n  index: number;\n  isCompletedStep?: boolean;\n  isCurrentStep?: boolean;\n  isLastStep?: boolean;\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = forwardRef<HTMLLIElement, StepProps>((props, ref: Ref<any>) => {\n  const {\n    children,\n    description,\n    icon,\n    state,\n    checkIcon,\n    errorIcon,\n    index,\n    isCompletedStep,\n    isCurrentStep,\n    isLastStep,\n    isKeepError,\n    label,\n    onClickStep,\n  } = props as FullStepProps;\n\n  const { isVertical, isError, isLoading, clickable } = useStepper();\n\n  const hasVisited = isCurrentStep || isCompletedStep;\n\n  const sharedProps = {\n    isLastStep,\n    isCompletedStep,\n    isCurrentStep,\n    index,\n    isError,\n    isLoading,\n    clickable,\n    label,\n    description,\n    hasVisited,\n    icon,\n    isKeepError,\n    checkIcon,\n    state,\n    errorIcon,\n    onClickStep,\n  };\n\n  const renderStep = () => {\n    switch (isVertical) {\n      case true:\n        return (\n          <VerticalStep ref={ref} {...sharedProps}>\n            {children}\n          </VerticalStep>\n        );\n      default:\n        return <HorizontalStep ref={ref} {...sharedProps} />;\n    }\n  };\n\n  return renderStep();\n});\n\nexport { Step };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/types.ts",
      "content": "import type { LucideIcon } from 'lucide-react';\n\ntype IconType = LucideIcon | React.ComponentType<any> | undefined;\n\ntype StepItem = {\n  id?: string;\n  label?: string;\n  description?: string;\n  icon?: IconType;\n  optional?: boolean;\n};\n\ninterface StepOptions {\n  orientation?: 'vertical' | 'horizontal';\n  state?: 'loading' | 'error';\n  responsive?: boolean;\n  checkIcon?: IconType;\n  errorIcon?: IconType;\n  onClickStep?: (step: number, setStep: (step: number) => void) => void;\n  mobileBreakpoint?: string;\n  variant?: 'circle' | 'circle-alt' | 'line';\n  expandVerticalSteps?: boolean;\n  size?: 'sm' | 'md' | 'lg';\n  // Indicator treatment (Figma Progress steps Theme): `light` bare icon, `mid` outlined circle, `dark` filled circle. Omit for the established look.\n  theme?: 'light' | 'mid' | 'dark';\n  // `figma` sizes icons and step numbers with the icon size tokens and a type role (the default classes `size-4` and `text-md` do not exist in the preset). Omit for the established look.\n  iconSize?: 'default' | 'figma';\n  styles?: {\n    /** Styles for the main container */\n    'main-container'?: string;\n    /** Styles for the horizontal step */\n    'horizontal-step'?: string;\n    /** Styles for the horizontal step container (button and labels) */\n    'horizontal-step-container'?: string;\n    /** Styles for the vertical step */\n    'vertical-step'?: string;\n    /** Styles for the vertical step container (button and labels) */\n    'vertical-step-container'?: string;\n    /** Styles for the vertical step content */\n    'vertical-step-content'?: string;\n    /** Styles for the step button container */\n    'step-button-container'?: string;\n    /** Styles for the label and description container */\n    'step-label-container'?: string;\n    /** Styles for the step label */\n    'step-label'?: string;\n    /** Styles for the step description */\n    'step-description'?: string;\n  };\n  variables?: {\n    '--step-icon-size'?: string;\n    '--step-gap'?: string;\n  };\n  scrollTracking?: boolean;\n  // Controlled mode: the active step is owned by the parent. Pair with `onStepChange`. Omit for the uncontrolled `initialStep` behaviour.\n  activeStep?: number;\n  // Called with the next step index whenever the stepper asks to move (nextStep, prevStep, setStep, resetSteps, clicks).\n  onStepChange?: (step: number) => void;\n}\n\ninterface StepperProps extends StepOptions {\n  children?: React.ReactNode;\n  className?: string;\n  initialStep: number;\n  steps: StepItem[];\n}\n\ninterface StepProps extends React.HTMLAttributes<HTMLLIElement> {\n  label?: string | React.ReactNode;\n  description?: string;\n  icon?: IconType;\n  state?: 'loading' | 'error';\n  checkIcon?: IconType;\n  errorIcon?: IconType;\n  isCompletedStep?: boolean;\n  isKeepError?: boolean;\n  onClickStep?: (step: number, setStep: (step: number) => void) => void;\n}\n\ninterface StepSharedProps extends StepProps {\n  isLastStep?: boolean;\n  isCurrentStep?: boolean;\n  index?: number;\n  hasVisited: boolean | undefined;\n  isError?: boolean;\n  isLoading?: boolean;\n}\n\nexport type { IconType, StepItem, StepOptions, StepperProps, StepProps, StepSharedProps };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/use-media-query.tsx",
      "content": "'use client';\n\nimport { useEffect, useState } from 'react';\n\n// Works without window.matchMedia (older test DOMs, some embedded webviews): the query then never matches.\nexport function useMediaQuery(query: string) {\n  const [value, setValue] = useState(false);\n\n  useEffect(() => {\n    if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return undefined;\n\n    function onChange(event: MediaQueryListEvent) {\n      setValue(event.matches);\n    }\n\n    const result = window.matchMedia(query);\n    setValue(result.matches);\n    if (typeof result.addEventListener === 'function') {\n      result.addEventListener('change', onChange);\n      return () => result.removeEventListener('change', onChange);\n    }\n    // Safari before 14 only knows the deprecated listener API.\n    result.addListener?.(onChange);\n    return () => result.removeListener?.(onChange);\n  }, [query]);\n\n  return value;\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/use-stepper.ts",
      "content": "'use client';\n\nimport { useContext, useEffect, useRef } from 'react';\nimport { StepperContext } from './context';\n\nfunction usePrevious<T>(value: T): T | undefined {\n  const ref = useRef<T>();\n\n  useEffect(() => {\n    ref.current = value;\n  }, [value]);\n\n  return ref.current;\n}\n\nexport function useStepper() {\n  const context = useContext(StepperContext);\n\n  if (context === undefined) {\n    throw new Error('useStepper must be used within a StepperProvider');\n  }\n\n  const { children, className, ...rest } = context;\n\n  const isLastStep = context.activeStep === context.steps.length - 1;\n  const hasCompletedAllSteps = context.activeStep === context.steps.length;\n\n  const previousActiveStep = usePrevious(context.activeStep);\n\n  const currentStep = context.steps[context.activeStep];\n  const isOptionalStep = !!currentStep?.optional;\n\n  const isDisabledStep = context.activeStep === 0;\n\n  return {\n    ...rest,\n    isLastStep,\n    hasCompletedAllSteps,\n    isOptionalStep,\n    isDisabledStep,\n    currentStep,\n    previousActiveStep,\n  };\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/ui/stepper/vertical-step.tsx",
      "content": "'use client';\n\nimport { cva } from 'class-variance-authority';\nimport { forwardRef } from 'react';\nimport { Collapsible, CollapsibleContent } from '@/components/ui/collapsible';\nimport { cn } from '@/lib/utils';\nimport { StepButtonContainer } from './step-button-container';\nimport { StepIcon } from './step-icon';\nimport { StepLabel } from './step-label';\nimport type { StepSharedProps } from './types';\nimport { useStepper } from './use-stepper';\n\ntype VerticalStepProps = StepSharedProps & {\n  children?: React.ReactNode;\n};\n\nconst verticalStepVariants = cva(\n  [\n    'flex flex-col relative transition-all duration-200',\n    'data-[completed=true]:[&:not(:last-child)]:after:bg-primary',\n    'data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive',\n  ],\n  {\n    variants: {\n      variant: {\n        circle: cn(\n          '[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]',\n          \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-button-border-outline-default\",\n          '[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]',\n          '[&:not(:last-child)]:after:absolute',\n          '[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]',\n          '[&:not(:last-child)]:after:bottom-[var(--step-gap)]',\n          '[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200',\n        ),\n        line: 'flex-1 border-t-0 mb-4',\n      },\n    },\n  },\n);\n\nconst VerticalStep = forwardRef<HTMLDivElement, VerticalStepProps>((props, ref) => {\n  const {\n    children,\n    index,\n    isCompletedStep,\n    isCurrentStep,\n    label,\n    description,\n    icon,\n    hasVisited,\n    state,\n    checkIcon: checkIconProp,\n    errorIcon: errorIconProp,\n    onClickStep,\n  } = props;\n\n  const {\n    checkIcon: checkIconContext,\n    errorIcon: errorIconContext,\n    isError,\n    isLoading,\n    variant,\n    onClickStep: onClickStepGeneral,\n    clickable,\n    expandVerticalSteps,\n    styles,\n    scrollTracking,\n    orientation,\n    steps,\n    setStep,\n    isLastStep: isLastStepCurrentStep,\n    iconSize,\n    previousActiveStep,\n  } = useStepper();\n\n  const localIsLoading = isLoading || state === 'loading';\n  const localIsError = isError || state === 'error';\n\n  const isLastStep = index === steps.length - 1;\n\n  const active = variant === 'line' ? isCompletedStep || isCurrentStep : isCompletedStep;\n  const checkIcon = checkIconProp || checkIconContext;\n  const errorIcon = errorIconProp || errorIconContext;\n\n  const renderChildren = () => {\n    if (!expandVerticalSteps) {\n      return (\n        <Collapsible open={isCurrentStep}>\n          <CollapsibleContent\n            ref={(node) => {\n              if (\n                // If the step is the first step and the previous step\n                // was the last step or if the step is not the first step\n                // This prevents initial scrolling when the stepper\n                // is located anywhere other than the top of the view.\n                scrollTracking &&\n                ((index === 0 && previousActiveStep && previousActiveStep === steps.length) || (index && index > 0))\n              ) {\n                node?.scrollIntoView({\n                  behavior: 'smooth',\n                  block: 'center',\n                });\n              }\n            }}\n            className=\"data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n          >\n            {children}\n          </CollapsibleContent>\n        </Collapsible>\n      );\n    }\n    return children;\n  };\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        'stepper__vertical-step',\n        verticalStepVariants({\n          variant: variant?.includes('circle') ? 'circle' : 'line',\n        }),\n        isLastStepCurrentStep && 'gap-[var(--step-gap)]',\n        styles?.['vertical-step'],\n      )}\n      data-optional={steps[index || 0]?.optional}\n      data-completed={isCompletedStep}\n      data-active={active}\n      data-clickable={clickable || !!onClickStep}\n      data-invalid={localIsError}\n      onClick={() => onClickStep?.(index || 0, setStep) || onClickStepGeneral?.(index || 0, setStep)}\n    >\n      <div\n        data-vertical={true}\n        data-active={active}\n        className={cn(\n          'stepper__vertical-step-container',\n          'flex items-center',\n          variant === 'line' && 'border-s-[3px] border-solid py-2 ps-3 data-[active=true]:border-primary',\n          styles?.['vertical-step-container'],\n        )}\n      >\n        <StepButtonContainer {...{ isLoading: localIsLoading, isError: localIsError, ...props }}>\n          <StepIcon\n            {...{\n              index,\n              isError: localIsError,\n              isLoading: localIsLoading,\n              isCurrentStep,\n              isCompletedStep,\n            }}\n            icon={icon}\n            checkIcon={checkIcon}\n            errorIcon={errorIcon}\n          />\n        </StepButtonContainer>\n        <StepLabel label={label} description={description} {...{ isCurrentStep, hasVisited }} />\n      </div>\n      <div\n        className={cn(\n          'stepper__vertical-step-content',\n          !isLastStep && (iconSize === 'figma' ? 'pb-4' : 'min-h-4'),\n          variant !== 'line' && 'ps-[--step-icon-size]',\n          variant === 'line' && orientation === 'vertical' && 'min-h-0',\n          styles?.['vertical-step-content'],\n        )}\n      >\n        {renderChildren()}\n      </div>\n    </div>\n  );\n});\n\nexport { VerticalStep };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}