LyteNyte Grid provides the grid.api.positionFromElement
method to help implement context menu
functionality. The context menu component itself must come from your preferred UI library.
The example below demonstrates integration with the Radix UI Context Menu.
"use client";
import { useClientRowDataSource, Grid } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type {
Column,
PositionUnion,
} from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { ContextMenu } from "radix-ui";
import { useId, useState } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital" },
{ id: "default" },
{ id: "housing" },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function ContextMenuDemo() {
const ds = useClientRowDataSource({ data: bankDataSmall });
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
});
const view = grid.view.useValue();
const [position, setPosition] = useState<PositionUnion | null>(null);
return (
<div>
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<ContextMenu.Root
modal
onOpenChange={(b) => {
if (!b) setPosition(null);
}}
>
<ContextMenu.Trigger
asChild
onContextMenu={(c) => {
const element = c.target as HTMLElement;
setPosition(grid.api.positionFromElement(element));
}}
>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</ContextMenu.Trigger>
<GridContextMenu position={position} />
</ContextMenu.Root>
</Grid.Viewport>
</Grid.Root>
</div>
</div>
);
}
export function GridContextMenu({
position,
}: {
position: PositionUnion | null;
}) {
return (
<ContextMenu.Portal>
<ContextMenu.Content
className="bg-ln-gray-05 border-ln-gray-30 border z-50 rounded relative p-2"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<div className="text-sm pb-1 text-ln-gray-70">
Context Menu For Position: {position?.kind}
</div>
<ContextMenu.Item className="py-1 text-sm">Item 1</ContextMenu.Item>
<ContextMenu.Item className="py-1 text-sm">Item 2</ContextMenu.Item>
<ContextMenu.Item className="py-1 text-sm">Item 3</ContextMenu.Item>
<ContextMenu.Separator />
</ContextMenu.Content>
</ContextMenu.Portal>
);
}
In the example, grid rows are wrapped in Radix UI's ContextMenu.Root
. This illustrates one of the
advantages of a headless grid: you can integrate third-party UI components easily and decide where
and how to attach them.
The grid.api.positionFromElement
method returns either a PositionUnion
object or null
.
A PositionUnion
represents the resolved grid position of an element and can identify:
The element you pass does not have to be the direct grid element-it can be a child element. LyteNyte Grid resolves it to the correct grid position automatically.