refactor(claim-drawer): mount on shared DrillDrawerHeader shell

This commit is contained in:
Tyler
2026-06-21 18:01:07 -06:00
parent 8db5db7610
commit 1c0d855b8e
2 changed files with 79 additions and 77 deletions
@@ -131,14 +131,17 @@ describe("ClaimDrawerHeader", () => {
it("test_renders_claim_id_label_and_value", () => {
const { container, unmount } = renderHeader({ id: "CLM-42" });
// The eyebrow label "Claim" is rendered as uppercase text.
// The eyebrow label "Claim" is rendered as uppercase text by
// DrillDrawerHeader.
const text = (container.textContent ?? "").toLowerCase();
expect(text).toContain("claim");
// The claim id sits in a node tagged with data-testid="header-id".
const idEl = container.querySelector('[data-testid="header-id"]');
expect(idEl).not.toBeNull();
expect(idEl?.textContent).toBe("CLM-42");
// SP21 Phase 5 Task 5.10: the claim id is now the title passed
// to DrillDrawerHeader, which renders it inside an <h2>. Find
// the h2 and assert its text matches the claim id.
const titleEl = container.querySelector("h2");
expect(titleEl).not.toBeNull();
expect(titleEl?.textContent).toBe("CLM-42");
unmount();
});
@@ -212,8 +215,11 @@ describe("ClaimDrawerHeader", () => {
const onClose = vi.fn();
const { container, unmount } = renderHeader({}, onClose);
// SP21 Phase 5 Task 5.10: the close button is now rendered by
// DrillDrawerHeader (no `data-testid`); find it via its
// accessible name instead.
const closeBtn = container.querySelector(
'[data-testid="header-close"]'
'button[aria-label="Close drawer"]'
) as HTMLButtonElement | null;
expect(closeBtn).not.toBeNull();
expect(onClose).not.toHaveBeenCalled();
@@ -226,18 +232,25 @@ describe("ClaimDrawerHeader", () => {
unmount();
});
it("test_uses_modern_palette_surface", () => {
// The drawer header anchors itself on the light surface palette
// token (matches ClaimDrawerSkeleton / ClaimDrawerError). The root
// <header> element is tagged with data-testid="claim-drawer-header"
// so we can sniff its className without coupling to the badge
// or close-button wrappers.
const { container, unmount } = renderHeader({});
it("test_uses_shared_drilldrawerheader_shell", () => {
// SP21 Phase 5 Task 5.10: the header is now a thin wrapper
// around DrillDrawerHeader — verify the wrapper is present and
// that the underlying shell is the shared one (an h2 with the
// expected Tailwind treatment). The "Claim" eyebrow + claim id
// title prove the shell rendered.
const { container, unmount } = renderHeader({ id: "CLM-42" });
const root = container.querySelector('[data-testid="claim-drawer-header"]');
expect(root).not.toBeNull();
const cls = root?.className ?? "";
expect(cls).toContain("bg-[color:var(--m-surface)]");
// The h2 is DrillDrawerHeader's title slot.
const titleEl = root?.querySelector("h2");
expect(titleEl).not.toBeNull();
expect(titleEl?.textContent).toBe("CLM-42");
// The className pattern DrillDrawerHeader uses for the title.
const cls = titleEl?.className ?? "";
expect(cls).toContain("text-[18px]");
expect(cls).toContain("font-semibold");
unmount();
});
@@ -1,11 +1,11 @@
import { useState } from "react";
import { Download, X } from "lucide-react";
import { Download } from "lucide-react";
import { Badge, type BadgeProps } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { DrillDrawerHeader } from "@/components/drill/DrillDrawerHeader";
import { api } from "@/lib/api";
import { downloadTextFile } from "@/lib/download";
import { fmt } from "@/lib/format";
import { cn } from "@/lib/utils";
import type { ClaimDetail } from "@/types";
type ClaimDrawerHeaderProps = {
@@ -47,12 +47,19 @@ function badgeVariantFor(state: string): BadgeProps["variant"] {
}
/**
* Header band for the claim detail drawer (SP4).
* Header band for the claim detail drawer (SP4 → refactored SP21
* Phase 5 Task 5.10).
*
* Top-left: instrument-style "Claim" eyebrow + large mono ID.
* Top-right: state badge + total billed amount + close button. The
* badge color encodes state so the user can read the drawer's status
* at a glance without scrolling.
* The shell is now the shared `DrillDrawerHeader` (same as
* `ProviderDrawer` / `AckDrawer`) — eyebrow + title on the left,
* close button on the right. The right-side `action` slot carries
* the state badge, total billed amount, and the "Download 837"
* button, all of which used to live in a custom <header> block.
*
* Top-left: instrument-style "Claim" eyebrow + the claim ID.
* Top-right: state badge + total billed amount + download button.
* The badge color encodes state so the user can read the drawer's
* status at a glance without scrolling.
*/
export function ClaimDrawerHeader({
claim,
@@ -80,31 +87,12 @@ export function ClaimDrawerHeader({
}
}
return (
<header
className={cn(
"flex items-start justify-between gap-4 px-6 py-5",
"border-b border-[color:var(--m-border-heavy)]/40",
"bg-[color:var(--m-surface)]"
)}
data-testid="claim-drawer-header"
>
{/* Left: eyebrow + mono claim ID */}
<div className="flex flex-col gap-1 min-w-0">
<span className="eyebrow text-[color:var(--m-ink-tertiary)]">
Claim
</span>
<span
data-testid="header-id"
className="mono text-2xl font-semibold tracking-tight text-[color:var(--m-ink-primary)]"
>
{claim.id}
</span>
</div>
{/* Right: state badge + total amount + download + close */}
<div className="flex items-start gap-2">
<div className="flex flex-col items-end gap-1">
// The action slot is rendered by DrillDrawerHeader to the left of
// the close button. Group the three action pieces (badge, amount,
// download) in a single flex row so they read as a unit.
const action = (
<div className="flex items-center gap-2" data-testid="claim-header-actions">
<div className="flex items-center gap-2">
<Badge
variant={badgeVariantFor(claim.state)}
data-testid="header-state"
@@ -114,7 +102,7 @@ export function ClaimDrawerHeader({
</Badge>
<span
data-testid="header-amount"
className="mono text-lg tabular-nums text-[color:var(--m-ink-primary)]"
className="mono text-sm tabular-nums text-muted-foreground"
>
{fmt.usdPrecise(claim.billedAmount)}
</span>
@@ -130,16 +118,17 @@ export function ClaimDrawerHeader({
>
<Download className="h-4 w-4" strokeWidth={1.75} />
</Button>
<Button
variant="ghost"
size="icon"
onClick={onClose}
aria-label="Close drawer"
data-testid="header-close"
>
<X className="h-4 w-4" strokeWidth={1.75} />
</Button>
</div>
);
return (
<header data-testid="claim-drawer-header">
<DrillDrawerHeader
eyebrow="Claim"
title={claim.id}
onClose={onClose}
action={action}
/>
</header>
);
}