From dd19cb3de423e86762d785cceb5f28288754d012 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 12:13:21 -0800 Subject: [PATCH 1/6] Add new `displayHeaderCellProps` column API --- src-docs/src/views/datagrid/_snippets.tsx | 1 + .../body/header/data_grid_header_cell.tsx | 12 ++++---- src/components/datagrid/data_grid.test.tsx | 30 +++++++++++++++++++ src/components/datagrid/data_grid_types.ts | 4 +++ upcoming_changelogs/6609.md | 1 + 5 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 upcoming_changelogs/6609.md diff --git a/src-docs/src/views/datagrid/_snippets.tsx b/src-docs/src/views/datagrid/_snippets.tsx index 66f82019fcd..1f2c3ce3111 100644 --- a/src-docs/src/views/datagrid/_snippets.tsx +++ b/src-docs/src/views/datagrid/_snippets.tsx @@ -11,6 +11,7 @@ inMemory={{ level: 'sorting' }}`, id: 'A', // required display: <>Column A , // optional column header rendering displayAsText: 'Column A', // column header as plain text + displayHeaderCellProps: { className: 'eui-textCenter' }, // optional column header cell props initialWidth: 150, // starting width of 150px isResizable: false, // prevents the user from resizing width isExpandable: false, // doesn't allow clicking in to see the content in a popup diff --git a/src/components/datagrid/body/header/data_grid_header_cell.tsx b/src/components/datagrid/body/header/data_grid_header_cell.tsx index 6f8e939543f..df95e2633de 100644 --- a/src/components/datagrid/body/header/data_grid_header_cell.tsx +++ b/src/components/datagrid/body/header/data_grid_header_cell.tsx @@ -48,13 +48,14 @@ export const EuiDataGridHeaderCell: FunctionComponent { - const { id, display, displayAsText } = column; + const { id, display, displayAsText, displayHeaderCellProps } = column; const width = columnWidths[id] || defaultColumnWidth; const columnType = schema[id] ? schema[id].columnType : null; - const classes = classnames({ - [`euiDataGridHeaderCell--${columnType}`]: columnType, - }); + const classes = classnames( + { [`euiDataGridHeaderCell--${columnType}`]: columnType }, + displayHeaderCellProps?.className + ); const { setFocusedCell, focusFirstVisibleInteractiveCell } = useContext( DataGridFocusContext @@ -95,11 +96,12 @@ export const EuiDataGridHeaderCell: FunctionComponent {column.isResizable !== false && width != null ? ( diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx index 1ee9c75b647..e875a3a7238 100644 --- a/src/components/datagrid/data_grid.test.tsx +++ b/src/components/datagrid/data_grid.test.tsx @@ -1459,6 +1459,36 @@ describe('EuiDataGrid', () => { expect(getCellColorAt(0)).toEqual('blue'); expect(getCellColorAt(1)).toEqual(undefined); }); + + test('column display, displayAsText, and displayHeaderCellProps', () => { + const component = render( + {}, + }} + columns={[ + { + id: 'ColumnA', + display: Hello world, + displayAsText: 'displayAsText', + displayHeaderCellProps: { className: 'displayHeaderCellProps' }, + }, + ]} + rowCount={1} + renderCellValue={({ rowIndex, columnId }) => + `${rowIndex}, ${columnId}` + } + /> + ); + const colHeaderCell = component.find( + '.euiDataGridHeaderCell.displayHeaderCellProps' + ); + expect(colHeaderCell).toHaveLength(1); + expect(colHeaderCell.find('[data-test-subj="display"]')).toHaveLength(1); + expect(colHeaderCell.find('[title="displayAsText"]')).toHaveLength(1); + }); }); describe('column sorting', () => { diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 180ed1f2c3c..f2fe89b19c8 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -577,6 +577,10 @@ export interface EuiDataGridColumn { * If not passed, `id` will be shown as the column name. */ displayAsText?: string; + /** + * Optional props to pass to the column header cell + */ + displayHeaderCellProps?: HTMLAttributes; /** * Initial width (in pixels) of the column */ diff --git a/upcoming_changelogs/6609.md b/upcoming_changelogs/6609.md new file mode 100644 index 00000000000..4930985535d --- /dev/null +++ b/upcoming_changelogs/6609.md @@ -0,0 +1 @@ +- Added the `displayHeaderCellProps` API to `EuiDataGrid`'s columns, which allows passing custom props directly to column header cells From da033142b5f32b157462a49ee0bca4b7ec8d32f4 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 12:31:42 -0800 Subject: [PATCH 2/6] Add new `headerCellProps` control column API --- src-docs/src/views/datagrid/_snippets.tsx | 4 +++- .../__snapshots__/data_grid.test.tsx.snap | 4 ++-- .../body/header/data_grid_control_header_cell.tsx | 15 +++++++++++++-- src/components/datagrid/data_grid.test.tsx | 4 ++++ src/components/datagrid/data_grid_types.ts | 4 ++++ upcoming_changelogs/6609.md | 1 + 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src-docs/src/views/datagrid/_snippets.tsx b/src-docs/src/views/datagrid/_snippets.tsx index 1f2c3ce3111..9f4256ea3de 100644 --- a/src-docs/src/views/datagrid/_snippets.tsx +++ b/src-docs/src/views/datagrid/_snippets.tsx @@ -35,6 +35,7 @@ inMemory={{ level: 'sorting' }}`, id: 'selection', width: 31, headerCellRender: () => Select a Row, + headerCellProps: { className: 'eui-textCenter' }, rowCellRender: () =>
, }, ]}`, @@ -42,7 +43,8 @@ inMemory={{ level: 'sorting' }}`, { id: 'actions', width: 40, - headerCellRender: () => null, + headerCellRender: () => 'Actions', + headerCellProps: { className: 'euiScreenReaderOnly' }, rowCellRender: MyGridActionsComponent, }, ]}`, diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index fe51a6c7c16..fa2037f092a 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -1071,7 +1071,7 @@ Array [ role="row" >
{ - const { headerCellRender: HeaderCellRender, width, id } = controlColumn; + const { + headerCellRender: HeaderCellRender, + headerCellProps, + width, + id, + } = controlColumn; return ( { id: 'leading', width: 50, headerCellRender: () => leading heading, + headerCellProps: { className: 'leadingControlCol' }, rowCellRender: ({ rowIndex }) => rowIndex, }, ]} @@ -687,6 +688,7 @@ describe('EuiDataGrid', () => { id: 'trailing', width: 50, headerCellRender: () => trailing heading, + headerCellProps: { className: 'trailingControlCol' }, rowCellRender: ({ rowIndex }) => rowIndex, }, ]} @@ -699,6 +701,8 @@ describe('EuiDataGrid', () => { ); expect(component).toMatchSnapshot(); + expect(component.find('.leadingControlCol')).toHaveLength(1); + expect(component.find('.trailingControlCol')).toHaveLength(1); }); it('can hide the toolbar', () => { diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index f2fe89b19c8..86532e9cb83 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -551,6 +551,10 @@ export interface EuiDataGridControlColumn { * Component to render in the column header */ headerCellRender: ComponentType; + /** + * Optional props to pass to the column header cell + */ + headerCellProps?: HTMLAttributes; /** * Component to render for each row in the column */ diff --git a/upcoming_changelogs/6609.md b/upcoming_changelogs/6609.md index 4930985535d..0b6c793b421 100644 --- a/upcoming_changelogs/6609.md +++ b/upcoming_changelogs/6609.md @@ -1 +1,2 @@ - Added the `displayHeaderCellProps` API to `EuiDataGrid`'s columns, which allows passing custom props directly to column header cells +- Added a new `headerCellProps` API to `EuiDataGrid`'s control columns, which allows passing custom props directly to control column header cells From a18467042acb1bac49af088728062f9e9a765ffe Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 12:40:20 -0800 Subject: [PATCH 3/6] [Setup] Move footer-related components to own folder - for better organization/architecture --- src/components/datagrid/_index.scss | 2 +- src/components/datagrid/body/data_grid_body.tsx | 2 +- .../body/{ => footer}/_data_grid_footer_row.scss | 0 .../body/{ => footer}/data_grid_footer_row.spec.tsx | 4 ++-- .../body/{ => footer}/data_grid_footer_row.test.tsx | 0 .../datagrid/body/{ => footer}/data_grid_footer_row.tsx | 6 +++--- src/components/datagrid/body/footer/index.ts | 9 +++++++++ 7 files changed, 16 insertions(+), 7 deletions(-) rename src/components/datagrid/body/{ => footer}/_data_grid_footer_row.scss (100%) rename src/components/datagrid/body/{ => footer}/data_grid_footer_row.spec.tsx (97%) rename src/components/datagrid/body/{ => footer}/data_grid_footer_row.test.tsx (100%) rename src/components/datagrid/body/{ => footer}/data_grid_footer_row.tsx (94%) create mode 100644 src/components/datagrid/body/footer/index.ts diff --git a/src/components/datagrid/_index.scss b/src/components/datagrid/_index.scss index f2ac50334ec..8a242ea1532 100644 --- a/src/components/datagrid/_index.scss +++ b/src/components/datagrid/_index.scss @@ -2,7 +2,7 @@ @import 'mixins'; @import 'data_grid'; @import 'body/header/data_grid_header_row'; -@import 'body/data_grid_footer_row'; +@import 'body/footer/data_grid_footer_row'; @import 'body/header/data_grid_column_resizer'; @import 'data_grid_data_row'; @import 'controls/data_grid_toolbar'; diff --git a/src/components/datagrid/body/data_grid_body.tsx b/src/components/datagrid/body/data_grid_body.tsx index 3ca0c4f9d11..ec42ad967d4 100644 --- a/src/components/datagrid/body/data_grid_body.tsx +++ b/src/components/datagrid/body/data_grid_body.tsx @@ -25,7 +25,7 @@ import { import { useMutationObserver } from '../../observer/mutation_observer'; import { useResizeObserver } from '../../observer/resize_observer'; import { EuiDataGridCell } from './data_grid_cell'; -import { EuiDataGridFooterRow } from './data_grid_footer_row'; +import { EuiDataGridFooterRow } from './footer'; import { EuiDataGridHeaderRow } from './header'; import { DataGridCellPopoverContext } from './data_grid_cell_popover'; import { diff --git a/src/components/datagrid/body/_data_grid_footer_row.scss b/src/components/datagrid/body/footer/_data_grid_footer_row.scss similarity index 100% rename from src/components/datagrid/body/_data_grid_footer_row.scss rename to src/components/datagrid/body/footer/_data_grid_footer_row.scss diff --git a/src/components/datagrid/body/data_grid_footer_row.spec.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.spec.tsx similarity index 97% rename from src/components/datagrid/body/data_grid_footer_row.spec.tsx rename to src/components/datagrid/body/footer/data_grid_footer_row.spec.tsx index eaad99596a1..49d90eec1e2 100644 --- a/src/components/datagrid/body/data_grid_footer_row.spec.tsx +++ b/src/components/datagrid/body/footer/data_grid_footer_row.spec.tsx @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -/// +/// import React, { useState, useEffect } from 'react'; -import { EuiDataGrid } from '../'; +import { EuiDataGrid } from '../../'; describe('EuiDataGridFooterRow', () => { const mockData = [10, 15, 20]; diff --git a/src/components/datagrid/body/data_grid_footer_row.test.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.test.tsx similarity index 100% rename from src/components/datagrid/body/data_grid_footer_row.test.tsx rename to src/components/datagrid/body/footer/data_grid_footer_row.test.tsx diff --git a/src/components/datagrid/body/data_grid_footer_row.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.tsx similarity index 94% rename from src/components/datagrid/body/data_grid_footer_row.tsx rename to src/components/datagrid/body/footer/data_grid_footer_row.tsx index ca78dacc3cd..2cb37b2363c 100644 --- a/src/components/datagrid/body/data_grid_footer_row.tsx +++ b/src/components/datagrid/body/footer/data_grid_footer_row.tsx @@ -8,9 +8,9 @@ import classnames from 'classnames'; import React, { forwardRef, memo, useContext } from 'react'; -import { EuiDataGridCell } from './data_grid_cell'; -import { DataGridCellPopoverContext } from './data_grid_cell_popover'; -import { EuiDataGridFooterRowProps } from '../data_grid_types'; +import { EuiDataGridCell } from '../data_grid_cell'; +import { DataGridCellPopoverContext } from '../data_grid_cell_popover'; +import { EuiDataGridFooterRowProps } from '../../data_grid_types'; const EuiDataGridFooterRow = memo( forwardRef( diff --git a/src/components/datagrid/body/footer/index.ts b/src/components/datagrid/body/footer/index.ts new file mode 100644 index 00000000000..9f98e8693c9 --- /dev/null +++ b/src/components/datagrid/body/footer/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { EuiDataGridFooterRow } from './data_grid_footer_row'; From 3f34ae581b72569c9accb9d8b9db6a74d4df7ae1 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 13:48:00 -0800 Subject: [PATCH 4/6] Add new `footerCellRender` control column API + fix footer control columns rendering with `isExpandable` true as a default - should not be true + adds docs, fix broken amount total + reorder types slightly --- src-docs/src/views/datagrid/_snippets.tsx | 4 +- .../datagrid_columns_example.js | 7 +- .../datagrid/schema_columns/footer_row.js | 47 +++- .../body/footer/data_grid_footer_row.test.tsx | 238 ++++++++++-------- .../body/footer/data_grid_footer_row.tsx | 14 +- src/components/datagrid/data_grid_types.ts | 8 +- upcoming_changelogs/6609.md | 5 + 7 files changed, 209 insertions(+), 114 deletions(-) diff --git a/src-docs/src/views/datagrid/_snippets.tsx b/src-docs/src/views/datagrid/_snippets.tsx index 9f4256ea3de..94d11fc0d78 100644 --- a/src-docs/src/views/datagrid/_snippets.tsx +++ b/src-docs/src/views/datagrid/_snippets.tsx @@ -34,9 +34,10 @@ inMemory={{ level: 'sorting' }}`, { id: 'selection', width: 31, - headerCellRender: () => Select a Row, + headerCellRender: () => Select a row, headerCellProps: { className: 'eui-textCenter' }, rowCellRender: () =>
, + footerCellRender: () => Select a row, }, ]}`, trailingControlColumns: `trailingControlColumns={[ @@ -46,6 +47,7 @@ inMemory={{ level: 'sorting' }}`, headerCellRender: () => 'Actions', headerCellProps: { className: 'euiScreenReaderOnly' }, rowCellRender: MyGridActionsComponent, + footerCellRender: () => null, }, ]}`, renderCellValue: 'renderCellValue={({ rowIndex, columnId }) => {}}', diff --git a/src-docs/src/views/datagrid/schema_columns/datagrid_columns_example.js b/src-docs/src/views/datagrid/schema_columns/datagrid_columns_example.js index ef65697d7fe..6119176f991 100644 --- a/src-docs/src/views/datagrid/schema_columns/datagrid_columns_example.js +++ b/src-docs/src/views/datagrid/schema_columns/datagrid_columns_example.js @@ -293,8 +293,7 @@ schemaDetectors={[

The footer row is defined by passing{' '} renderFooterCellValue function prop into{' '} - EuiDataGrid.{' '} - renderFooterCellValue acts like a React + EuiDataGrid. This function acts like a React component, receiving{' '} EuiDataGridCellValueElementProps and returning a React node. @@ -304,6 +303,10 @@ schemaDetectors={[ expansion on cells without content with{' '} setCellProps({'{ isExpandable: false }'}).

+

+ Control columns will render empty footer cells by default, unless a + custom footerCellRender function is passed. +

), props: { diff --git a/src-docs/src/views/datagrid/schema_columns/footer_row.js b/src-docs/src/views/datagrid/schema_columns/footer_row.js index cda16af202b..8a92932cf71 100644 --- a/src-docs/src/views/datagrid/schema_columns/footer_row.js +++ b/src-docs/src/views/datagrid/schema_columns/footer_row.js @@ -3,6 +3,8 @@ import { faker } from '@faker-js/faker'; import { EuiDataGrid, + EuiCheckbox, + EuiButtonIcon, EuiSwitch, EuiFlexGroup, EuiFlexItem, @@ -14,7 +16,7 @@ for (let i = 1; i < 20; i++) { raw_data.push({ name: `${faker.name.lastName()}, ${faker.name.firstName()} ${faker.name.suffix()}`, date: `${faker.date.past()}`, - amount: faker.commerce.price(), + amount: `$${faker.commerce.price()}`, phone: faker.phone.number(), version: faker.system.semver(), }); @@ -65,6 +67,47 @@ const columns = [ }, ]; +const leadingControlColumns = [ + { + id: 'selection', + width: 32, + // Check state doesn't actually work - this is just a static example + headerCellRender: () => ( + {}} + /> + ), + rowCellRender: ({ rowIndex }) => ( + {}} + /> + ), + footerCellRender: () => ( + {}} + /> + ), + }, +]; +const trailingControlColumns = [ + { + id: 'actions', + width: 36, + headerCellRender: () => ( + Actions + ), + rowCellRender: () => ( + + ), + }, +]; + const footerCellValues = { amount: `Total: ${raw_data .reduce((acc, { amount }) => acc + Number(amount.split('$')[1]), 0) @@ -125,6 +168,8 @@ export default () => { aria-label="Data grid footer row demo" columns={columns} columnVisibility={{ visibleColumns, setVisibleColumns }} + leadingControlColumns={leadingControlColumns} + trailingControlColumns={trailingControlColumns} rowCount={raw_data.length} renderCellValue={RenderCellValue} renderFooterCellValue={ 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 eca2c81be9c..d2cf858d8d7 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 @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, render } from 'enzyme'; import { EuiDataGridFooterRow } from './data_grid_footer_row'; @@ -87,116 +87,148 @@ describe('EuiDataGridFooterRow', () => { `); }); - it('renders leading control columns', () => { - const component = shallow( -
, - rowCellRender: () =>
, - width: 25, - }, - ]} - /> - ); + describe('control columns', () => { + const requiredColumnProps = { + headerCellRender: () =>
, + rowCellRender: () =>
, + width: 25, + }; - expect(component).toMatchInlineSnapshot(` -
- { + const component = shallow( + -
- `); + ); - const renderCellValue: Function = component - .find('EuiDataGridCell') - .prop('renderCellValue'); - expect(renderCellValue()).toEqual(null); - }); + expect(component).toMatchInlineSnapshot(` +
+ +
+ `); - it('renders trailing control columns', () => { - const component = shallow( -
, - rowCellRender: () =>
, - width: 50, - }, - ]} - /> - ); + const renderCellValue: Function = component + .find('EuiDataGridCell') + .prop('renderCellValue'); + expect(renderCellValue()).toEqual(null); + }); - expect(component).toMatchInlineSnapshot(` -
- { + const component = shallow( + + ); + + expect(component).toMatchInlineSnapshot(` +
+ +
+ `); + + const renderCellValue: Function = component + .find('EuiDataGridCell') + .prop('renderCellValue'); + expect(renderCellValue()).toEqual(null); + }); + + it('renders control column `footerCellRender`s if passed', () => { + const component = render( + ( +
+ ), + }, + ]} + trailingControlColumns={[ + { + id: 'someTrailingColumn', + ...requiredColumnProps, + footerCellRender: () => ( +
+ ), + }, + ]} /> -
- `); + ); - const renderCellValue: Function = component - .find('EuiDataGridCell') - .prop('renderCellValue'); - expect(renderCellValue()).toEqual(null); + expect( + component.find('[data-test-subj="customLeadingControlFooterCell"]') + ).toHaveLength(1); + expect( + component.find('[data-test-subj="customTrailingControlFooterCell"]') + ).toHaveLength(1); + }); }); it('renders striped styling if the footer row is odd', () => { diff --git a/src/components/datagrid/body/footer/data_grid_footer_row.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.tsx index 2cb37b2363c..146a4d011bb 100644 --- a/src/components/datagrid/body/footer/data_grid_footer_row.tsx +++ b/src/components/datagrid/body/footer/data_grid_footer_row.tsx @@ -12,6 +12,8 @@ import { EuiDataGridCell } from '../data_grid_cell'; import { DataGridCellPopoverContext } from '../data_grid_cell_popover'; import { EuiDataGridFooterRowProps } from '../../data_grid_types'; +const renderEmpty = () => null; + const EuiDataGridFooterRow = memo( forwardRef( ( @@ -50,7 +52,6 @@ const EuiDataGridFooterRow = memo( rowIndex, visibleRowIndex, interactiveCellId, - isExpandable: true, popoverContext, }; @@ -62,14 +63,15 @@ const EuiDataGridFooterRow = memo( data-test-subj={dataTestSubj} {...rest} > - {leadingControlColumns.map(({ id, width }, i) => ( + {leadingControlColumns.map(({ id, width, footerCellRender }, i) => ( null} + renderCellValue={footerCellRender ?? renderEmpty} + isExpandable={false} className="euiDataGridFooterCell euiDataGridRowCell--controlColumn" /> ))} @@ -88,11 +90,12 @@ const EuiDataGridFooterRow = memo( width={width || undefined} renderCellValue={renderCellValue} renderCellPopover={renderCellPopover} + isExpandable={true} className="euiDataGridFooterCell" /> ); })} - {trailingControlColumns.map(({ id, width }, i) => { + {trailingControlColumns.map(({ id, width, footerCellRender }, i) => { const colIndex = i + columns.length + leadingControlColumns.length; return ( @@ -102,7 +105,8 @@ const EuiDataGridFooterRow = memo( colIndex={colIndex} columnId={id} width={width} - renderCellValue={() => null} + renderCellValue={footerCellRender ?? renderEmpty} + isExpandable={false} className="euiDataGridFooterCell euiDataGridRowCell--controlColumn" /> ); diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 86532e9cb83..40dc087a9d7 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -547,6 +547,10 @@ export interface EuiDataGridControlColumn { * Used as the React `key` when rendering content */ id: string; + /** + * Width of the column, users are unable to change this + */ + width: number; /** * Component to render in the column header */ @@ -560,9 +564,9 @@ export interface EuiDataGridControlColumn { */ rowCellRender: EuiDataGridCellProps['renderCellValue']; /** - * Width of the column, uses are unable to change this + * Component to render in the optional column footer */ - width: number; + footerCellRender?: EuiDataGridCellProps['renderCellValue']; } export interface EuiDataGridColumn { diff --git a/upcoming_changelogs/6609.md b/upcoming_changelogs/6609.md index 0b6c793b421..b13643ccfc3 100644 --- a/upcoming_changelogs/6609.md +++ b/upcoming_changelogs/6609.md @@ -1,2 +1,7 @@ - Added the `displayHeaderCellProps` API to `EuiDataGrid`'s columns, which allows passing custom props directly to column header cells - Added a new `headerCellProps` API to `EuiDataGrid`'s control columns, which allows passing custom props directly to control column header cells +- Added a new `footerCellRender` API to `EuiDataGrid`'s control columns, which allows completely customizing control column rendering (previously rendered an empty cell) + +**Bug fixes** + +- Fixed `EuiDataGrid` footer control columns rendering with cell expansion popovers when they should not have been From 695a1c97a40b423eccd01369da8848c294adb9b4 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 13:57:11 -0800 Subject: [PATCH 5/6] Add new `footerCellProps` control column API --- src-docs/src/views/datagrid/_snippets.tsx | 2 + .../body/footer/data_grid_footer_row.test.tsx | 7 +- .../body/footer/data_grid_footer_row.tsx | 69 +++++++++++-------- src/components/datagrid/data_grid_types.ts | 4 ++ upcoming_changelogs/6609.md | 2 +- 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src-docs/src/views/datagrid/_snippets.tsx b/src-docs/src/views/datagrid/_snippets.tsx index 94d11fc0d78..672e1417364 100644 --- a/src-docs/src/views/datagrid/_snippets.tsx +++ b/src-docs/src/views/datagrid/_snippets.tsx @@ -38,6 +38,7 @@ inMemory={{ level: 'sorting' }}`, headerCellProps: { className: 'eui-textCenter' }, rowCellRender: () =>
, footerCellRender: () => Select a row, + footerCellProps: { className: 'eui-textCenter' }, }, ]}`, trailingControlColumns: `trailingControlColumns={[ @@ -48,6 +49,7 @@ inMemory={{ level: 'sorting' }}`, headerCellProps: { className: 'euiScreenReaderOnly' }, rowCellRender: MyGridActionsComponent, footerCellRender: () => null, + footerCellProps: { data-test-subj: 'emptyFooterCell' }, }, ]}`, renderCellValue: 'renderCellValue={({ rowIndex, columnId }) => {}}', 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 d2cf858d8d7..9373b798ab2 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 @@ -196,7 +196,7 @@ describe('EuiDataGridFooterRow', () => { expect(renderCellValue()).toEqual(null); }); - it('renders control column `footerCellRender`s if passed', () => { + it('renders control column `footerCellRender`s and `footerCellProps` if passed', () => { const component = render( { footerCellRender: () => (
), + footerCellProps: { className: 'leading' }, }, ]} trailingControlColumns={[ @@ -217,14 +218,18 @@ describe('EuiDataGridFooterRow', () => { footerCellRender: () => (
), + footerCellProps: { className: 'trailing' }, }, ]} /> ); + expect(component.find('.euiDataGridFooterCell.leading')).toHaveLength(1); expect( component.find('[data-test-subj="customLeadingControlFooterCell"]') ).toHaveLength(1); + + expect(component.find('.euiDataGridFooterCell.trailing')).toHaveLength(1); expect( component.find('[data-test-subj="customTrailingControlFooterCell"]') ).toHaveLength(1); diff --git a/src/components/datagrid/body/footer/data_grid_footer_row.tsx b/src/components/datagrid/body/footer/data_grid_footer_row.tsx index 146a4d011bb..085bef3dcc1 100644 --- a/src/components/datagrid/body/footer/data_grid_footer_row.tsx +++ b/src/components/datagrid/body/footer/data_grid_footer_row.tsx @@ -63,18 +63,25 @@ const EuiDataGridFooterRow = memo( data-test-subj={dataTestSubj} {...rest} > - {leadingControlColumns.map(({ id, width, footerCellRender }, i) => ( - - ))} + {leadingControlColumns.map( + ({ id, width, footerCellRender, footerCellProps }, i) => ( + + ) + )} {columns.map(({ id }, i) => { const columnType = schema[id] ? schema[id].columnType : null; const width = columnWidths[id] || defaultColumnWidth; @@ -95,22 +102,30 @@ const EuiDataGridFooterRow = memo( /> ); })} - {trailingControlColumns.map(({ id, width, footerCellRender }, i) => { - const colIndex = i + columns.length + leadingControlColumns.length; + {trailingControlColumns.map( + ({ id, width, footerCellRender, footerCellProps }, i) => { + const colIndex = + i + columns.length + leadingControlColumns.length; - return ( - - ); - })} + return ( + + ); + } + )}
); } diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 40dc087a9d7..c03d3a4bb10 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -567,6 +567,10 @@ export interface EuiDataGridControlColumn { * Component to render in the optional column footer */ footerCellRender?: EuiDataGridCellProps['renderCellValue']; + /** + * Optional props to pass to the column footer cell + */ + footerCellProps?: HTMLAttributes; } export interface EuiDataGridColumn { diff --git a/upcoming_changelogs/6609.md b/upcoming_changelogs/6609.md index b13643ccfc3..16632cc60ee 100644 --- a/upcoming_changelogs/6609.md +++ b/upcoming_changelogs/6609.md @@ -1,5 +1,5 @@ - Added the `displayHeaderCellProps` API to `EuiDataGrid`'s columns, which allows passing custom props directly to column header cells -- Added a new `headerCellProps` API to `EuiDataGrid`'s control columns, which allows passing custom props directly to control column header cells +- Added the new `headerCellProps`/`footerCellProps` APIs to `EuiDataGrid`'s control columns, which allows passing custom props directly to control column header or footer cells - Added a new `footerCellRender` API to `EuiDataGrid`'s control columns, which allows completely customizing control column rendering (previously rendered an empty cell) **Bug fixes** From ea6b1d3e847d8fe07f936e59ee5884083d13f5c8 Mon Sep 17 00:00:00 2001 From: Constance Chen Date: Thu, 16 Feb 2023 14:04:39 -0800 Subject: [PATCH 6/6] [Setup] DRY out header & footer instantiation to hooks - this commit isn't specifically needed by this PR, but *will* be needed once we implement a custom body renderer --- .../datagrid/body/data_grid_body.tsx | 86 ++++--------------- src/components/datagrid/body/footer/index.ts | 1 + .../body/footer/use_data_grid_footer.tsx | 41 +++++++++ src/components/datagrid/body/header/index.ts | 1 + .../body/header/use_data_grid_header.tsx | 43 ++++++++++ 5 files changed, 103 insertions(+), 69 deletions(-) create mode 100644 src/components/datagrid/body/footer/use_data_grid_footer.tsx create mode 100644 src/components/datagrid/body/header/use_data_grid_header.tsx diff --git a/src/components/datagrid/body/data_grid_body.tsx b/src/components/datagrid/body/data_grid_body.tsx index ec42ad967d4..be8080e4851 100644 --- a/src/components/datagrid/body/data_grid_body.tsx +++ b/src/components/datagrid/body/data_grid_body.tsx @@ -13,20 +13,17 @@ import React, { createContext, useContext, useEffect, - useMemo, useRef, - useState, } from 'react'; import { GridChildComponentProps, VariableSizeGrid as Grid, VariableSizeGridProps, } from 'react-window'; -import { useMutationObserver } from '../../observer/mutation_observer'; import { useResizeObserver } from '../../observer/resize_observer'; import { EuiDataGridCell } from './data_grid_cell'; -import { EuiDataGridFooterRow } from './footer'; -import { EuiDataGridHeaderRow } from './header'; +import { useDataGridHeader } from './header'; +import { useDataGridFooter } from './footer'; import { DataGridCellPopoverContext } from './data_grid_cell_popover'; import { EuiDataGridBodyProps, @@ -41,7 +38,6 @@ import { } from '../utils/grid_height_width'; import { useDefaultColumnWidth, useColumnWidths } from '../utils/col_widths'; import { useRowHeightUtils, useDefaultRowHeight } from '../utils/row_heights'; -import { useHeaderFocusWorkaround } from '../utils/focus'; import { useScrollBars, useScroll } from '../utils/scrolling'; import { DataGridSortingContext } from '../utils/sorting'; import { IS_JEST_ENVIRONMENT } from '../../../utils'; @@ -284,33 +280,11 @@ export const EuiDataGridBody: FunctionComponent = ( }); /** - * Header + * Header & footer */ - const [headerRowRef, setHeaderRowRef] = useState(null); - useMutationObserver(headerRowRef, handleHeaderMutation, { - subtree: true, - childList: true, - }); - const { height: headerRowHeight } = useResizeObserver(headerRowRef, 'height'); - - const headerRow = useMemo(() => { - return ( - - ); - }, [ + const { headerRow, headerRowHeight } = useDataGridHeader({ + headerIsInteractive, + handleHeaderMutation, switchColumnPos, setVisibleColumns, leadingControlColumns, @@ -321,47 +295,21 @@ export const EuiDataGridBody: FunctionComponent = ( setColumnWidth, schema, schemaDetectors, - headerIsInteractive, - ]); - - useHeaderFocusWorkaround(headerIsInteractive); + }); - /** - * Footer - */ - const [footerRowRef, setFooterRowRef] = useState(null); - const { height: footerRowHeight } = useResizeObserver(footerRowRef, 'height'); - - const footerRow = useMemo(() => { - if (renderFooterCellValue == null) return null; - return ( - - ); - }, [ - columnWidths, - columns, - defaultColumnWidth, - interactiveCellId, - leadingControlColumns, + const { footerRow, footerRowHeight } = useDataGridFooter({ renderFooterCellValue, renderCellPopover, - schema, + rowIndex: visibleRowCount, + visibleRowIndex: visibleRowCount, + interactiveCellId, + leadingControlColumns, trailingControlColumns, - visibleRowCount, - ]); + columns, + columnWidths, + defaultColumnWidth, + schema, + }); /** * Handle scrolling cells fully into view diff --git a/src/components/datagrid/body/footer/index.ts b/src/components/datagrid/body/footer/index.ts index 9f98e8693c9..bcad6737661 100644 --- a/src/components/datagrid/body/footer/index.ts +++ b/src/components/datagrid/body/footer/index.ts @@ -7,3 +7,4 @@ */ export { EuiDataGridFooterRow } from './data_grid_footer_row'; +export { useDataGridFooter } from './use_data_grid_footer'; diff --git a/src/components/datagrid/body/footer/use_data_grid_footer.tsx b/src/components/datagrid/body/footer/use_data_grid_footer.tsx new file mode 100644 index 00000000000..03873087592 --- /dev/null +++ b/src/components/datagrid/body/footer/use_data_grid_footer.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useMemo } from 'react'; + +import { useResizeObserver } from '../../../observer/resize_observer'; + +import { EuiDataGridFooterRowProps } from '../../data_grid_types'; +import { EuiDataGridFooterRow } from './data_grid_footer_row'; + +type Props = Omit & { + renderFooterCellValue?: EuiDataGridFooterRowProps['renderCellValue']; +}; + +/** + * DRY out setting up the grid footer and its refs & observers + */ +export const useDataGridFooter = (props: Props) => { + const [footerRowRef, setFooterRowRef] = useState(null); + const { height: footerRowHeight } = useResizeObserver(footerRowRef, 'height'); + + const footerRow = useMemo(() => { + const { renderFooterCellValue, ...footerProps } = props; + if (renderFooterCellValue == null) return null; + + return ( + + ); + }, Object.values(props)); // eslint-disable-line react-hooks/exhaustive-deps + + return { footerRow, footerRowHeight }; +}; diff --git a/src/components/datagrid/body/header/index.ts b/src/components/datagrid/body/header/index.ts index 283cf2a6f02..703425876a5 100644 --- a/src/components/datagrid/body/header/index.ts +++ b/src/components/datagrid/body/header/index.ts @@ -7,3 +7,4 @@ */ export { EuiDataGridHeaderRow } from './data_grid_header_row'; +export { useDataGridHeader } from './use_data_grid_header'; diff --git a/src/components/datagrid/body/header/use_data_grid_header.tsx b/src/components/datagrid/body/header/use_data_grid_header.tsx new file mode 100644 index 00000000000..b709fe829e3 --- /dev/null +++ b/src/components/datagrid/body/header/use_data_grid_header.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useMemo } from 'react'; + +import { useMutationObserver } from '../../../observer/mutation_observer'; +import { useResizeObserver } from '../../../observer/resize_observer'; + +import { useHeaderFocusWorkaround } from '../../utils/focus'; +import { EuiDataGridHeaderRowProps } from '../../data_grid_types'; +import { EuiDataGridHeaderRow } from './data_grid_header_row'; + +type Props = EuiDataGridHeaderRowProps & { + handleHeaderMutation: MutationCallback; +}; + +/** + * DRY out setting up the grid header and its refs & observers + */ +export const useDataGridHeader = ({ + handleHeaderMutation, + ...props +}: Props) => { + const [headerRowRef, setHeaderRowRef] = useState(null); + useMutationObserver(headerRowRef, handleHeaderMutation, { + subtree: true, + childList: true, + }); + const { height: headerRowHeight } = useResizeObserver(headerRowRef, 'height'); + + const headerRow = useMemo(() => { + return ; + }, Object.values(props)); // eslint-disable-line react-hooks/exhaustive-deps + + useHeaderFocusWorkaround(props.headerIsInteractive); + + return { headerRow, headerRowHeight }; +};