diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ceff92dd7..503fc98a178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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** diff --git a/src-docs/src/views/datagrid/cell_popover_is_expandable.tsx b/src-docs/src/views/datagrid/cell_popover_is_expandable.tsx index aeb7e31c3f2..90e6546b5a7 100644 --- a/src-docs/src/views/datagrid/cell_popover_is_expandable.tsx +++ b/src-docs/src/views/datagrid/cell_popover_is_expandable.tsx @@ -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'; @@ -37,7 +37,7 @@ const columns: EuiDataGridColumn[] = [ }, { id: 'boolean', - isExpandable: false, + isExpandable: true, // Overridden by setCellProps for specific cells }, ]; @@ -58,11 +58,21 @@ export default () => { return ( 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; + }} /> ); }; diff --git a/src-docs/src/views/datagrid/datagrid_cell_popover_example.js b/src-docs/src/views/datagrid/datagrid_cell_popover_example.js index 4825c869604..c19698b700d 100644 --- a/src-docs/src/views/datagrid/datagrid_cell_popover_example.js +++ b/src-docs/src/views/datagrid/datagrid_cell_popover_example.js @@ -135,12 +135,21 @@ export const DataGridCellPopoverExample = { { title: 'Disabling cell expansion popovers', text: ( -

- Popovers can sometimes be unnecessary for short form content. In the - example below we've turned them off by setting{' '} - isExpandable=false on specific{' '} - columns. -

+ <> +

+ Popovers can sometimes be unnecessary for short form content. In the + example below we've turned them off by setting{' '} + isExpandable=false on specific{' '} + columns. +

+

+ To set isExpandable at a per-cell level instead + of per-column, you can use the setCellProps{' '} + callback passed by renderCellValue. The below + example conditionally disables the expansion popover for boolean + cells that are 'false'. +

+ ), demo: , components: { IsExpandablePopover }, @@ -152,6 +161,7 @@ export const DataGridCellPopoverExample = { ], props: { EuiDataGridColumn, + EuiDataGridCellValueElementProps, }, }, ], diff --git a/src/components/datagrid/body/data_grid_cell.test.tsx b/src/components/datagrid/body/data_grid_cell.test.tsx index 47eefab0e63..4355fd8dffc 100644 --- a/src/components/datagrid/body/data_grid_cell.test.tsx +++ b/src/components/datagrid/body/data_grid_cell.test.tsx @@ -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'; @@ -390,6 +390,36 @@ describe('EuiDataGridCell', () => { }); }); + describe('isExpandable', () => { + it('falls back to props.isExpandable which is derived from the column config', () => { + const component = mount( + + ); + + 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( + + ); + + expect(component.find('RenderCellValue').prop('isExpandable')).toBe( + false + ); + }); + }); + // TODO: Test ResizeObserver logic in Cypress alongside Jest describe('row height logic & resize observers', () => { describe('recalculateAutoHeight', () => { diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx index b8568c4267e..a207b63e379 100644 --- a/src/components/datagrid/body/data_grid_cell.tsx +++ b/src/components/datagrid/body/data_grid_cell.tsx @@ -12,7 +12,6 @@ import React, { createRef, FocusEvent, FunctionComponent, - HTMLAttributes, JSXElementConstructor, KeyboardEvent, memo, @@ -29,6 +28,7 @@ import { DataGridFocusContext } from '../utils/focus'; import { EuiDataGridCellProps, EuiDataGridCellState, + EuiDataGridSetCellProps, EuiDataGridCellValueElementProps, EuiDataGridCellValueProps, EuiDataGridCellPopoverElementProps, @@ -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 { @@ -356,7 +356,7 @@ export class EuiDataGridCell extends Component< return false; } - setCellProps = (cellProps: HTMLAttributes) => { + setCellProps = (cellProps: EuiDataGridSetCellProps) => { this.setState({ cellProps }); }; @@ -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) { @@ -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 }); } @@ -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 @@ -496,7 +501,6 @@ export class EuiDataGridCell extends Component< render() { const { width, - isExpandable, popoverContext: { closeCellPopover, openCellPopover }, interactiveCellId, columnType, @@ -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 = @@ -527,13 +532,18 @@ 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 = { @@ -541,7 +551,7 @@ export class EuiDataGridCell extends Component< 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) => { diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 9a3f8ef8e19..da8614e9998 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -410,13 +410,18 @@ interface SharedRenderCellElementProps { schema: string | undefined | null; } +export type EuiDataGridSetCellProps = CommonProps & + HTMLAttributes & { + 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) => void; + setCellProps: (props: EuiDataGridSetCellProps) => void; /** * Whether or not the cell is expandable, comes from the #EuiDataGridColumn `isExpandable` which defaults to `true` */ @@ -482,7 +487,7 @@ export interface EuiDataGridCellProps { } export interface EuiDataGridCellState { - cellProps: CommonProps & HTMLAttributes; + 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