/* Copyright 2026 Marimo. All rights reserved. */ import type { Cell } from "@tanstack/react-table"; import { useAtomValue } from "jotai"; import { CopyIcon, FilterIcon, SquareStack } from "lucide-react"; import type { RefObject } from "react"; import useEvent from "react-use-event-hook"; import { copyToClipboard } from "@/utils/copy"; import { Logger } from "@/utils/Logger"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, } from "../ui/context-menu"; import { DATA_CELL_ID } from "./cell-utils"; import { Filter } from "./filters"; import { selectedCellsAtom } from "./range-focus/atoms"; import { getClipboardContent, getRawValue } from "./utils"; export const DataTableContextMenu = ({ contextMenuRef, tableBody, tableRef, copyAllCells, }: { contextMenuRef: RefObject | null>; tableBody: React.ReactNode; tableRef: RefObject; copyAllCells: () => void; }) => { const handleContextMenuChange = useEvent((open: boolean) => { const cell = contextMenuRef.current; if (!cell) { return; } // Add a background color to the cell when the context menu is open const cellElement = tableRef.current?.querySelector( `[${DATA_CELL_ID}="${cell.id}"]`, ); if (!cellElement) { Logger.error("Context menu cell not found in table"); return; } if (open) { cellElement.classList.add("bg-(--green-4)"); } else { cellElement.classList.remove("bg-(--green-4)"); } }); return ( {tableBody} ); }; export const CellContextMenu = ({ cellRef, copySelectedCells, }: { cellRef: RefObject | null>; copySelectedCells: () => void; }) => { const selectedCells = useAtomValue(selectedCellsAtom); const multipleSelectedCells = selectedCells.size > 1; const cell = cellRef.current; if (!cell) { Logger.error("No cell found in context menu"); return; } const table = cell.getContext().table; const displayedValue = cell.getValue(); const rawValue = getRawValue(table, cell.row.index, cell.column.id) ?? displayedValue; const handleCopyCell = () => { try { const { text, html } = getClipboardContent(rawValue, displayedValue); copyToClipboard(text, html); } catch (error) { Logger.error("Failed to copy context menu cell", error); } }; const column = cell.column; const canFilter = column.getCanFilter() && column.columnDef.meta?.filterType; const handleFilterCell = (operator: "in" | "not_in") => { column.setFilterValue( Filter.select({ options: [rawValue], operator, }), ); }; return ( Copy cell {multipleSelectedCells && ( Copy selected cells )} {canFilter && ( <> handleFilterCell("in")}> Filter by this value handleFilterCell("not_in")}> Remove rows with this value )} ); };