{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "description": "Numbered steps that fill as you advance.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/utils.json",
    "https://ui.ndekocode.dev/r/icons.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/ui/stepper.tsx",
      "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { createContext, useCallback, useContext, useState } from 'react';\n\nimport { CheckIcon, LoaderCircleIcon } from '@/registry/ndeko/lib/icons';\nimport { cn } from '@/registry/ndeko/lib/utils';\n\ntype StepperOrientation = 'horizontal' | 'vertical';\n\ntype StepState = 'active' | 'completed' | 'inactive';\n\ntype StepperContextValue = {\n  activeStep: number;\n  setActiveStep: (step: number) => void;\n  orientation: StepperOrientation;\n};\n\ntype StepItemContextValue = {\n  step: number;\n  state: StepState;\n  isDisabled: boolean;\n  isLoading: boolean;\n};\n\nconst StepperContext = createContext<StepperContextValue | undefined>(\n  undefined\n);\nconst StepItemContext = createContext<StepItemContextValue | undefined>(\n  undefined\n);\n\nfunction useStepper() {\n  const context = useContext(StepperContext);\n  if (!context) {\n    throw new Error('useStepper must be used within a Stepper');\n  }\n  return context;\n}\n\nfunction useStepItem() {\n  const context = useContext(StepItemContext);\n  if (!context) {\n    throw new Error('useStepItem must be used within a StepperItem');\n  }\n  return context;\n}\n\ntype StepperProps = React.ComponentProps<'div'> & {\n  defaultValue?: number;\n  value?: number;\n  onValueChange?: (value: number) => void;\n  orientation?: StepperOrientation;\n};\n\nfunction Stepper({\n  defaultValue = 0,\n  value,\n  onValueChange,\n  orientation = 'horizontal',\n  className,\n  ...props\n}: StepperProps) {\n  const [internalStep, setInternalStep] = useState(defaultValue);\n  const isControlled = value !== undefined;\n  const activeStep = value ?? internalStep;\n\n  const setActiveStep = useCallback(\n    (step: number) => {\n      if (!isControlled) {\n        setInternalStep(step);\n      }\n      onValueChange?.(step);\n    },\n    [isControlled, onValueChange]\n  );\n\n  return (\n    <StepperContext.Provider value={{ activeStep, setActiveStep, orientation }}>\n      <div\n        data-slot='stepper'\n        data-orientation={orientation}\n        className={cn(\n          'group/stepper inline-flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col',\n          className\n        )}\n        {...props}\n      />\n    </StepperContext.Provider>\n  );\n}\n\nfunction stateOfStep(step: number, activeStep: number, completed: boolean) {\n  const isBefore = step < activeStep;\n  if (completed || isBefore) {\n    return 'completed' as const;\n  }\n  if (step === activeStep) {\n    return 'active' as const;\n  }\n  return 'inactive' as const;\n}\n\ntype StepperItemProps = React.ComponentProps<'div'> & {\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 = stateOfStep(step, activeStep, completed);\n  const isActive = step === activeStep;\n  const isLoading = loading && isActive;\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 group-data-[orientation=horizontal]/stepper:flex-row group-data-[orientation=vertical]/stepper:flex-col',\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </StepItemContext.Provider>\n  );\n}\n\nfunction StepperTrigger({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'>) {\n  const { setActiveStep } = useStepper();\n  const { step, isDisabled } = useStepItem();\n  const defaultProps = {\n    'data-slot': 'stepper-trigger',\n    type: 'button' as const,\n    disabled: isDisabled,\n    onClick: () => setActiveStep(step),\n    className: cn(\n      'inline-flex items-center gap-3 rounded-full text-left outline-none focus-visible:z-10 focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:pointer-events-none disabled:opacity-50',\n      'transition-[scale] duration-150 ease-(--ease-out-strong) active:scale-[0.97]',\n      className\n    ),\n  };\n\n  return useRender({\n    defaultTagName: 'button',\n    render,\n    props: mergeProps<'button'>(defaultProps, props),\n  });\n}\n\nconst STEP_SWAP =\n  'transition-[scale,opacity,filter] duration-200 ease-(--ease-out-strong)';\n\nconst STEP_NUMBER = [\n  STEP_SWAP,\n  'group-data-loading/step:scale-60 group-data-loading/step:opacity-0 group-data-loading/step:blur-[2px]',\n  'group-data-[state=completed]/step:scale-60 group-data-[state=completed]/step:opacity-0 group-data-[state=completed]/step:blur-[2px]',\n].join(' ');\n\nconst STEP_CHECK = [\n  STEP_SWAP,\n  'absolute size-3.5 scale-60 opacity-0 blur-[2px]',\n  'group-data-[state=completed]/step:scale-100 group-data-[state=completed]/step:opacity-100 group-data-[state=completed]/step:blur-none',\n].join(' ');\n\nconst INDICATOR = [\n  'relative flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-medium tabular-nums',\n  'bg-foreground/[0.06] text-muted-foreground transition-colors duration-200',\n  'data-[state=active]:bg-primary data-[state=active]:text-primary-foreground',\n  'data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground',\n].join(' ');\n\nfunction StepperIndicator({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<'span'>) {\n  const { state, step, isLoading } = useStepItem();\n  const hasCustomContent = children !== undefined;\n\n  return (\n    <span\n      data-slot='stepper-indicator'\n      data-state={state}\n      className={cn(INDICATOR, className)}\n      {...props}\n    >\n      {hasCustomContent && children}\n      {!hasCustomContent && (\n        <>\n          <span className={STEP_NUMBER}>{step}</span>\n          <CheckIcon strokeWidth={2.5} className={STEP_CHECK} />\n          {isLoading && (\n            <LoaderCircleIcon className='absolute size-3.5 animate-spin' />\n          )}\n        </>\n      )}\n    </span>\n  );\n}\n\nfunction StepperTitle({ className, ...props }: React.ComponentProps<'h3'>) {\n  return (\n    <h3\n      data-slot='stepper-title'\n      className={cn('text-[13px] font-medium', className)}\n      {...props}\n    />\n  );\n}\n\nfunction StepperDescription({\n  className,\n  ...props\n}: React.ComponentProps<'p'>) {\n  return (\n    <p\n      data-slot='stepper-description'\n      className={cn('text-muted-foreground text-xs', className)}\n      {...props}\n    />\n  );\n}\n\nconst SEPARATOR = [\n  'bg-foreground/10 relative m-0.5 rounded-full transition-colors duration-300',\n  'group-data-[orientation=horizontal]/stepper:h-0.5 group-data-[orientation=horizontal]/stepper:w-full group-data-[orientation=horizontal]/stepper:flex-1',\n  'group-data-[orientation=vertical]/stepper:h-12 group-data-[orientation=vertical]/stepper:w-0.5',\n].join(' ');\n\nconst SEPARATOR_FILL = [\n  'after:bg-primary after:absolute after:inset-0 after:rounded-full after:transition-[scale] after:duration-200 after:ease-(--ease-out-strong)',\n  'group-data-[state=completed]/step:after:duration-300',\n  'group-data-[orientation=horizontal]/stepper:after:origin-left group-data-[orientation=horizontal]/stepper:rtl:after:origin-right',\n  'group-data-[orientation=horizontal]/stepper:after:scale-x-0 group-data-[orientation=horizontal]/stepper:group-data-[state=completed]/step:after:scale-x-100',\n  'group-data-[orientation=vertical]/stepper:after:origin-top',\n  'group-data-[orientation=vertical]/stepper:after:scale-y-0 group-data-[orientation=vertical]/stepper:group-data-[state=completed]/step:after:scale-y-100',\n].join(' ');\n\nfunction StepperSeparator({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='stepper-separator'\n      className={cn(SEPARATOR, SEPARATOR_FILL, className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Stepper,\n  StepperDescription,\n  StepperIndicator,\n  StepperItem,\n  StepperSeparator,\n  StepperTitle,\n  StepperTrigger,\n  useStepItem,\n  useStepper,\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}