"use client"; import * as React from "react"; import { MoreVertical } from "lucide-react"; import { cn } from "@/lib/utils"; export type MenuItem = { label: string; onClick: () => void; destructive?: boolean; }; /** * A kebab (⋯) trigger with a small dropdown of actions — keeps secondary / * destructive row actions hidden until opened. The menu is fixed-positioned * from the trigger so the table's overflow container can't clip it; it closes * on outside-click, Escape, or scroll. */ export function RowMenu({ items }: { items: MenuItem[] }) { const [open, setOpen] = React.useState(false); const [pos, setPos] = React.useState<{ top: number; right: number } | null>( null, ); const btnRef = React.useRef(null); const menuRef = React.useRef(null); React.useEffect(() => { if (!open) return; function onDown(e: MouseEvent) { const t = e.target as Node; if (btnRef.current?.contains(t) || menuRef.current?.contains(t)) return; setOpen(false); } function onKey(e: KeyboardEvent) { if (e.key === "Escape") setOpen(false); } function onScroll() { setOpen(false); } document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); window.addEventListener("scroll", onScroll, true); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); window.removeEventListener("scroll", onScroll, true); }; }, [open]); function toggle() { if (!open && btnRef.current) { const r = btnRef.current.getBoundingClientRect(); setPos({ top: r.bottom + 4, right: window.innerWidth - r.right }); } setOpen((v) => !v); } return ( <> {open && pos ? (
{items.map((it, i) => ( ))}
) : null} ); }