diff --git a/CHANGELOG.md b/CHANGELOG.md index c04461834aa..f5421481e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`main`](https://github.com/elastic/eui/tree/main) - Updated the organization of `EuiDataGrid`'s toolbar/grid controls ([#5334](https://github.com/elastic/eui/pull/5334)) +- Added `left.append` and `left.prepend` to `EuiDataGrid`'s `toolbarVisibility.additionalControls` prop [#5394](https://github.com/elastic/eui/pull/5394)) **Bug fixes** diff --git a/src-docs/src/views/datagrid/additional_controls.tsx b/src-docs/src/views/datagrid/additional_controls.tsx index 1e98546c3bf..60ceb9044fb 100644 --- a/src-docs/src/views/datagrid/additional_controls.tsx +++ b/src-docs/src/views/datagrid/additional_controls.tsx @@ -87,9 +87,11 @@ export default () => { const popoverId = useGeneratedHtmlId({ prefix: 'dataGridAdditionalControlsPopover', }); - const alertAndClosePopover = () => { + const alertAndClosePopover = (position?: string) => { setPopover(false); - window.alert('This is not a real control.'); + window.alert( + `This is not a real control. It was passed into the \`${position}\` position.` + ); }; const [visibleColumns, setVisibleColumns] = useState(() => @@ -128,41 +130,53 @@ export default () => { }} toolbarVisibility={{ additionalControls: { - left: ( - setPopover((open) => !open)} - > - Download - - } - isOpen={isPopoverOpen} - closePopover={() => setPopover(false)} - panelPaddingSize="none" - > - - CSV - , - alertAndClosePopover('left.prepend')} + > + {data.length} results + + ), + append: ( + setPopover((open) => !open)} > - JSON - , - ]} - /> - - ), + Download + + } + isOpen={isPopoverOpen} + closePopover={() => setPopover(false)} + panelPaddingSize="none" + > + alertAndClosePopover('left.append')} + > + CSV + , + alertAndClosePopover('left.append')} + > + JSON + , + ]} + /> + + ), + }, right: ( { aria-label="Refresh grid data" size="xs" iconType="refresh" - onClick={() => { - window.alert('This is not a real control.'); - }} + onClick={() => alertAndClosePopover('right')} /> diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js index 4c5e4285dad..1d8d6938431 100644 --- a/src-docs/src/views/datagrid/datagrid_example.js +++ b/src-docs/src/views/datagrid/datagrid_example.js @@ -28,6 +28,7 @@ import { EuiDataGridStyle, EuiDataGridToolBarVisibilityOptions, EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridToolBarAdditionalControlsLeftOptions, EuiDataGridColumnVisibility, EuiDataGridColumnActions, EuiDataGridPopoverContentProps, @@ -405,6 +406,7 @@ export const DataGridExample = { EuiDataGridToolBarVisibilityOptions, EuiDataGridToolBarVisibilityColumnSelectorOptions, EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridToolBarAdditionalControlsLeftOptions, EuiDataGridPopoverContentProps, EuiDataGridRowHeightsOptions, }, diff --git a/src-docs/src/views/datagrid/datagrid_styling_example.js b/src-docs/src/views/datagrid/datagrid_styling_example.js index 332c4b1bc23..359f0d0cb6f 100644 --- a/src-docs/src/views/datagrid/datagrid_styling_example.js +++ b/src-docs/src/views/datagrid/datagrid_styling_example.js @@ -43,6 +43,7 @@ import { EuiDataGridStyle, EuiDataGridToolBarVisibilityOptions, EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridToolBarAdditionalControlsLeftOptions, } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types'; const gridSnippet = ` - {}}> - New button - - {}}> - Another button - - - ), + left: { + prepend: ( + + {}}> + New button + + {}}> + Another button + + + ), + append: ( + + {}}> + New button + + {}}> + Another button + + + ), + }, right: ( @@ -261,22 +278,31 @@ export const DataGridStylingExample = { <>

Use the toolbarVisibility.additionalControls prop - to pass more buttons to the toolbar. It will respect the{' '} - toolbarVisibility={'{false}'}{' '} - setting and hide when appropriate. + to pass more buttons to the toolbar.

Passing a single node to additionalControls will - default to being appended to the left side of the toolbar. To - configure which side of the toolbar your controls display in, pass - an object with either the left or{' '} - right properties: + default to being placed in the left.append{' '} + position of the toolbar. To configure which side of the toolbar your + controls display in, pass an object with the left{' '} + or right properties:

  • additionalControls.left appends the passed - custom control into the left side of the toolbar, after the column - & sort controls. + custom control into the left side of the toolbar. +
      +
    • + left.prepend prepends the passed node into + the left side of the toolbar, before the column & sort + controls. +
    • +
    • + left.append appends the passed node into + the left side of the toolbar, after the column & sort + controls. +
    • +
  • additionalControls.right prepends the passed @@ -299,6 +325,7 @@ export const DataGridStylingExample = { EuiDataGrid, EuiDataGridToolBarVisibilityOptions, EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridToolBarAdditionalControlsLeftOptions, }, demo: , }, diff --git a/src/components/datagrid/controls/data_grid_toolbar.test.tsx b/src/components/datagrid/controls/data_grid_toolbar.test.tsx index 4621c2f7c95..a1c513df46b 100644 --- a/src/components/datagrid/controls/data_grid_toolbar.test.tsx +++ b/src/components/datagrid/controls/data_grid_toolbar.test.tsx @@ -188,67 +188,140 @@ describe('renderAdditionalControls', () => { const mockControl =
    ; it('does not render if a boolean was passed into toolbarVisibility', () => { - expect(renderAdditionalControls(false, 'left')).toEqual(null); - expect(renderAdditionalControls(true, 'left')).toEqual(null); + expect(renderAdditionalControls(false, 'right')).toEqual(null); + expect(renderAdditionalControls(true, 'right')).toEqual(null); }); it('does not render if toolbarVisibility is undefined or additionalControls is undefined', () => { - expect(renderAdditionalControls(undefined, 'left')).toEqual(null); + expect(renderAdditionalControls(undefined, 'right')).toEqual(null); expect( - renderAdditionalControls({ additionalControls: undefined }, 'left') + renderAdditionalControls({ additionalControls: undefined }, 'right') ).toEqual(null); }); - describe('left', () => { - it('renders a react node passed into the left side toolbar', () => { + describe('left.append', () => { + it('renders a react node appended into the left side toolbar', () => { expect( renderAdditionalControls( - { additionalControls: { left: mockControl } }, - 'left' + { additionalControls: { left: { append: mockControl } } }, + 'left.append' ) ).toEqual(mockControl); }); - it('does not render right side positions', () => { + it('does not render other positions', () => { + expect( + renderAdditionalControls( + { additionalControls: { left: { prepend: mockControl } } }, + 'left.append' + ) + ).toEqual(null); expect( renderAdditionalControls( { additionalControls: { right: mockControl } }, - 'left' + 'left.append' ) ).toEqual(null); }); + + describe('additionalControls.left fallback', () => { + it('renders `additionalControls.left` nodes into `left.append` by default', () => { + expect( + renderAdditionalControls( + { additionalControls: { left: mockControl } }, + 'left.append' + ) + ).toEqual(mockControl); + }); + + it('does not render other positions', () => { + expect( + renderAdditionalControls( + { additionalControls: { left: mockControl } }, + 'left.prepend' + ) + ).toEqual(null); + expect( + renderAdditionalControls( + { additionalControls: { left: mockControl } }, + 'right' + ) + ).toEqual(null); + }); + }); + + describe('additionalControls fallback', () => { + it('renders `additionalControls` nodes into `left.append` by default', () => { + expect( + renderAdditionalControls( + { additionalControls: mockControl }, + 'left.append' + ) + ).toEqual(mockControl); + }); + + it('does not render other positions', () => { + expect( + renderAdditionalControls( + { additionalControls: mockControl }, + 'left.prepend' + ) + ).toEqual(null); + expect( + renderAdditionalControls({ additionalControls: mockControl }, 'right') + ).toEqual(null); + }); + }); }); - describe('right', () => { - it('renders a react node passed into the right side toolbar', () => { + describe('left.prepend', () => { + it('renders a react node prepended into the left side toolbar', () => { expect( renderAdditionalControls( - { additionalControls: { right: mockControl } }, - 'right' + { additionalControls: { left: { prepend: mockControl } } }, + 'left.prepend' ) ).toEqual(mockControl); }); - it('does not render left side positions', () => { + it('does not render other positions', () => { expect( renderAdditionalControls( - { additionalControls: { left: mockControl } }, - 'right' + { additionalControls: { left: { append: mockControl } } }, + 'left.prepend' + ) + ).toEqual(null); + expect( + renderAdditionalControls( + { additionalControls: { right: mockControl } }, + 'left.prepend' ) ).toEqual(null); }); }); - describe('single node', () => { - it('renders into the left side of toolbar by default', () => { + describe('right', () => { + it('renders a react node passed into the right side toolbar', () => { expect( - renderAdditionalControls({ additionalControls: mockControl }, 'left') + renderAdditionalControls( + { additionalControls: { right: mockControl } }, + 'right' + ) ).toEqual(mockControl); }); - it('does not render into the right side of the toolbar', () => { + it('does not render left side positions', () => { expect( - renderAdditionalControls({ additionalControls: mockControl }, 'right') + renderAdditionalControls( + { additionalControls: { left: { prepend: mockControl } } }, + 'right' + ) + ).toEqual(null); + expect( + renderAdditionalControls( + { additionalControls: { left: { append: mockControl } } }, + 'right' + ) ).toEqual(null); }); }); diff --git a/src/components/datagrid/controls/data_grid_toolbar.tsx b/src/components/datagrid/controls/data_grid_toolbar.tsx index d1b29488865..fe3e5344794 100644 --- a/src/components/datagrid/controls/data_grid_toolbar.tsx +++ b/src/components/datagrid/controls/data_grid_toolbar.tsx @@ -15,6 +15,7 @@ import { EuiDataGridToolbarProps, EuiDataGridToolBarVisibilityOptions, EuiDataGridToolBarAdditionalControlsOptions, + EuiDataGridToolBarAdditionalControlsLeftOptions, } from '../data_grid_types'; // Used to simplify some sizing logic which is difficult to account for in tests @@ -93,6 +94,7 @@ export const EuiDataGridToolbar = ({ > {hasRoomForGridControls && (
    + {renderAdditionalControls(toolbarVisibility, 'left.prepend')} {checkOrDefaultToolBarDisplayOptions( toolbarVisibility, 'showColumnSelector' @@ -105,7 +107,7 @@ export const EuiDataGridToolbar = ({ ) ? columnSorting : null} - {renderAdditionalControls(toolbarVisibility, 'left')} + {renderAdditionalControls(toolbarVisibility, 'left.append')}
    )}
    @@ -159,7 +161,7 @@ export function checkOrDefaultToolBarDisplayOptions< export function renderAdditionalControls( toolbarVisibility: EuiDataGridProps['toolbarVisibility'], - position: 'left' | 'right' + position: 'left.prepend' | 'left.append' | 'right' ) { if (typeof toolbarVisibility === 'boolean') return null; const { additionalControls } = toolbarVisibility || {}; @@ -168,16 +170,30 @@ export function renderAdditionalControls( // Typescript is having obj issues, so we need to force cast to EuiDataGridToolBarAdditionalControlsOptions here const additionalControlsObj: EuiDataGridToolBarAdditionalControlsOptions = additionalControls?.constructor === Object ? additionalControls : {}; + // Typescript workarounds continued + const leftPositionObj: EuiDataGridToolBarAdditionalControlsLeftOptions = + additionalControlsObj.left?.constructor === Object + ? additionalControlsObj.left + : {}; if (position === 'right') { if (additionalControlsObj?.right) { return additionalControlsObj.right; } - } else if (position === 'left') { - if (additionalControlsObj?.left) { + } else if (position === 'left.prepend') { + if (leftPositionObj?.prepend) { + return leftPositionObj.prepend; + } + } else if (position === 'left.append') { + if (leftPositionObj?.append) { + return leftPositionObj.append; + } + if (React.isValidElement(additionalControlsObj?.left)) { + // If the consumer passed a single ReactNode to `additionalControls.left`, default to the left append position return additionalControlsObj.left; - } else if (React.isValidElement(additionalControls)) { - // API backwards compatability: if the user passed in a single ReactNode, default to the the left position + } + if (React.isValidElement(additionalControls)) { + // API backwards compatability: if the consumer passed a single ReactNode to `additionalControls`, default to the the left append position return additionalControls; } } diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 0a992ffffe1..c3a5eeff132 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -617,10 +617,11 @@ export interface EuiDataGridToolBarVisibilityOptions { export interface EuiDataGridToolBarAdditionalControlsOptions { /** - * Will append the passed node into the left side of the toolbar, **after** the column & sort controls. + * If passed a `ReactNode`, appends the passed node into the left side of the toolbar, **after** the column & sort controls. + * Or use #EuiDataGridToolBarAdditionalControlsLeftOptions to customize the location of your control. * We recommend using `` to match the existing controls on the left. */ - left?: ReactNode; + left?: ReactNode | EuiDataGridToolBarAdditionalControlsLeftOptions; /** * Will prepend the passed node into the right side of the toolbar, **before** the density & full screen controls. * We recommend using `` to match the existing controls on the right. @@ -628,6 +629,17 @@ export interface EuiDataGridToolBarAdditionalControlsOptions { right?: ReactNode; } +export interface EuiDataGridToolBarAdditionalControlsLeftOptions { + /** + * Will prepend the passed node into the left side of the toolbar, **before** the column & sort controls. + */ + prepend?: ReactNode; + /** + * Will append the passed node into the left side of the toolbar, **after** the column & sort controls. + */ + append?: ReactNode; +} + // ideally this would use a generic to enforce `pageSize` exists in `pageSizeOptions`, // but TypeScript's default understanding of an array is number[] unless `as const` is used // which defeats the generic's purpose & functionality as it would check for `number` in `number[]`