'use client'; import { useState } from 'react'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger, } from '@djangocfg/ui-core/components'; import type { TreeContextMenuItem, TreeRowRenderProps, } from '../../types'; interface RowContextMenuProps { rowProps: TreeRowRenderProps; items: TreeContextMenuItem[]; trigger: React.ReactNode; } /** * Per-row context menu. * * PERF: every visible row wraps its trigger in a ``, so a * large tree mounts hundreds of these. Building the full * `` item subtree for each of them on every render is * what made right-click feel laggy (the selection change on right-click * re-renders all rows, each rebuilding its menu body). We now render the * content body ONLY while this row's menu is actually open — closed rows * cost just the lightweight `ContextMenu` + `Trigger` shell. */ function RowContextMenu({ rowProps, items, trigger, }: RowContextMenuProps) { const [open, setOpen] = useState(false); return ( {trigger} {open ? ( {items.map((item, idx) => { if (item === 'separator') { return ; } const Icon = item.icon; return ( item.onSelect(rowProps)} > {Icon ? : null} {item.label} {item.shortcut ? ( {item.shortcut} ) : null} ); })} ) : null} ); } /** * Render an array of declarative menu items as a themed `` * wrapped around the supplied trigger element. Pure presentational layer * — the caller resolves and merges items. */ export function renderItemsAsContextMenu( rowProps: TreeRowRenderProps, items: TreeContextMenuItem[], trigger: React.ReactNode, ): React.ReactNode { return ; } /** * Drop trailing / leading / duplicate separators so a merged menu never * shows a separator next to a section header or another separator. */ export function tidyMenuItems( items: TreeContextMenuItem[], ): TreeContextMenuItem[] { const out: TreeContextMenuItem[] = []; for (const it of items) { if (it === 'separator') { if (out.length === 0) continue; if (out[out.length - 1] === 'separator') continue; out.push(it); } else { out.push(it); } } while (out.length > 0 && out[out.length - 1] === 'separator') out.pop(); return out; }