Skip to content

Commit

Permalink
[EuiDataGrid] Allow isExpandable to be set via setCellProps (#5667)
Browse files Browse the repository at this point in the history
* DRY out type for setCellProps

* Add logic that checks for cellProps.isExpandable

- as an override over props.isExpandable

* Fix `isExpandable` being passed to underlying DOM node and causing errors

+ refactor cell props destructuring slightly

* Changelog

* Add documentation example

* Import useEffect in demo

Co-authored-by: Chandler Prall <chandler.prall@gmail.com>
  • Loading branch information
Constance and chandlerprall authored Mar 2, 2022
1 parent 334df0e commit 6ed06df
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 30 deletions.
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

0 comments on commit 6ed06df

Please sign in to comment.