feat(drill): DrillableCell — hover-reveal button wrapper
This commit is contained in:
@@ -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(
|
||||||
|
<DrillableCell onClick={onClick}>
|
||||||
|
<span>CLM-114</span>
|
||||||
|
</DrillableCell>,
|
||||||
|
);
|
||||||
|
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(
|
||||||
|
<DrillableCell onClick={onClick} disabled>
|
||||||
|
<span>unavailable</span>
|
||||||
|
</DrillableCell>,
|
||||||
|
);
|
||||||
|
const btn = screen.getByRole("button") as HTMLButtonElement;
|
||||||
|
expect(btn.disabled).toBe(true);
|
||||||
|
expect(btn.classList.contains("drillable")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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 <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.
|
||||||
|
*/
|
||||||
|
export function DrillableCell({ children, onClick, disabled, ariaLabel }: Props) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
className={cn(
|
||||||
|
!disabled && "drillable",
|
||||||
|
"inline-flex items-center gap-0 rounded-sm border-0 bg-transparent p-0 text-left",
|
||||||
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
|
||||||
|
disabled && "text-muted-foreground cursor-not-allowed",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user