{"$schema":"https://ui.logic2b.com/schema/registry-item.json","name":"stepper","type":"registry:ui","description":"A multi-step progress indicator for wizards, checkouts and onboarding flows, horizontal or vertical. Steps derive completed/active/upcoming state from the root value; the active step carries aria-current. Parts: Stepper (root, value/orientation), StepperItem (step), StepperTrigger, StepperIndicator (number/check), StepperTitle, StepperDescription, StepperSeparator.","dependencies":["lucide-react"],"registryDependencies":["utils"],"files":[{"path":"ui/stepper.tsx","type":"registry:ui","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Stepper — a multi-step progress indicator for wizards and onboarding\n * flows. Steps derive their state (completed / active / upcoming) from the\n * root value; the active step carries aria-current=\"step\".\n */\n\ninterface StepperContextValue {\n  value: number\n  setValue: (step: number) => void\n  orientation: \"horizontal\" | \"vertical\"\n}\n\nconst StepperContext = React.createContext<StepperContextValue | null>(null)\nconst StepItemContext = React.createContext<{\n  step: number\n  state: \"completed\" | \"active\" | \"upcoming\"\n  disabled: boolean\n} | null>(null)\n\nfunction useStepper() {\n  const ctx = React.useContext(StepperContext)\n  if (!ctx) throw new Error(\"Stepper parts must be used within a Stepper.\")\n  return ctx\n}\n\nfunction useStepItem() {\n  const ctx = React.useContext(StepItemContext)\n  if (!ctx) throw new Error(\"StepperItem parts must be used within a StepperItem.\")\n  return ctx\n}\n\nfunction Stepper({\n  className,\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  orientation = \"horizontal\",\n  ...props\n}: Omit<React.ComponentProps<\"ol\">, \"defaultValue\"> & {\n  value?: number\n  defaultValue?: number\n  onValueChange?: (step: number) => void\n  orientation?: \"horizontal\" | \"vertical\"\n}) {\n  const [internal, setInternal] = React.useState(defaultValue ?? 1)\n  const isControlled = valueProp !== undefined\n  const value = isControlled ? valueProp : internal\n  const setValue = React.useCallback(\n    (step: number) => {\n      if (!isControlled) setInternal(step)\n      onValueChange?.(step)\n    },\n    [isControlled, onValueChange]\n  )\n\n  return (\n    <StepperContext.Provider value={{ value, setValue, orientation }}>\n      <ol\n        data-slot=\"stepper\"\n        data-orientation={orientation}\n        className={cn(\n          \"group/stepper flex\",\n          orientation === \"horizontal\"\n            ? \"w-full flex-row items-center\"\n            : \"flex-col\",\n          className\n        )}\n        {...props}\n      />\n    </StepperContext.Provider>\n  )\n}\n\nfunction StepperItem({\n  className,\n  step,\n  disabled = false,\n  ...props\n}: React.ComponentProps<\"li\"> & {\n  step: number\n  disabled?: boolean\n}) {\n  const { value, orientation } = useStepper()\n  const state =\n    step < value ? \"completed\" : step === value ? \"active\" : \"upcoming\"\n\n  return (\n    <StepItemContext.Provider value={{ step, state, disabled }}>\n      <li\n        data-slot=\"stepper-item\"\n        data-state={state}\n        aria-current={state === \"active\" ? \"step\" : undefined}\n        className={cn(\n          \"group/stepper-item relative flex items-center gap-2\",\n          orientation === \"horizontal\"\n            ? \"flex-1 not-last:pr-2\"\n            : \"w-full flex-col items-start not-last:pb-2 [&:not(:last-child)]:min-h-16\",\n          className\n        )}\n        {...props}\n      />\n    </StepItemContext.Provider>\n  )\n}\n\nfunction StepperTrigger({\n  className,\n  ...props\n}: React.ComponentProps<\"button\">) {\n  const { setValue } = useStepper()\n  const { step, disabled } = useStepItem()\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"stepper-trigger\"\n      disabled={disabled}\n      onClick={() => setValue(step)}\n      className={cn(\n        \"focus-visible:border-ring focus-visible:ring-ring/50 flex items-center gap-3 rounded-md text-left outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction StepperIndicator({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  const { step, state } = useStepItem()\n  return (\n    <span\n      data-slot=\"stepper-indicator\"\n      data-state={state}\n      className={cn(\n        \"flex size-6 shrink-0 items-center justify-center rounded-full text-xs font-medium transition-colors\",\n        state === \"completed\" && \"bg-primary text-primary-foreground\",\n        state === \"active\" && \"bg-primary text-primary-foreground ring-primary/20 ring-4\",\n        state === \"upcoming\" && \"bg-muted text-muted-foreground\",\n        className\n      )}\n      {...props}\n    >\n      {children ??\n        (state === \"completed\" ? <CheckIcon className=\"size-3.5\" /> : step)}\n    </span>\n  )\n}\n\nfunction StepperTitle({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"stepper-title\"\n      className={cn(\"text-sm leading-tight font-medium\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction StepperDescription({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"stepper-description\"\n      className={cn(\"text-muted-foreground text-xs leading-tight\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction StepperSeparator({\n  className,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const { orientation } = useStepper()\n  const { state } = useStepItem()\n  return (\n    <div\n      data-slot=\"stepper-separator\"\n      data-state={state}\n      aria-hidden=\"true\"\n      className={cn(\n        \"bg-border transition-colors\",\n        state === \"completed\" && \"bg-primary\",\n        orientation === \"horizontal\"\n          ? \"mx-2 h-px flex-1\"\n          : \"absolute top-8 left-3 h-[calc(100%-2.5rem)] w-px -translate-x-1/2\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Stepper,\n  StepperItem,\n  StepperTrigger,\n  StepperIndicator,\n  StepperTitle,\n  StepperDescription,\n  StepperSeparator,\n}\n"}],"categories":["navigation"],"accessibility":{"support":"authored","pattern":"step list","primitive":"HTML ordered list + buttons","keyboard":[{"keys":["Enter","Space"],"action":"Activate the focused control."}],"aria":["The active item exposes aria-current=step; separators are aria-hidden."],"consumer":["Give every trigger descriptive text and do not rely on color alone for completed/current state."]},"api":{"source":"src/ui/stepper.tsx","exports":[{"name":"Stepper","kind":"component","propsType":"Omit<React.ComponentProps<\"ol\">, \"defaultValue\"> & { value?: number defaultValue?: number onValueChange?: (step: number) => void orientation?: \"horizontal\" | \"vertical\" }","props":[{"name":"value","type":"number","required":false},{"name":"defaultValue","type":"number","required":false},{"name":"onValueChange","type":"(step: number) => void","required":false},{"name":"orientation","type":"\"horizontal\" | \"vertical\"","required":false,"default":"\"horizontal\""}]},{"name":"StepperItem","kind":"component","propsType":"React.ComponentProps<\"li\"> & { step: number disabled?: boolean }","props":[{"name":"step","type":"number","required":true},{"name":"disabled","type":"boolean","required":false,"default":"false"}]},{"name":"StepperTrigger","kind":"component","propsType":"React.ComponentProps<\"button\">"},{"name":"StepperIndicator","kind":"component","propsType":"React.ComponentProps<\"span\">"},{"name":"StepperTitle","kind":"component","propsType":"React.ComponentProps<\"span\">"},{"name":"StepperDescription","kind":"component","propsType":"React.ComponentProps<\"span\">"},{"name":"StepperSeparator","kind":"component","propsType":"React.ComponentProps<\"div\">"}]}}