feat(claims): provider cell drillable to /providers?provider=NPI
This commit is contained in:
@@ -34,4 +34,27 @@ describe("DrillableCell", () => {
|
||||
expect(btn.disabled).toBe(true);
|
||||
expect(btn.classList.contains("drillable")).toBe(false);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Regression for Task 2.4 — event bubbling.
|
||||
//
|
||||
// DrillableCell renders a <button>, and <button> clicks bubble up the
|
||||
// DOM by default. Claims wraps each row in a <TableRow onClick={...}>
|
||||
// so a click on the provider cell used to (1) navigate to /providers
|
||||
// and then (2) bubble to the row and re-fire buildUrl() on the now-
|
||||
// /providers URL, appending a phantom ?claim=… param. We fix it at
|
||||
// the DrillableCell level so future tables that adopt the component
|
||||
// are correct by default.
|
||||
// ---------------------------------------------------------------------
|
||||
it("test_click_does_not_bubble_to_parent", () => {
|
||||
const parentClick = vi.fn();
|
||||
const { container } = render(
|
||||
<div onClick={parentClick}>
|
||||
<DrillableCell onClick={() => {}}>Click me</DrillableCell>
|
||||
</div>,
|
||||
);
|
||||
const btn = container.querySelector("button")!;
|
||||
fireEvent.click(btn);
|
||||
expect(parentClick).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { MouseEvent, ReactNode } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
onClick: () => void;
|
||||
/**
|
||||
* Click handler. Receives the underlying React mouse event so we can
|
||||
* call `e.stopPropagation()` before invoking the caller's logic — see
|
||||
* the JSDoc on the component for why this matters when a DrillableCell
|
||||
* is nested inside a row-level click handler.
|
||||
*/
|
||||
onClick: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
disabled?: boolean;
|
||||
/** Optional aria-label; defaults to the visible text content. */
|
||||
ariaLabel?: string;
|
||||
@@ -18,12 +24,21 @@ interface Props {
|
||||
* Renders as a <button> (disabled when `disabled`) so it gets keyboard
|
||||
* activation (Enter/Space) and the standard disabled-button semantics.
|
||||
* The `drillable` affordance class is omitted when disabled.
|
||||
*
|
||||
* Calls `e.stopPropagation()` on the button click before invoking the
|
||||
* caller's handler. This prevents the click from bubbling to a
|
||||
* row-level `onClick` (e.g. Claims' `<TableRow onClick={() => open(c.id)}>`),
|
||||
* which would otherwise re-fire `buildUrl()` on the now-navigated URL
|
||||
* and corrupt history. Precedent: `src/components/inbox/Lane.tsx:41`.
|
||||
*/
|
||||
export function DrillableCell({ children, onClick, disabled, ariaLabel }: Props) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick(e);
|
||||
}}
|
||||
disabled={disabled}
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
|
||||
Reference in New Issue
Block a user