{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cicd-pipeline",
  "type": "registry:component",
  "title": "CI/CD Pipeline",
  "description": "Build, test, deploy, verify pipeline status with typed stage metadata.",
  "author": "Stepperize",
  "dependencies": [
    "@stepperize/react",
    "lucide-react"
  ],
  "registryDependencies": [],
  "categories": [
    "async"
  ],
  "meta": {
    "capabilities": [
      "metadata"
    ],
    "level": "intermediate",
    "tags": [
      "ci",
      "cd",
      "pipeline",
      "stages",
      "devops",
      "metadata"
    ]
  },
  "files": [
    {
      "path": "components/stepperize/cicd-pipeline.tsx",
      "type": "registry:component",
      "target": "components/stepperize/cicd-pipeline.tsx",
      "content": "\"use client\";\n\nimport { defineStepper } from \"@stepperize/react\";\nimport {\n\tCheck,\n\tHammer,\n\tLoader2,\n\tRocket,\n\tShieldCheck,\n\tTestTube,\n} from \"lucide-react\";\nimport type { ComponentType } from \"react\";\nimport { useState } from \"react\";\n\n// `duration` is typed metadata on each stage, read back off the typed `step` in\n// the render prop — no parallel timing map to keep in sync.\nconst steps = [\n\t{\n\t\tid: \"build\",\n\t\ttitle: \"Build\",\n\t\tdescription: \"Compile & bundle\",\n\t\ticon: Hammer,\n\t\tduration: \"1m 12s\",\n\t},\n\t{\n\t\tid: \"test\",\n\t\ttitle: \"Test\",\n\t\tdescription: \"Run the suite\",\n\t\ticon: TestTube,\n\t\tduration: \"3m 04s\",\n\t},\n\t{\n\t\tid: \"deploy\",\n\t\ttitle: \"Deploy\",\n\t\tdescription: \"Push to production\",\n\t\ticon: Rocket,\n\t\tduration: \"0m 48s\",\n\t},\n\t{\n\t\tid: \"verify\",\n\t\ttitle: \"Verify\",\n\t\tdescription: \"Smoke checks\",\n\t\ticon: ShieldCheck,\n\t\tduration: \"0m 22s\",\n\t},\n] as const;\n\nconst icons: Record<\n\tstring,\n\tComponentType<{ className?: string }>\n> = Object.fromEntries(steps.map((s) => [s.id, s.icon]));\n\nconst { Stepper } = defineStepper(steps);\n\nexport function CicdPipelineBlock() {\n\tconst [released, setReleased] = useState(false);\n\n\treturn (\n\t\t<Stepper.Root\n\t\t\torientation=\"vertical\"\n\t\t\tclassName=\"w-full max-w-md rounded-xl border bg-background p-6 shadow-sm\"\n\t\t>\n\t\t\t{({ stepper }) => (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"mb-5 flex items-center gap-2 font-mono text-sm\">\n\t\t\t\t\t\t<span className=\"size-2 rounded-full bg-chart-2\" />\n\t\t\t\t\t\t<span className=\"font-semibold\">pipeline</span>\n\t\t\t\t\t\t<span className=\"text-muted-foreground\">#1024 · main</span>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<Stepper.List orientation=\"vertical\" className=\"flex flex-col\">\n\t\t\t\t\t\t<Stepper.Items>\n\t\t\t\t\t\t\t{(step, index) => {\n\t\t\t\t\t\t\t\tconst Icon = icons[step.id];\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<Stepper.Item\n\t\t\t\t\t\t\t\t\t\tkey={step.id}\n\t\t\t\t\t\t\t\t\t\tstep={step.id}\n\t\t\t\t\t\t\t\t\t\tclassName=\"group/item relative flex gap-3 pb-5 last:pb-0\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{index < stepper.count - 1 && (\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"absolute top-9 left-4 h-[calc(100%-2.25rem)] w-px bg-border group-data-[status=previous]/item:bg-chart-2\" />\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t<Stepper.Indicator className=\"grid size-8 shrink-0 place-items-center rounded-lg border transition-colors data-[status=active]:border-primary data-[status=active]:bg-primary/10 data-[status=active]:text-primary data-[status=previous]:border-chart-2/40 data-[status=previous]:bg-chart-2/10 data-[status=previous]:text-chart-2 data-[status=upcoming]:border-border data-[status=upcoming]:text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t<Check className=\"hidden size-4 group-data-[status=previous]/item:block\" />\n\t\t\t\t\t\t\t\t\t\t\t<Loader2 className=\"hidden size-4 animate-spin group-data-[status=active]/item:block\" />\n\t\t\t\t\t\t\t\t\t\t\t<Icon className=\"size-4 group-data-[status=active]/item:hidden group-data-[status=previous]/item:hidden\" />\n\t\t\t\t\t\t\t\t\t\t</Stepper.Indicator>\n\n\t\t\t\t\t\t\t\t\t\t<div className=\"flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Stepper.Title className=\"text-sm font-medium\" />\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"text-xs font-medium text-muted-foreground group-data-[status=previous]/item:text-chart-2 group-data-[status=active]/item:text-primary\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"hidden group-data-[status=previous]/item:inline\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tpassed\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"hidden group-data-[status=active]/item:inline\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{released ? \"released\" : \"running\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"hidden group-data-[status=upcoming]/item:inline\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tqueued\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Stepper.Description className=\"text-xs text-muted-foreground\" />\n\t\t\t\t\t\t\t\t\t\t\t\t{/* typed metadata: only show timing once a stage has run */}\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"hidden font-mono text-xs text-muted-foreground group-data-[status=previous]/item:inline group-data-[status=active]/item:inline\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{step.duration}\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t</Stepper.Item>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t</Stepper.Items>\n\t\t\t\t\t</Stepper.List>\n\n\t\t\t\t\t<Stepper.Actions className=\"mt-5 flex gap-2 border-t pt-5\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetReleased(false);\n\t\t\t\t\t\t\t\tstepper.reset();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tclassName=\"inline-flex h-8 items-center rounded-lg border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tRestart\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t{stepper.isLast ? (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => setReleased(true)}\n\t\t\t\t\t\t\t\tclassName=\"inline-flex h-8 flex-1 items-center justify-center rounded-lg bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tRelease build\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<Stepper.Next className=\"inline-flex h-8 flex-1 items-center justify-center rounded-lg bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\">\n\t\t\t\t\t\t\t\tRun next stage\n\t\t\t\t\t\t\t</Stepper.Next>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Stepper.Actions>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</Stepper.Root>\n\t);\n}\n\nexport default CicdPipelineBlock;\n"
    }
  ]
}
