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/src-docs/src/views/tables/actions/actions.tsx b/src-docs/src/views/tables/actions/actions.tsx index 6021ffbf523..34da8a8174b 100644 --- a/src-docs/src/views/tables/actions/actions.tsx +++ b/src-docs/src/views/tables/actions/actions.tsx @@ -74,9 +74,9 @@ const columns: Array> = [ sortable: true, mobileOptions: { render: (user: User) => ( - + <> {user.firstName} {user.lastName} - + ), header: false, truncateText: false, @@ -202,7 +202,7 @@ export default () => { if (multiAction) { actions = [ { - name: Clone, + name: <>Clone, description: 'Clone this user', icon: 'copy', type: 'icon', 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 b9c33df3588..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} - + ), }, { 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.

-
+ ), components: { EuiTable }, props: { diff --git a/src-docs/src/views/tables/expanding_rows/expanding_rows.tsx b/src-docs/src/views/tables/expanding_rows/expanding_rows.tsx index faa1f019178..e2837373ebb 100644 --- a/src-docs/src/views/tables/expanding_rows/expanding_rows.tsx +++ b/src-docs/src/views/tables/expanding_rows/expanding_rows.tsx @@ -52,9 +52,9 @@ const columns: Array> = [ truncateText: true, mobileOptions: { render: (user: User) => ( - + <> {user.firstName} {user.lastName} - + ), header: false, truncateText: false, @@ -134,9 +134,10 @@ export default () => { isExpander: true, name: ( - Expand rows + Expand row ), + mobileOptions: { header: false }, render: (user: User) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; diff --git a/src-docs/src/views/tables/footer/footer.tsx b/src-docs/src/views/tables/footer/footer.tsx index 8e34806d9b3..fab97bb010e 100644 --- a/src-docs/src/views/tables/footer/footer.tsx +++ b/src-docs/src/views/tables/footer/footer.tsx @@ -51,9 +51,9 @@ const columns: Array> = [ truncateText: true, mobileOptions: { render: (user: User) => ( - + <> {user.firstName} {user.lastName} - + ), header: false, truncateText: false, @@ -72,7 +72,7 @@ const columns: Array> = [ { field: 'github', name: 'Github', - footer: ({ items }: { items: User[] }) => {items.length} users, + footer: ({ items }: { items: User[] }) => <>{items.length} users, render: (username: User['github']) => ( {username} @@ -96,7 +96,7 @@ const columns: Array> = [ const uniqueCountries = new Set( items.map((user) => user.location.country) ); - return {uniqueCountries.size} countries; + return <>{uniqueCountries.size} countries; }, render: (location: User['location']) => { return `${location.city}, ${location.country}`; @@ -106,9 +106,7 @@ const columns: Array> = [ field: 'online', name: 'Online', footer: ({ items }: { items: User[] }) => { - return ( - {items.filter((user: User) => !!user.online).length} online - ); + return <>{items.filter((user: User) => !!user.online).length} online; }, dataType: 'boolean', render: (online: User['online']) => { diff --git a/src-docs/src/views/tables/in_memory/in_memory.tsx b/src-docs/src/views/tables/in_memory/in_memory.tsx index 8bfe861bf6a..c1b7a6ae175 100644 --- a/src-docs/src/views/tables/in_memory/in_memory.tsx +++ b/src-docs/src/views/tables/in_memory/in_memory.tsx @@ -47,9 +47,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/in_memory/in_memory_controlled_pagination.tsx b/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination.tsx index 0f6a4004073..6d99d7b47c8 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination.tsx +++ b/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination.tsx @@ -49,9 +49,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/in_memory/in_memory_controlled_pagination_section.js b/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination_section.js index e7da5170060..9a3a5c445b0 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_controlled_pagination_section.js @@ -12,13 +12,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, @@ -37,14 +37,14 @@ export const controlledPaginationSection = { }, ], text: ( -
+ <>

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.

-
+ ), props: { EuiInMemoryTable, diff --git a/src-docs/src/views/tables/in_memory/in_memory_custom_sorting_section.js b/src-docs/src/views/tables/in_memory/in_memory_custom_sorting_section.js index 50505712592..3cfc81e7d77 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_custom_sorting_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_custom_sorting_section.js @@ -12,13 +12,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, @@ -37,16 +37,14 @@ export const customSortingSection = { }, ], text: ( -
-

- 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. +

), props: { EuiInMemoryTable, diff --git a/src-docs/src/views/tables/in_memory/in_memory_search.tsx b/src-docs/src/views/tables/in_memory/in_memory_search.tsx index f92b3558736..1be5219eed6 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search.tsx +++ b/src-docs/src/views/tables/in_memory/in_memory_search.tsx @@ -54,9 +54,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/in_memory/in_memory_search_callback.tsx b/src-docs/src/views/tables/in_memory/in_memory_search_callback.tsx index a0e70f63780..daad7e56417 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search_callback.tsx +++ b/src-docs/src/views/tables/in_memory/in_memory_search_callback.tsx @@ -48,9 +48,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/in_memory/in_memory_search_callback_section.js b/src-docs/src/views/tables/in_memory/in_memory_search_callback_section.js index ad702b45f06..520ee05e7bd 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search_callback_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_search_callback_section.js @@ -11,13 +11,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, diff --git a/src-docs/src/views/tables/in_memory/in_memory_search_external.tsx b/src-docs/src/views/tables/in_memory/in_memory_search_external.tsx index 6cdc9d690ae..a200174eaa0 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search_external.tsx +++ b/src-docs/src/views/tables/in_memory/in_memory_search_external.tsx @@ -77,9 +77,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/in_memory/in_memory_search_external_section.js b/src-docs/src/views/tables/in_memory/in_memory_search_external_section.js index 553bab0a679..d6b83ba21d0 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search_external_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_search_external_section.js @@ -11,13 +11,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, @@ -36,12 +36,10 @@ export const searchExternalSection = { }, ], text: ( -
-

- The example shows how to configure EuiInMemoryTable{' '} - when both external and internal search/filter states are in use. -

-
+

+ The example shows how to configure EuiInMemoryTable when + both external and internal search/filter states are in use. +

), props: { EuiInMemoryTable, diff --git a/src-docs/src/views/tables/in_memory/in_memory_search_section.js b/src-docs/src/views/tables/in_memory/in_memory_search_section.js index 7a74191d70c..0380ed6ad9a 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_search_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_search_section.js @@ -12,13 +12,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, @@ -37,17 +37,15 @@ export const searchSection = { }, ], text: ( -
-

- 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 + {' '} + . +

), props: { EuiInMemoryTable, diff --git a/src-docs/src/views/tables/in_memory/in_memory_section.js b/src-docs/src/views/tables/in_memory/in_memory_section.js index 2fc8a6d732a..5948ba2840d 100644 --- a/src-docs/src/views/tables/in_memory/in_memory_section.js +++ b/src-docs/src/views/tables/in_memory/in_memory_section.js @@ -12,13 +12,13 @@ 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 { + EuiTableComputedColumnType, + EuiTableActionsColumnType, DefaultItemActionProps as DefaultItemAction, SearchProps as Search, SearchFilterConfigProps as SearchFilterConfig, @@ -36,7 +36,7 @@ export const section = { }, ], text: ( -
+ <>

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 171b004b1e1..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, 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 df4c723c6fe..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, diff --git a/src-docs/src/views/tables/mobile/mobile.tsx b/src-docs/src/views/tables/mobile/mobile.tsx index ec9f757bf0d..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'); diff --git a/src-docs/src/views/tables/mobile/mobile_section.js b/src-docs/src/views/tables/mobile/mobile_section.js index 0e475070d88..1f9862ab9b6 100644 --- a/src-docs/src/views/tables/mobile/mobile_section.js +++ b/src-docs/src/views/tables/mobile/mobile_section.js @@ -2,22 +2,23 @@ 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 { 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 } }`; @@ -48,22 +49,21 @@ export const section = { columns), you may set{' '} {'responsiveBreakpoint={false}'}. Inversely, if you always want your table to render in a mobile-friendly - manner, pass true. -

-

- The mobileOptions object can be passed to the{' '} - EuiTableRowCell directly or with each column item - provided to EuiBasicTable. + 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.

), props: { EuiTableRowCellMobileOptionsShape }, + snippet: exampleColumnSnippet, demo: , }; diff --git a/src-docs/src/views/tables/paginated/paginated.tsx b/src-docs/src/views/tables/paginated/paginated.tsx index 75cc28fc5d7..88bf5e45280 100644 --- a/src-docs/src/views/tables/paginated/paginated.tsx +++ b/src-docs/src/views/tables/paginated/paginated.tsx @@ -52,9 +52,9 @@ const columns: Array> = [ truncateText: true, mobileOptions: { render: (user: User) => ( - + <> {user.firstName} {user.lastName} - + ), header: false, truncateText: false, @@ -169,10 +169,10 @@ export default () => { + <> Hide per page options with{' '} pagination.showPerPageOptions = false - + } onChange={togglePerPageOptions} /> diff --git a/src-docs/src/views/tables/props/props.tsx b/src-docs/src/views/tables/props/props.tsx index 05a4af848db..8f9c8752b6e 100644 --- a/src-docs/src/views/tables/props/props.tsx +++ b/src-docs/src/views/tables/props/props.tsx @@ -1,4 +1,8 @@ import React, { FunctionComponent } from 'react'; +import { + EuiTableComputedColumnType as _EuiTableComputedColumnType, + EuiTableActionsColumnType as _EuiTableActionsColumnType, +} from '../../../../../src/components/basic_table/table_types'; import { DefaultItemAction } from '../../../../../src/components/basic_table/action_types'; import { Search } from '../../../../../src/components/basic_table/in_memory_table'; import { SearchFilterConfig } from '../../../../../src/components/search_bar/filters'; @@ -20,3 +24,11 @@ export const SearchFilterConfigProps: FunctionComponent< export const EuiTableRowCellMobileOptionsShape: FunctionComponent< _EuiTableRowCellMobileOptionsShape > = () =>
; + +export const EuiTableComputedColumnType: FunctionComponent< + _EuiTableComputedColumnType +> = () =>
; + +export const EuiTableActionsColumnType: FunctionComponent< + _EuiTableActionsColumnType +> = () =>
; diff --git a/src-docs/src/views/tables/selection/selection_controlled.tsx b/src-docs/src/views/tables/selection/selection_controlled.tsx index 6d68541f596..115541f6a9b 100644 --- a/src-docs/src/views/tables/selection/selection_controlled.tsx +++ b/src-docs/src/views/tables/selection/selection_controlled.tsx @@ -60,9 +60,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/selection/selection_uncontrolled.tsx b/src-docs/src/views/tables/selection/selection_uncontrolled.tsx index c954f958758..5c544fc5aba 100644 --- a/src-docs/src/views/tables/selection/selection_uncontrolled.tsx +++ b/src-docs/src/views/tables/selection/selection_uncontrolled.tsx @@ -58,9 +58,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/sorting/sorting.tsx b/src-docs/src/views/tables/sorting/sorting.tsx index bd0d1b73f3f..ac67d9a9ecf 100644 --- a/src-docs/src/views/tables/sorting/sorting.tsx +++ b/src-docs/src/views/tables/sorting/sorting.tsx @@ -56,9 +56,9 @@ const columns: Array> = [ truncateText: true, mobileOptions: { render: (user: User) => ( - + <> {user.firstName} {user.lastName} - + ), header: false, truncateText: false, @@ -78,7 +78,7 @@ const columns: Array> = [ field: 'github', name: ( - + <> Github{' '} > = [ type="questionInCircle" className="eui-alignTop" /> - + ), render: (username: User['github']) => ( @@ -99,7 +99,7 @@ const columns: Array> = [ field: 'dateOfBirth', name: ( - + <> Date of Birth{' '} > = [ type="questionInCircle" className="eui-alignTop" /> - + ), render: (dateOfBirth: User['dateOfBirth']) => @@ -117,7 +117,7 @@ const columns: Array> = [ field: 'location', name: ( - + <> Nationality{' '} > = [ type="questionInCircle" className="eui-alignTop" /> - + ), render: (location: User['location']) => { @@ -138,7 +138,7 @@ const columns: Array> = [ field: 'online', name: ( - + <> Online{' '} > = [ type="questionInCircle" className="eui-alignTop" /> - + ), render: (online: User['online']) => { diff --git a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap index 32cae740486..4ed70fc8a71 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -73,11 +73,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = `
`; -exports[`sorting does not render a button with readOnly 1`] = ` +exports[`EuiTableHeaderCell sorting does not render a button with readOnly 1`] = `
-
- Name -
@@ -95,11 +90,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = `
-
- Name -
@@ -117,11 +107,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = `
-
- Name -
@@ -303,11 +288,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Name -
@@ -317,11 +297,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- ID -
@@ -335,11 +310,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Age -
@@ -424,11 +394,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Name -
@@ -438,11 +403,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- ID -
@@ -456,11 +416,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Age -
@@ -545,11 +500,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Name -
@@ -559,11 +509,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- ID -
@@ -577,11 +522,6 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- Age -
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`] = `
-
- Name -
@@ -311,11 +306,6 @@ exports[`EuiInMemoryTable with items 1`] = `
-
- Name -
@@ -333,11 +323,6 @@ exports[`EuiInMemoryTable with items 1`] = `
-
- Name -
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`] = ` - - - - - - -
-
- - children - -
-
-`; - -exports[`renders td when children is null/undefined 1`] = ` - - - - - - -
-
- -
-
+exports[`EuiTableHeaderCell renders 1`] = ` +
+
+ + children + +
+
@@ -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 header +
+
+ + 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} - )} - - ); + + ); + } } };