@@ -95,11 +90,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = `
-
@@ -117,11 +107,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = `
-
@@ -303,11 +288,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -317,11 +297,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -335,11 +310,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -424,11 +394,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -438,11 +403,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -456,11 +416,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -545,11 +500,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -559,11 +509,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
@@ -577,11 +522,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
diff --git a/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap b/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap
index 00b2d98b963..918fd7179f7 100644
--- a/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap
+++ b/src/components/basic_table/__snapshots__/in_memory_table.test.tsx.snap
@@ -289,11 +289,6 @@ exports[`EuiInMemoryTable with items 1`] = `
-
@@ -311,11 +306,6 @@ exports[`EuiInMemoryTable with items 1`] = `
-
@@ -333,11 +323,6 @@ exports[`EuiInMemoryTable with items 1`] = `
-
diff --git a/src/components/basic_table/basic_table.tsx b/src/components/basic_table/basic_table.tsx
index b486d9409db..5199cfa33ca 100644
--- a/src/components/basic_table/basic_table.tsx
+++ b/src/components/basic_table/basic_table.tsx
@@ -272,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<
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 e4f8d89dc91..5aedbd0eddc 100644
--- a/src/components/basic_table/in_memory_table.test.tsx
+++ b/src/components/basic_table/in_memory_table.test.tsx
@@ -1325,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'));
@@ -1341,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(
@@ -1356,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', () => {
@@ -1381,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);
@@ -1415,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');
});
});
diff --git a/src/components/basic_table/table_types.ts b/src/components/basic_table/table_types.ts
index 657687ab9e6..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,50 +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
+ * If passing `itemIdToExpandedRowMap` to your table, set this flag to `true`
+ * for the custom column or cell used to toggle the expanded row.
*/
- 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 overflowing text content.
- * - Set to `true` to enable single-line truncation.
- * - To enable multi-line truncation, use a configuration object with `lines`
- * set to a number of lines to truncate to.
- */
- truncateText?: EuiTableRowCellProps['truncateText'];
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
*/
@@ -144,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/table/__snapshots__/table_header_cell.test.tsx.snap b/src/components/table/__snapshots__/table_header_cell.test.tsx.snap
index 71870701547..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,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`align defaults to left 1`] = `
+exports[`EuiTableHeaderCell align defaults to left 1`] = `
@@ -21,7 +21,7 @@ exports[`align defaults to left 1`] = `
`;
-exports[`align renders center when specified 1`] = `
+exports[`EuiTableHeaderCell align renders center when specified 1`] = `
@@ -42,7 +42,7 @@ exports[`align renders center when specified 1`] = `
`;
-exports[`align renders right when specified 1`] = `
+exports[`EuiTableHeaderCell align renders right when specified 1`] = `
@@ -63,57 +63,28 @@ exports[`align renders right when specified 1`] = `
`;
-exports[`renders EuiTableHeaderCell 1`] = `
-
-`;
-
-exports[`renders td when children is null/undefined 1`] = `
-
+exports[`EuiTableHeaderCell renders 1`] = `
+
`;
-exports[`sorting does not render a button with readOnly 1`] = `
+exports[`EuiTableHeaderCell sorting does not render a button with readOnly 1`] = `
@@ -144,7 +115,7 @@ exports[`sorting does not render a button with readOnly 1`] = `
`;
-exports[`sorting is rendered with isSortAscending 1`] = `
+exports[`EuiTableHeaderCell sorting is rendered with isSortAscending 1`] = `
@@ -175,7 +146,7 @@ exports[`sorting is rendered with isSortAscending 1`] = `
`;
-exports[`sorting is rendered with isSorted 1`] = `
+exports[`EuiTableHeaderCell sorting is rendered with isSorted 1`] = `
@@ -206,7 +177,7 @@ exports[`sorting is rendered with isSorted 1`] = `
`;
-exports[`sorting renders a button with onSort 1`] = `
+exports[`EuiTableHeaderCell sorting renders a button with onSort 1`] = `
@@ -243,7 +214,7 @@ 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`] = `
@@ -269,7 +240,7 @@ exports[`width and style accepts style attribute 1`] = `
`;
-exports[`width and style accepts width attribute 1`] = `
+exports[`EuiTableHeaderCell width and style accepts width attribute 1`] = `
@@ -295,7 +266,7 @@ exports[`width and style accepts width attribute 1`] = `
`;
-exports[`width and style accepts width attribute as number 1`] = `
+exports[`EuiTableHeaderCell width and style accepts width attribute as number 1`] = `
@@ -321,7 +292,7 @@ exports[`width and style accepts width attribute as number 1`] = `
`;
-exports[`width and style resolves style and width attribute 1`] = `
+exports[`EuiTableHeaderCell width and style resolves style and width attribute 1`] = `
diff --git a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap
index 6979da75ba3..edc53bd464b 100644
--- a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap
+++ b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`align defaults to left 1`] = `
+exports[`EuiTableRowCell align defaults to left 1`] = `
@@ -20,7 +20,7 @@ exports[`align defaults to left 1`] = `
`;
-exports[`align renders center when specified 1`] = `
+exports[`EuiTableRowCell align renders center when specified 1`] = `
@@ -40,7 +40,7 @@ exports[`align renders center when specified 1`] = `
`;
-exports[`align renders right when specified 1`] = `
+exports[`EuiTableRowCell align renders right when specified 1`] = `
@@ -60,7 +60,7 @@ exports[`align renders right when specified 1`] = `
`;
-exports[`children's className merges new classnames into existing ones 1`] = `
+exports[`EuiTableRowCell children's className merges new classnames into existing ones 1`] = `
@@ -80,31 +80,46 @@ exports[`children's className merges new classnames into existing ones 1`] = `
`;
-exports[`renders EuiTableRowCell 1`] = `
-
-
-
-
-
-
- children
-
-
- |
-
-
-
+exports[`EuiTableRowCell renders 1`] = `
+
+
+
+ children
+
+
+ |
+`;
+
+exports[`EuiTableRowCell renders mobile views 1`] = `
+
+
+
+
+ mobile render
+
+
+ |
`;
-exports[`textOnly defaults to true 1`] = `
+exports[`EuiTableRowCell textOnly defaults to true 1`] = `
@@ -124,7 +139,7 @@ exports[`textOnly defaults to true 1`] = `
`;
-exports[`textOnly is rendered when specified 1`] = `
+exports[`EuiTableRowCell textOnly is rendered when specified 1`] = `
@@ -140,7 +155,7 @@ exports[`textOnly is rendered when specified 1`] = `
`;
-exports[`truncateText defaults to false 1`] = `
+exports[`EuiTableRowCell truncateText defaults to false 1`] = `
@@ -160,7 +175,7 @@ exports[`truncateText defaults to false 1`] = `
`;
-exports[`truncateText renders lines configuration 1`] = `
+exports[`EuiTableRowCell truncateText renders lines configuration 1`] = `
@@ -180,7 +195,7 @@ exports[`truncateText renders lines configuration 1`] = `
`;
-exports[`truncateText renders true 1`] = `
+exports[`EuiTableRowCell truncateText renders true 1`] = `
@@ -200,7 +215,7 @@ exports[`truncateText renders true 1`] = `
`;
-exports[`valign defaults to middle 1`] = `
+exports[`EuiTableRowCell valign defaults to middle 1`] = `
@@ -220,7 +235,7 @@ exports[`valign defaults to middle 1`] = `
`;
-exports[`valign renders bottom when specified 1`] = `
+exports[`EuiTableRowCell valign renders bottom when specified 1`] = `
@@ -240,7 +255,7 @@ exports[`valign renders bottom when specified 1`] = `
`;
-exports[`valign renders top when specified 1`] = `
+exports[`EuiTableRowCell valign renders top when specified 1`] = `
@@ -260,7 +275,7 @@ exports[`valign renders top when specified 1`] = `
`;
-exports[`width and style accepts style attribute 1`] = `
+exports[`EuiTableRowCell width and style accepts style attribute 1`] = `
@@ -283,7 +298,7 @@ exports[`width and style accepts style attribute 1`] = `
`;
-exports[`width and style accepts width attribute 1`] = `
+exports[`EuiTableRowCell width and style accepts width attribute 1`] = `
@@ -306,7 +321,7 @@ exports[`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`] = `
@@ -329,7 +344,7 @@ exports[`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`] = `
diff --git a/src/components/table/_table_cell_content.tsx b/src/components/table/_table_cell_content.tsx
index 2abc04b5f48..228736e0ad1 100644
--- a/src/components/table/_table_cell_content.tsx
+++ b/src/components/table/_table_cell_content.tsx
@@ -40,7 +40,7 @@ export const EuiTableCellContent: FunctionComponent<
const styles = useEuiMemoizedStyles(euiTableCellContentStyles);
const cssStyles = [
styles.euiTableCellContent,
- !isResponsive && styles[align], // On mobile, always align cells to the left
+ styles[align],
truncateText === true && styles.truncateText,
truncateText === false && styles.wrapText,
...(hasActions
diff --git a/src/components/table/table.stories.tsx b/src/components/table/table.stories.tsx
new file mode 100644
index 00000000000..debd090af61
--- /dev/null
+++ b/src/components/table/table.stories.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 {
+ EuiTableBody,
+ EuiTableFooter,
+ EuiTableFooterCell,
+ EuiTableHeader,
+ EuiTableHeaderCell,
+ EuiTableRow,
+ EuiTableRowCell,
+} from './index';
+
+import { EuiTable, EuiTableProps } from './table';
+
+const meta: Meta = {
+ title: 'Tabular Content/EuiTable/EuiTable',
+ component: EuiTable,
+ args: {
+ responsiveBreakpoint: 'm',
+ tableLayout: 'fixed',
+ compressed: false, // TODO: Where is this prop even used, and why isn't this documented?
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ argTypes: {
+ responsiveBreakpoint: {
+ control: 'select',
+ options: ['xs', 's', 'm', 'l', 'xl', true, false],
+ },
+ },
+ render: ({ ...args }) => (
+
+
+ Column 1
+ Column 2
+
+ Long text that will truncate, because header cells default to that
+
+
+
+ {Array.from({ length: 20 }, (_, i) => (
+
+ {Array.from({ length: 3 }, (_, j) => (
+
+ {j === 2 ? (
+ 'Long text that will change width when table layout is set to auto'
+ ) : (
+ <>
+ Row {i + 1}, Cell {j + 1}
+ >
+ )}
+
+ ))}
+
+ ))}
+
+
+ Total: 20
+ Total: 20
+
+ Long text that will truncate, because footer cells default to that
+
+
+
+ ),
+};
diff --git a/src/components/table/table.styles.ts b/src/components/table/table.styles.ts
index f4a34bc2502..7a54b1cda2b 100644
--- a/src/components/table/table.styles.ts
+++ b/src/components/table/table.styles.ts
@@ -91,19 +91,10 @@ export const euiTableStyles = (euiThemeContext: UseEuiTheme) => {
`,
/**
* Responsive/mobile vs desktop styles
+ * Individual row/cells handle their own desktop vs mobile styles
*/
- desktop: css`
- .euiTableHeaderCell--hideForDesktop,
- .euiTableRowCell--hideForDesktop,
- .euiTableRowCell__mobileHeader {
- display: none;
- }
- `,
+ desktop: css``,
mobile: css`
- .euiTableRowCell--hideForMobile {
- display: none;
- }
-
thead {
display: none; /* Use mobile versions of selecting and filtering instead */
}
diff --git a/src/components/table/table_cells_shared.styles.ts b/src/components/table/table_cells_shared.styles.ts
index e8c0bd14071..6c30695d682 100644
--- a/src/components/table/table_cells_shared.styles.ts
+++ b/src/components/table/table_cells_shared.styles.ts
@@ -9,7 +9,11 @@
import { css } from '@emotion/react';
import { UseEuiTheme } from '../../services';
-import { euiFontSize, logicalCSS } from '../../global_styling';
+import {
+ euiFontSize,
+ logicalCSS,
+ logicalTextAlignCSS,
+} from '../../global_styling';
import { euiTableVariables } from './table.styles';
@@ -64,6 +68,10 @@ export const euiTableCellCheckboxStyles = (euiThemeContext: UseEuiTheme) => {
return {
euiTableHeaderCellCheckbox: css`
${sharedCheckboxStyles}
+
+ /* Without this the visually hidden checkbox doesn't line up properly with the custom render 🙃
+ TODO: Could be removed if we use inset: 0 on checkboxes once they're converted to Emotion */
+ ${logicalTextAlignCSS('left')}
`,
euiTableRowCellCheckbox: css`
${sharedCheckboxStyles}
diff --git a/src/components/table/table_header_cell.test.tsx b/src/components/table/table_header_cell.test.tsx
index 78c960b07dc..c2ba36e5333 100644
--- a/src/components/table/table_header_cell.test.tsx
+++ b/src/components/table/table_header_cell.test.tsx
@@ -10,10 +10,11 @@ import React from 'react';
import { requiredProps } from '../../test/required_props';
import { render } from '../../test/rtl';
-import { EuiTableHeaderCell } from './table_header_cell';
-
import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services';
import { WARNING_MESSAGE } from './utils';
+import { EuiTableIsResponsiveContext } from './mobile/responsive_context';
+
+import { EuiTableHeaderCell } from './table_header_cell';
const renderInTableHeader = (cell: React.ReactElement) =>
render(
@@ -24,129 +25,156 @@ const renderInTableHeader = (cell: React.ReactElement) =>
);
-test('renders EuiTableHeaderCell', () => {
- const { container } = renderInTableHeader(
- children
- );
-
- expect(container.firstChild).toMatchSnapshot();
-});
-
-test('renders td when children is null/undefined', () => {
- const { container } = renderInTableHeader(
-
- );
-
- expect(container.firstChild).toMatchSnapshot();
-});
-
-describe('align', () => {
- test('defaults to left', () => {
- const { container } = renderInTableHeader();
-
- expect(container.firstChild).toMatchSnapshot();
- });
-
- test('renders right when specified', () => {
- const { container } = renderInTableHeader(
-
+describe('EuiTableHeaderCell', () => {
+ it('renders', () => {
+ const { getByRole } = renderInTableHeader(
+ children
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(getByRole('columnheader')).toMatchSnapshot();
+ expect(getByRole('columnheader').nodeName).toEqual('TH');
});
- test('renders center when specified', () => {
- const { container } = renderInTableHeader(
-
+ it('renders td when children is null/undefined', () => {
+ const { getByRole } = renderInTableHeader(
+
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(getByRole('columnheader').nodeName).toEqual('TD');
});
-});
-
-describe('sorting', () => {
- test('is rendered with isSorted', () => {
- const { container } = renderInTableHeader(
- Test
- );
- expect(container.firstChild).toMatchSnapshot();
- });
-
- test('is rendered with isSortAscending', () => {
- const { container } = renderInTableHeader(
-
- Test
+ it('does not render if on desktop and mobileOptions.only is set to true', () => {
+ const { queryByRole } = renderInTableHeader(
+
+ children
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(queryByRole('columnheader')).not.toBeInTheDocument();
});
- test('renders a button with onSort', () => {
- const { container } = renderInTableHeader(
- {}}>
- Test
-
+ it('does not render if on mobile and mobileOptions.show is set to false', () => {
+ const { queryByRole } = renderInTableHeader(
+ // Context provider mocks mobile state
+
+
+ children
+
+
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(queryByRole('columnheader')).not.toBeInTheDocument();
});
- test('does not render a button with readOnly', () => {
- const { container } = renderInTableHeader(
- {}}>
- Test
-
- );
+ // TODO: These should likely be visual snapshots instead
+ describe('align', () => {
+ it('defaults to left', () => {
+ const { container } = renderInTableHeader();
- expect(container.firstChild).toMatchSnapshot();
- });
-});
+ expect(container.firstChild).toMatchSnapshot();
+ });
-describe('width and style', () => {
- const _consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = (...args: [any?, ...any[]]) => {
- // Suppress an expected warning
- if (args.length === 1 && args[0] === WARNING_MESSAGE) return;
- _consoleWarn.apply(console, args);
- };
- });
- afterAll(() => {
- console.warn = _consoleWarn;
- });
+ it('renders right when specified', () => {
+ const { container } = renderInTableHeader(
+
+ );
- test('accepts style attribute', () => {
- const { container } = renderInTableHeader(
- Test
- );
-
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
- test('accepts width attribute', () => {
- const { container } = renderInTableHeader(
- Test
- );
+ it('renders center when specified', () => {
+ const { container } = renderInTableHeader(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
- test('accepts width attribute as number', () => {
- const { container } = renderInTableHeader(
- Test
- );
-
- expect(container.firstChild).toMatchSnapshot();
+ describe('sorting', () => {
+ it('is rendered with isSorted', () => {
+ const { container } = renderInTableHeader(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('is rendered with isSortAscending', () => {
+ const { container } = renderInTableHeader(
+
+ Test
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('renders a button with onSort', () => {
+ const { container } = renderInTableHeader(
+ {}}>
+ Test
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('does not render a button with readOnly', () => {
+ const { container } = renderInTableHeader(
+ {}}>
+ Test
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
- test('resolves style and width attribute', () => {
- const { container } = renderInTableHeader(
-
- Test
-
- );
- expect(container.firstChild).toMatchSnapshot();
+ describe('width and style', () => {
+ const _consoleWarn = console.warn;
+ beforeAll(() => {
+ console.warn = (...args: [any?, ...any[]]) => {
+ // Suppress an expected warning
+ if (args.length === 1 && args[0] === WARNING_MESSAGE) return;
+ _consoleWarn.apply(console, args);
+ };
+ });
+ afterAll(() => {
+ console.warn = _consoleWarn;
+ });
+
+ it('accepts style attribute', () => {
+ const { container } = renderInTableHeader(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('accepts width attribute', () => {
+ const { container } = renderInTableHeader(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('accepts width attribute as number', () => {
+ const { container } = renderInTableHeader(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('resolves style and width attribute', () => {
+ const { container } = renderInTableHeader(
+
+ Test
+
+ );
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
});
diff --git a/src/components/table/table_header_cell.tsx b/src/components/table/table_header_cell.tsx
index 90d4fc4ab43..f426b2b5833 100644
--- a/src/components/table/table_header_cell.tsx
+++ b/src/components/table/table_header_cell.tsx
@@ -24,7 +24,9 @@ import { CommonProps, NoArgCallback } from '../common';
import { EuiIcon } from '../icon';
import { EuiInnerText } from '../inner_text';
+import type { EuiTableRowCellMobileOptionsShape } from './table_row_cell';
import { resolveWidthAsStyle } from './utils';
+import { useEuiTableIsResponsive } from './mobile/responsive_context';
import { EuiTableCellContent } from './_table_cell_content';
import { euiTableHeaderFooterCellStyles } from './table_cells_shared.styles';
@@ -35,20 +37,7 @@ export type EuiTableHeaderCellProps = CommonProps &
align?: HorizontalAlignment;
isSortAscending?: boolean;
isSorted?: boolean;
- /**
- * Mobile options for displaying differently at small screens
- */
- mobileOptions?: {
- /**
- * If false, will not render the column at all for mobile
- */
- show?: boolean;
- /**
- * Only show for mobile? If true, will not render the column at all
- * for desktop
- */
- only?: boolean;
- };
+ mobileOptions?: Pick;
onSort?: NoArgCallback;
scope?: TableHeaderCellScope;
width?: string | number;
@@ -126,9 +115,7 @@ export const EuiTableHeaderCell: FunctionComponent = ({
isSortAscending,
className,
scope,
- mobileOptions = {
- show: true,
- },
+ mobileOptions,
width,
style,
readOnly,
@@ -137,11 +124,12 @@ export const EuiTableHeaderCell: FunctionComponent = ({
}) => {
const styles = useEuiMemoizedStyles(euiTableHeaderFooterCellStyles);
- const classes = classNames('euiTableHeaderCell', className, {
- 'euiTableHeaderCell--hideForDesktop': mobileOptions.only,
- 'euiTableHeaderCell--hideForMobile': !mobileOptions.show,
- });
+ const isResponsive = useEuiTableIsResponsive();
+ const hideForDesktop = !isResponsive && mobileOptions?.only;
+ const hideForMobile = isResponsive && mobileOptions?.show === false;
+ if (hideForDesktop || hideForMobile) return null;
+ const classes = classNames('euiTableHeaderCell', className);
const inlineStyles = resolveWidthAsStyle(style, width);
const CellComponent = children ? 'th' : 'td';
diff --git a/src/components/table/table_row.stories.tsx b/src/components/table/table_row.stories.tsx
index 0310aaaa0f1..153bbb386a6 100644
--- a/src/components/table/table_row.stories.tsx
+++ b/src/components/table/table_row.stories.tsx
@@ -10,7 +10,7 @@ import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
-import { EuiButtonIcon } from '../button';
+import { EuiButtonIcon, EuiButtonEmpty } from '../button';
import { EuiCheckbox } from '../form';
import {
EuiTable,
@@ -33,6 +33,10 @@ export const Playground: Story = {
argTypes: {
// For quicker/easier testing
onClick: { control: 'boolean' },
+ hasActions: {
+ control: 'select',
+ options: [false, true, 'custom'],
+ },
},
args: {
// @ts-ignore - using a switch for easiser testing
@@ -41,6 +45,7 @@ export const Playground: Story = {
hasActions: false,
isExpandable: false,
isExpandedRow: false,
+ hasSelection: false,
isSelectable: false,
isSelected: false,
},
@@ -75,8 +80,14 @@ export const Playground: Story = {
Last name
Some other data
{hasActions && (
-
-
+
+ {hasActions === 'custom' ? (
+
+ Copy
+
+ ) : (
+
+ )}
)}
{isExpandable && (
diff --git a/src/components/table/table_row.styles.ts b/src/components/table/table_row.styles.ts
index 4cae6bdc6a8..e86e603b93f 100644
--- a/src/components/table/table_row.styles.ts
+++ b/src/components/table/table_row.styles.ts
@@ -9,7 +9,11 @@
import { css, keyframes } from '@emotion/react';
import { UseEuiTheme, tint, shade, transparentize } from '../../services';
-import { euiBackgroundColor, logicalCSS } from '../../global_styling';
+import {
+ euiCanAnimate,
+ euiBackgroundColor,
+ logicalCSS,
+} from '../../global_styling';
import { euiShadow } from '../../themes/amsterdam/global_styling/mixins';
import { euiTableVariables } from './table.styles';
@@ -146,9 +150,11 @@ const _expandedRowAnimation = ({ euiTheme }: UseEuiTheme) => {
// Animation must be on the contents div inside, not the row itself
return css`
- .euiTableCellContent {
- animation: ${euiTheme.animation.fast} ${euiTheme.animation.resistance} 1
- normal none ${expandRow};
+ ${euiCanAnimate} {
+ .euiTableCellContent {
+ animation: ${euiTheme.animation.fast} ${euiTheme.animation.resistance} 1
+ normal none ${expandRow};
+ }
}
`;
};
diff --git a/src/components/table/table_row_cell.styles.ts b/src/components/table/table_row_cell.styles.ts
index 6035d504811..0ef75bb310e 100644
--- a/src/components/table/table_row_cell.styles.ts
+++ b/src/components/table/table_row_cell.styles.ts
@@ -9,7 +9,12 @@
import { css } from '@emotion/react';
import { UseEuiTheme } from '../../services';
-import { euiFontSize, euiTextTruncate, logicalCSS } from '../../global_styling';
+import {
+ euiCanAnimate,
+ euiFontSize,
+ euiTextTruncate,
+ logicalCSS,
+} from '../../global_styling';
import { euiTableVariables } from './table.styles';
@@ -26,6 +31,10 @@ export const euiTableRowCellStyles = (euiThemeContext: UseEuiTheme) => {
euiTableRowCell: css`
color: ${euiTheme.colors.text};
`,
+ rowHeader: css`
+ /* Unset the automatic browser bolding applied to [th] elements */
+ font-weight: ${euiTheme.font.weight.regular};
+ `,
isExpander: css`
${hasIcons}
`,
@@ -54,8 +63,11 @@ export const euiTableRowCellStyles = (euiThemeContext: UseEuiTheme) => {
actions: css`
.euiBasicTableAction-showOnHover {
opacity: 0;
- transition: opacity ${euiTheme.animation.normal}
- ${euiTheme.animation.resistance};
+
+ ${euiCanAnimate} {
+ transition: opacity ${euiTheme.animation.normal}
+ ${euiTheme.animation.resistance};
+ }
}
&:focus-within,
diff --git a/src/components/table/table_row_cell.test.tsx b/src/components/table/table_row_cell.test.tsx
index 80c39cfcf33..54bf384f101 100644
--- a/src/components/table/table_row_cell.test.tsx
+++ b/src/components/table/table_row_cell.test.tsx
@@ -10,10 +10,11 @@ import React from 'react';
import { requiredProps } from '../../test/required_props';
import { render } from '../../test/rtl';
-import { EuiTableRowCell } from './table_row_cell';
-
import { CENTER_ALIGNMENT, RIGHT_ALIGNMENT } from '../../services/alignment';
import { WARNING_MESSAGE } from './utils';
+import { EuiTableIsResponsiveContext } from './mobile/responsive_context';
+
+import { EuiTableRowCell } from './table_row_cell';
const renderInTableRow = (cell: React.ReactElement) =>
render(
@@ -24,163 +25,210 @@ const renderInTableRow = (cell: React.ReactElement) =>
);
-test('renders EuiTableRowCell', () => {
- const { container } = renderInTableRow(
- children
- );
-
- expect(container.firstChild).toMatchSnapshot();
-});
+describe('EuiTableRowCell', () => {
+ it('renders', () => {
+ const { getByRole } = renderInTableRow(
+ children
+ );
-describe('align', () => {
- test('defaults to left', () => {
- const { container } = renderInTableRow();
+ expect(getByRole('cell')).toMatchSnapshot();
+ });
+
+ it('renders mobile views', () => {
+ const { getByRole } = renderInTableRow(
+ // Context provider mocks mobile state
+
+
+ children
+
+
+ );
- expect(container.firstChild).toMatchSnapshot();
+ expect(getByRole('cell')).toMatchSnapshot();
+ expect(getByRole('cell')).toHaveTextContent('mobile headermobile render');
});
- test('renders right when specified', () => {
- const { container } = renderInTableRow(
-
+ it('does not render if on desktop and mobileOptions.only is set to true', () => {
+ const { queryByRole } = renderInTableRow(
+ children
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(queryByRole('cell')).not.toBeInTheDocument();
});
- test('renders center when specified', () => {
- const { container } = renderInTableRow(
-
+ it('does not render if on mobile and mobileOptions.show is set to false', () => {
+ const { queryByRole } = renderInTableRow(
+ // Context provider mocks mobile state
+
+
+ children
+
+
);
- expect(container.firstChild).toMatchSnapshot();
+ expect(queryByRole('cell')).not.toBeInTheDocument();
});
-});
-describe('valign', () => {
- test('defaults to middle', () => {
- const { container } = renderInTableRow();
+ // TODO: These should likely be visual snapshots instead
+ describe('align', () => {
+ it('defaults to left', () => {
+ const { container } = renderInTableRow();
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
- test('renders top when specified', () => {
- const { container } = renderInTableRow();
+ it('renders right when specified', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
- test('renders bottom when specified', () => {
- const { container } = renderInTableRow();
+ it('renders center when specified', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
-});
-describe('textOnly', () => {
- test('defaults to true', () => {
- const { container } = renderInTableRow();
+ describe('valign', () => {
+ it('defaults to middle', () => {
+ const { container } = renderInTableRow();
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
- test('is rendered when specified', () => {
- const { container } = renderInTableRow(
-
- );
+ it('renders top when specified', () => {
+ const { container } = renderInTableRow();
- expect(container.firstChild).toMatchSnapshot();
- });
-});
+ expect(container.firstChild).toMatchSnapshot();
+ });
-describe('truncateText', () => {
- it('defaults to false', () => {
- const { container } = renderInTableRow();
+ it('renders bottom when specified', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
- expect(
- container.querySelector('.euiTableCellContent')!.className
- ).toContain('euiTableCellContent-wrapText');
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
- it('renders true', () => {
- const { container } = renderInTableRow(
-
- );
+ describe('textOnly', () => {
+ it('defaults to true', () => {
+ const { container } = renderInTableRow();
- expect(container.firstChild).toMatchSnapshot();
- expect(
- container.querySelector('.euiTableCellContent')!.className
- ).toContain('euiTableCellContent-truncateText');
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
- test('renders lines configuration', () => {
- const { container } = renderInTableRow(
-
- );
+ it('is rendered when specified', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
- expect(container.querySelector('.euiTableCellContent__text')).toHaveClass(
- 'euiTextBlockTruncate'
- );
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
-});
-describe("children's className", () => {
- test('merges new classnames into existing ones', () => {
- const { container } = renderInTableRow(
-
-
-
- );
+ describe('truncateText', () => {
+ it('defaults to false', () => {
+ const { container } = renderInTableRow();
- expect(container.firstChild).toMatchSnapshot();
- });
-});
+ expect(container.firstChild).toMatchSnapshot();
+ expect(
+ container.querySelector('.euiTableCellContent')!.className
+ ).toContain('euiTableCellContent-wrapText');
+ });
-describe('width and style', () => {
- const _consoleWarn = console.warn;
- beforeAll(() => {
- console.warn = (...args: [any?, ...any[]]) => {
- // Suppress an expected warning
- if (args.length === 1 && args[0] === WARNING_MESSAGE) return;
- _consoleWarn.apply(console, args);
- };
- });
- afterAll(() => {
- console.warn = _consoleWarn;
- });
-
- test('accepts style attribute', () => {
- const { container } = renderInTableRow(
- Test
- );
+ it('renders true', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ expect(
+ container.querySelector('.euiTableCellContent')!.className
+ ).toContain('euiTableCellContent-truncateText');
+ });
- test('accepts width attribute', () => {
- const { container } = renderInTableRow(
- Test
- );
+ it('renders lines configuration', () => {
+ const { container } = renderInTableRow(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
+ expect(container.firstChild).toMatchSnapshot();
+ expect(container.querySelector('.euiTableCellContent__text')).toHaveClass(
+ 'euiTextBlockTruncate'
+ );
+ });
});
- test('accepts width attribute as number', () => {
- const { container } = renderInTableRow(
- Test
- );
+ describe("children's className", () => {
+ it('merges new classnames into existing ones', () => {
+ const { container } = renderInTableRow(
+
+
+
+ );
- expect(container.firstChild).toMatchSnapshot();
- });
+ expect(container.firstChild).toMatchSnapshot();
+ });
+ });
- test('resolves style and width attribute', () => {
- const { container } = renderInTableRow(
-
- Test
-
- );
+ describe('width and style', () => {
+ const _consoleWarn = console.warn;
+ beforeAll(() => {
+ console.warn = (...args: [any?, ...any[]]) => {
+ // Suppress an expected warning
+ if (args.length === 1 && args[0] === WARNING_MESSAGE) return;
+ _consoleWarn.apply(console, args);
+ };
+ });
+ afterAll(() => {
+ console.warn = _consoleWarn;
+ });
- expect(container.firstChild).toMatchSnapshot();
+ it('accepts style attribute', () => {
+ const { container } = renderInTableRow(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('accepts width attribute', () => {
+ const { container } = renderInTableRow(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('accepts width attribute as number', () => {
+ const { container } = renderInTableRow(
+ Test
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('resolves style and width attribute', () => {
+ const { container } = renderInTableRow(
+
+ Test
+
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
});
});
diff --git a/src/components/table/table_row_cell.tsx b/src/components/table/table_row_cell.tsx
index 4dc739b4af3..7e52261ce94 100644
--- a/src/components/table/table_row_cell.tsx
+++ b/src/components/table/table_row_cell.tsx
@@ -52,10 +52,12 @@ export interface EuiTableRowCellMobileOptionsShape
extends EuiTableRowCellSharedPropsShape {
/**
* If false, will not render the cell at all for mobile
+ * @default true
*/
show?: boolean;
/**
* Only show for mobile? If true, will not render the column at all for desktop
+ * @default false
*/
only?: boolean;
/**
@@ -70,10 +72,12 @@ export interface EuiTableRowCellMobileOptionsShape
header?: ReactNode | boolean;
/**
* Increase text size compared to rest of cells
+ * @default false
*/
enlarge?: boolean;
/**
* Applies the value to the width of the cell in mobile view (typically 50%)
+ * @default 50%
*/
width?: CSSProperties['width'];
}
@@ -120,22 +124,21 @@ export const EuiTableRowCell: FunctionComponent = ({
style,
width,
valign = 'middle',
- mobileOptions = {
- show: true,
- },
+ mobileOptions,
...rest
}) => {
const isResponsive = useEuiTableIsResponsive();
const styles = useEuiMemoizedStyles(euiTableRowCellStyles);
const cssStyles = [
styles.euiTableRowCell,
+ setScopeRow && styles.rowHeader,
isExpander && styles.isExpander,
hasActions && styles.hasActions,
styles[valign],
...(isResponsive
? [
styles.mobile.mobile,
- mobileOptions.enlarge && styles.mobile.enlarge,
+ mobileOptions?.enlarge && styles.mobile.enlarge,
hasActions === 'custom' && styles.mobile.customActions,
hasActions === true && styles.mobile.actions,
isExpander && styles.mobile.expander,
@@ -146,20 +149,16 @@ export const EuiTableRowCell: FunctionComponent = ({
const cellClasses = classNames('euiTableRowCell', className, {
'euiTableRowCell--hasActions': hasActions,
'euiTableRowCell--isExpander': isExpander,
- 'euiTableRowCell--hideForDesktop': mobileOptions.only,
});
const widthValue = isResponsive
? hasActions || isExpander
? undefined // On mobile, actions are shifted to a right column via CSS
- : mobileOptions.width
+ : mobileOptions?.width
: width;
const styleObj = resolveWidthAsStyle(style, widthValue);
- const hideForMobileClasses = 'euiTableRowCell--hideForMobile';
- const showForMobileClasses = 'euiTableRowCell--hideForDesktop';
-
const Element = setScopeRow ? 'th' : 'td';
const sharedProps = {
scope: setScopeRow ? 'row' : undefined,
@@ -174,54 +173,44 @@ export const EuiTableRowCell: FunctionComponent = ({
hasActions: hasActions || isExpander,
};
- if (mobileOptions.show === false) {
- return (
-
-
- {children}
-
-
- );
- } else {
- return (
-
- {/* Mobile-only header */}
- {mobileOptions.header && (
-
- {mobileOptions.header}
-
- )}
-
- {/* Content depending on mobile render existing */}
- {mobileOptions.render ? (
- <>
-
- {mobileOptions.render}
-
-
+ {mobileOptions?.header && (
+
- {children}
-
- >
- ) : (
+ {mobileOptions.header}
+
+ )}
+
+ {mobileOptions?.render || children}
+
+
+ );
+ }
+ } else {
+ // Desktop view
+ if (mobileOptions?.only) {
+ return null;
+ } else {
+ return (
+
{children}
- )}
-
- );
+
+ );
+ }
}
};
| | | | | | | | | | | | | |