Skip to content

Commit

Permalink
Context menu from ReactElement
Browse files Browse the repository at this point in the history
  • Loading branch information
matttdawson committed Jun 30, 2023
1 parent a458353 commit 81d4f1a
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 130 deletions.
42 changes: 21 additions & 21 deletions src/components/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
import { CellClickedEvent, ColDef, ColumnResizedEvent, ModelUpdatedEvent } from "ag-grid-community";
import { CellClassParams, EditableCallback, EditableCallbackParams } from "ag-grid-community/dist/lib/entities/colDef";
import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
import {
CellContextMenuEvent,
CellEvent,
GridReadyEvent,
SelectionChangedEvent,
} from "ag-grid-community/dist/lib/events";
import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
import { AgGridReact } from "ag-grid-react";
import clsx from "clsx";
import { defer, difference, isEmpty, last, omit, xorBy } from "lodash-es";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";

import { GridContext } from "../contexts/GridContext";
import { GridUpdatingContext } from "../contexts/GridUpdatingContext";
import { useIntervalHook } from "../lui/timeoutHook";
import { IFormTestRow } from "../stories/grid/FormTest";
import { fnOrVar, isNotEmpty } from "../utils/util";
import { GridNoRowsOverlay } from "./GridNoRowsOverlay";
import { usePostSortRowsHook } from "./PostSortRowsHook";
import { GridHeaderSelect } from "./gridHeader";
import { GridContextMenuItem, useGridContextMenu } from "./gridPopoverEdit/GridContextMenu";
import { useGridContextMenu } from "./gridHook";

export interface GridBaseRow {
id: string | number;
}

export interface GridContextMenuComponentProps {
selectedRows: IFormTestRow[];
colDef: ColDef;
close: () => void;
}

export type GridContextMenuComponent = (props: GridContextMenuComponentProps) => ReactElement | null;

