diff --git a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx
index 54436489535..11a9eea4968 100644
--- a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx
+++ b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx
@@ -1,4 +1,4 @@
-import React, { useState, ReactNode } from 'react';
+import React, { useState, useEffect, ReactNode } from 'react';
import { faker } from '@faker-js/faker';
import {
@@ -73,12 +73,18 @@ const RenderCellPopover = (props: EuiDataGridCellPopoverElementProps) => {
cellActions,
cellContentsElement,
DefaultCellPopover,
+ setCellPopoverProps,
} = props;
let title: ReactNode = 'Custom popover';
let content: ReactNode = {children};
let footer: ReactNode = cellActions;
+ // Set custom cell expansion popover props
+ useEffect(() => {
+ setCellPopoverProps({ panelClassName: 'customCellPopover' });
+ }, [setCellPopoverProps]);
+
// An example of custom popover content
if (schema === 'favoriteFranchise') {
title = 'Custom popover with custom content';
diff --git a/src-docs/src/views/datagrid/cells_popovers/datagrid_cell_popover_example.js b/src-docs/src/views/datagrid/cells_popovers/datagrid_cell_popover_example.js
index b25ee99741d..e8f319b0bd3 100644
--- a/src-docs/src/views/datagrid/cells_popovers/datagrid_cell_popover_example.js
+++ b/src-docs/src/views/datagrid/cells_popovers/datagrid_cell_popover_example.js
@@ -112,6 +112,15 @@ export const DataGridCellPopoverExample = {
rendering for other cells.
+
+
+ setCellPopoverProps - this callback is passed
+ to allow customizing the cell expansion popover. Accepts any
+ prop that EuiPopover accepts, except for{' '}
+ button & closePopover,
+ which is controlled by the data grid.
+
+
Take a look at the below example's demo code to see the above
diff --git a/src/components/datagrid/body/data_grid_cell.test.tsx b/src/components/datagrid/body/data_grid_cell.test.tsx
index de149228cd6..4e2da6b78bd 100644
--- a/src/components/datagrid/body/data_grid_cell.test.tsx
+++ b/src/components/datagrid/body/data_grid_cell.test.tsx
@@ -30,6 +30,7 @@ describe('EuiDataGridCell', () => {
openCellPopover: jest.fn(),
setPopoverAnchor: jest.fn(),
setPopoverContent: jest.fn(),
+ setCellPopoverProps: () => {},
};
const requiredProps = {
rowIndex: 0,
diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx
index 866a77e8cbd..e90328415b2 100644
--- a/src/components/datagrid/body/data_grid_cell.tsx
+++ b/src/components/datagrid/body/data_grid_cell.tsx
@@ -478,7 +478,11 @@ export class EuiDataGridCell extends Component<
handleCellPopover = () => {
if (this.isPopoverOpen()) {
- const { setPopoverAnchor, setPopoverContent } = this.props.popoverContext;
+ const {
+ setPopoverAnchor,
+ setPopoverContent,
+ setCellPopoverProps,
+ } = this.props.popoverContext;
// Set popover anchor
const cellAnchorEl = this.popoverAnchorRef.current!;
@@ -515,6 +519,7 @@ export class EuiDataGridCell extends Component<
}
DefaultCellPopover={DefaultCellPopover}
+ setCellPopoverProps={setCellPopoverProps}
>
-import React from 'react';
+import React, { useEffect } from 'react';
import { EuiDataGrid, EuiDataGridProps } from '../';
const baseProps: EuiDataGridProps = {
@@ -100,4 +100,31 @@ describe('EuiDataGridCellPopover', () => {
'not.exist'
);
});
+
+ it('allows consumers to use setCellPopoverProps, passed from renderCellPopover, to customize popover props', () => {
+ const RenderCellPopover = ({
+ DefaultCellPopover,
+ setCellPopoverProps,
+ ...props
+ }) => {
+ useEffect(() => {
+ setCellPopoverProps({
+ panelClassName: 'hello',
+ panelProps: { className: 'world' },
+ });
+ }, [setCellPopoverProps]);
+
+ return ;
+ };
+
+ cy.realMount(
+
+ );
+ cy.get(
+ '[data-gridcell-row-index="0"][data-gridcell-column-index="0"]'
+ ).realClick();
+ cy.get('[data-test-subj="euiDataGridCellExpandButton"]').realClick();
+
+ cy.get('.euiDataGridRowCell__popover.hello.world').should('exist');
+ });
});
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 da00dc334cd..def6ced9387 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,10 @@
*/
import React from 'react';
-import { act } from 'react-dom/test-utils';
+import { renderHook, act } from '@testing-library/react-hooks';
import { shallow } from 'enzyme';
+
import { keys } from '../../../services';
-import { testCustomHook } from '../../../test/internal';
import { DataGridCellPopoverContextShape } from '../data_grid_types';
import { useCellPopover, DefaultCellPopover } from './data_grid_cell_popover';
@@ -18,55 +18,57 @@ import { useCellPopover, DefaultCellPopover } 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);
+ const { result } = renderHook(useCellPopover);
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false);
act(() =>
- cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0 })
+ result.current.cellPopoverContext.openCellPopover({
+ rowIndex: 0,
+ colIndex: 0,
+ })
);
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(true);
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
});
it('does nothing if called again on a popover that is already open', () => {
- const {
- return: { cellPopoverContext, cellPopover },
- getUpdatedState,
- } = testCustomHook(() => useCellPopover());
- expect(cellPopover).toBeFalsy();
+ const { result } = renderHook(useCellPopover);
+ expect(result.current.cellPopover).toBeFalsy();
act(() => {
- cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0 });
- cellPopoverContext.setPopoverAnchor(document.createElement('div'));
+ result.current.cellPopoverContext.openCellPopover({
+ rowIndex: 0,
+ colIndex: 0,
+ });
+ result.current.cellPopoverContext.setPopoverAnchor(
+ document.createElement('div')
+ );
});
- expect(getUpdatedState().cellPopover).not.toBeFalsy();
+ expect(result.current.cellPopover).not.toBeFalsy();
act(() => {
- getUpdatedState().cellPopoverContext.openCellPopover({
+ result.current.cellPopoverContext.openCellPopover({
rowIndex: 0,
colIndex: 0,
});
});
- expect(getUpdatedState().cellPopover).not.toBeFalsy();
+ expect(result.current.cellPopover).not.toBeFalsy();
});
});
describe('closeCellPopover', () => {
it('sets popoverIsOpen state to false', () => {
- const {
- return: { cellPopoverContext },
- getUpdatedState,
- } = testCustomHook(() => useCellPopover());
+ const { result } = renderHook(useCellPopover);
act(() =>
- cellPopoverContext.openCellPopover({ rowIndex: 0, colIndex: 0 })
+ result.current.cellPopoverContext.openCellPopover({
+ rowIndex: 0,
+ colIndex: 0,
+ })
);
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(true);
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
- act(() => cellPopoverContext.closeCellPopover());
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(false);
+ act(() => result.current.cellPopoverContext.closeCellPopover());
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false);
});
});
@@ -86,14 +88,11 @@ describe('useCellPopover', () => {
};
it('renders', () => {
- const {
- return: { cellPopoverContext, cellPopover },
- getUpdatedState,
- } = testCustomHook(() => useCellPopover());
- expect(cellPopover).toBeFalsy(); // Should be empty on init
+ const { result } = renderHook(useCellPopover);
+ expect(result.current.cellPopover).toBeFalsy(); // Should be empty on init
- populateCellPopover(cellPopoverContext);
- expect(getUpdatedState().cellPopover).toMatchInlineSnapshot(`
+ populateCellPopover(result.current.cellPopoverContext);
+ expect(result.current.cellPopover).toMatchInlineSnapshot(`
}
closePopover={[Function]}
@@ -134,25 +133,16 @@ describe('useCellPopover', () => {
mockCell.appendChild(mockPopoverAnchor);
const renderCellPopover = () => {
- const {
- return: { cellPopoverContext },
- getUpdatedState,
- } = testCustomHook>(() =>
- useCellPopover()
- );
- populateCellPopover(cellPopoverContext);
-
- const { cellPopover } = getUpdatedState();
- const component = shallow({cellPopover}
);
+ const { result } = renderHook(useCellPopover);
+ populateCellPopover(result.current.cellPopoverContext);
+ const component = shallow({result.current.cellPopover}
);
- return { component, getUpdatedState };
+ return { result, component };
};
it('closes the popover and refocuses the cell when the Escape key is pressed', () => {
- const { component, getUpdatedState } = renderCellPopover();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- true
- );
+ const { result, component } = renderCellPopover();
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
const event = {
key: keys.ESCAPE,
@@ -165,17 +155,13 @@ describe('useCellPopover', () => {
expect(event.preventDefault).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- false
- );
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false);
expect(document.activeElement).toEqual(mockCell);
});
it('closes the popover when the F2 key is pressed', () => {
- const { component, getUpdatedState } = renderCellPopover();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- true
- );
+ const { result, component } = renderCellPopover();
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
const event = {
key: keys.F2,
@@ -188,17 +174,13 @@ describe('useCellPopover', () => {
expect(event.preventDefault).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- false
- );
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(false);
expect(document.activeElement).toEqual(mockCell);
});
it('does nothing when other keys are pressed', () => {
- const { component, getUpdatedState } = renderCellPopover();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- true
- );
+ const { result, component } = renderCellPopover();
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
const event = {
key: keys.ENTER,
@@ -212,12 +194,12 @@ describe('useCellPopover', () => {
expect(event.stopPropagation).not.toHaveBeenCalled();
expect(rafSpy).not.toHaveBeenCalled();
- expect(getUpdatedState().cellPopoverContext.popoverIsOpen).toEqual(
- true
- );
+ expect(result.current.cellPopoverContext.popoverIsOpen).toEqual(true);
});
});
});
+
+ // setCellPopoverProps is tested in the Cypress .spec file
});
describe('popover content renderers', () => {
@@ -232,6 +214,7 @@ describe('popover content renderers', () => {
cellActions: Action
,
cellContentsElement,
DefaultCellPopover,
+ setCellPopoverProps: () => {},
};
test('default cell popover', () => {
diff --git a/src/components/datagrid/body/data_grid_cell_popover.tsx b/src/components/datagrid/body/data_grid_cell_popover.tsx
index 32a38364aae..0a51b131af2 100644
--- a/src/components/datagrid/body/data_grid_cell_popover.tsx
+++ b/src/components/datagrid/body/data_grid_cell_popover.tsx
@@ -7,9 +7,10 @@
*/
import React, { createContext, useState, useCallback, ReactNode } from 'react';
+import classNames from 'classnames';
import { keys } from '../../../services';
-import { EuiWrappingPopover } from '../../popover';
+import { EuiWrappingPopover, EuiPopoverProps } from '../../popover';
import {
DataGridCellPopoverContextShape,
EuiDataGridCellPopoverElementProps,
@@ -24,6 +25,7 @@ export const DataGridCellPopoverContext = createContext<
closeCellPopover: () => {},
setPopoverAnchor: () => {},
setPopoverContent: () => {},
+ setCellPopoverProps: () => {},
});
export const useCellPopover = (): {
@@ -39,6 +41,10 @@ export const useCellPopover = (): {
// Popover anchor & content are passed by individual `EuiDataGridCell`s
const [popoverAnchor, setPopoverAnchor] = useState(null);
const [popoverContent, setPopoverContent] = useState();
+ // Allow customization of most (not all) popover props by consumers
+ const [cellPopoverProps, setCellPopoverProps] = useState<
+ Partial
+ >({});
const closeCellPopover = useCallback(() => setPopoverIsOpen(false), []);
const openCellPopover = useCallback(
@@ -68,21 +74,26 @@ export const useCellPopover = (): {
cellLocation,
setPopoverAnchor,
setPopoverContent,
+ setCellPopoverProps,
};
// Note that this popover is rendered once at the top grid level, rather than one popover per cell
const cellPopover = popoverIsOpen && popoverAnchor && (
{
if (event.key === keys.F2 || event.key === keys.ESCAPE) {
event.preventDefault();
@@ -92,6 +103,8 @@ export const useCellPopover = (): {
requestAnimationFrame(() => popoverAnchor.parentElement!.focus());
}
}}
+ button={popoverAnchor}
+ closePopover={closeCellPopover}
>
{popoverContent}
diff --git a/src/components/datagrid/body/footer/data_grid_footer_row.test.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.test.tsx
index 9373b798ab2..8017f317ca9 100644
--- a/src/components/datagrid/body/footer/data_grid_footer_row.test.tsx
+++ b/src/components/datagrid/body/footer/data_grid_footer_row.test.tsx
@@ -49,6 +49,7 @@ describe('EuiDataGridFooterRow', () => {
"closeCellPopover": [Function],
"openCellPopover": [Function],
"popoverIsOpen": false,
+ "setCellPopoverProps": [Function],
"setPopoverAnchor": [Function],
"setPopoverContent": [Function],
}
@@ -75,6 +76,7 @@ describe('EuiDataGridFooterRow', () => {
"closeCellPopover": [Function],
"openCellPopover": [Function],
"popoverIsOpen": false,
+ "setCellPopoverProps": [Function],
"setPopoverAnchor": [Function],
"setPopoverContent": [Function],
}
@@ -127,6 +129,7 @@ describe('EuiDataGridFooterRow', () => {
"closeCellPopover": [Function],
"openCellPopover": [Function],
"popoverIsOpen": false,
+ "setCellPopoverProps": [Function],
"setPopoverAnchor": [Function],
"setPopoverContent": [Function],
}
@@ -178,6 +181,7 @@ describe('EuiDataGridFooterRow', () => {
"closeCellPopover": [Function],
"openCellPopover": [Function],
"popoverIsOpen": false,
+ "setCellPopoverProps": [Function],
"setPopoverAnchor": [Function],
"setPopoverContent": [Function],
}
diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts
index c03d3a4bb10..0ae7fb714f7 100644
--- a/src/components/datagrid/data_grid_types.ts
+++ b/src/components/datagrid/data_grid_types.ts
@@ -28,6 +28,7 @@ import { ExclusiveUnion, CommonProps, OneOf } from '../common';
import { RowHeightUtils } from './utils/row_heights';
import { IconType } from '../icon';
import { EuiTokenProps } from '../token';
+import { EuiPopoverProps } from '../popover';
// since react-window doesn't export a type with the imperative api only we can
// use this to omit the react-specific class component methods
@@ -206,6 +207,7 @@ export interface DataGridCellPopoverContextShape {
closeCellPopover(): void;
setPopoverAnchor(anchor: HTMLElement): void;
setPopoverContent(content: ReactNode): void;
+ setCellPopoverProps: EuiDataGridCellPopoverElementProps['setCellPopoverProps'];
}
export type CommonGridProps = CommonProps &
@@ -501,6 +503,13 @@ export interface EuiDataGridCellPopoverElementProps
* If so, that component is provided here as a passed React function component for your usage.
*/
DefaultCellPopover: JSXElementConstructor;
+ /**
+ * Allows passing props to the wrapping cell expansion popover and panel.
+ * Accepts any props that `EuiPopover` accepts, except for `button` and `closePopover`.
+ */
+ setCellPopoverProps: (
+ props: Omit
+ ) => void;
}
export interface EuiDataGridCellProps {
diff --git a/upcoming_changelogs/6632.md b/upcoming_changelogs/6632.md
new file mode 100644
index 00000000000..38281b6abeb
--- /dev/null
+++ b/upcoming_changelogs/6632.md
@@ -0,0 +1 @@
+- Added new `setCellPopoverProps` parameter callback to `EuiDataGrid`'s `renderCellPopover` prop