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"
/>
Select all rows
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[]) => (
)}