diff --git a/packages/eui/changelogs/upcoming/7817.md b/packages/eui/changelogs/upcoming/7817.md new file mode 100644 index 00000000000..ba972530cb4 --- /dev/null +++ b/packages/eui/changelogs/upcoming/7817.md @@ -0,0 +1 @@ +- Updated `EuiBasicTable` and `EuiInMemoryTable`s with `selection` - the header row checkbox will now render an indeterminate state if some (but not all) rows are selected diff --git a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap index 37c0b232424..2334b0aee41 100644 --- a/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/packages/eui/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -151,7 +151,8 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin aria-label="Select all rows" class="euiCheckbox__input" data-test-subj="checkboxSelectAll" - id="_selection_column-checkbox_generated-id_desktop" + id="_selection_column-checkbox_generated-id" + title="Select all rows" type="checkbox" />
diff --git a/packages/eui/src/components/basic_table/basic_table.test.tsx b/packages/eui/src/components/basic_table/basic_table.test.tsx index a213e9887c3..59b81251feb 100644 --- a/packages/eui/src/components/basic_table/basic_table.test.tsx +++ b/packages/eui/src/components/basic_table/basic_table.test.tsx @@ -7,6 +7,7 @@ */ import React from 'react'; +import { fireEvent } from '@testing-library/react'; import { render, screen } from '../../test/rtl'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; @@ -461,6 +462,72 @@ describe('EuiBasicTable', () => { expect(onSelectionChange).toHaveBeenCalledWith([]); expect(container.querySelectorAll('[checked]')).toHaveLength(0); }); + + describe('header checkbox', () => { + it('selects all rows', () => { + const props: EuiBasicTableProps = { + items: basicItems, + columns: basicColumns, + itemId: 'id', + selection: { + onSelectionChange: () => {}, + initialSelected: [], + }, + }; + const { getByTestSubject } = render(); + expect(getByTestSubject('checkboxSelectAll')).not.toBeChecked(); + + fireEvent.click(getByTestSubject('checkboxSelectAll')); + + expect(getByTestSubject('checkboxSelectAll')).toBeChecked(); + expect(getCheckboxAt(1)).toBeChecked(); + expect(getCheckboxAt(2)).toBeChecked(); + expect(getCheckboxAt(3)).toBeChecked(); + }); + + it('deselects all rows', () => { + const props: EuiBasicTableProps = { + items: basicItems, + columns: basicColumns, + itemId: 'id', + selection: { + onSelectionChange: () => {}, + initialSelected: basicItems, + }, + }; + const { getByTestSubject } = render(); + expect(getByTestSubject('checkboxSelectAll')).toBeChecked(); + + fireEvent.click(getByTestSubject('checkboxSelectAll')); + + expect(getByTestSubject('checkboxSelectAll')).not.toBeChecked(); + expect(getCheckboxAt(1)).not.toBeChecked(); + expect(getCheckboxAt(2)).not.toBeChecked(); + expect(getCheckboxAt(3)).not.toBeChecked(); + }); + + it('renders an indeterminate header checkbox if some but not all rows are selected', () => { + const props: EuiBasicTableProps = { + items: basicItems, + columns: basicColumns, + itemId: 'id', + selection: { + onSelectionChange: () => {}, + initialSelected: [], + }, + }; + const { getByTestSubject } = render(); + expect(getByTestSubject('checkboxSelectAll')).not.toBeChecked(); + + fireEvent.click(getCheckboxAt(1)); + expect(getCheckboxAt(1)).toBeChecked(); + expect(getByTestSubject('checkboxSelectAll')).toBePartiallyChecked(); + + // Should deselect all rows on indeterminate click + fireEvent.click(getByTestSubject('checkboxSelectAll')); + expect(getCheckboxAt(1)).not.toBeChecked(); + }); + }); }); test('footers', () => { diff --git a/packages/eui/src/components/basic_table/basic_table.tsx b/packages/eui/src/components/basic_table/basic_table.tsx index 3409ce01053..0f09e5e16aa 100644 --- a/packages/eui/src/components/basic_table/basic_table.tsx +++ b/packages/eui/src/components/basic_table/basic_table.tsx @@ -688,10 +688,16 @@ export class EuiBasicTable extends Component< selectableItems.length > 0 && this.state.selection.length === selectableItems.length; + const indeterminate = + !checked && + this.state.selection && + selectableItems.length > 0 && + this.state.selection.length > 0; + const disabled = selectableItems.length === 0; const onChange = (event: React.ChangeEvent) => { - if (event.target.checked) { + if (event.target.checked && !indeterminate) { this.changeSelection(selectableItems); } else { this.changeSelection([]); @@ -699,16 +705,20 @@ export class EuiBasicTable extends Component< }; return ( - - {(selectAllRows: string) => ( + + {([selectAllRows, deselectRows]: string[]) => ( )}