From 488e365cc91961caea875162d8d267751c76e884 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 1 Sep 2023 23:39:55 +0200 Subject: [PATCH] [EuiDataGrid] Introduce a renderCustomToolbar render prop (#7150) --- .../datagrid/advanced/custom_toolbar.tsx | 170 +++++++++++++ .../advanced/datagrid_advanced_example.js | 24 ++ .../datagrid/controls/display_selector.tsx | 237 +++++++++--------- src/components/datagrid/data_grid.tsx | 67 +++-- src/components/datagrid/data_grid_types.ts | 15 ++ 5 files changed, 371 insertions(+), 142 deletions(-) create mode 100644 src-docs/src/views/datagrid/advanced/custom_toolbar.tsx diff --git a/src-docs/src/views/datagrid/advanced/custom_toolbar.tsx b/src-docs/src/views/datagrid/advanced/custom_toolbar.tsx new file mode 100644 index 00000000000..915391469b2 --- /dev/null +++ b/src-docs/src/views/datagrid/advanced/custom_toolbar.tsx @@ -0,0 +1,170 @@ +import React, { useCallback, useState } from 'react'; +import { faker } from '@faker-js/faker'; + +import { + EuiDataGrid, + EuiDataGridColumnCellActionProps, + EuiButtonIcon, + EuiDataGridPaginationProps, + EuiDataGridSorting, + EuiDataGridColumnSortingConfig, + EuiPopover, + EuiDataGridCustomToolbarProps, + EuiFormRow, + EuiRange, +} from '../../../../../src'; + +const raw_data: Array<{ [key: string]: string }> = []; +for (let i = 1; i < 100; i++) { + raw_data.push({ + name: `${faker.name.lastName()}, ${faker.name.firstName()}`, + email: faker.internet.email(), + location: `${faker.address.city()}, ${faker.address.country()}`, + date: `${faker.date.past()}`, + amount: faker.commerce.price(1, 1000, 2, '$'), + }); +} + +const columns = [ + { + id: 'name', + displayAsText: 'Name', + cellActions: [ + ({ Component }: EuiDataGridColumnCellActionProps) => ( + alert('action')} + iconType="faceHappy" + aria-label="Some action" + > + Some action + + ), + ], + }, + { + id: 'email', + displayAsText: 'Email address', + initialWidth: 130, + }, + { + id: 'location', + displayAsText: 'Location', + }, + { + id: 'date', + displayAsText: 'Date', + }, + { + id: 'amount', + displayAsText: 'Amount', + }, +]; + +export default () => { + // Column visibility + const [visibleColumns, setVisibleColumns] = useState(() => + columns.map(({ id }) => id) + ); + + // Pagination + const [pagination, setPagination] = useState({ pageIndex: 0 }); + const onChangePage = useCallback( + (pageIndex) => { + setPagination((pagination) => ({ ...pagination, pageIndex })); + }, + [] + ); + const onChangePageSize = useCallback< + EuiDataGridPaginationProps['onChangeItemsPerPage'] + >((pageSize) => { + setPagination((pagination) => ({ ...pagination, pageSize })); + }, []); + + // Sorting + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >([]); + const onSort = useCallback((sortingColumns) => { + setSortingColumns(sortingColumns); + }, []); + const [isDisplaySelectorOpen, setIsDisplaySelectorOpen] = useState(false); + + // Custom toolbar body renderer + const RenderCustomToolbar = ({ + fullScreenSelector, + keyboardShortcuts, + rowHeightsControls, + densityControls, + columnSelector, + columnSorting, + }: EuiDataGridCustomToolbarProps) => { + return ( +
+
+ Always look at the left side of grid! +
+
+ {fullScreenSelector} + {keyboardShortcuts} + setIsDisplaySelectorOpen(true)} + /> + } + isOpen={isDisplaySelectorOpen} + data-test-subj="dataGridDisplaySelectorPopover" + closePopover={() => setIsDisplaySelectorOpen(false)} + anchorPosition="downRight" + panelPaddingSize="s" + panelClassName="euiDataGrid__displayPopoverPanel" + > + {rowHeightsControls} + {densityControls} + + + + + + {columnSorting} + {columnSelector} +
+
+ ); + }; + + return ( + <> + + raw_data[rowIndex][columnId] + } + renderCustomToolbar={RenderCustomToolbar} + height={undefined} + gridStyle={{ border: 'none', header: 'underline' }} + /> + + ); +}; diff --git a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js index ce49e6dda5a..861790a37d4 100644 --- a/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js +++ b/src-docs/src/views/datagrid/advanced/datagrid_advanced_example.js @@ -8,6 +8,7 @@ import { EuiCallOut, EuiTitle, EuiLink, + EuiDataGridCustomToolbarProps, } from '../../../../../src/components'; import { @@ -36,7 +37,9 @@ dataGridRef.current.closeCellPopover(); `; import CustomRenderer from './custom_renderer'; +import CustomToolbarRenderer from './custom_toolbar'; const customRendererSource = require('!!raw-loader!./custom_renderer'); +const customToolbarSource = require('!!raw-loader!./custom_toolbar'); const customRendererSnippet = `const CustomGridBody = ({ visibleColumns, visibleRowData, Cell }) => { const visibleRows = raw_data.slice( visibleRowData.startRow, @@ -253,5 +256,26 @@ export const DataGridAdvancedExample = { snippet: customRendererSnippet, props: { EuiDataGridCustomBodyProps }, }, + { + title: 'Custom toolbar renderer', + source: [ + { + type: GuideSectionTypes.TSX, + code: customToolbarSource, + }, + ], + text: ( + <> +

+ For advanced use cases, the renderCustomToolbar{' '} + prop may be used to take complete control over rendering the + toolbar. This may be useful where custom row layouts (e.g., all + button on the right side) are required. +

+ + ), + demo: , + props: { EuiDataGridCustomToolbarProps }, + }, ], }; diff --git a/src/components/datagrid/controls/display_selector.tsx b/src/components/datagrid/controls/display_selector.tsx index 2eab2cc5aab..339d0d37f36 100644 --- a/src/components/datagrid/controls/display_selector.tsx +++ b/src/components/datagrid/controls/display_selector.tsx @@ -95,7 +95,13 @@ export const useDataGridDisplaySelector = ( showDisplaySelector: EuiDataGridToolBarVisibilityOptions['showDisplaySelector'], initialStyles: EuiDataGridStyle, initialRowHeightsOptions?: EuiDataGridRowHeightsOptions -): [ReactNode, EuiDataGridStyle, EuiDataGridRowHeightsOptions] => { +): [ + ReactNode, + EuiDataGridStyle, + EuiDataGridRowHeightsOptions, + ReactNode, + ReactNode +] => { const [isOpen, setIsOpen] = useState(false); const showDensityControls = getNestedObjectOptions( @@ -108,11 +114,6 @@ export const useDataGridDisplaySelector = ( 'allowRowHeight' ); - const additionalDisplaySettings = - typeof showDisplaySelector === 'boolean' - ? null - : showDisplaySelector?.additionalDisplaySettings ?? null; - // Track styles specified by the user at run time const [userGridStyles, setUserGridStyles] = useState({}); const [userRowHeightsOptions, setUserRowHeightsOptions] = useState({}); @@ -221,6 +222,107 @@ export const useDataGridDisplaySelector = ( 'Reset to default' ); + const rowHeightsControls = ( + + {([ + rowHeightLabel, + labelSingle, + labelAuto, + labelCustom, + lineCountLabel, + ]: string[]) => ( + <> + + + + {rowHeightSelection === rowHeightButtonOptions[2] && ( + + + + )} + + )} + + ); + const densityControls = ( + + {([densityLabel, labelCompact, labelNormal, labelExpanded]: string[]) => ( + + + + )} + + ); + const displaySelector = showDensityControls || showRowHeightControls ? ( } > - {showDensityControls && ( - - {([ - densityLabel, - labelCompact, - labelNormal, - labelExpanded, - ]: string[]) => ( - - - - )} - - )} - {showRowHeightControls && ( - - {([ - rowHeightLabel, - labelSingle, - labelAuto, - labelCustom, - lineCountLabel, - ]: string[]) => ( - <> - - - - {rowHeightSelection === rowHeightButtonOptions[2] && ( - - - - )} - - )} - - )} - {additionalDisplaySettings} - + {showDensityControls && densityControls} + {showRowHeightControls && rowHeightsControls} {showResetButton && ( @@ -382,5 +373,11 @@ export const useDataGridDisplaySelector = ( ) : null; - return [displaySelector, gridStyles, rowHeightsOptions]; + return [ + displaySelector, + gridStyles, + rowHeightsOptions, + rowHeightsControls, + densityControls, + ]; }; diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index 92b190a7565..1e64d8b9154 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -129,6 +129,7 @@ export const EuiDataGrid = forwardRef( rowHeightsOptions: _rowHeightsOptions, virtualizationOptions, renderCustomGridBody, + renderCustomToolbar, ...rest } = props; @@ -203,15 +204,20 @@ export const EuiDataGrid = forwardRef( ); }, [columns]); - const [displaySelector, gridStyles, rowHeightsOptions] = - useDataGridDisplaySelector( - checkOrDefaultToolBarDisplayOptions( - toolbarVisibility, - 'showDisplaySelector' - ), - gridStyleWithDefaults, - _rowHeightsOptions - ); + const [ + displaySelector, + gridStyles, + rowHeightsOptions, + rowHeightsControls, + densityControls, + ] = useDataGridDisplaySelector( + checkOrDefaultToolBarDisplayOptions( + toolbarVisibility, + 'showDisplaySelector' + ), + gridStyleWithDefaults, + _rowHeightsOptions + ); /** * Column order & visibility @@ -389,19 +395,36 @@ export const EuiDataGrid = forwardRef( ref={setResizeRef} {...rest} > - {showToolbar && ( - - )} + <> + {showToolbar && + renderCustomToolbar && + renderCustomToolbar({ + gridWidth, + minSizeForControls, + toolbarVisibility, + isFullScreen, + fullScreenSelector, + keyboardShortcuts, + displaySelector, + columnSelector, + columnSorting, + rowHeightsControls, + densityControls, + })} + {showToolbar && !renderCustomToolbar && ( + + )} + {inMemory ? ( ReactNode; + /** + * An optional function called to completely customize and control the rendering of + * EuiDataGrid's toolbar. This can be used to add custom buttons, reorder existing ones. + * + * Behind the scenes, this function is treated as a React component, + * allowing hooks, context, and other React concepts to be used. + * It receives #EuiDataGridCustomBodyProps as its only argument. + */ + renderCustomToolbar?: (args: EuiDataGridCustomToolbarProps) => ReactNode; /** * Defines the initial style of the grid. Accepts a partial #EuiDataGridStyle object. * Settings provided may be overwritten or merged with user defined preferences if `toolbarVisibility.showDisplaySelector.allowDensity = true` (which is the default). @@ -482,6 +491,12 @@ export interface EuiDataGridCustomBodyProps { */ setCustomGridBodyProps: (props: EuiDataGridSetCustomGridBodyProps) => void; } + +export interface EuiDataGridCustomToolbarProps extends EuiDataGridToolbarProps { + rowHeightsControls: ReactNode; + densityControls: ReactNode; +} + export type EuiDataGridSetCustomGridBodyProps = CommonProps & HTMLAttributes & { ref?: MutableRefObject | Ref;