{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stepper",
  "title": "Stepper",
  "author": "@fma-ui/ui",
  "description": "Multi-step navigation, horizontal or vertical, built on react-aria `Tabs`: arrow keys, Home and End move between steps and Enter or Space selects one (manual activation). `value`/`defaultValue`/`onValueChange` hold the current step. Each `StepperItem` is a tab whose `data-state` is `completed` (before the current step, or `isCompleted`), `active` or `inactive`; `isDisabled` skips it and `isLoading` shows the loading indicator. `indicators` swaps an indicator's content per state. `StepperContent` panels show the current step's content.",
  "dependencies": [
    "cn@^0.4.0",
    "react-aria-components@^1.21.1"
  ],
  "files": [
    {
      "path": "src/core/stepper/stepper.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport * as React from \"react\";\nimport {\n  composeRenderProps,\n  Tab,\n  TabList,\n  type TabListProps,\n  TabPanel,\n  type TabPanelProps,\n  Tabs,\n  type TabsProps,\n} from \"react-aria-components\";\n\ntype StepState = \"active\" | \"completed\" | \"inactive\";\n\ntype StepIndicators = {\n  active?: React.ReactNode;\n  completed?: React.ReactNode;\n  inactive?: React.ReactNode;\n  loading?: React.ReactNode;\n};\n\n// Provided above the TabList, so it also reaches StepperItem while react-aria\n// builds the collection (TabListStateContext doesn't exist yet there).\nconst StepperContext = React.createContext<{ activeStep: number; indicators: StepIndicators }>({\n  activeStep: 1,\n  indicators: {},\n});\n\ntype StepItemContextValue = { step: number; state: StepState; isLoading: boolean };\n\nconst StepItemContext = React.createContext<StepItemContextValue | null>(null);\n\nfunction useStepItem() {\n  const context = React.useContext(StepItemContext);\n  if (!context) throw new Error(\"Stepper parts must be used within a StepperItem\");\n  return context;\n}\n\ntype StepperProps = Omit<\n  TabsProps,\n  \"selectedKey\" | \"defaultSelectedKey\" | \"onSelectionChange\" | \"keyboardActivation\"\n> & {\n  /** The current step (controlled). */\n  value?: number;\n  /** The initial step (uncontrolled). */\n  defaultValue?: number;\n  onValueChange?: (value: number) => void;\n  /** Replace an indicator's content per state; falls back to its children. */\n  indicators?: StepIndicators;\n};\n\n/**\n * Built on react-aria `Tabs` with manual activation: arrow keys, Home and End\n * move focus between steps, Enter or Space selects one.\n */\nfunction Stepper({\n  value,\n  defaultValue = 1,\n  onValueChange,\n  orientation = \"horizontal\",\n  indicators = {},\n  className,\n  ...props\n}: StepperProps) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultValue);\n  const activeStep = value ?? uncontrolled;\n  const context = React.useMemo(() => ({ activeStep, indicators }), [activeStep, indicators]);\n  return (\n    <StepperContext.Provider value={context}>\n      <Tabs\n        data-slot=\"stepper\"\n        selectedKey={activeStep}\n        onSelectionChange={(key) => {\n          setUncontrolled(Number(key));\n          onValueChange?.(Number(key));\n        }}\n        orientation={orientation}\n        keyboardActivation=\"manual\"\n        className={composeRenderProps(className, (className) => cn(\"w-full\", className))}\n        {...props}\n      />\n    </StepperContext.Provider>\n  );\n}\n\nfunction StepperNav<T extends object>({ className, ...props }: TabListProps<T>) {\n  return (\n    <TabList\n      data-slot=\"stepper-nav\"\n      className={composeRenderProps(className, (className) =>\n        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      )}\n      {...props}\n    />\n  );\n}\n\ntype StepperItemProps = Omit<React.ComponentProps<typeof Tab>, \"id\" | \"children\"> & {\n  step: number;\n  /** Marks the step completed even when it isn't before the current one. */\n  isCompleted?: boolean;\n  /** Shows the loading indicator while this is the current step. */\n  isLoading?: boolean;\n  children?: React.ReactNode;\n};\n\n/** One step: the tab itself, holding its trigger and the separator after it. */\nfunction StepperItem({\n  step,\n  isCompleted = false,\n  isLoading = false,\n  className,\n  children,\n  ...props\n}: StepperItemProps) {\n  const { activeStep } = React.useContext(StepperContext);\n  const state: StepState =\n    isCompleted || step < activeStep ? \"completed\" : step === activeStep ? \"active\" : \"inactive\";\n  const loading = isLoading && step === activeStep;\n\n  return (\n    <Tab\n      id={step}\n      data-slot=\"stepper-item\"\n      data-state={state}\n      data-loading={loading || undefined}\n      className={composeRenderProps(className, (className) =>\n        cn(\n          \"group/step flex items-center justify-center outline-none not-last:flex-1 group-data-[orientation=horizontal]/stepper-nav:flex-row group-data-[orientation=vertical]/stepper-nav:flex-col group-data-[orientation=horizontal]/stepper-nav:items-start group-data-[orientation=vertical]/stepper-nav:items-start data-disabled:opacity-60\",\n          className,\n        ),\n      )}\n      {...props}\n    >\n      <StepItemContext.Provider value={{ step, state, isLoading: loading }}>\n        {children}\n      </StepItemContext.Provider>\n    </Tab>\n  );\n}\n\n/** The clickable-looking part of a step; carries the focus ring. */\n// The separator's 11px offset (top when horizontal, start when vertical)\n// lines it up with the center of the 24px indicator, so keep the indicator\n// first in the trigger.\nfunction StepperTrigger({ className, ...props }: React.ComponentProps<\"span\">) {\n  const { state, isLoading } = useStepItem();\n  return (\n    <span\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 group-data-disabled/step:cursor-default group-data-focus-visible/step:z-10 group-data-focus-visible/step:ring-3 group-data-focus-visible/step:ring-ring/50\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction StepperIndicator({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const { state, isLoading } = useStepItem();\n  const { indicators } = React.useContext(StepperContext);\n  const custom = (isLoading && indicators.loading) || indicators[state];\n\n  return (\n    <div\n      data-slot=\"stepper-indicator\"\n      data-state={state}\n      className={cn(\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=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:bg-primary data-[state=completed]:text-primary-foreground\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"absolute\">{custom || children}</div>\n    </div>\n  );\n}\n\nfunction StepperSeparator({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { state } = useStepItem();\n  return (\n    <div\n      aria-hidden=\"true\"\n      data-slot=\"stepper-separator\"\n      data-state={state}\n      className={cn(\n        \"m-0.5 rounded-sm bg-muted data-[state=completed]:bg-primary group-data-[orientation=horizontal]/stepper-nav:mt-[11px] group-data-[orientation=horizontal]/stepper-nav:h-0.5 group-data-[orientation=horizontal]/stepper-nav:flex-1 group-data-[orientation=vertical]/stepper-nav:ms-[11px] group-data-[orientation=vertical]/stepper-nav:h-12 group-data-[orientation=vertical]/stepper-nav:w-0.5\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction StepperTitle({ className, ...props }: React.ComponentProps<\"h3\">) {\n  const { state } = useStepItem();\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  );\n}\n\nfunction StepperDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { state } = useStepItem();\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  );\n}\n\nfunction StepperPanel({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"stepper-panel\" className={cn(\"w-full\", className)} {...props} />;\n}\n\ntype StepperContentProps = Omit<TabPanelProps, \"id\"> & {\n  /** The step this content belongs to. */\n  value: number;\n};\n\n/** A step's content, shown while that step is current. `shouldForceMount` keeps it mounted. */\nfunction StepperContent({ value, className, ...props }: StepperContentProps) {\n  return (\n    <TabPanel\n      id={value}\n      data-slot=\"stepper-content\"\n      className={composeRenderProps(className, (className) =>\n        cn(\"w-full outline-none data-inert:hidden\", className),\n      )}\n      {...props}\n    />\n  );\n}\n\nexport type { StepperContentProps, StepperItemProps, StepperProps };\nexport {\n  Stepper,\n  StepperContent,\n  StepperDescription,\n  StepperIndicator,\n  StepperItem,\n  StepperNav,\n  StepperPanel,\n  StepperSeparator,\n  StepperTitle,\n  StepperTrigger,\n  useStepItem,\n};\n",
      "type": "registry:component",
      "target": "components/fma-ui/stepper.tsx"
    }
  ],
  "meta": {
    "status": "stable",
    "source": "third-party",
    "origin": "reui.io (https://reui.io/docs/components/base/stepper, MIT), via EMITTE; rebuilt on react-aria Tabs",
    "tags": [
      "navigation",
      "stepper",
      "wizard",
      "tabs",
      "react-aria"
    ]
  },
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}