refactor(claim-drawer): mount on shared DrillDrawerHeader shell
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user