Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, ReactNode } from 'react';
import React, { useState, useEffect, ReactNode } from 'react';
import { faker } from '@faker-js/faker';

import {
Expand Down Expand Up @@ -73,12 +73,18 @@ const RenderCellPopover = (props: EuiDataGridCellPopoverElementProps) => {
cellActions,
cellContentsElement,
DefaultCellPopover,
setCellPopoverProps,
} = props;

let title: ReactNode = 'Custom popover';
let content: ReactNode = <EuiText size="s">{children}</EuiText>;
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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ export const DataGridCellPopoverExample = {
rendering for other cells.
</p>
</li>
<li>
<p>
<EuiCode>setCellPopoverProps</EuiCode> - this callback is passed
to allow customizing the cell expansion popover. Accepts any
prop that <EuiCode>EuiPopover</EuiCode> accepts, except for{' '}
<EuiCode>button</EuiCode> & <EuiCode>closePopover</EuiCode>,
which is controlled by the data grid.
</p>
</li>
</ul>
<p>
Take a look at the below example&apos;s demo code to see the above
Expand Down
1 change: 1 addition & 0 deletions src/components/datagrid/body/data_grid_cell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('EuiDataGridCell', () => {
openCellPopover: jest.fn(),
setPopoverAnchor: jest.fn(),
setPopoverContent: jest.fn(),
setCellPopoverProps: () => {},
};
const requiredProps = {
rowIndex: 0,
Expand Down
7 changes: 6 additions & 1 deletion src/components/datagrid/body/data_grid_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
Expand Down Expand Up @@ -515,6 +519,7 @@ export class EuiDataGridCell extends Component<
<EuiDataGridCellPopoverActions {...sharedProps} column={column} />
}
DefaultCellPopover={DefaultCellPopover}
setCellPopoverProps={setCellPopoverProps}
>
<CellElement
{...sharedProps}
Expand Down
29 changes: 28 additions & 1 deletion src/components/datagrid/body/data_grid_cell_popover.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/// <reference types="../../../../cypress/support"/>

import React from 'react';
import React, { useEffect } from 'react';
import { EuiDataGrid, EuiDataGridProps } from '../';

const baseProps: EuiDataGridProps = {
Expand Down Expand Up @@ -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 <DefaultCellPopover {...props} />;
};

cy.realMount(
<EuiDataGrid {...baseProps} renderCellPopover={RenderCellPopover} />
);
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');
});
});
115 changes: 49 additions & 66 deletions src/components/datagrid/body/data_grid_cell_popover.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,68 @@
*/

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';

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);
});
});

Expand All @@ -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(`
<EuiWrappingPopover
button={<div />}
closePopover={[Function]}
Expand Down Expand Up @@ -134,25 +133,16 @@ describe('useCellPopover', () => {
mockCell.appendChild(mockPopoverAnchor);

const renderCellPopover = () => {
const {
return: { cellPopoverContext },
getUpdatedState,
} = testCustomHook<ReturnType<typeof useCellPopover>>(() =>
useCellPopover()
);
populateCellPopover(cellPopoverContext);

const { cellPopover } = getUpdatedState();
const component = shallow(<div>{cellPopover}</div>);
const { result } = renderHook(useCellPopover);
populateCellPopover(result.current.cellPopoverContext);
const component = shallow(<div>{result.current.cellPopover}</div>);

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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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', () => {
Expand All @@ -232,6 +214,7 @@ describe('popover content renderers', () => {
cellActions: <div data-test-subj="mockCellActions">Action</div>,
cellContentsElement,
DefaultCellPopover,
setCellPopoverProps: () => {},
};

test('default cell popover', () => {
Expand Down
21 changes: 17 additions & 4 deletions src/components/datagrid/body/data_grid_cell_popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,6 +25,7 @@ export const DataGridCellPopoverContext = createContext<
closeCellPopover: () => {},
setPopoverAnchor: () => {},
setPopoverContent: () => {},
setCellPopoverProps: () => {},
});

export const useCellPopover = (): {
Expand All @@ -39,6 +41,10 @@ export const useCellPopover = (): {
// Popover anchor & content are passed by individual `EuiDataGridCell`s
const [popoverAnchor, setPopoverAnchor] = useState<HTMLElement | null>(null);
const [popoverContent, setPopoverContent] = useState<ReactNode>();
// Allow customization of most (not all) popover props by consumers
const [cellPopoverProps, setCellPopoverProps] = useState<
Partial<EuiPopoverProps>
>({});

const closeCellPopover = useCallback(() => setPopoverIsOpen(false), []);
const openCellPopover = useCallback(
Expand Down Expand Up @@ -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 && (
<EuiWrappingPopover
isOpen={popoverIsOpen}
button={popoverAnchor}
display="block"
hasArrow={false}
panelPaddingSize="s"
panelClassName="euiDataGridRowCell__popover"
{...cellPopoverProps}
panelProps={{
'data-test-subj': 'euiDataGridExpansionPopover',
...(cellPopoverProps.panelProps || {}),
}}
closePopover={closeCellPopover}
panelClassName={classNames(
'euiDataGridRowCell__popover',
cellPopoverProps.panelClassName,
cellPopoverProps.panelProps?.className
)}
onKeyDown={(event) => {
if (event.key === keys.F2 || event.key === keys.ESCAPE) {
event.preventDefault();
Expand All @@ -92,6 +103,8 @@ export const useCellPopover = (): {
requestAnimationFrame(() => popoverAnchor.parentElement!.focus());
}
}}
button={popoverAnchor}
closePopover={closeCellPopover}
>
{popoverContent}
</EuiWrappingPopover>
Expand Down
Loading