Fix right-click column visibility context menu crash

Replace base-ui DropdownMenu (requires anchor trigger) with a custom
positioned panel for the header right-click context menu. The base-ui
Menu primitive cannot be rendered free-floating without a trigger element.

Made-with: Cursor
This commit is contained in:
Randall Stillwell 2026-03-11 16:16:00 -05:00
parent 7ff9724a63
commit 1168c96ae2

View file

@ -28,13 +28,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { import { Checkbox } from "@/components/ui/checkbox";
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { ResponseCard } from "./columns"; import type { ResponseCard } from "./columns";
@ -120,6 +114,7 @@ export function DataTable<TData extends ResponseCard>({
x: number; x: number;
y: number; y: number;
} | null>(null); } | null>(null);
const contextMenuRef = React.useRef<HTMLDivElement>(null);
const table = useReactTable({ const table = useReactTable({
data, data,
@ -185,7 +180,15 @@ export function DataTable<TData extends ResponseCard>({
React.useEffect(() => { React.useEffect(() => {
if (!contextMenu) return; if (!contextMenu) return;
const close = () => setContextMenu(null); const close = (e: MouseEvent) => {
if (
contextMenuRef.current &&
contextMenuRef.current.contains(e.target as Node)
) {
return;
}
setContextMenu(null);
};
window.addEventListener("click", close); window.addEventListener("click", close);
window.addEventListener("contextmenu", close); window.addEventListener("contextmenu", close);
return () => { return () => {
@ -463,35 +466,30 @@ export function DataTable<TData extends ResponseCard>({
{/* Right-click column visibility context menu */} {/* Right-click column visibility context menu */}
{contextMenu && ( {contextMenu && (
<DropdownMenu <div
open={true} ref={contextMenuRef}
onOpenChange={(open) => { style={{ left: contextMenu.x, top: contextMenu.y }}
if (!open) setContextMenu(null); className="fixed z-50 min-w-[180px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 animate-in fade-in-0 zoom-in-95"
}}
> >
<DropdownMenuContent <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
style={{ Toggle Columns
position: "fixed", </div>
left: contextMenu.x, <div className="my-1 h-px bg-border" />
top: contextMenu.y, {TOGGLEABLE_COLUMNS.map((col) => (
}} <label
className="w-48" key={col.id}
> className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground"
<DropdownMenuLabel>Toggle Columns</DropdownMenuLabel> >
<DropdownMenuSeparator /> <Checkbox
{TOGGLEABLE_COLUMNS.map((col) => (
<DropdownMenuCheckboxItem
key={col.id}
checked={isColumnVisible(col.id)} checked={isColumnVisible(col.id)}
onCheckedChange={(checked) => onCheckedChange={(checked) =>
toggleColumn(col.id, checked !== false) toggleColumn(col.id, checked !== false)
} }
> />
{col.label} {col.label}
</DropdownMenuCheckboxItem> </label>
))} ))}
</DropdownMenuContent> </div>
</DropdownMenu>
)} )}
</div> </div>
); );