An insurance-claims table where each row carries a four-stage pipeline from submitted to paid, with denial states and patient-responsibility amounts in tabular figures.
npx shadcn@latest add @paragon/claims-status"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
const STAGES = ["Submitted", "Received", "Adjudicated", "Paid"] as const;
export type ClaimStage = (typeof STAGES)[number];
export interface Claim {
id: string;
/** e.g. "Office visit — 99213". */
service: string;
provider: string;
date: string;
billed: number;
/** Patient responsibility, in dollars. */
responsibility?: number;
/** Current stage; earlier stages render complete. */
stage: ClaimStage;
/** Marks the claim as denied at its current stage. */
denied?: boolean;
}
export interface ClaimsStatusProps extends React.ComponentProps<"div"> {
claims?: Claim[];
}
const usd = (n: number) =>
n.toLocaleString("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 });
const DEFAULT_CLAIMS: Claim[] = [
{ id: "CLM-90412", service: "Office visit — 99214", provider: "Dr. Priya Nair", date: "Jun 12", billed: 245, responsibility: 35, stage: "Paid" },
{ id: "CLM-90455", service: "Lab panel — 80053", provider: "Quest Diagnostics", date: "Jun 14", billed: 118, responsibility: 0, stage: "Adjudicated" },
{ id: "CLM-90478", service: "X-ray, chest — 71046", provider: "Metro Imaging", date: "Jun 16", billed: 320, stage: "Received" },
{ id: "CLM-90501", service: "MRI, knee — 73721", provider: "Metro Imaging", date: "Jun 18", billed: 1240, stage: "Submitted", denied: true },
];
function Pipeline({ stage, denied }: { stage: ClaimStage; denied?: boolean }) {
const activeIdx = STAGES.indexOf(stage);
return (
<div className="flex items-center gap-1" aria-hidden>
{STAGES.map((s, i) => {
const complete = i < activeIdx || (i === activeIdx && stage === "Paid" && !denied);
const isCurrent = i === activeIdx;
const failed = denied && isCurrent;
return (
<React.Fragment key={s}>
<span
className={cn(
"size-2.5 shrink-0 rounded-full transition-colors duration-150",
failed
? "bg-destructive"
: complete
? "bg-success"
: isCurrent
? "bg-warning"
: "bg-secondary",
)}
/>
{i < STAGES.length - 1 && (
<span
className={cn(
"h-0.5 w-4 shrink-0 rounded-full transition-colors duration-150 sm:w-6",
i < activeIdx ? "bg-success" : "bg-secondary",
)}
/>
)}
</React.Fragment>
);
})}
</div>
);
}
/**
* A claims table where each row carries a four-stage pipeline
* (submitted → received → adjudicated → paid). Completed stages read green,
* the current stage amber, a denial red. Amounts use tabular figures. Static
* composition — a ledger is read, not animated.
*/
export function ClaimsStatus({
claims = DEFAULT_CLAIMS,
className,
...props
}: ClaimsStatusProps) {
return (
<div
data-slot="claims-status"
className={cn(
"w-full max-w-2xl overflow-hidden rounded-xl bg-card text-card-foreground shadow-border",
className,
)}
{...props}
>
<ul className="divide-y divide-border">
{claims.map((c) => {
const stageLabel = c.denied ? "Denied" : c.stage;
return (
<li
key={c.id}
className="flex flex-col gap-3 p-3.5 sm:flex-row sm:items-center sm:gap-4"
>
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-2">
<p className="truncate text-sm font-medium">{c.service}</p>
<span className="shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums">
{c.id}
</span>
</div>
<p className="mt-0.5 text-xs text-muted-foreground tabular-nums">
{c.provider} · {c.date}
</p>
</div>
<div className="flex flex-col gap-1.5 sm:items-center">
<Pipeline stage={c.stage} denied={c.denied} />
<span
className={cn(
"text-[11px] font-medium",
c.denied
? "text-destructive"
: c.stage === "Paid"
? "text-success"
: "text-muted-foreground",
)}
>
{stageLabel}
</span>
</div>
<div className="shrink-0 text-right sm:w-24">
<p className="text-sm font-semibold tabular-nums">{usd(c.billed)}</p>
{c.responsibility !== undefined && (
<p className="text-[11px] text-muted-foreground tabular-nums">
You owe {usd(c.responsibility)}
</p>
)}
</div>
</li>
);
})}
</ul>
</div>
);
}