feat(ui): cohesive frontend polish — design system + per-screen refinement

Distill the UI into a single, recognizable voice: a precision
instrument for one operator on one machine. Bloomberg-coded chrome,
warm-paper detail surfaces, mono-heavy numerics, with one editorial
serif accent for moments of weight.

Design system
- Three-font stack: Geist Sans (UI), Geist Mono (data), Instrument
  Serif (editorial display). No Inter, no system fonts.
- Two surfaces: dark chrome (--background, --accent, --signal) and
  warm paper detail surfaces (--surface, --surface-ink*).
- Inbox has its own Ticker Tape terminal palette (--tt-*).
- Shared component classes: .eyebrow, .mono, .display, .surface,
  .surface-2, .row-hover, .nav-active, .kbd, .editorial, .hairline.
- --m-* token aliases for the legacy drawer components so test-
  asserted class strings keep resolving to the same hue family.

Drawers (Claim + Remit)
- Editorial display face for totals (paid/adjustment amounts).
- Color-coded money tiles: green-tinted paid card, amber-tinted
  adjustment card when non-zero, muted otherwise.
- Tabs get accent-blue underline + CSS-driven active state.
- Validation banners with proper background opacity + ring-around-
  dot success badge.
- StateHistoryTimeline: dashed border, ring around dots, ↳ prefix
  for remit ids.
- DiagnosesList, PartiesGrid, MatchedRemitCard, CasAdjustmentsPanel:
  refined typography, dashed dividers, italic descriptions, mono
  amounts, font-semibold totals, hover row tints.

Inbox Ticker Tape
- Custom RowCheckbox with sr-only input + amber accent.
- Alternating row striping, hover tints, accent rail with inset
  shadow on selection.
- Refined sparkline with glow at high values.
- BulkBar: bottom-floating bar with amber count chip, larger shadow.
- CandidateBreakdown: animated progress bars with amber gradient.
- InboxHeader: Instrument Serif headline, mono day/date stamp,
  pulsing amber status dot.

Dialogs & search
- NewClaimDialog: editorial title, mono NPI/CPT/amount fields.
- SearchBar: refined input row with mono, footer with pulsing
  loading indicator.
- KeyboardCheatsheet: monogram icon chip, hover row states, refined
  eyebrow header.

Primitives
- StatusBadge / ClaimStateBadge: per-state dot indicators.
- SelectItem: data-[highlighted]:outline tokens for keyboard a11y.
- Table primitives: refined header treatment, hover/focus states.

Tests + build
- 354/354 tests passing across 59 files.
- Vite build clean (53.84 kB CSS / 560.86 kB JS).
- Eyebrow assertions updated to match the consolidated .eyebrow
  class (intact visual contract, abstracted class string).
- Badge variant tokens updated to the polished bg-muted/80 /
  /0.14 opacity scale.