export interface GridProps {
readOnly?: boolean; // set all editables to false when read only, make all styles black, otherwise style is gray for not editable
selectable?: boolean;
Expand Down Expand Up @@ -77,7 +81,12 @@ export interface GridProps {
/**
* Context menu definition if required.
*/
contextMenu?: (selectedRows: any[]) => GridContextMenuItem[] | null;
contextMenu?: GridContextMenuComponent;

/**
* Whether to select row on context menu.
*/
contextMenuSelectRow?: boolean;
}

/**
Expand All @@ -90,6 +99,7 @@ export const Grid = ({
theme = "ag-theme-alpine",
sizeColumns = "auto",
selectColumnPinned = null,
contextMenuSelectRow = false,
...params
}: GridProps): JSX.Element => {
const {
Expand Down Expand Up @@ -562,17 +572,7 @@ export const Grid = ({
}
}, []);

const gridContextMenu = useGridContextMenu(params.contextMenu);

const cellContextMenu = useCallback(
(event: CellContextMenuEvent) => {
if (!event.node.isSelected()) {
event.node.setSelected(true, true);
}
gridContextMenu.cellContextMenu(event);
},
[gridContextMenu],
);
const gridContextMenu = useGridContextMenu({ contextMenu: params.contextMenu, contextMenuSelectRow });

// This is setting a ref in the GridContext so won't be triggering an update loop
setOnCellEditingComplete(params.onCellEditingComplete);
Expand Down Expand Up @@ -629,7 +629,7 @@ export const Grid = ({
doesExternalFilterPass={doesExternalFilterPass}
maintainColumnOrder={true}
preventDefaultOnContextMenu={true}
onCellContextMenu={cellContextMenu}
onCellContextMenu={gridContextMenu.cellContextMenu}
/>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/gridHook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useGridContextMenu";
80 changes: 80 additions & 0 deletions src/components/gridHook/useGridContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ColDef } from "ag-grid-community";
import { CellContextMenuEvent } from "ag-grid-community/dist/lib/events";
import { useCallback, useContext, useRef, useState } from "react";

import { GridContext } from "../../contexts/GridContext";
import { ControlledMenu } from "../../react-menu3";
import { GridContextMenuComponent } from "../Grid";

export const useGridContextMenu = ({
contextMenuSelectRow,
contextMenu: ContextMenu,
}: {
contextMenuSelectRow: boolean;
contextMenu?: GridContextMenuComponent;
}) => {
const { redrawRows, prePopupOps, postPopupOps, getSelectedRows } = useContext(GridContext);

const [isOpen, setOpen] = useState(false);
const [anchorPoint, setAnchorPoint] = useState({ x: 0, y: 0 });

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const clickedColDefRef = useRef<ColDef>(null!);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const selectedRowsRef = useRef<any[]>([]);

const openMenu = useCallback(
(e: PointerEvent | null | undefined) => {
if (!e) return;
prePopupOps();
setAnchorPoint({ x: e.clientX, y: e.clientY });
setOpen(true);
},
[prePopupOps],
);

const closeMenu = useCallback(() => {
setOpen(false);
redrawRows();
postPopupOps();
}, [postPopupOps, redrawRows]);

const cellContextMenu = useCallback(
(event: CellContextMenuEvent) => {
if (contextMenuSelectRow && !event.node.isSelected()) {
event.node.setSelected(true, true);
}
const selectedRows = contextMenuSelectRow ? getSelectedRows() : [event.data];

clickedColDefRef.current = event.colDef;
selectedRowsRef.current = selectedRows;
// This is actually a pointer event
openMenu(event.event as PointerEvent);
},
[contextMenuSelectRow, getSelectedRows, openMenu],
);

// global onclick
return {
openMenu,
cellContextMenu,
component: isOpen ? (
<>
<ControlledMenu
anchorPoint={anchorPoint}
state={isOpen ? "open" : "closed"}
direction="right"
onClose={() => closeMenu()}
>
{ContextMenu && (
<ContextMenu
selectedRows={selectedRowsRef.current}
colDef={clickedColDefRef.current ?? {}}
close={closeMenu}
/>
)}
</ControlledMenu>
</>
) : null,
};
};
82 changes: 0 additions & 82 deletions src/components/gridPopoverEdit/GridContextMenu.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/gridPopoverEdit/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./GridContextMenu";
export * from "./GridPopoutEditMultiSelect";
export * from "./GridPopoutEditMultiSelectGrid";
export * from "./GridPopoverMenu";
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./GridCellMultiSelectClassRules";
export * from "./gridFilter";
export * from "./gridForm";
export * from "./gridHeader";
export * from "./gridHook";
export * from "./GridIcon";
export * from "./GridLoadableCell";
export * from "./GridNoRowsOverlay";
Expand Down
53 changes: 27 additions & 26 deletions src/stories/grid/GridPopoutContextMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentMeta, ComponentStory } from "@storybook/react/dist/ts3.9/client/preview/types-6-3";
import { useCallback, useContext, useMemo, useState } from "react";
import { ReactElement, useCallback, useContext, useMemo, useState } from "react";

import "@linzjs/lui/dist/fonts";
import "@linzjs/lui/dist/scss/base.scss";
Expand All @@ -10,12 +10,13 @@ import {
Grid,
GridCell,
GridContext,
GridContextMenuComponentProps,
GridContextProvider,
GridProps,
GridUpdatingContextProvider,
MenuItem,
wait,
} from "../..";
import { GridContextMenuItem } from "../../components/gridPopoverEdit/GridContextMenu";
import "../../styles/GridTheme.scss";
import "../../styles/index.scss";
import { IFormTestRow } from "./FormTest";
Expand All @@ -40,6 +41,29 @@ export default {
],
} as ComponentMeta<typeof Grid>;

const ContextMenu = ({ selectedRows, colDef, close }: GridContextMenuComponentProps): ReactElement => {
const onClick = useCallback(() => {
selectedRows.forEach((row) => {
switch (colDef.field) {
case "name":
row.name = "";
break;
case "distance":
row.distance = null;
break;
}
});
close();
}, [close, colDef.field, selectedRows]);

return (
<>
<button onClick={onClick}>Button - Clear cell</button>
<MenuItem onClick={onClick}>Menu Item - Clear cell</MenuItem>
</>
);
};

const GridPopoutContextMenuTemplate: ComponentStory<typeof Grid> = (props: GridProps) => {
const { selectRowsWithFlashDiff } = useContext(GridContext);
const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
Expand Down Expand Up @@ -89,29 +113,6 @@ const GridPopoutContextMenuTemplate: ComponentStory<typeof Grid> = (props: GridP
});
}, [rowData, selectRowsWithFlashDiff]);

const contextMenu = useCallback(
(selectedRows: IFormTestRow[]): GridContextMenuItem[] => [
{
label: "Clear cell...",
onSelect: async ({ colDef }) => {
selectedRows.forEach((row) => {
switch (colDef.field) {
case "name":
row.name = "";
break;
case "distance":
row.distance = null;
break;
}
});
},
},
{ label: "Should be invisible", visible: false, onSelect: () => {} },
{ label: "Disabled", disabled: true, onSelect: () => {} },
],
[],
);

return (
<>
<Grid
Expand All @@ -127,7 +128,7 @@ const GridPopoutContextMenuTemplate: ComponentStory<typeof Grid> = (props: GridP
/* eslint-disable-next-line no-console */
console.log("Cell editing complete");
}}
contextMenu={contextMenu}
contextMenu={ContextMenu}
/>
<ActionButton icon={"ic_add"} name={"Add new row"} inProgressName={"Adding..."} onClick={addRowAction} />
</>
Expand Down

0 comments on commit 81d4f1a

Please sign in to comment.