{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-steps",
  "title": "Progress Steps",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "An ordered progress sequence with current-step semantics and optional navigation to completed steps.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/progress-steps.tsx",
      "content": "// Ballmac UI: Progress Steps. https://ui.ballmac.com/components/progress-steps\n\"use client\"\n\nimport * as React from \"react\"\nimport { Check } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { useMessages } from \"@/lib/ballmac/i18n\"\n\ntype ProgressStep = {\n  /** Stable step ID. */ id: string\n  /** Visible step label. */ label: string\n  /** Optional short description. */ description?: string\n}\ntype ProgressStepsProps = Omit<React.ComponentProps<\"ol\">, \"children\"> & {\n  /** Ordered steps in the process. */\n  steps: ProgressStep[]\n  /** Controlled active step index. */\n  activeIndex?: number\n  /** Initially active step index. */\n  defaultActiveIndex?: number\n  /** Called when a navigable step is selected. */\n  onActiveIndexChange?: (index: number) => void\n  /** Allow returning to completed steps with a button. */\n  navigable?: boolean\n  /** Accessible name for the sequence. */\n  label?: string\n}\n\nfunction ProgressSteps({\n  className,\n  steps,\n  activeIndex,\n  defaultActiveIndex = 0,\n  onActiveIndexChange,\n  navigable = false,\n  label,\n  ...props\n}: ProgressStepsProps) {\n  const msg = useMessages()\n  label ??= msg(\"progress-steps.label\", \"Progress\")\n  const [internal, setInternal] = React.useState(defaultActiveIndex)\n  const active = Math.max(\n    0,\n    Math.min(steps.length - 1, activeIndex ?? internal),\n  )\n  function choose(index: number) {\n    if (activeIndex === undefined) setInternal(index)\n    onActiveIndexChange?.(index)\n  }\n  return (\n    <ol\n      data-slot=\"progress-steps\"\n      aria-label={label}\n      className={cn(\"grid w-full min-w-0 gap-2\", className)}\n      {...props}\n    >\n      {steps.map((step, index) => {\n        const complete = index < active\n        const current = index === active\n        const content = (\n          <>\n            <span\n              aria-hidden=\"true\"\n              className={cn(\n                \"flex size-7 shrink-0 items-center justify-center rounded-full border text-xs font-semibold tabular-nums\",\n                complete && \"border-primary bg-primary text-primary-foreground\",\n                current && \"border-primary text-primary\",\n                !complete &&\n                  !current &&\n                  \"border-border bg-muted text-muted-foreground\",\n              )}\n            >\n              {complete ? <Check className=\"size-3.5\" /> : index + 1}\n            </span>\n            <span className=\"min-w-0 text-start\">\n              <span\n                className={cn(\n                  \"block text-sm font-medium\",\n                  !complete && !current && \"text-muted-foreground\",\n                )}\n              >\n                {step.label}\n              </span>\n              {step.description && (\n                <span className=\"text-muted-foreground block text-xs\">\n                  {step.description}\n                </span>\n              )}\n            </span>\n          </>\n        )\n        return (\n          <li\n            key={step.id}\n            data-slot=\"progress-steps-item\"\n            className=\"relative min-w-0 pb-2 before:absolute before:top-8 before:bottom-0 before:start-3.5 before:w-px before:bg-border last:pb-0 last:before:hidden\"\n          >\n            {navigable && complete ? (\n              <button\n                type=\"button\"\n                aria-label={msg(\"progress-steps.returnTo\", \"Return to {label}\", { label: step.label })}\n                onClick={() => choose(index)}\n                className=\"hover:bg-accent relative flex min-h-9 w-full items-center gap-3 rounded-md px-1 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n              >\n                {content}\n              </button>\n            ) : (\n              <div\n                aria-current={current ? \"step\" : undefined}\n                className=\"relative flex min-h-9 items-center gap-3 px-1\"\n              >\n                {content}\n              </div>\n            )}\n          </li>\n        )\n      })}\n    </ol>\n  )\n}\n\nexport { ProgressSteps, type ProgressStepsProps, type ProgressStep }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/progress-steps.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "steps",
      "wizard",
      "progress"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "An ordered progress sequence with current-step semantics and optional navigation to completed steps.",
      "whenToUse": [
        "Show a multi-step flow",
        "Let users revisit completed steps"
      ],
      "whenNotToUse": [
        "Use progress for an unlabelled percentage"
      ],
      "composesWith": [
        "stepper-form",
        "progress"
      ],
      "a11y": [
        {
          "keys": "Tab / Enter",
          "action": "Return to a completed step when navigable"
        }
      ],
      "customization": [
        "Controlled or uncontrolled active index",
        "Optional descriptions"
      ]
    },
    "examples": [
      "progress-steps-demo",
      "progress-steps-states"
    ],
    "url": "https://ui.ballmac.com/components/progress-steps"
  },
  "categories": [
    "feedback"
  ],
  "type": "registry:ui"
}