From 7dd6a5d0257be4afcf2c6ad7f87921488c4c5fe8 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 21 Jun 2026 11:47:05 -0600 Subject: [PATCH] =?UTF-8?q?feat(drill):=20DrillableCell=20=E2=80=94=20hove?= =?UTF-8?q?r-reveal=20button=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/drill/DrillableCell.test.tsx | 37 +++++++++++++++++++ src/components/drill/DrillableCell.tsx | 39 +++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/components/drill/DrillableCell.test.tsx create mode 100644 src/components/drill/DrillableCell.tsx diff --git a/src/components/drill/DrillableCell.test.tsx b/src/components/drill/DrillableCell.test.tsx new file mode 100644 index 0000000..42f7960 --- /dev/null +++ b/src/components/drill/DrillableCell.test.tsx @@ -0,0 +1,37 @@ +// @vitest-environment happy-dom +(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + +import { afterEach, describe, it, expect, vi } from "vitest"; +import { cleanup, render, screen, fireEvent } from "@testing-library/react"; +import { DrillableCell } from "@/components/drill/DrillableCell"; + +// happy-dom keeps `document.body` between tests; without cleanup, +// `screen.getByRole("button")` finds buttons from earlier renders. +afterEach(() => cleanup()); + +describe("DrillableCell", () => { + it("renders children, applies hover affordance classes, calls onClick", () => { + const onClick = vi.fn(); + render( + + CLM-114 + , + ); + const btn = screen.getByRole("button") as HTMLButtonElement; + expect(btn.classList.contains("drillable")).toBe(true); + fireEvent.click(btn); + expect(onClick).toHaveBeenCalledOnce(); + }); + + it("disabled state hides affordance and blocks click", () => { + const onClick = vi.fn(); + render( + + unavailable + , + ); + const btn = screen.getByRole("button") as HTMLButtonElement; + expect(btn.disabled).toBe(true); + expect(btn.classList.contains("drillable")).toBe(false); + }); +}); diff --git a/src/components/drill/DrillableCell.tsx b/src/components/drill/DrillableCell.tsx new file mode 100644 index 0000000..f620c05 --- /dev/null +++ b/src/components/drill/DrillableCell.tsx @@ -0,0 +1,39 @@ +import type { ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +interface Props { + children: ReactNode; + onClick: () => void; + disabled?: boolean; + /** Optional aria-label; defaults to the visible text content. */ + ariaLabel?: string; +} + +/** + * Wrap any clickable cell with hover-reveal affordance: + * cursor: pointer + accent background tint + trailing "›" chevron, + * applied via the `drillable` class on hover (see `src/index.css` + * in Task 1.4). + * + * Renders as a + ); +}