{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "approval-timeline",
  "type": "registry:component",
  "title": "Approval Timeline",
  "description": "A vertical audit timeline where each step carries typed metadata (actor, role, SLA hours) read straight off `step` and `stepper.current` with full inference.",
  "author": "Stepperize",
  "dependencies": [
    "@stepperize/react",
    "lucide-react"
  ],
  "registryDependencies": [],
  "categories": [
    "async"
  ],
  "meta": {
    "capabilities": [
      "metadata",
      "type-safe"
    ],
    "level": "advanced",
    "tags": [
      "timeline",
      "audit",
      "approval",
      "metadata",
      "type-safe"
    ]
  },
  "files": [
    {
      "path": "components/stepperize/approval-timeline.tsx",
      "type": "registry:component",
      "target": "components/stepperize/approval-timeline.tsx",
      "content": "\"use client\";\n\nimport { defineStepper } from \"@stepperize/react\";\nimport { Check, Clock, ShieldCheck } from \"lucide-react\";\nimport { useState } from \"react\";\n\n// Every field beyond `id` is typed user data: it flows through `stepper.current`,\n// the `Items` render prop, and `stepper.all` with full inference — no casts, no\n// lookup tables. This is how Stepperize models per-step metadata.\nconst { Stepper } = defineStepper([\n\t{\n\t\tid: \"submitted\",\n\t\ttitle: \"Submitted\",\n\t\tactor: \"Jane Cooper\",\n\t\trole: \"Requester\",\n\t\tslaHours: 0,\n\t},\n\t{\n\t\tid: \"manager\",\n\t\ttitle: \"Manager review\",\n\t\tactor: \"Tom Hardy\",\n\t\trole: \"Engineering Manager\",\n\t\tslaHours: 24,\n\t},\n\t{\n\t\tid: \"finance\",\n\t\ttitle: \"Finance approval\",\n\t\tactor: \"Priya Patel\",\n\t\trole: \"Finance\",\n\t\tslaHours: 48,\n\t},\n\t{\n\t\tid: \"approved\",\n\t\ttitle: \"Approved\",\n\t\tactor: \"System\",\n\t\trole: \"Automated\",\n\t\tslaHours: 0,\n\t},\n]);\n\nexport function ApprovalTimelineBlock() {\n\tconst [acknowledged, setAcknowledged] = useState(false);\n\n\treturn (\n\t\t<Stepper.Root\n\t\t\tlinear\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 justify-between\">\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t<p className=\"text-sm font-semibold\">Expense #2043</p>\n\t\t\t\t\t\t\t<p className=\"text-xs text-muted-foreground\">$1,250 · Travel</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{/* `stepper.current` is narrowed to the step's exact type, so its\n                metadata is available with no casting. */}\n\t\t\t\t\t\t<span className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t<ShieldCheck className=\"size-3.5\" />\n\t\t\t\t\t\t\t{stepper.current.role}\n\t\t\t\t\t\t</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\t<Stepper.Item\n\t\t\t\t\t\t\t\t\tkey={step.id}\n\t\t\t\t\t\t\t\t\tstep={step.id}\n\t\t\t\t\t\t\t\t\tclassName=\"group/item relative pb-6 pl-9 last:pb-0\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{index < stepper.count - 1 && (\n\t\t\t\t\t\t\t\t\t\t<div className=\"absolute top-7 bottom-1 left-3.25 w-px bg-border group-data-[status=previous]/item:bg-primary\" />\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t<Stepper.Trigger className=\"flex w-full items-start gap-3 text-left disabled:cursor-not-allowed\">\n\t\t\t\t\t\t\t\t\t\t<Stepper.Indicator className=\"group absolute left-0 grid size-7 place-items-center rounded-full border bg-background text-xs font-semibold transition-colors data-[status=active]:border-primary data-[status=active]:bg-primary data-[status=active]:text-primary-foreground data-[status=previous]:border-primary data-[status=previous]:bg-primary data-[status=previous]:text-primary-foreground data-[status=upcoming]:border-border data-[status=upcoming]:text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"group-data-[status=previous]:hidden\">\n\t\t\t\t\t\t\t\t\t\t\t\t{index + 1}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<Check className=\"hidden size-3.5 group-data-[status=previous]:block\" />\n\t\t\t\t\t\t\t\t\t\t</Stepper.Indicator>\n\t\t\t\t\t\t\t\t\t\t<span className=\"flex-1\">\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"flex items-center justify-between gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Stepper.Title className=\"text-sm font-medium leading-none\" />\n\t\t\t\t\t\t\t\t\t\t\t\t{/* Typed metadata read straight off `step` in the Items\n                            render prop. */}\n\t\t\t\t\t\t\t\t\t\t\t\t{step.slaHours > 0 && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Clock className=\"size-3\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{step.slaHours}h SLA\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)}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t<span className=\"mt-1 block text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t\t{step.actor} · {step.role}\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t</Stepper.Trigger>\n\n\t\t\t\t\t\t\t\t\t<div className=\"mt-3 hidden group-data-[status=active]/item:block\">\n\t\t\t\t\t\t\t\t\t\t<p className=\"rounded-lg border bg-muted/30 p-3 text-xs text-muted-foreground\">\n\t\t\t\t\t\t\t\t\t\t\t{acknowledged\n\t\t\t\t\t\t\t\t\t\t\t\t? \"Approval acknowledged. Audit trail closed.\"\n\t\t\t\t\t\t\t\t\t\t\t\t: stepper.isLast\n\t\t\t\t\t\t\t\t\t\t\t\t\t? \"All approvals collected. Reimbursement scheduled.\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t: `Awaiting ${step.actor} — due within ${step.slaHours}h.`}\n\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t<Stepper.Actions className=\"mt-3 flex gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t<Stepper.Prev className=\"inline-flex h-8 items-center rounded-lg border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50\">\n\t\t\t\t\t\t\t\t\t\t\t\tSend back\n\t\t\t\t\t\t\t\t\t\t\t</Stepper.Prev>\n\t\t\t\t\t\t\t\t\t\t\t{acknowledged ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetAcknowledged(false);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstepper.reset();\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"inline-flex h-8 items-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\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tRestart flow\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t) : stepper.isLast ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => setAcknowledged(true)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"inline-flex h-8 items-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\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tAcknowledge\n\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<Stepper.Next className=\"inline-flex h-8 items-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\t\t\t\t\t\tApprove\n\t\t\t\t\t\t\t\t\t\t\t\t</Stepper.Next>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</Stepper.Actions>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</Stepper.Item>\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\t\t\t\t</>\n\t\t\t)}\n\t\t</Stepper.Root>\n\t);\n}\n\nexport default ApprovalTimelineBlock;\n"
    }
  ]
}
