Blocks
Building blocks for health apps
Whole screens made from PREDICT components: dashboards, reports, onboarding, booking, and sign in. Preview them at any width, then copy the code or add them with the shadcn CLI.
- app
- report
app/report/page.tsx
"use client"
import { DownloadIcon, ShareIcon } from "lucide-react"
import { Badge } from "@workspace/ui/components/badge"
import { Button } from "@workspace/ui/components/button"
import { Separator } from "@workspace/ui/components/separator"
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@workspace/ui/components/tabs"
import {
biomarkerCategoryLabels,
biomarkerRange,
getBiomarker,
type BiomarkerCategory,
} from "@workspace/ui/lib/biomarkers"
import { flagValue, isConcern } from "@workspace/ui/lib/reference-range"
import { ReferenceRange } from "@workspace/ui/tools/reference-range"
// Example results, grouped the way the report reads.
const panel: Partial<Record<BiomarkerCategory, { id: string; value: number }[]>> = {
inflammation: [
{ id: "hs-crp", value: 3.5 },
{ id: "nlr", value: 3.2 },
{ id: "il-6", value: 9.8 },
{ id: "tnf-alpha", value: 4.1 },
],
lipids: [
{ id: "total-cholesterol", value: 225.6 },
{ id: "ldl-c", value: 185.5 },
{ id: "hdl-c", value: 35.7 },
{ id: "triglycerides", value: 120 },
],
metabolic: [
{ id: "fbs", value: 94 },
{ id: "fsi", value: 11 },
{ id: "hba1c", value: 7.6 },
],
kidney: [
{ id: "egfr", value: 82.7 },
{ id: "creatinine", value: 1.8 },
{ id: "bun", value: 21.8 },
{ id: "albumin", value: 4.3 },
],
}
const groups = Object.keys(panel) as BiomarkerCategory[]
const all = groups.flatMap((group) => panel[group]!)
// Only count results on the side the direction marks as bad.
const outOfRange = all.filter(({ id, value }) => {
const range = biomarkerRange(getBiomarker(id)!)
return isConcern(flagValue(value, range), range.direction)
}).length
export default function Page() {
return (
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6 p-4 md:p-8">
<header className="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<h1 className="text-xl font-medium tracking-tight">Biomarker report</h1>
<Badge variant="outline">October panel</Badge>
</div>
<p className="text-sm text-muted-foreground">
Priya Sharma · Collected 12 Oct 2026 · Fasting 10 hours
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<ShareIcon data-icon="inline-start" />
Share
</Button>
<Button size="sm">
<DownloadIcon data-icon="inline-start" />
Download PDF
</Button>
</div>
</header>
<dl className="grid grid-cols-3 rounded-lg border">
{[
{ label: "Biomarkers", value: all.length },
{ label: "In range", value: all.length - outOfRange },
{ label: "Needs attention", value: outOfRange },
].map((stat) => (
<div key={stat.label} className="flex flex-col gap-1 p-4 not-first:border-l">
<dt className="text-xs text-muted-foreground">{stat.label}</dt>
<dd className="text-2xl font-medium tabular-nums">{stat.value}</dd>
</div>
))}
</dl>
<Separator />
<Tabs defaultValue={groups[0]}>
<TabsList className="max-w-full overflow-x-auto">
{groups.map((group) => (
<TabsTrigger key={group} value={group}>
{biomarkerCategoryLabels[group]}
</TabsTrigger>
))}
</TabsList>
{groups.map((group) => (
<TabsContent key={group} value={group} className="pt-4">
<div className="grid gap-4 sm:grid-cols-2">
{panel[group]!.map(({ id, value }) => {
const biomarker = getBiomarker(id)!
return (
<ReferenceRange
key={id}
name={biomarker.abbreviation}
description={biomarker.name}
value={value}
unit={biomarker.unit}
range={biomarkerRange(biomarker)}
className="max-w-none"
/>
)
})}
</div>
</TabsContent>
))}
</Tabs>
<p className="text-xs text-muted-foreground">
General information, not medical advice. Discuss your results with a clinician.
</p>
</div>
)
}
- app
- index
app/index/page.tsx
"use client"
import { InfoIcon } from "lucide-react"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@workspace/ui/components/accordion"
import { Badge } from "@workspace/ui/components/badge"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@workspace/ui/components/card"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@workspace/ui/components/tooltip"
// The CBMI worked example. Each domain's Mean |Z| comes from its biomarker
// Z-scores, Wd is its share of the total, and αd is its priority.
// Score = αd × Mean |Z| × Wd.
const domains = [
{
name: "Cardiometabolic Risk",
abbr: "CMR",
alpha: 6,
biomarkers: [
{ name: "HbA1c", z: 7 },
{ name: "TC", z: 3.338 },
{ name: "LDL-C", z: 9.024 },
{ name: "HDL-C", z: -2.43 },
],
},
{
name: "Inflammatory Risk",
abbr: "IR",
alpha: 2,
biomarkers: [
{ name: "hs-CRP", z: 2.714 },
{ name: "NLR", z: 2.4 },
{ name: "IL-6", z: 3.712 },
],
},
{
name: "Muscle-Cartilage Degeneration Risk",
abbr: "MCDR",
alpha: 2,
biomarkers: [
{ name: "CK-MM", z: 2.667 },
{ name: "Aldolase A", z: 2.627 },
{ name: "IGF-1", z: -2.107 },
{ name: "Myostatin", z: 4.52 },
{ name: "CTX-II", z: 2.736 },
],
},
{
name: "Bone Turnover Risk",
abbr: "BTR",
alpha: 1,
biomarkers: [
{ name: "PTH", z: 2.824 },
{ name: "Ca/P ratio", z: 2.5 },
{ name: "Vitamin D", z: -2.263 },
{ name: "Osteocalcin", z: -2.263 },
{ name: "PINP", z: -2.687 },
],
},
{
name: "Neurovascular Function Risk",
abbr: "NVFR",
alpha: 1,
biomarkers: [
{ name: "BDNF", z: -2.352 },
{ name: "VEGF", z: 3.011 },
{ name: "NOx", z: -2.11 },
],
},
]
const withMeanZ = domains.map((d) => ({
...d,
meanZ: d.biomarkers.reduce((sum, b) => sum + Math.abs(b.z), 0) / d.biomarkers.length,
}))
const totalMeanZ = withMeanZ.reduce((sum, d) => sum + d.meanZ, 0)
const scored = withMeanZ.map((d) => {
const weight = d.meanZ / totalMeanZ
return { ...d, weight, score: d.alpha * d.meanZ * weight }
})
const total = scored.reduce((sum, d) => sum + d.score, 0)
const maxScore = Math.max(...scored.map((d) => d.score))
export default function Page() {
return (
<TooltipProvider>
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4 p-4 md:p-8">
<Card>
<CardHeader>
<CardTitle className="text-sm">Composite Biological Musculoskeletal Index</CardTitle>
<CardDescription>Five domains, 21 biomarkers.</CardDescription>
<CardAction>
<Tooltip>
<TooltipTrigger
aria-label="How the score works"
className="text-muted-foreground"
>
<InfoIcon className="size-4" />
</TooltipTrigger>
<TooltipContent className="max-w-60">
Each domain scores αd × Mean |Z| × Wd. The index is their sum.
</TooltipContent>
</Tooltip>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-6">
<div className="flex items-end gap-3">
<span className="text-5xl font-medium tracking-tight tabular-nums">
{total.toFixed(2)}
</span>
<Badge variant="destructive" className="mb-2">
CBMI
</Badge>
</div>
<ul className="flex flex-col gap-3">
{scored.map((d) => (
<li key={d.abbr} className="grid grid-cols-[3.5rem_1fr_3rem] items-center gap-3">
<span className="text-xs font-medium">{d.abbr}</span>
<div className="h-2 overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary"
style={{ width: `${(d.score / maxScore) * 100}%` }}
/>
</div>
<span className="text-right text-xs tabular-nums">{d.score.toFixed(2)}</span>
</li>
))}
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-sm">Domains</CardTitle>
<CardDescription>Open a domain to see its biomarker Z-scores.</CardDescription>
</CardHeader>
<CardContent>
<Accordion defaultValue={["CMR"]}>
{scored.map((d) => (
<AccordionItem key={d.abbr} value={d.abbr}>
<AccordionTrigger>
<span className="flex flex-1 items-center justify-between gap-4 pr-2">
<span>
{d.name} <span className="text-muted-foreground">({d.abbr})</span>
</span>
<span className="text-muted-foreground tabular-nums">
αd {d.alpha} · Wd {d.weight.toFixed(2)}
</span>
</span>
</AccordionTrigger>
<AccordionContent>
<table className="w-full text-xs">
<thead className="text-muted-foreground">
<tr>
<th className="py-1 text-left font-normal">Biomarker</th>
<th className="py-1 text-right font-normal">Z</th>
<th className="py-1 text-right font-normal">|Z|</th>
</tr>
</thead>
<tbody className="tabular-nums">
{d.biomarkers.map((b) => (
<tr key={b.name} className="border-t">
<td className="py-1.5">{b.name}</td>
<td className="py-1.5 text-right">{b.z.toFixed(2)}</td>
<td className="py-1.5 text-right">{Math.abs(b.z).toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</CardContent>
</Card>
</div>
</TooltipProvider>
)
}