diff --git a/src/components/datagrid/body/data_grid_cell_popover.test.tsx b/src/components/datagrid/body/data_grid_cell_popover.test.tsx index 0e87a303c8e..9ab0d50e2d6 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.test.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.test.tsx @@ -7,10 +7,49 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { shallow } from 'enzyme'; import { keys } from '../../../services'; +import { testCustomHook } from '../../../test'; -import { EuiDataGridCellPopover } from './data_grid_cell_popover'; +import { + useCellPopover, + EuiDataGridCellPopover, +} from './data_grid_cell_popover'; + +describe('useCellPopover', () => { + describe('openCellPopover', () => { + it('sets popoverIsOpen state to true', () => { + const { + return: { cellPopoverContext }, + getUpdatedState, + } = testCustomHook(() => useCellPopover()); + expect(cellPopoverContext.popoverIsOpen).toEqual(false); + + act(() => + cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0 }) + ); + expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(true); + }); + }); + + describe('closeCellPopover', () => { + it('sets popoverIsOpen state to false', () => { + const { + return: { cellPopoverContext }, + getUpdatedState, + } = testCustomHook(() => useCellPopover()); + + act(() => + cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0 }) + ); + expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(true); + + act(() => cellPopoverContext.closeCellPopover()); + expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(false); + }); + }); +}); describe('EuiDataGridCellPopover', () => { const requiredProps = { diff --git a/src/components/datagrid/body/data_grid_cell_popover.tsx b/src/components/datagrid/body/data_grid_cell_popover.tsx index 88eb4a9112d..dedfa31ebc8 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.tsx @@ -5,18 +5,62 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { JSXElementConstructor } from 'react'; + +import React, { + createContext, + useState, + useCallback, + JSXElementConstructor, +} from 'react'; import { keys } from '../../../services'; import { EuiButtonEmpty, EuiButtonEmptyProps } from '../../button/button_empty'; import { EuiFlexGroup, EuiFlexItem } from '../../flex'; import { EuiPopover, EuiPopoverFooter } from '../../popover'; import { + DataGridCellPopoverContextShape, EuiDataGridCellPopoverProps, EuiDataGridCellValueElementProps, EuiDataGridColumnCellAction, EuiDataGridColumnCellActionProps, } from '../data_grid_types'; +export const DataGridCellPopoverContext = createContext< + DataGridCellPopoverContextShape +>({ + popoverIsOpen: false, + openCellLocation: { rowIndex: 0, colIndex: 0 }, + openCellPopover: () => {}, + closeCellPopover: () => {}, +}); + +export const useCellPopover = (): { + cellPopoverContext: DataGridCellPopoverContextShape; +} => { + // Current open state & cell location are handled here + const [popoverIsOpen, setPopoverIsOpen] = useState(false); + const [openCellLocation, setOpenCellLocation] = useState({ + rowIndex: 0, + colIndex: 0, + }); + + const closeCellPopover = useCallback(() => setPopoverIsOpen(false), []); + const openCellPopover = useCallback(({ rowIndex, colIndex }) => { + // TODO: Popover anchor & content + + setOpenCellLocation({ rowIndex, colIndex }); + setPopoverIsOpen(true); + }, []); + + const cellPopoverContext = { + popoverIsOpen, + closeCellPopover, + openCellPopover, + openCellLocation, + }; + + return { cellPopoverContext }; +}; + export function EuiDataGridCellPopover({ anchorContent, cellContentProps, diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx index 0df619f7de6..55bfb80a3ee 100644 --- a/src/components/datagrid/data_grid.test.tsx +++ b/src/components/datagrid/data_grid.test.tsx @@ -2762,6 +2762,8 @@ describe('EuiDataGrid', () => { expect(gridRef.current).toEqual({ setIsFullScreen: expect.any(Function), setFocusedCell: expect.any(Function), + openCellPopover: expect.any(Function), + closeCellPopover: expect.any(Function), }); }); }); diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index dbb0256b477..3e1e0b893d3 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -45,6 +45,10 @@ import { EuiDataGridInMemoryRenderer, } from './utils/in_memory'; import { useHeaderIsInteractive } from './body/header/header_is_interactive'; +import { + DataGridCellPopoverContext, + useCellPopover, +} from './body/data_grid_cell_popover'; import { providedPopoverContents } from './body/popover_utils'; import { computeVisibleRows } from './utils/row_count'; import { EuiDataGridPaginationRenderer } from './utils/data_grid_pagination'; @@ -273,6 +277,11 @@ export const EuiDataGrid = forwardRef( gridItemsRendered, }); + /** + * Cell popover + */ + const { cellPopoverContext } = useCellPopover(); + /** * Toolbar & full-screen */ @@ -302,8 +311,10 @@ export const EuiDataGrid = forwardRef( setFocusedCell: ({ rowIndex, colIndex }) => { focusContext.setFocusedCell([colIndex, rowIndex]); }, + openCellPopover: cellPopoverContext.openCellPopover, + closeCellPopover: cellPopoverContext.closeCellPopover, }), - [focusContext] + [focusContext, cellPopoverContext] ); /** @@ -381,122 +392,124 @@ export const EuiDataGrid = forwardRef( return ( - - -
+ + - {showToolbar && ( - - )} - {inMemory ? ( - - ) : null} -
- -
- {pagination && props['aria-labelledby'] && ( -
+ +
+ {pagination && props['aria-labelledby'] && ( + + )} + {pagination && ( + + )} + - )} - {pagination && ( - - )} - -
-
-
+ + + +
); } diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 05f563e4e37..151a17cedee 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -195,6 +195,13 @@ export interface DataGridFocusContextShape { focusFirstVisibleInteractiveCell: () => void; } +export interface DataGridCellPopoverContextShape { + popoverIsOpen: boolean; + closeCellPopover(): void; + openCellPopover(args: { rowIndex: number; colIndex: number }): void; + openCellLocation: { rowIndex: number; colIndex: number }; +} + export type CommonGridProps = CommonProps & HTMLAttributes & { /** @@ -301,11 +308,17 @@ export interface EuiDataGridRefProps { * toggles a modal or flyout - focus must be restored to the grid on close * to prevent keyboard or screen reader users from being stranded. */ - setFocusedCell(targetCell: EuiDataGridCellLocation): void; + setFocusedCell: (cell: { rowIndex: number; colIndex: number }) => void; + /** + * Allows manually opening the popover of the specified cell in the grid. + */ + openCellPopover: (cell: { rowIndex: number; colIndex: number }) => void; + /** + * Closes any currently open popovers in the data grid. + */ + closeCellPopover: () => void; } -export type EuiDataGridCellLocation = { rowIndex: number; colIndex: number }; - export interface EuiDataGridColumnResizerProps { columnId: string; columnWidth: number;