diff --git a/changelogs/upcoming/7621.md b/changelogs/upcoming/7621.md
new file mode 100644
index 00000000000..548a7ad74dc
--- /dev/null
+++ b/changelogs/upcoming/7621.md
@@ -0,0 +1,3 @@
+**Breaking changes**
+
+- Removed unused `EuiTableHeaderButton` component
diff --git a/changelogs/upcoming/7625.md b/changelogs/upcoming/7625.md
new file mode 100644
index 00000000000..359270b08d7
--- /dev/null
+++ b/changelogs/upcoming/7625.md
@@ -0,0 +1,10 @@
+- Updated `EuiTable`, `EuiBasicTable`, and `EuiInMemoryTable` with a new `responsiveBreakpoint` prop, which allows customizing the point at which the table collapses into a mobile-friendly view with cards
+- Updated `EuiProvider`'s `componentDefaults` prop to allow configuring `EuiTable.responsiveBreakpoint`
+
+**Breaking changes**
+
+- Removed the `responsive` prop from `EuiTable`, `EuiBasicTable`, and `EuiInMemoryTable`. Use the new `responsiveBreakpoint` prop instead
+
+**DOM changes**
+
+- `EuiTable` mobile headers no longer render in the DOM when not visible (previously rendered with `display: none`). This may affect DOM testing assertions.
diff --git a/changelogs/upcoming/7631.md b/changelogs/upcoming/7631.md
new file mode 100644
index 00000000000..f2647c0b2d3
--- /dev/null
+++ b/changelogs/upcoming/7631.md
@@ -0,0 +1,3 @@
+**DOM changes**
+
+- `EuiTableRowCell` now applies passed `className`s to the parent `
` element, instead of to the inner cell content `
`.
diff --git a/changelogs/upcoming/7632.md b/changelogs/upcoming/7632.md
new file mode 100644
index 00000000000..4bcdfa32ca4
--- /dev/null
+++ b/changelogs/upcoming/7632.md
@@ -0,0 +1,10 @@
+**Breaking changes**
+
+- The following props are no longer needed by `EuiBasicTable` or `EuiInMemoryTable` for responsive table behavior to work correctly, and can be removed:
+ - `isSelectable`
+ - `isExpandable`
+ - `hasActions`
+
+**DOM changes**
+
+- `EuiTableRow`s rendered by basic and memory tables now only render a `.euiTableRow-isSelectable` className if the selection checkbox is not disabled
diff --git a/changelogs/upcoming/7640.md b/changelogs/upcoming/7640.md
new file mode 100644
index 00000000000..a24b726ba53
--- /dev/null
+++ b/changelogs/upcoming/7640.md
@@ -0,0 +1,7 @@
+**Bug fixes**
+
+- `EuiBasicTable` & `EuiInMemoryTable` `isPrimary` actions are now correctly shown on mobile views
+
+**Breaking changes**
+
+- Removed the `showOnHover` prop from `EuiTableRowCell` / `EuiBasicTable`/`EuiInMemoryTable`'s `columns` API. Use the new actions `columns[].actions[].showOnHover` API instead.
diff --git a/changelogs/upcoming/7641.md b/changelogs/upcoming/7641.md
new file mode 100644
index 00000000000..7a007a7b5b9
--- /dev/null
+++ b/changelogs/upcoming/7641.md
@@ -0,0 +1,3 @@
+**DOM changes**
+
+- `EuiTableRowCell`s with `textOnly` set to `false` will no longer attempt to apply the `.euiTableCellContent__text` className to child elements.
diff --git a/changelogs/upcoming/7642.md b/changelogs/upcoming/7642.md
new file mode 100644
index 00000000000..ca6038416d5
--- /dev/null
+++ b/changelogs/upcoming/7642.md
@@ -0,0 +1,15 @@
+**Bug fixes**
+
+- Table `mobileOptions`:
+ - `mobileOptions.align` is now respected instead of all cells being forced to left alignment
+ - `textTruncate` and `textOnly` are now respected even if a `render` function is not passed
+
+**Breaking changes**
+
+- Removed top-level `textOnly` prop from `EuiBasicTable` and `EuiInMemoryTable`. Use `columns[].textOnly` instead.
+
+**DOM changes**
+
+- `EuiTableRowCell` no longer renders mobile headers to the DOM unless the current table is displaying its responsive view.
+- `EuiTableHeaderCell` and `EuiTableRowCell` will no longer render in the DOM at all on mobile if their columns' `mobileOptions.show` is set to `false`.
+- `EuiTableHeaderCell` and `EuiTableRowCell` will no longer render in the DOM at all on desktop if their columns' `mobileOptions.only` is set to `true`.
diff --git a/changelogs/upcoming/7654.md b/changelogs/upcoming/7654.md
new file mode 100644
index 00000000000..a5f1b4baf5e
--- /dev/null
+++ b/changelogs/upcoming/7654.md
@@ -0,0 +1,17 @@
+**CSS-in-JS conversions**
+
+- Converted `EuiTable`, `EuiTableRow`, `EuiTableRowCell`, and all other table subcomponents to Emotion
+- Removed the following `EuiTable` Sass variables:
+ - `$euiTableCellContentPadding`
+ - `$euiTableCellContentPaddingCompressed`
+ - `$euiTableCellCheckboxWidth`
+ - `$euiTableHoverColor`
+ - `$euiTableSelectedColor`
+ - `$euiTableHoverSelectedColor`
+ - `$euiTableActionsBorderColor`
+ - `$euiTableHoverClickableColor`
+ - `$euiTableFocusClickableColor`
+- Removed the following `EuiTable` Sass mixins:
+ - `euiTableActionsBackgroundMobile`
+ - `euiTableCellCheckbox`
+ - `euiTableCell`
diff --git a/src-docs/src/views/provider/provider_component_defaults.tsx b/src-docs/src/views/provider/provider_component_defaults.tsx
index f2561c5a6f1..bcfd198dcd7 100644
--- a/src-docs/src/views/provider/provider_component_defaults.tsx
+++ b/src-docs/src/views/provider/provider_component_defaults.tsx
@@ -12,6 +12,7 @@ export const EuiComponentDefaultsProps: FunctionComponent<
// Exported in one place for DRYness
export const euiProviderComponentDefaultsSnippet = `> = [
sortable: true,
mobileOptions: {
render: (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
),
header: false,
truncateText: false,
@@ -162,6 +162,11 @@ export default () => {
];
if (multiAction) {
actions = [
+ {
+ ...actions[0],
+ isPrimary: true,
+ showOnHover: true,
+ },
{
render: (user: User) => {
return (
@@ -176,7 +181,6 @@ export default () => {
return {}}>Edit;
},
},
- ...actions,
];
}
return actions;
@@ -198,7 +202,7 @@ export default () => {
if (multiAction) {
actions = [
{
- name: Clone,
+ name: <>Clone>,
description: 'Clone this user',
icon: 'copy',
type: 'icon',
@@ -396,7 +400,6 @@ export default () => {
pagination={pagination}
sorting={sorting}
selection={selection}
- hasActions={customAction ? false : true}
onChange={onTableChange}
/>
>
diff --git a/src-docs/src/views/tables/auto/auto.tsx b/src-docs/src/views/tables/auto/auto.tsx
index 80c861df198..e0f98b12dc5 100644
--- a/src-docs/src/views/tables/auto/auto.tsx
+++ b/src-docs/src/views/tables/auto/auto.tsx
@@ -46,9 +46,9 @@ const columns: Array> = [
truncateText: true,
mobileOptions: {
render: (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
),
header: false,
truncateText: false,
diff --git a/src-docs/src/views/tables/basic/basic.tsx b/src-docs/src/views/tables/basic/basic.tsx
index 4e58f781fe5..743c8b81fc3 100644
--- a/src-docs/src/views/tables/basic/basic.tsx
+++ b/src-docs/src/views/tables/basic/basic.tsx
@@ -48,9 +48,9 @@ export default () => {
'data-test-subj': 'firstNameCell',
mobileOptions: {
render: (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
),
header: false,
truncateText: false,
diff --git a/src-docs/src/views/tables/basic/basic_section.js b/src-docs/src/views/tables/basic/basic_section.js
index 3bee817ff28..93c1aaba256 100644
--- a/src-docs/src/views/tables/basic/basic_section.js
+++ b/src-docs/src/views/tables/basic/basic_section.js
@@ -10,13 +10,15 @@ import {
import { Pagination } from '../paginated/_props';
import {
EuiTableFieldDataColumnType,
- EuiTableComputedColumnType,
- EuiTableActionsColumnType,
EuiTableSelectionType,
EuiTableSortingType,
} from '!!prop-loader!../../../../../src/components/basic_table/table_types';
import { CustomItemAction } from '!!prop-loader!../../../../../src/components/basic_table/action_types';
-import { DefaultItemActionProps as DefaultItemAction } from '../props/props';
+import {
+ EuiTableComputedColumnType,
+ EuiTableActionsColumnType,
+ DefaultItemActionProps as DefaultItemAction,
+} from '../props/props';
const source = require('!!raw-loader!./basic');
diff --git a/src-docs/src/views/tables/custom/custom.tsx b/src-docs/src/views/tables/custom/custom.tsx
index aad9499ad03..62186127dfa 100644
--- a/src-docs/src/views/tables/custom/custom.tsx
+++ b/src-docs/src/views/tables/custom/custom.tsx
@@ -113,10 +113,10 @@ export default class extends Component<{}, State> {
{
id: 2,
title: (
-
+ <>
A very long line in an ELEMENT which will wrap on narrower screens and
NOT become truncated and replaced by an ellipsis
-
+ >
),
type: 'user',
dateCreated: 'Tue Dec 01 2016',
@@ -127,11 +127,11 @@ export default class extends Component<{}, State> {
id: 3,
title: {
value: (
-
+ <>
A very long line in an ELEMENT which will not wrap on narrower
screens and instead will become truncated and replaced by an
ellipsis
-
+ >
),
truncateText: true,
},
@@ -290,14 +290,14 @@ export default class extends Component<{}, State> {
width: '100%',
},
render: (title: DataItem['title'], item: DataItem) => (
-
+ <>
{' '}
{title as ReactNode}
-
+ >
),
},
{
@@ -686,7 +686,7 @@ export default class extends Component<{}, State> {
{cells}
diff --git a/src-docs/src/views/tables/custom/custom_section.js b/src-docs/src/views/tables/custom/custom_section.js
index d666fad7edf..038d5f254ac 100644
--- a/src-docs/src/views/tables/custom/custom_section.js
+++ b/src-docs/src/views/tables/custom/custom_section.js
@@ -29,7 +29,7 @@ export const section = {
},
],
text: (
-
+ <>
As an alternative to EuiBasicTable you can instead
construct a table from individual{' '}
@@ -58,7 +58,7 @@ export const section = {
and EuiTableSortMobileItem components to supply
mobile sorting. See demo below.
By default EuiInMemoryTable resets its page index
when receiving a new EuiInMemoryTable array. To avoid
- this behavior the pagination object optionally takes a
+ this behavior the pagination object optionally takes a{' '}
pageIndex value to control this yourself.
Additionally, pageSize can also be controlled the
- same way. Both of these are provided to your app during the
+ same way. Both of these are provided to your app during the{' '}
onTableChange callback.
@@ -52,7 +52,7 @@ export const controlledPaginationSection = {
toggling their online status. Pagination state is maintained by the app,
preventing it from being reset by the updates.
- Sometimes the value displayed in a column is not appropriate to use for
- sorting, such as pre-formatting values to be human-readable. In these
- cases it's possible to pass the sortable prop as
- a function instead of true or{' '}
- false. The function is used to extract or calculate
- the intended sort value for each item.
-
-
+
+ Sometimes the value displayed in a column is not appropriate to use for
+ sorting, such as pre-formatting values to be human-readable. In these
+ cases it's possible to pass the sortable prop as a
+ function instead of true or false.
+ The function is used to extract or calculate the intended sort value for
+ each item.
+
- The example shows how to configure EuiInMemoryTable
- to display a search bar by passing the search prop. You can read more
- about the search bar's properties and its syntax{' '}
-
- here
- {' '}
- .
-
-
+
+ The example shows how to configure EuiInMemoryTable
+ to display a search bar by passing the search prop. You can read more
+ about the search bar's properties and its syntax{' '}
+
+ here
+ {' '}
+ .
+
The EuiInMemoryTable is a higher level component
wrapper around EuiBasicTable aimed at displaying tables
@@ -57,7 +57,7 @@ export const section = {
and preserved between renders.
-
+ >
),
props: {
EuiInMemoryTable,
diff --git a/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx b/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
index 6fb5fe5f0b7..dfa4c30eced 100644
--- a/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
+++ b/src-docs/src/views/tables/in_memory/in_memory_selection_controlled.tsx
@@ -46,9 +46,9 @@ const columns: Array> = [
truncateText: true,
mobileOptions: {
render: (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
),
header: false,
truncateText: false,
@@ -258,7 +258,6 @@ export default () => {
pagination={pagination}
sorting={true}
selection={selectionValue}
- isSelectable={true}
/>
>
);
diff --git a/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx b/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
index 6b497e46b14..6f24553b974 100644
--- a/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
+++ b/src-docs/src/views/tables/in_memory/in_memory_selection_uncontrolled.tsx
@@ -47,9 +47,9 @@ const columns: Array> = [
truncateText: true,
mobileOptions: {
render: (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
),
header: false,
truncateText: false,
@@ -250,7 +250,6 @@ export default () => {
pagination={pagination}
sorting={true}
selection={selectionValue}
- isSelectable={true}
/>
);
};
diff --git a/src-docs/src/views/tables/mobile/mobile.tsx b/src-docs/src/views/tables/mobile/mobile.tsx
index 8f50d211256..0b6b4a9fd7f 100644
--- a/src-docs/src/views/tables/mobile/mobile.tsx
+++ b/src-docs/src/views/tables/mobile/mobile.tsx
@@ -79,9 +79,9 @@ export default () => {
mobileOptions: {
render: customHeader
? (user: User) => (
-
+ <>
{user.firstName} {user.lastName}
-
+ >
)
: undefined,
header: customHeader ? false : true,
@@ -193,7 +193,7 @@ export default () => {
* Pagination & sorting
*/
const [pageIndex, setPageIndex] = useState(0);
- const [pageSize, setPageSize] = useState(5);
+ const [pageSize, setPageSize] = useState(3);
const [sortField, setSortField] = useState('firstName');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
@@ -300,9 +300,7 @@ export default () => {
pagination={pagination}
sorting={sorting}
selection={selection}
- isSelectable={true}
- hasActions={true}
- responsive={isResponsive}
+ responsiveBreakpoint={isResponsive}
onChange={onTableChange}
/>
>
diff --git a/src-docs/src/views/tables/mobile/mobile_section.js b/src-docs/src/views/tables/mobile/mobile_section.js
index 1575e5c81da..1f9862ab9b6 100644
--- a/src-docs/src/views/tables/mobile/mobile_section.js
+++ b/src-docs/src/views/tables/mobile/mobile_section.js
@@ -1,23 +1,24 @@
import React from 'react';
+import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../../components';
+import { EuiCode } from '../../../../../src/components/code';
+
import Table from './mobile';
-import { EuiTextColor } from '../../../../../src/components/text';
-import { EuiCode, EuiCodeBlock } from '../../../../../src/components/code';
const source = require('!!raw-loader!./mobile');
import { EuiTableRowCellMobileOptionsShape } from '../props/props';
/* eslint-disable local/css-logical-properties */
-const exampleItem = `{
+const exampleColumnSnippet = `{
field: 'firstName',
name: 'First Name',
truncateText: true,
mobileOptions: {
- render: (item) => ({item.firstName} {item.lastName}), // Custom renderer for mobile view only
- header: false, // Won't show inline header in mobile view
+ render: (item) => (<>{item.firstName} {item.lastName}>), // Custom renderer for mobile view only
+ header: false, // Won't show inline header in mobile view
width: '100%', // Applies a specific width
- enlarge: true, // Increase text size compared to rest of cells
- truncateText: false, // Only works if a 'render()' is also provided
+ enlarge: true, // Increase text size compared to rest of cells
+ truncateText: false, // Text will wrap instead of truncating to one line
}
}`;
@@ -32,46 +33,37 @@ export const section = {
text: (
<>
- Allowing a table to be responsive means breaking each row down into its
- own section and individually displaying each table header above the cell
- contents. There are few times when you may want to exclude this behavior
- from your table, for instance, when the table has very few columns or
- the table does not break down easily into this format. For these use
- cases, you may set responsive=false.
-
-
- To make your table work responsively, please make sure you add the
- following additional props
- to the top level table component (EuiBasicTable or{' '}
- EuiInMemoryTable):
+ Tables will be mobile-responsive by default, breaking down each row into
+ its own card section and individually displaying each table header above
+ the cell contents. The default breakpoint at which the table will
+ responsively shift into cards is the{' '}
+
+ m window size
+
+ , which can be customized with the{' '}
+ responsiveBreakpoint prop (e.g.,{' '}
+ {'responsiveBreakpoint="s"'}).
-
-
- isSelectable: if the table has a single column of
- checkboxes for selecting rows
-
-
- isExpandable: if the table has rows that can expand
-
-
- hasActions: if the table has a column for actions
- which may/may not be hidden in hover
-
-
- The mobileOptions object can be passed to the{' '}
- EuiTableRowCell directly or with each column item
- provided to EuiBasicTable.
+ To never render your table responsively (e.g. for tables with very few
+ columns), you may set{' '}
+ {'responsiveBreakpoint={false}'}.
+ Inversely, if you always want your table to render in a mobile-friendly
+ manner, pass true. The below example table switches
+ between true/false for quick/easy preview between
+ mobile and desktop table UIs at all breakpoints.
- {exampleItem}
- Note: You can also change basic table row cell props
- like truncateText and textOnly for
- mobile layouts, though you must also be passing a mobile specific render
- function.
+ To customize your cell's appearance/rendering in mobile vs. desktop
+ view, use the mobileOptions configuration. This
+ object can be passed to each column item in{' '}
+ EuiBasicTable or to EuiTableRowCell{' '}
+ directly. See the "Snippet" tab in the below example, or the "Props" tab
+ for a full list of configuration options.
+ Name
+
+
-
- Name
-
-
- description
-
+ description
-
-
-
-
+
+
+
+
+
-
-
-
- Name
-
-
-
- name1
-
-
-
-
-
+
+
+
+
+
-
-
- Name
-
-
-
- name2
-
-
-
-
-
+
+
+
+
+
-
-
- Name
-
-
-
- name3
-
-
-
-
-
-
-
+ name3
+
+
+
+
+
+
`;
diff --git a/src/components/basic_table/action_types.ts b/src/components/basic_table/action_types.ts
index cb8d6981cbf..1ac557ee5dd 100644
--- a/src/components/basic_table/action_types.ts
+++ b/src/components/basic_table/action_types.ts
@@ -39,8 +39,21 @@ export interface DefaultItemActionBase {
* A callback function that determines whether the action is enabled
*/
enabled?: (item: T) => boolean;
- isPrimary?: boolean;
'data-test-subj'?: string | ((item: T) => string);
+ /**
+ * If more than 3 actions are passed, 2 primary actions will show (on hover)
+ * next to an expansion menu of all actions.
+ *
+ * On mobile, primary actions will be tucked away in the expansion menu for space.
+ */
+ isPrimary?: boolean;
+ /**
+ * Allows only showing the action on mouse hover or keyboard focus.
+ * If more than 3 actions are passed, this will always be true for `isPrimary` actions.
+ *
+ * Has no effect on mobile, or if `hasActions` is not set.
+ */
+ showOnHover?: boolean;
}
export interface DefaultItemEmptyButtonAction
@@ -70,7 +83,7 @@ export type DefaultItemAction = ExclusiveUnion<
DefaultItemIconButtonAction
>;
-export interface CustomItemAction {
+export type CustomItemAction = {
/**
* Allows rendering a totally custom action
*/
@@ -83,8 +96,7 @@ export interface CustomItemAction {
* A callback that defines whether the action is enabled
*/
enabled?: (item: T) => boolean;
- isPrimary?: boolean;
-}
+} & Pick, 'isPrimary' | 'showOnHover'>;
export type Action =
| DefaultItemAction
diff --git a/src/components/basic_table/basic_table.styles.ts b/src/components/basic_table/basic_table.styles.ts
index a11071b592a..2aaea2280e0 100644
--- a/src/components/basic_table/basic_table.styles.ts
+++ b/src/components/basic_table/basic_table.styles.ts
@@ -54,12 +54,6 @@ export const euiBasicTableBodyLoading = ({ euiTheme }: UseEuiTheme) => css`
// Fix to make the loading indicator position correctly in Safari
// For whatever annoying reason, Safari doesn't respect `position: relative;`
// on `tbody` without `position: relative` on the parent `table`
-export const safariLoadingWorkaround = () => css`
+export const safariLoadingWorkaround = css`
position: relative;
`;
-
-// Unsets the extra height caused by tooltip/popover wrappers around table action buttons
-// Without this, the row height jumps whenever actions are disabled
-export const euiBasicTableActionsWrapper = css`
- line-height: 1;
-`;
diff --git a/src/components/basic_table/basic_table.test.tsx b/src/components/basic_table/basic_table.test.tsx
index bf9513474ff..a213e9887c3 100644
--- a/src/components/basic_table/basic_table.test.tsx
+++ b/src/components/basic_table/basic_table.test.tsx
@@ -224,7 +224,6 @@ describe('EuiBasicTable', () => {
itemIdToExpandedRowMap: {
'1':
Expanded row
,
},
- isExpandable: true,
};
const { getByText } = render();
@@ -565,7 +564,7 @@ describe('EuiBasicTable', () => {
// Numbers should be right aligned
expect(
- container.querySelectorAll('.euiTableCellContent--alignRight')
+ container.querySelectorAll('[class*="euiTableCellContent-right"]')
).toHaveLength(3);
// Booleans should output as Yes or No
@@ -665,9 +664,12 @@ describe('EuiBasicTable', () => {
},
],
};
- const { getAllByText } = render();
+ const { getAllByText, container } = render();
expect(getAllByText('Delete')).toHaveLength(basicItems.length);
+ expect(
+ container.querySelector('.euiBasicTableAction-showOnHover')
+ ).not.toBeInTheDocument();
});
test('multiple actions with custom availability', () => {
@@ -681,7 +683,7 @@ describe('EuiBasicTable', () => {
},
],
};
- const { getAllByText, getAllByTestSubject } = render(
+ const { getAllByText, getAllByTestSubject, container } = render(
);
@@ -690,6 +692,57 @@ describe('EuiBasicTable', () => {
expect(getAllByTestSubject('euiCollapsedItemActionsButton')).toHaveLength(
4
);
+ expect(
+ container.querySelector('.euiBasicTable__collapsedActions')
+ ).toBeInTheDocument();
+ expect(
+ container.querySelector('.euiBasicTableAction-showOnHover')
+ ).toBeInTheDocument();
+ });
+
+ test('custom item actions', () => {
+ const props: EuiBasicTableProps = {
+ items: basicItems,
+ columns: [
+ {
+ name: 'Actions',
+ actions: [
+ {
+ render: ({ id }) => (
+
+ Custom action
+
+ ),
+ available: ({ id }) => id !== '3',
+ },
+ ],
+ },
+ ],
+ responsiveBreakpoint: true, // Needs to be in mobile to render customAction cell CSS
+ };
+ const { queryByTestSubject, container } = render(
+
+ );
+
+ expect(queryByTestSubject('customAction-1')).toBeInTheDocument();
+ expect(queryByTestSubject('customAction-2')).toBeInTheDocument();
+ expect(queryByTestSubject('customAction-3')).not.toBeInTheDocument();
+
+ // TODO: These assertions should ideally be visual regression snapshots instead
+ expect(
+ container.querySelector('.euiTableRowCell--hasActions')!.className
+ ).toContain('-customActions');
+ expect(
+ container.querySelector(
+ '.euiTableRowCell--hasActions .euiTableCellContent'
+ )!.className
+ ).not.toContain('-actions-mobile');
+ expect(
+ container.querySelector('.euiTableRow-hasActions')!.className
+ ).not.toContain('-hasRightColumn');
+ expect(
+ container.querySelector('.euiTableRow-hasActions')
+ ).toMatchSnapshot();
});
describe('are disabled on selection', () => {
diff --git a/src/components/basic_table/basic_table.tsx b/src/components/basic_table/basic_table.tsx
index 18ef806dbf6..5199cfa33ca 100644
--- a/src/components/basic_table/basic_table.tsx
+++ b/src/components/basic_table/basic_table.tsx
@@ -30,7 +30,6 @@ import {
import { CommonProps } from '../common';
import { isFunction } from '../../services/predicate';
import { get } from '../../services/objects';
-import { EuiFlexGroup, EuiFlexItem } from '../flex';
import { EuiCheckbox } from '../form';
import { EuiComponentDefaultsContext } from '../provider/component_defaults';
@@ -50,6 +49,7 @@ import {
EuiTableRowCellCheckbox,
EuiTableSortMobile,
} from '../table';
+import { euiTableCaptionStyles } from '../table/table.styles';
import { CollapsedItemActions } from './collapsed_item_actions';
import { ExpandedItemActions } from './expanded_item_actions';
@@ -61,7 +61,7 @@ import { EuiI18n } from '../i18n';
import { EuiDelayRender } from '../delay_render';
import { htmlIdGenerator } from '../../services/accessibility';
-import { Action } from './action_types';
+import { Action, CustomItemAction } from './action_types';
import {
EuiTableActionsColumnType,
EuiTableComputedColumnType,
@@ -78,7 +78,6 @@ import { EuiTableSortMobileProps } from '../table/mobile/table_sort_mobile';
import {
euiBasicTableBodyLoading,
safariLoadingWorkaround,
- euiBasicTableActionsWrapper,
} from './basic_table.styles';
type DataTypeProfiles = Record<
@@ -241,9 +240,6 @@ interface BasicTableProps
* Indicates which column should be used as the identifying cell in each row. Should match a "field" prop in FieldDataColumn
*/
rowHeader?: string;
- hasActions?: boolean;
- isExpandable?: boolean;
- isSelectable?: boolean;
/**
* Provides an infinite loading indicator
*/
@@ -260,10 +256,6 @@ interface BasicTableProps
* Configures #Pagination
*/
pagination?: undefined;
- /**
- * If true, will convert table to cards in mobile view
- */
- responsive?: boolean;
/**
* Applied to `EuiTableRow`
*/
@@ -280,13 +272,6 @@ interface BasicTableProps
* Sets the table-layout CSS property. Note that auto tableLayout prevents truncateText from working properly.
*/
tableLayout?: 'fixed' | 'auto';
- /**
- * Applied to table cells. Any cell using a render function will set this to be false.
- *
- * Creates a text wrapper around cell content that helps word break or truncate
- * long text correctly.
- */
- textOnly?: boolean;
}
type BasicTableWithPaginationProps = Omit<
@@ -328,7 +313,6 @@ export class EuiBasicTable extends Component<
declare context: ContextType;
static defaultProps = {
- responsive: true,
tableLayout: 'fixed',
noItemsMessage: (
@@ -527,10 +511,7 @@ export class EuiBasicTable extends Component<
noItemsMessage,
compressed,
itemIdToExpandedRowMap,
- responsive,
- isSelectable,
- isExpandable,
- hasActions,
+ responsiveBreakpoint,
rowProps,
cellProps,
tableCaption,
@@ -557,40 +538,28 @@ export class EuiBasicTable extends Component<
}
renderTable() {
- const { compressed, responsive, tableLayout, loading } = this.props;
-
- const mobileHeader = responsive ? (
-
-
- {this.renderSelectAll(true)}
- {this.renderTableMobileSort()}
-
-
- ) : undefined;
- const caption = this.renderTableCaption();
- const head = this.renderTableHead();
- const body = this.renderTableBody();
- const footer = this.renderTableFooter();
+ const { compressed, responsiveBreakpoint, tableLayout, loading } =
+ this.props;
+
return (
-
@@ -982,15 +951,8 @@ export class EuiBasicTable extends Component<
}
renderItemRow(item: T, rowIndex: number) {
- const {
- columns,
- selection,
- isSelectable,
- hasActions,
- rowHeader,
- itemIdToExpandedRowMap = {},
- isExpandable,
- } = this.props;
+ const { columns, selection, rowHeader, itemIdToExpandedRowMap } =
+ this.props;
const cells = [];
@@ -1007,24 +969,37 @@ export class EuiBasicTable extends Component<
getItemId(selectedItem, itemIdCallback) === itemId
);
- let calculatedHasSelection;
+ let rowSelectionDisabled = false;
if (selection) {
- cells.push(this.renderItemSelectionCell(itemId, item, selected));
- calculatedHasSelection = true;
+ const [checkboxCell, isDisabled] = this.renderItemSelectionCell(
+ itemId,
+ item,
+ selected
+ );
+ cells.push(checkboxCell);
+ rowSelectionDisabled = !!isDisabled;
}
- let calculatedHasActions;
+ let hasActions: 'custom' | boolean = false;
columns.forEach((column: EuiBasicTableColumn, columnIndex: number) => {
- if ((column as EuiTableActionsColumnType).actions) {
+ const columnActions = (column as EuiTableActionsColumnType).actions;
+
+ if (columnActions) {
+ const hasCustomActions = columnActions.some(
+ (action) => !!(action as CustomItemAction).render
+ );
cells.push(
this.renderItemActionsCell(
itemId,
item,
column as EuiTableActionsColumnType,
- columnIndex
+ columnIndex,
+ hasCustomActions
)
);
- calculatedHasActions = true;
+ // A table theoretically could have both custom and default action items
+ // If it has both, default action mobile row styles take precedence over custom
+ hasActions = !hasActions && hasCustomActions ? 'custom' : true;
} else if ((column as EuiTableFieldDataColumnType).field) {
const fieldDataColumn = column as EuiTableFieldDataColumnType;
cells.push(
@@ -1060,7 +1035,7 @@ export class EuiBasicTable extends Component<
expandedRowColSpan = expandedRowColSpan - mobileOnlyCols;
// We'll use the ID to associate the expanded row with the original.
- const hasExpandedRow = itemIdToExpandedRowMap.hasOwnProperty(itemId);
+ const hasExpandedRow = itemIdToExpandedRowMap?.hasOwnProperty(itemId);
const expandedRowId = hasExpandedRow
? `row_${itemId}_expansion`
: undefined;
@@ -1068,10 +1043,10 @@ export class EuiBasicTable extends Component<
- {itemIdToExpandedRowMap[itemId]}
+ {itemIdToExpandedRowMap![itemId]}
) : undefined;
@@ -1081,12 +1056,11 @@ export class EuiBasicTable extends Component<
const row = (
{cells}
@@ -1124,7 +1098,7 @@ export class EuiBasicTable extends Component<
);
}
};
- return (
+ return [
{(selectThisRow: string) => (
@@ -1140,15 +1114,17 @@ export class EuiBasicTable extends Component<
/>
)}
-
- );
+ ,
+ disabled,
+ ];
}
renderItemActionsCell(
itemId: ItemIdResolved,
item: T,
column: EuiTableActionsColumnType,
- columnIndex: number
+ columnIndex: number,
+ hasCustomActions: boolean
) {
// Disable all actions if any row(s) are selected
const allDisabled = this.state.selection.length > 0;
@@ -1161,9 +1137,15 @@ export class EuiBasicTable extends Component<
// If all actions are disabled, do not show any actions but the popover toggle
actualActions = [];
} else {
- // if any of the actions `isPrimary`, add them inline as well, but only the first 2
- const primaryActions = actualActions.filter((o) => o.isPrimary);
- actualActions = primaryActions.slice(0, 2);
+ // if any of the actions `isPrimary`, add them inline as well, but only the first 2,
+ // which we'll force to only show on hover for desktop views
+ const primaryActions = actualActions.filter(
+ (action) => action.isPrimary
+ );
+ actualActions = primaryActions.slice(0, 2).map((action) => ({
+ ...action,
+ showOnHover: true,
+ }));
}
// if we have more than 1 action, we don't show them all in the cell, instead we
@@ -1174,39 +1156,32 @@ export class EuiBasicTable extends Component<
actualActions.push({
name: 'All actions',
- render: (item: T) => {
- return (
-
- );
- },
+ render: (item: T) => (
+
+ ),
});
}
- const tools = (
-
- );
-
const key = `record_actions_${itemId}_${columnIndex}`;
return (
- {tools}
+
);
}
diff --git a/src/components/basic_table/expanded_item_actions.test.tsx b/src/components/basic_table/expanded_item_actions.test.tsx
index 99118a9de92..6a23ee0888f 100644
--- a/src/components/basic_table/expanded_item_actions.test.tsx
+++ b/src/components/basic_table/expanded_item_actions.test.tsx
@@ -7,14 +7,15 @@
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { render } from '../../test/rtl';
+
import {
ExpandedItemActions,
ExpandedItemActionsProps,
} from './expanded_item_actions';
describe('ExpandedItemActions', () => {
- test('render', () => {
+ it('renders', () => {
const props: ExpandedItemActionsProps<{ id: string }> = {
actions: [
{
@@ -27,14 +28,19 @@ describe('ExpandedItemActions', () => {
description: 'custom 1',
render: (_item) => <>>,
},
+ {
+ name: 'showOnHover',
+ description: 'show on hover',
+ href: '#',
+ showOnHover: true,
+ },
],
itemId: 'xyz',
item: { id: 'xyz' },
actionsDisabled: false,
};
+ const { container } = render();
- const component = shallow();
-
- expect(component).toMatchSnapshot();
+ expect(container).toMatchSnapshot();
});
});
diff --git a/src/components/basic_table/expanded_item_actions.tsx b/src/components/basic_table/expanded_item_actions.tsx
index aa07ebece8c..3fd319cb0be 100644
--- a/src/components/basic_table/expanded_item_actions.tsx
+++ b/src/components/basic_table/expanded_item_actions.tsx
@@ -33,8 +33,6 @@ export const ExpandedItemActions = ({
actionsDisabled,
className,
}: ExpandedItemActionsProps): ReactElement => {
- const moreThanThree = actions.length > 2;
-
return (
<>
{actions.reduce((tools, action, index) => {
@@ -48,7 +46,7 @@ export const ExpandedItemActions = ({
const key = `item_action_${itemId}_${index}`;
const classes = classNames(className, {
- expandedItemActions__completelyHide: moreThanThree && index < 2,
+ 'euiBasicTableAction-showOnHover': action.showOnHover,
});
if (isCustomItemAction(action)) {
diff --git a/src/components/basic_table/in_memory_table.stories.tsx b/src/components/basic_table/in_memory_table.stories.tsx
new file mode 100644
index 00000000000..a9ff289e02a
--- /dev/null
+++ b/src/components/basic_table/in_memory_table.stories.tsx
@@ -0,0 +1,246 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { faker } from '@faker-js/faker';
+import { moveStorybookControlsToCategory } from '../../../.storybook/utils';
+
+import { EuiLink } from '../link';
+import { EuiHealth } from '../health';
+import type { EuiBasicTableColumn } from './basic_table';
+
+import { EuiInMemoryTable, EuiInMemoryTableProps } from './in_memory_table';
+
+const meta: Meta = {
+ title: 'Tabular Content/EuiInMemoryTable',
+ // @ts-ignore complex
+ component: EuiInMemoryTable,
+ args: {
+ allowNeutralSort: true,
+ searchFormat: 'eql',
+ error: '',
+ loading: false,
+ // Set to strings for easier testing
+ message: '',
+ childrenBetween: '',
+ // Inherited from EuiTable
+ responsiveBreakpoint: 'm',
+ tableLayout: 'fixed',
+ },
+};
+moveStorybookControlsToCategory(
+ meta,
+ ['responsiveBreakpoint', 'tableLayout'],
+ 'EuiTable props'
+);
+
+export default meta;
+type Story = StoryObj>;
+
+type User = {
+ id: number;
+ firstName: string | null | undefined;
+ lastName: string;
+ online: boolean;
+ location: {
+ city: string;
+ country: string;
+ };
+};
+
+const users: User[] = [];
+
+for (let i = 0; i < 20; i++) {
+ users.push({
+ id: i + 1,
+ firstName: faker.person.firstName(),
+ lastName: faker.person.lastName(),
+ online: faker.datatype.boolean(),
+ location: {
+ city: faker.location.city(),
+ country: faker.location.country(),
+ },
+ });
+}
+
+const columns: Array> = [
+ {
+ field: 'firstName',
+ name: 'First Name',
+ sortable: true,
+ truncateText: true,
+ mobileOptions: {
+ render: (user: User) => (
+
+ {user.firstName} {user.lastName}
+
+ ),
+ header: false,
+ truncateText: false,
+ enlarge: true,
+ width: '100%',
+ },
+ },
+ {
+ field: 'lastName',
+ name: 'Last Name',
+ truncateText: true,
+ sortable: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+ {
+ field: 'location',
+ name: 'Location',
+ truncateText: true,
+ textOnly: true,
+ render: (location: User['location']) => {
+ return `${location.city}, ${location.country}`;
+ },
+ },
+ {
+ field: 'online',
+ name: 'Online',
+ dataType: 'boolean',
+ render: (online: User['online']) => {
+ const color = online ? 'success' : 'danger';
+ const label = online ? 'Online' : 'Offline';
+ return {label};
+ },
+ sortable: true,
+ mobileOptions: {
+ show: false,
+ },
+ },
+ {
+ name: 'Actions',
+ actions: [
+ {
+ name: 'User profile',
+ description: ({ firstName, lastName }) =>
+ `Visit ${firstName} ${lastName}'s profile`,
+ icon: 'editorLink',
+ color: 'primary',
+ type: 'icon',
+ enabled: ({ online }) => !!online,
+ href: ({ id }) => `${window.location.href}?id=${id}`,
+ target: '_self',
+ 'data-test-subj': 'action-outboundlink',
+ },
+ {
+ name: <>Clone>,
+ description: 'Clone this user',
+ icon: 'copy',
+ type: 'icon',
+ onClick: () => {},
+ 'data-test-subj': 'action-clone',
+ },
+ {
+ name: (user: User) => (user.id ? 'Delete' : 'Remove'),
+ description: ({ firstName, lastName }) =>
+ `Delete ${firstName} ${lastName}`,
+ icon: 'trash',
+ color: 'danger',
+ type: 'icon',
+ onClick: () => {},
+ isPrimary: true,
+ 'data-test-subj': ({ id }) => `action-delete-${id}`,
+ },
+ {
+ name: 'Edit',
+ isPrimary: true,
+ available: ({ online }) => !online,
+ enabled: ({ online }) => !!online,
+ description: 'Edit this user',
+ icon: 'pencil',
+ type: 'icon',
+ onClick: () => {},
+ 'data-test-subj': 'action-edit',
+ },
+ {
+ name: 'Share',
+ isPrimary: true,
+ description: 'Share this user',
+ icon: 'share',
+ type: 'icon',
+ onClick: () => {},
+ 'data-test-subj': 'action-share',
+ },
+ ],
+ },
+ {
+ name: 'Custom actions',
+ actions: [
+ {
+ render: () => (
+ {}} color="danger">
+ Delete
+
+ ),
+ showOnHover: true,
+ },
+ {
+ render: () => {}}>Edit,
+ },
+ ],
+ },
+];
+
+export const KitchenSink: Story = {
+ args: {
+ tableCaption: 'Kitchen sink story',
+ items: users,
+ itemId: 'id',
+ rowHeader: 'firstName',
+ columns,
+ pagination: {
+ initialPageSize: 5,
+ pageSizeOptions: [3, 5, 8],
+ },
+ sorting: {
+ sort: {
+ field: 'lastName',
+ direction: 'asc' as const,
+ },
+ },
+ selection: {
+ selectable: (user) => user.online,
+ selectableMessage: (selectable) =>
+ !selectable ? 'User is currently offline' : '',
+ },
+ search: {
+ box: {
+ incremental: true,
+ },
+ filters: [
+ {
+ type: 'is',
+ field: 'online',
+ name: 'Online',
+ negatedName: 'Offline',
+ },
+ {
+ type: 'field_value_selection',
+ field: 'location.country',
+ name: 'Country',
+ multiSelect: false,
+ options: users.map(({ location: { country } }) => ({
+ value: country,
+ })),
+ },
+ ],
+ },
+ },
+ // Don't pass the default Storybook action listener for `onChange`,
+ // or the automatic uncontrolled pagination & sorting won't work
+ render: ({ onChange, ...args }: EuiInMemoryTableProps) => (
+
+ ),
+};
diff --git a/src/components/basic_table/in_memory_table.test.tsx b/src/components/basic_table/in_memory_table.test.tsx
index e15294c9eb4..5aedbd0eddc 100644
--- a/src/components/basic_table/in_memory_table.test.tsx
+++ b/src/components/basic_table/in_memory_table.test.tsx
@@ -638,7 +638,9 @@ describe('EuiInMemoryTable', () => {
onSelectionChange: () => undefined,
},
};
- const { getByText } = render();
+ const { getByText } = render(
+
+ );
expect(getByText('Page 1 of 1')).toBeTruthy();
expect(getByText('Select all rows')).toBeTruthy();
@@ -667,7 +669,9 @@ describe('EuiInMemoryTable', () => {
onSelectionChange: () => undefined,
},
};
- const { getByText } = render();
+ const { getByText } = render(
+
+ );
expect(getByText('Page 1 of 1')).toBeTruthy();
expect(getByText('Select all rows')).toBeTruthy();
@@ -700,7 +704,9 @@ describe('EuiInMemoryTable', () => {
onSelectionChange: () => undefined,
},
};
- const { getByText } = render();
+ const { getByText } = render(
+
+ );
expect(getByText('Page 1 of 2')).toBeTruthy();
expect(getByText('Select all rows')).toBeTruthy();
@@ -741,7 +747,9 @@ describe('EuiInMemoryTable', () => {
onSelectionChange: () => undefined,
},
};
- const { getByText } = render();
+ const { getByText } = render(
+
+ );
expect(getByText('Page 1 of 1')).toBeTruthy();
expect(getByText('Select all rows')).toBeTruthy();
@@ -784,7 +792,7 @@ describe('EuiInMemoryTable', () => {
},
};
const { getByText, getByPlaceholderText } = render(
-
+
);
expect(getByText('Page 1 of 1')).toBeTruthy();
@@ -881,7 +889,9 @@ describe('EuiInMemoryTable', () => {
onSelectionChange: () => undefined,
},
};
- const { container, queryByText } = render();
+ const { container, queryByText } = render(
+
+ );
expect(queryByText('Page 1 of 1')).toBeTruthy();
expect(queryByText('Select all rows')).toBeTruthy();
@@ -1088,6 +1098,38 @@ describe('EuiInMemoryTable', () => {
});
describe('behavior', () => {
+ test('mobile header', () => {
+ const props: EuiInMemoryTableProps = {
+ ...requiredProps,
+ items: [
+ { id: '1', name: 'name1' },
+ { id: '2', name: 'name2' },
+ { id: '3', name: 'name3' },
+ ],
+ itemId: 'id',
+ columns: [
+ {
+ field: 'name',
+ name: 'Name',
+ description: 'description',
+ sortable: true,
+ },
+ ],
+ pagination: true,
+ sorting: true,
+ selection: {
+ onSelectionChange: () => undefined,
+ },
+ };
+ const { container } = render(
+
+ );
+
+ expect(
+ container.querySelector('.euiTableHeaderMobile')
+ ).toMatchSnapshot();
+ });
+
test('pagination', async () => {
const props: EuiInMemoryTableProps = {
...requiredProps,
@@ -1283,8 +1325,8 @@ describe('EuiInMemoryTable', () => {
// ensure table is on 2nd page (pageIndex=1)
expect(getByTestSubject('pagination-button-1')).toBeDisabled();
- expect(container.querySelectorAll('td')[0]).toHaveTextContent('Index2');
- expect(container.querySelectorAll('td')[1]).toHaveTextContent('Index3');
+ expect(container.querySelectorAll('td')[0]).toHaveTextContent('2');
+ expect(container.querySelectorAll('td')[1]).toHaveTextContent('3');
// click the first pagination button
fireEvent.click(getByTestSubject('pagination-button-0'));
@@ -1299,8 +1341,8 @@ describe('EuiInMemoryTable', () => {
// ensure table is still on the 2nd page (pageIndex=1)
expect(getByTestSubject('pagination-button-1')).toBeDisabled();
- expect(container.querySelectorAll('td')[0]).toHaveTextContent('Index2');
- expect(container.querySelectorAll('td')[1]).toHaveTextContent('Index3');
+ expect(container.querySelectorAll('td')[0]).toHaveTextContent('2');
+ expect(container.querySelectorAll('td')[1]).toHaveTextContent('3');
// re-render with an updated `pageIndex` value
rerender(
@@ -1314,8 +1356,8 @@ describe('EuiInMemoryTable', () => {
// ensure table is on 3rd page (pageIndex=2)
expect(getByTestSubject('pagination-button-2')).toBeDisabled();
- expect(container.querySelectorAll('td')[0]).toHaveTextContent('Index4');
- expect(container.querySelectorAll('td')[1]).toHaveTextContent('Index5');
+ expect(container.querySelectorAll('td')[0]).toHaveTextContent('4');
+ expect(container.querySelectorAll('td')[1]).toHaveTextContent('5');
});
it('respects pageSize', () => {
@@ -1339,7 +1381,7 @@ describe('EuiInMemoryTable', () => {
},
];
const onTableChange = jest.fn();
- const component = mount(
+ const { container, getByTestSubject, rerender } = render(
{
);
// check that the first 2 items rendered
- expect(component.find('td').length).toBe(2);
- expect(component.find('td').at(0).text()).toBe('Index0');
- expect(component.find('td').at(1).text()).toBe('Index1');
+ let cells = container.querySelectorAll('td');
+ expect(cells.length).toBe(2);
+ expect(cells[0]).toHaveTextContent('0');
+ expect(cells[1]).toHaveTextContent('1');
// change the page size
- component
- .find('button[data-test-subj="tablePaginationPopoverButton"]')
- .simulate('click');
- component.update();
- component
- .find('button[data-test-subj="tablePagination-4-rows"]')
- .simulate('click');
+ fireEvent.click(getByTestSubject('tablePaginationPopoverButton'));
+ fireEvent.click(getByTestSubject('tablePagination-4-rows'));
// check callback
expect(onTableChange).toHaveBeenCalledTimes(1);
@@ -1373,20 +1411,28 @@ describe('EuiInMemoryTable', () => {
});
// verify still only rendering the first 2 rows
- expect(component.find('td').length).toBe(2);
- expect(component.find('td').at(0).text()).toBe('Index0');
- expect(component.find('td').at(1).text()).toBe('Index1');
+ cells = container.querySelectorAll('td');
+ expect(cells.length).toBe(2);
+ expect(cells[0]).toHaveTextContent('0');
+ expect(cells[1]).toHaveTextContent('1');
// update the controlled page size
- pagination.pageSize = 4;
- component.setProps({ pagination });
+ rerender(
+
+ );
// verify it now renders 4 rows
- expect(component.find('td').length).toBe(4);
- expect(component.find('td').at(0).text()).toBe('Index0');
- expect(component.find('td').at(1).text()).toBe('Index1');
- expect(component.find('td').at(2).text()).toBe('Index2');
- expect(component.find('td').at(3).text()).toBe('Index3');
+ cells = container.querySelectorAll('td');
+ expect(cells.length).toBe(4);
+ expect(cells[0]).toHaveTextContent('0');
+ expect(cells[1]).toHaveTextContent('1');
+ expect(cells[2]).toHaveTextContent('2');
+ expect(cells[3]).toHaveTextContent('3');
});
});
@@ -1396,7 +1442,7 @@ describe('EuiInMemoryTable', () => {
const columns = [{ field: 'title', name: 'Title' }];
const query = Query.parse('baz');
- const component = mount(
+ const { container } = render(
{
/>
);
- const tableContent = component.find(
+ const tableContent = container.querySelectorAll(
'.euiTableRowCell .euiTableCellContent'
);
expect(tableContent.length).toBe(1); // only 1 match
- expect(tableContent.at(0).text()).toBe('baz');
+ expect(tableContent[0]).toHaveTextContent('baz');
});
it('does not execute the Query and renders the items passed as is', () => {
@@ -1418,7 +1464,7 @@ describe('EuiInMemoryTable', () => {
const columns = [{ field: 'title', name: 'Title' }];
const query = Query.parse('baz');
- const component = mount(
+ const { container } = render(
{
/>
);
- const tableContent = component.find(
+ const tableContent = container.querySelectorAll(
'.euiTableRowCell .euiTableCellContent'
);
expect(tableContent.length).toBe(3);
- expect(tableContent.at(0).text()).toBe('foo');
- expect(tableContent.at(1).text()).toBe('bar');
- expect(tableContent.at(2).text()).toBe('baz');
+ expect(tableContent[0]).toHaveTextContent('foo');
+ expect(tableContent[1]).toHaveTextContent('bar');
+ expect(tableContent[2]).toHaveTextContent('baz');
});
});
diff --git a/src/components/basic_table/in_memory_table.tsx b/src/components/basic_table/in_memory_table.tsx
index a1c63ff2d25..c85fefba32d 100644
--- a/src/components/basic_table/in_memory_table.tsx
+++ b/src/components/basic_table/in_memory_table.tsx
@@ -299,7 +299,6 @@ export class EuiInMemoryTable extends Component<
static contextType = EuiComponentDefaultsContext;
static defaultProps = {
- responsive: true,
tableLayout: 'fixed',
searchFormat: 'eql',
};
@@ -678,8 +677,6 @@ export class EuiInMemoryTable extends Component<
message,
error,
selection,
- isSelectable,
- hasActions,
compressed,
pagination: hasPagination,
sorting: hasSorting,
@@ -748,8 +745,6 @@ export class EuiInMemoryTable extends Component<
pagination={pagination}
sorting={sorting}
selection={selection}
- isSelectable={isSelectable}
- hasActions={hasActions}
onChange={this.onTableChange}
error={error}
loading={loading}
diff --git a/src/components/basic_table/table_types.ts b/src/components/basic_table/table_types.ts
index 60856e1e2ee..3641f271c94 100644
--- a/src/components/basic_table/table_types.ts
+++ b/src/components/basic_table/table_types.ts
@@ -33,7 +33,7 @@ export interface EuiTableFooterProps {
}
export interface EuiTableFieldDataColumnType
extends CommonProps,
- TdHTMLAttributes {
+ Omit, 'width' | 'align'> {
/**
* A field of the item (may be a nested field)
*/
@@ -60,16 +60,22 @@ export interface EuiTableFieldDataColumnType
* Defines whether the user can sort on this column. If a function is provided, this function returns the value to sort against
*/
sortable?: boolean | ((item: T) => Primitive);
- isExpander?: boolean;
/**
- * Creates a text wrapper around cell content that helps word break or truncate
- * long text correctly.
+ * Disables the user's ability to change the sort, but will still
+ * show the current sort direction in the column header
*/
- textOnly?: boolean;
+ readOnly?: boolean;
/**
* Defines the horizontal alignment of the column
+ * @default left
*/
align?: HorizontalAlignment;
+ /**
+ * Creates a text wrapper around cell content that helps word break or truncate
+ * long text correctly.
+ * @default true
+ */
+ textOnly?: boolean;
/**
* Indicates whether this column should truncate overflowing text content.
* - Set to `true` to enable single-line truncation.
@@ -77,6 +83,10 @@ export interface EuiTableFieldDataColumnType
* set to a number of lines to truncate to.
*/
truncateText?: EuiTableRowCellProps['truncateText'];
+ /**
+ * Allows configuring custom render options or appearances for column cells
+ * when the table responsively collapses into a mobile-friendly view
+ */
mobileOptions?: Omit & {
render?: (item: T) => ReactNode;
};
@@ -92,47 +102,37 @@ export interface EuiTableFieldDataColumnType
| ReactElement
| ((props: EuiTableFooterProps) => ReactNode);
/**
- * Disables the user's ability to change the sort but still shows the current direction
- */
- readOnly?: boolean;
-}
-
-export interface EuiTableComputedColumnType
- extends CommonProps,
- TdHTMLAttributes {
- /**
- * A function that computes the value for each item and renders it
- */
- render: (record: T) => ReactNode;
- /**
- * The display name of the column
- */
- name?: ReactNode;
- /**
- * A description of the column (will be presented as a title over the column header
- */
- description?: string;
- /**
- * If provided, allows this column to be sorted on. Must return the value to sort against.
- */
- sortable?: (item: T) => Primitive;
- /**
- * A CSS width property. Hints for the required width of the column
- */
- width?: string;
- /**
- * Indicates whether this column should truncate its content when it doesn't fit
+ * If passing `itemIdToExpandedRowMap` to your table, set this flag to `true`
+ * for the custom column or cell used to toggle the expanded row.
*/
- truncateText?: boolean;
isExpander?: boolean;
- align?: HorizontalAlignment;
- /**
- * Disables the user's ability to change the sort but still shows the current direction
- */
- readOnly?: boolean;
}
-export interface EuiTableActionsColumnType {
+export type EuiTableComputedColumnType = CommonProps &
+ Omit, 'width' | 'align'> & {
+ /**
+ * A function that computes the value for each item and renders it
+ */
+ render: (record: T) => ReactNode;
+ /**
+ * The display name of the column
+ */
+ name?: ReactNode;
+ /**
+ * If provided, allows this column to be sorted on. Must return the value to sort against.
+ */
+ sortable?: (item: T) => Primitive;
+ } & Pick<
+ EuiTableFieldDataColumnType,
+ | 'readOnly'
+ | 'description'
+ | 'width'
+ | 'align'
+ | 'truncateText'
+ | 'isExpander'
+ >;
+
+export type EuiTableActionsColumnType = {
/**
* An array of one of the objects: #DefaultItemAction or #CustomItemAction
*/
@@ -141,15 +141,7 @@ export interface EuiTableActionsColumnType {
* The display name of the column
*/
name?: ReactNode;
- /**
- * A description of the column (will be presented as a title over the column header
- */
- description?: string;
- /**
- * A CSS width property. Hints for the required width of the column
- */
- width?: string;
-}
+} & Pick, 'description' | 'width'>;
export interface EuiTableSortingType {
/**
diff --git a/src/components/index.scss b/src/components/index.scss
index dcfd6a3c324..79063613970 100644
--- a/src/components/index.scss
+++ b/src/components/index.scss
@@ -7,4 +7,3 @@
@import 'form/index';
@import 'markdown_editor/index';
@import 'selectable/index';
-@import 'table/index';
diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx
index 8a422924d2b..0dc2ee6165c 100644
--- a/src/components/provider/component_defaults/component_defaults.tsx
+++ b/src/components/provider/component_defaults/component_defaults.tsx
@@ -16,7 +16,7 @@ import React, {
import type { EuiPortalProps } from '../../portal';
import type { EuiFocusTrapProps } from '../../focus_trap';
-import type { EuiTablePaginationProps } from '../../table';
+import type { EuiTablePaginationProps, EuiTableProps } from '../../table';
export type EuiComponentDefaults = {
/**
@@ -37,6 +37,12 @@ export type EuiComponentDefaults = {
EuiTablePaginationProps,
'itemsPerPage' | 'itemsPerPageOptions' | 'showPerPageOptions'
>;
+ /**
+ * Provide a global configuration for EuiTable's `responsiveBreakpoint` prop. Defaults to `'s'`.
+ *
+ * Defaults will be inherited by all `EuiBasicTable`s and `EuiInMemoryTable`s.
+ */
+ EuiTable?: Pick;
};
// Declaring as a static const for reference integrity/reducing rerenders
diff --git a/src/components/table/__snapshots__/table.test.tsx.snap b/src/components/table/__snapshots__/table.test.tsx.snap
index f95aeb94579..afca98fa5d7 100644
--- a/src/components/table/__snapshots__/table.test.tsx.snap
+++ b/src/components/table/__snapshots__/table.test.tsx.snap
@@ -1,57 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders EuiTable 1`] = `
+exports[`EuiTable renders 1`] = `
-
Hi Title
-
+
-
Bye Title
-
+
-
-
-`;
diff --git a/src/components/table/__snapshots__/table_header_cell.test.tsx.snap b/src/components/table/__snapshots__/table_header_cell.test.tsx.snap
index e60c0af7ec8..8c4f03559ae 100644
--- a/src/components/table/__snapshots__/table_header_cell.test.tsx.snap
+++ b/src/components/table/__snapshots__/table_header_cell.test.tsx.snap
@@ -1,134 +1,105 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`align defaults to left 1`] = `
+exports[`EuiTableHeaderCell align defaults to left 1`] = `
-
-
+
`;
-exports[`align renders center when specified 1`] = `
+exports[`EuiTableHeaderCell align renders center when specified 1`] = `
-
-
+
`;
-exports[`align renders right when specified 1`] = `
+exports[`EuiTableHeaderCell align renders right when specified 1`] = `
-
-
+
`;
-exports[`renders EuiTableHeaderCell 1`] = `
-
-
-
-
-
-
- children
-
-
-
-
-
-
-`;
-
-exports[`renders td when children is null/undefined 1`] = `
-
-
-
-
-
-
-
-
-
-
-
+exports[`EuiTableHeaderCell renders 1`] = `
+
+
+
+ children
+
+
+
`;
-exports[`sorting does not render a button with readOnly 1`] = `
+exports[`EuiTableHeaderCell sorting does not render a button with readOnly 1`] = `
-
Test
@@ -137,29 +108,29 @@ exports[`sorting does not render a button with readOnly 1`] = `
class="euiTableSortIcon"
data-euiicon-type="sortDown"
/>
-
+
`;
-exports[`sorting is rendered with isSortAscending 1`] = `
+exports[`EuiTableHeaderCell sorting is rendered with isSortAscending 1`] = `
-
Test
@@ -168,29 +139,29 @@ exports[`sorting is rendered with isSortAscending 1`] = `
class="euiTableSortIcon"
data-euiicon-type="sortUp"
/>
-
+
`;
-exports[`sorting is rendered with isSorted 1`] = `
+exports[`EuiTableHeaderCell sorting is rendered with isSorted 1`] = `
-
Test
@@ -199,34 +170,34 @@ exports[`sorting is rendered with isSorted 1`] = `
class="euiTableSortIcon"
data-euiicon-type="sortDown"
/>
-
+
`;
-exports[`sorting renders a button with onSort 1`] = `
+exports[`EuiTableHeaderCell sorting renders a button with onSort 1`] = `
-
Test
@@ -235,7 +206,7 @@ exports[`sorting renders a button with onSort 1`] = `
class="euiTableSortIcon"
data-euiicon-type="sortDown"
/>
-
+
@@ -243,104 +214,104 @@ exports[`sorting renders a button with onSort 1`] = `
`;
-exports[`width and style accepts style attribute 1`] = `
+exports[`EuiTableHeaderCell width and style accepts style attribute 1`] = `
-
Test
-
+
`;
-exports[`width and style accepts width attribute 1`] = `
+exports[`EuiTableHeaderCell width and style accepts width attribute 1`] = `
-
Test
-
+
`;
-exports[`width and style accepts width attribute as number 1`] = `
+exports[`EuiTableHeaderCell width and style accepts width attribute as number 1`] = `
-
Test
-
+
`;
-exports[`width and style resolves style and width attribute 1`] = `
+exports[`EuiTableHeaderCell width and style resolves style and width attribute 1`] = `
`;
-exports[`valign defaults to middle 1`] = `
+exports[`EuiTableRowCell valign defaults to middle 1`] = `
`;
-exports[`valign renders bottom when specified 1`] = `
+exports[`EuiTableRowCell valign renders bottom when specified 1`] = `
`;
-exports[`valign renders top when specified 1`] = `
+exports[`EuiTableRowCell valign renders top when specified 1`] = `
`;
-exports[`width and style accepts style attribute 1`] = `
+exports[`EuiTableRowCell width and style accepts style attribute 1`] = `
`;
-exports[`width and style accepts width attribute 1`] = `
+exports[`EuiTableRowCell width and style accepts width attribute 1`] = `
`;
-exports[`width and style accepts width attribute as number 1`] = `
+exports[`EuiTableRowCell width and style accepts width attribute as number 1`] = `
`;
-exports[`width and style resolves style and width attribute 1`] = `
+exports[`EuiTableRowCell width and style resolves style and width attribute 1`] = `