This commit is contained in:
Tyler
2026-06-20 22:27:01 -06:00
parent 02841d7e6e
commit fbe9940a3f
70 changed files with 2582 additions and 1663 deletions
+71 -64
View File
@@ -9,6 +9,7 @@ import {
TrendingDown,
} from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { PageHeader } from "@/components/PageHeader";
import { KpiCard } from "@/components/KpiCard";
import { ActivityFeed } from "@/components/ActivityFeed";
import { AnimatedNumber } from "@/components/AnimatedNumber";
@@ -49,7 +50,6 @@ function buildMonthly(claims: ReturnType<typeof useAppStore.getState>["claims"])
months[i]!.received += c.receivedAmount;
if (c.status === "denied") months[i]!.denied += 1;
}
// Running outstanding AR (cumulative billed cumulative received)
let running = 0;
const ar: number[] = [];
for (const m of months) {
@@ -92,7 +92,7 @@ export function Dashboard() {
const monthly = useMemo(() => buildMonthly(claims), [claims]);
const topProviders = useMemo(
() => [...providers].sort((a, b) => b.claimCount - a.claimCount).slice(0, 3),
() => [...providers].sort((a, b) => b.claimCount - a.claimCount).slice(0, 4),
[providers]
);
@@ -101,69 +101,72 @@ export function Dashboard() {
[claims]
);
// Stagger choreography
// Stagger choreography — the hero lands first, then the KPIs in
// a left-to-right wave, then the supporting cards. Total
// choreography fits under 700ms.
const heroDelay = 0;
const kpiBase = 120;
const kpiStep = 70;
const sectionBase = kpiBase + kpiStep * 5 + 60; // after the last KPI
const kpiStep = 60;
const sectionBase = kpiBase + kpiStep * 5 + 80;
return (
<div className="space-y-6 md:space-y-10">
{/* Hero */}
<header
<div className="space-y-6 lg:space-y-10">
{/* Hero — the dashboard's only moment of editorial display. The
ghost total sits behind the greeting at single-digit opacity
so it reads as a watermark, not a competing headline. */}
<section
className="relative animate-fade-in overflow-hidden"
style={{ animationDelay: `${heroDelay}ms` }}
>
<div className="relative">
{/* The signature element: a ghosted total behind the greeting. */}
<div
aria-hidden="true"
className="pointer-events-none select-none absolute left-0 top-1/2 -translate-y-1/2 whitespace-nowrap display font-medium text-foreground"
style={{
fontSize: "clamp(80px, 14vw, 168px)",
letterSpacing: "-0.05em",
opacity: 0.055,
lineHeight: 1,
}}
>
{fmt.usd(kpis.billed)}
</div>
<div
aria-hidden="true"
className="pointer-events-none select-none absolute -left-4 top-1/2 -translate-y-1/2 whitespace-nowrap display text-foreground"
style={{
fontSize: "clamp(80px, 14vw, 168px)",
letterSpacing: "-0.04em",
opacity: 0.04,
lineHeight: 1,
}}
>
{fmt.usd(kpis.billed)}
</div>
<div className="relative z-10 flex items-end justify-between gap-4 sm:gap-6 flex-wrap">
<div className="min-w-0">
<div className="text-[10.5px] font-semibold uppercase tracking-[0.18em] text-muted-foreground mb-2 flex items-center gap-2">
<span className="inline-block h-px w-6 bg-border" />
Today · {fmt.date(new Date().toISOString())}
</div>
<h1 className="text-[22px] sm:text-[28px] leading-[1.15] font-semibold tracking-tight">
Good morning, Jordan.
</h1>
<p className="text-muted-foreground mt-1.5 max-w-xl text-[14px] leading-relaxed">
Three NPIs are in flight. {kpis.pending} claims pending,
clearinghouse cycle is normal.
</p>
</div>
<div className="flex items-center gap-2.5 rounded-full border border-border/60 bg-card/60 px-3 py-1.5 text-xs text-muted-foreground backdrop-blur">
<span className="relative inline-flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full rounded-full bg-[hsl(var(--success))] opacity-60 animate-ping" />
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-[hsl(var(--success))]" />
</span>
Clearinghouse live
</div>
<div className="relative z-10 flex items-end justify-between gap-4 sm:gap-6 flex-wrap">
<div className="min-w-0">
<PageHeader
eyebrow={`Today · ${fmt.date(new Date().toISOString())}`}
title={<>Good morning, Jordan.</>}
subtitle={
<>
<span className="display text-foreground">Three NPIs</span> are
in flight. {kpis.pending} claims pending, clearinghouse cycle
is normal.
</>
}
/>
</div>
<div className="flex items-center gap-2 rounded-full border border-border/60 bg-card/60 px-3 py-1.5 text-[11px] mono uppercase tracking-[0.12em] text-muted-foreground backdrop-blur">
<span className="relative inline-flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full rounded-full bg-[hsl(var(--success))] opacity-60 animate-ping" />
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-[hsl(var(--success))]" />
</span>
Clearinghouse live
</div>
</div>
</header>
</section>
{/* KPIs — every card carries a trend. */}
{/* KPIs — five tiles, each carrying a sparkline. Staggered
fade-in on first paint for a "the instrument powers up"
feel. */}
<section
aria-label="Key performance indicators"
className="grid gap-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5"
className="grid gap-3.5 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5"
>
<KpiCard
label="Claims"
icon={Receipt}
sparkline={monthly.count}
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${kpiBase + 0 * kpiStep}ms` }}
value={
<AnimatedNumber value={kpis.count} format={(n) => fmt.num(Math.round(n))} />
@@ -173,8 +176,9 @@ export function Dashboard() {
<KpiCard
label="Billed"
icon={CircleDollarSign}
accent="accent"
sparkline={monthly.billed}
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${kpiBase + 1 * kpiStep}ms` }}
value={<AnimatedNumber value={kpis.billed} format={fmt.usd} />}
delta={{ value: "+12.4%", direction: "up", positive: true }}
@@ -182,8 +186,9 @@ export function Dashboard() {
<KpiCard
label="Received"
icon={Banknote}
accent="success"
sparkline={monthly.received}
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${kpiBase + 2 * kpiStep}ms` }}
value={<AnimatedNumber value={kpis.received} format={fmt.usd} />}
delta={{ value: "+8.1%", direction: "up", positive: true }}
@@ -191,8 +196,9 @@ export function Dashboard() {
<KpiCard
label="Pending AR"
icon={Clock}
accent="warning"
sparkline={monthly.ar}
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${kpiBase + 3 * kpiStep}ms` }}
value={
<AnimatedNumber
@@ -205,8 +211,9 @@ export function Dashboard() {
<KpiCard
label="Denial rate"
icon={TrendingDown}
accent="destructive"
sparkline={monthly.denialRate}
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${kpiBase + 4 * kpiStep}ms` }}
value={
<AnimatedNumber
@@ -220,7 +227,7 @@ export function Dashboard() {
{/* Activity + Top providers */}
<section
className="grid gap-4 lg:grid-cols-3 animate-fade-in"
className="grid gap-4 lg:grid-cols-3 animate-fade-in-up"
style={{ animationDelay: `${sectionBase}ms` }}
>
<Card className="lg:col-span-2">
@@ -234,7 +241,7 @@ export function Dashboard() {
Newest submissions, denials, and remittances across all providers.
</p>
</div>
<span className="text-[10.5px] uppercase tracking-[0.14em] text-muted-foreground num">
<span className="mono text-[10.5px] uppercase tracking-[0.14em] text-muted-foreground">
{activity.length} events
</span>
</CardHeader>
@@ -254,18 +261,18 @@ export function Dashboard() {
<ul className="space-y-4">
{topProviders.map((p, i) => (
<li key={p.npi} className="flex items-center gap-3">
<div className="h-7 w-7 rounded-md bg-muted/60 flex items-center justify-center text-[11px] font-medium num text-muted-foreground">
<div className="h-7 w-7 rounded-md bg-muted/60 ring-1 ring-inset ring-border/40 flex items-center justify-center mono text-[10.5px] text-muted-foreground">
{String(i + 1).padStart(2, "0")}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{p.name}</div>
<div className="text-[11px] text-muted-foreground num">
<div className="text-[13px] font-medium truncate">{p.name}</div>
<div className="mono text-[10.5px] text-muted-foreground">
NPI {p.npi}
</div>
</div>
<div className="text-right">
<div className="display text-[15px]">{fmt.num(p.claimCount)}</div>
<div className="text-[11px] text-muted-foreground num">
<div className="display mono text-[15px]">{fmt.num(p.claimCount)}</div>
<div className="mono text-[10.5px] text-muted-foreground">
{fmt.usd(p.outstandingAr)} AR
</div>
</div>
@@ -278,7 +285,7 @@ export function Dashboard() {
{topDenials.length > 0 ? (
<section
className="animate-fade-in"
className="animate-fade-in-up"
style={{ animationDelay: `${sectionBase + 90}ms` }}
>
<Card>
@@ -301,20 +308,20 @@ export function Dashboard() {
key={c.id}
className="flex items-start gap-3 py-3 first:pt-0 last:pb-0"
>
<div className="display text-[13px] num text-muted-foreground pt-0.5 w-24 shrink-0">
<div className="display mono text-[12.5px] text-muted-foreground pt-0.5 w-24 shrink-0">
{c.id}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm">{c.patientName}</div>
<div className="text-[12px] text-muted-foreground">
<div className="text-[13px]">{c.patientName}</div>
<div className="text-[11.5px] text-muted-foreground">
{c.denialReason ?? "—"}
</div>
</div>
<div className="text-right shrink-0">
<div className="display text-[14px] num">
<div className="display mono text-[13.5px]">
{fmt.usd(c.billedAmount)}
</div>
<div className="text-[11px] text-muted-foreground">
<div className="text-[10.5px] text-muted-foreground">
{c.payerName}
</div>
</div>