Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiDataGrid] Allow isExpandable to be set via setCellProps #5667

Merged
merged 10 commits into from
Mar 2, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Added `readOnly` prop to `EuiMarkdownEditor` ([#5627](https://github.com/elastic/eui/pull/5627))
- Added support for supplying `breadcrumbs` and `breadcrumbProps` directly to `EuiPageHeader` ([#5634](https://github.com/elastic/eui/pull/5634))
- Extended props of `EuiBreadcrumb` to include `HTMLElement` and `color` inherited from `EuiLink` ([#5634](https://github.com/elastic/eui/pull/5634))
- Updated `EuiDataGrid` to allow setting individual cell `isExpandable` state via `setCellProps` ([#5667](https://github.com/elastic/eui/pull/5667))

**Bug fixes**

Expand Down
18 changes: 14 additions & 4 deletions src-docs/src/views/datagrid/cell_popover_is_expandable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, ReactNode } from 'react';
import React, { useEffect, useState, ReactNode } from 'react';
// @ts-ignore - faker does not have type declarations
import { fake } from 'faker';

Expand Down Expand Up @@ -37,7 +37,7 @@ const columns: EuiDataGridColumn[] = [
},
{
id: 'boolean',
isExpandable: false,
isExpandable: true, // Overridden by setCellProps for specific cells
},
];

Expand All @@ -58,11 +58,21 @@ export default () => {

return (
<EuiDataGrid
aria-label="Data grid example of columns.isExpandable"
aria-label="Data grid example of isExpandable false"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
rowCount={data.length}
renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]}
renderCellValue={({ rowIndex, columnId, setCellProps }) => {
const value = data[rowIndex][columnId];

useEffect(() => {
if (columnId === 'boolean' && value === 'false') {
setCellProps({ isExpandable: false });
}
}, [columnId, value, setCellProps]);

return value;
}}
/>
);
};
22 changes: 16 additions & 6 deletions src-docs/src/views/datagrid/datagrid_cell_popover_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,21 @@ export const DataGridCellPopoverExample = {
{
title: 'Disabling cell expansion popovers',
text: (
<p>
Popovers can sometimes be unnecessary for short form content. In the
example below we&apos;ve turned them off by setting{' '}
<EuiCode>isExpandable=false</EuiCode> on specific{' '}
<EuiCode>columns</EuiCode>.
</p>
<>
<p>
Popovers can sometimes be unnecessary for short form content. In the
example below we&apos;ve turned them off by setting{' '}
<EuiCode>isExpandable=false</EuiCode> on specific{' '}
<EuiCode>columns</EuiCode>.
</p>
<p>
To set <EuiCode>isExpandable</EuiCode> at a per-cell level instead
of per-column, you can use the <EuiCode>setCellProps</EuiCode>{' '}
callback passed by <EuiCode>renderCellValue</EuiCode>. The below
example conditionally disables the expansion popover for boolean
cells that are &apos;false&apos;.
</p>
</>
),
demo: <IsExpandablePopover />,
components: { IsExpandablePopover },
Expand All @@ -152,6 +161,7 @@ export const DataGridCellPopoverExample = {
],
props: {
EuiDataGridColumn,
EuiDataGridCellValueElementProps,
},
},
],
Expand Down
32 changes: 31 additions & 1 deletion src/components/datagrid/body/data_grid_cell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React from 'react';
import React, { useEffect } from 'react';
import { mount, render, ReactWrapper } from 'enzyme';
import { keys } from '../../../services';
import { mockRowHeightUtils } from '../utils/__mocks__/row_heights';
Expand Down Expand Up @@ -390,6 +390,36 @@ describe('EuiDataGridCell', () => {
});
});

describe('isExpandable', () => {
it('falls back to props.isExpandable which is derived from the column config', () => {
const component = mount(
<EuiDataGridCell {...requiredProps} isExpandable={true} />
);

expect(component.find('renderCellValue').prop('isExpandable')).toBe(true);
});

it('allows overriding column.isExpandable with setCellProps({ isExpandable })', () => {
const RenderCellValue = ({ setCellProps }: any) => {
useEffect(() => {
setCellProps({ isExpandable: false });
}, [setCellProps]);
return 'cell render';
};
const component = mount(
<EuiDataGridCell
{...requiredProps}
isExpandable={true}
renderCellValue={RenderCellValue}
/>
);

expect(component.find('RenderCellValue').prop('isExpandable')).toBe(
false
);
});
});

// TODO: Test ResizeObserver logic in Cypress alongside Jest
describe('row height logic & resize observers', () => {
describe('recalculateAutoHeight', () => {
Expand Down
44 changes: 27 additions & 17 deletions src/components/datagrid/body/data_grid_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import React, {
createRef,
FocusEvent,
FunctionComponent,
HTMLAttributes,
JSXElementConstructor,
KeyboardEvent,
memo,
Expand All @@ -29,6 +28,7 @@ import { DataGridFocusContext } from '../utils/focus';
import {
EuiDataGridCellProps,
EuiDataGridCellState,
EuiDataGridSetCellProps,
EuiDataGridCellValueElementProps,
EuiDataGridCellValueProps,
EuiDataGridCellPopoverElementProps,
Expand Down Expand Up @@ -160,7 +160,7 @@ export class EuiDataGridCell extends Component<

if (doFocusUpdate) {
const interactables = this.getInteractables();
if (this.props.isExpandable === false && interactables.length === 1) {
if (this.isExpandable() === false && interactables.length === 1) {
// Only one element can be interacted with
interactables[0].focus({ preventScroll });
} else {
Expand Down Expand Up @@ -356,7 +356,7 @@ export class EuiDataGridCell extends Component<
return false;
}

setCellProps = (cellProps: HTMLAttributes<HTMLDivElement>) => {
setCellProps = (cellProps: EuiDataGridSetCellProps) => {
this.setState({ cellProps });
};

Expand All @@ -382,7 +382,7 @@ export class EuiDataGridCell extends Component<
// * if the cell children include portalled content React will bubble the focus
// event up, which can trigger the focus() call below, causing focus lock fighting
if (this.cellRef.current === e.target) {
const { colIndex, visibleRowIndex, isExpandable } = this.props;
const { colIndex, visibleRowIndex } = this.props;
// focus in next tick to give potential focus capturing mechanisms time to release their traps
// also clear any previous focus timeout that may still be queued
if (EuiDataGridCell.activeFocusTimeoutId) {
Expand All @@ -393,7 +393,7 @@ export class EuiDataGridCell extends Component<
this.context.setFocusedCell([colIndex, visibleRowIndex]);

const interactables = this.getInteractables();
if (interactables.length === 1 && isExpandable === false) {
if (interactables.length === 1 && this.isExpandable() === false) {
interactables[0].focus();
this.setState({ disableCellTabIndex: true });
}
Expand Down Expand Up @@ -428,12 +428,17 @@ export class EuiDataGridCell extends Component<
}
};

isExpandable = () => {
// props.isExpandable inherits from column.isExpandable
// state.cellProps allows consuming applications to override isExpandable on a per-cell basis
return this.state.cellProps.isExpandable ?? this.props.isExpandable;
};

isPopoverOpen = () => {
const { isExpandable, popoverContext } = this.props;
const { popoverIsOpen, cellLocation } = popoverContext;
const { popoverIsOpen, cellLocation } = this.props.popoverContext;

return (
isExpandable &&
this.isExpandable() &&
popoverIsOpen &&
cellLocation.colIndex === this.props.colIndex &&
cellLocation.rowIndex === this.props.visibleRowIndex
Expand Down Expand Up @@ -496,7 +501,6 @@ export class EuiDataGridCell extends Component<
render() {
const {
width,
isExpandable,
popoverContext: { closeCellPopover, openCellPopover },
interactiveCellId,
columnType,
Expand All @@ -510,6 +514,7 @@ export class EuiDataGridCell extends Component<
} = this.props;
const { rowIndex, visibleRowIndex, colIndex } = rest;

const isExpandable = this.isExpandable();
const popoverIsOpen = this.isPopoverOpen();
const hasCellActions = isExpandable || column?.cellActions;
const showCellActions =
Expand All @@ -527,21 +532,26 @@ export class EuiDataGridCell extends Component<
className
);

const cellProps = {
...this.state.cellProps,
'data-test-subj': classNames(
'dataGridRowCell',
this.state.cellProps['data-test-subj']
),
className: classNames(cellClasses, this.state.cellProps.className),
const {
isExpandable: _, // Not a valid DOM property, so needs to be destructured out
style: cellPropsStyle,
className: cellPropsClassName,
'data-test-subj': cellPropsDataTestSubj,
...setCellProps
} = this.state.cellProps;

const cellProps: EuiDataGridSetCellProps = {
...setCellProps,
'data-test-subj': classNames('dataGridRowCell', cellPropsDataTestSubj),
className: classNames(cellClasses, cellPropsClassName),
};

cellProps.style = {
...style, // from react-window
top: 0, // The cell's row will handle top positioning
width, // column width, can be undefined
lineHeight: rowHeightsOptions?.lineHeight ?? undefined, // lineHeight configuration
...cellProps.style, // apply anything from setCellProps({style})
...cellPropsStyle, // apply anything from setCellProps({ style })
};

const handleCellKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
Expand Down
9 changes: 7 additions & 2 deletions src/components/datagrid/data_grid_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,18 @@ interface SharedRenderCellElementProps {
schema: string | undefined | null;
}

export type EuiDataGridSetCellProps = CommonProps &
HTMLAttributes<HTMLDivElement> & {
isExpandable?: boolean;
};

export interface EuiDataGridCellValueElementProps
extends SharedRenderCellElementProps {
/**
* Callback function to set custom props & attributes on the cell's wrapping `div` element;
* it's best to wrap calls to `setCellProps` in a `useEffect` hook
*/
setCellProps: (props: CommonProps & HTMLAttributes<HTMLDivElement>) => void;
setCellProps: (props: EuiDataGridSetCellProps) => void;
/**
* Whether or not the cell is expandable, comes from the #EuiDataGridColumn `isExpandable` which defaults to `true`
*/
Expand Down Expand Up @@ -482,7 +487,7 @@ export interface EuiDataGridCellProps {
}

export interface EuiDataGridCellState {
cellProps: CommonProps & HTMLAttributes<HTMLDivElement>;
cellProps: EuiDataGridSetCellProps;
isFocused: boolean; // tracks if this cell has focus or not, used to enable tabIndex on the cell
isEntered: boolean; // enables focus trap for non-expandable cells with multiple interactive elements
enableInteractions: boolean; // cell got hovered at least once, so cell button and popover interactions are rendered
Expand Down