From 4af6246090a8ee3ec31be2a7c862a1bb69583325 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 16:24:17 -0700 Subject: [PATCH 01/11] Set up new `responsiveBreakpoint` prop to allow customizing table responsive behavior - instead of having it be hardcoded in via CSS media queries --- .../provider/provider_component_defaults.tsx | 1 + src/components/basic_table/basic_table.tsx | 14 ++-- .../component_defaults/component_defaults.tsx | 8 +- .../table/__snapshots__/table.test.tsx.snap | 2 +- .../table/mobile/responsive_context.ts | 34 ++++++++ src/components/table/table.test.tsx | 83 +++++++++++++++---- src/components/table/table.tsx | 23 ++++- 7 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 src/components/table/mobile/responsive_context.ts diff --git a/src-docs/src/views/provider/provider_component_defaults.tsx b/src-docs/src/views/provider/provider_component_defaults.tsx index f2561c5a6f1..bcfd198dcd7 100644 --- a/src-docs/src/views/provider/provider_component_defaults.tsx +++ b/src-docs/src/views/provider/provider_component_defaults.tsx @@ -12,6 +12,7 @@ export const EuiComponentDefaultsProps: FunctionComponent< // Exported in one place for DRYness export const euiProviderComponentDefaultsSnippet = ` * Configures #Pagination */ pagination?: undefined; - /** - * If true, will convert table to cards in mobile view - */ - responsive?: boolean; /** * Applied to `EuiTableRow` */ @@ -529,6 +525,7 @@ export class EuiBasicTable extends Component< compressed, itemIdToExpandedRowMap, responsive, + responsiveBreakpoint, isSelectable, isExpandable, hasActions, @@ -558,7 +555,13 @@ export class EuiBasicTable extends Component< } renderTable() { - const { compressed, responsive, tableLayout, loading } = this.props; + const { + compressed, + responsive, + responsiveBreakpoint, + tableLayout, + loading, + } = this.props; const mobileHeader = responsive ? ( @@ -582,6 +585,7 @@ export class EuiBasicTable extends Component< ; + /** + * Provide a global configuration for EuiTable's `responsiveBreakpoint` prop. Defaults to `'s'`. + * + * Defaults will be inherited all `EuiBasicTable`s and `EuiInMemoryTable`s. + */ + EuiTable?: Pick; }; // Declaring as a static const for reference integrity/reducing rerenders diff --git a/src/components/table/__snapshots__/table.test.tsx.snap b/src/components/table/__snapshots__/table.test.tsx.snap index f4028daa048..a907b7df004 100644 --- a/src/components/table/__snapshots__/table.test.tsx.snap +++ b/src/components/table/__snapshots__/table.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders EuiTable 1`] = ` +exports[`EuiTable renders 1`] = ` { + const componentDefault = + useComponentDefaults().EuiTable?.responsiveBreakpoint; + const breakpoint = + componentProp ?? componentDefault ?? DEFAULT_TABLE_BREAKPOINT; + + const isBoolean = typeof breakpoint === 'boolean'; + + // We use ! and minBreakpoint to more accurately reflect the single point at which tables collapse + const isResponsive = !useIsWithinMinBreakpoint(isBoolean ? '' : breakpoint); + return isBoolean ? breakpoint : isResponsive; +}; diff --git a/src/components/table/table.test.tsx b/src/components/table/table.test.tsx index b94a635118c..53bf16aad3e 100644 --- a/src/components/table/table.test.tsx +++ b/src/components/table/table.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { requiredProps } from '../../test/required_props'; import { render } from '../../test/rtl'; +import { EuiProvider } from '../provider'; import { EuiTable } from './table'; import { EuiTableRow } from './table_row'; @@ -17,22 +18,68 @@ import { EuiTableBody } from './table_body'; import { EuiTableHeader } from './table_header'; import { EuiTableHeaderCell } from './table_header_cell'; -test('renders EuiTable', () => { - const { container } = render( - - - Hi Title - Bye Title - - - - Hi - - - Bye - - - - ); - expect(container.firstChild).toMatchSnapshot(); +describe('EuiTable', () => { + it('renders', () => { + const { container } = render( + + + Hi Title + Bye Title + + + + Hi + + + Bye + + + + ); + expect(container.firstChild).toMatchSnapshot(); + expect(container.firstChild).not.toHaveClass('euiTable--responsive'); + }); + + describe('responsive/mobile context', () => { + it('renders responsive styles if below the default m breakpoint', () => { + window.innerWidth = 767; + const { container } = render(); + expect(container.firstChild).toHaveClass('euiTable--responsive'); + }); + + it('allows customizing responsiveBreakpoint', () => { + const { container } = render(); + + expect(container.firstChild).toHaveClass('euiTable--responsive'); + }); + + it('allows customizing responsiveBreakpoint via EuiProvider.componentDefaults', () => { + const { container } = render( + + + , + { wrapper: undefined } + ); + + expect(container.firstChild).toHaveClass('euiTable--responsive'); + }); + }); + + it('always renders responsive tables styles if set to `true`', () => { + window.innerWidth = 2000; + const { container } = render(); + + expect(container.firstElementChild!.className).toContain('-mobile'); + }); + + it('allows never rendering responsive tables if set to `false`', () => { + window.innerWidth = 320; + const { container } = render(); + + expect(container.firstChild).not.toHaveClass('euiTable--responsive'); + }); }); diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx index a8087ba7a97..349356cfb20 100644 --- a/src/components/table/table.tsx +++ b/src/components/table/table.tsx @@ -9,16 +9,30 @@ import React, { FunctionComponent, TableHTMLAttributes } from 'react'; import classNames from 'classnames'; -import { useEuiMemoizedStyles, useIsWithinMaxBreakpoint } from '../../services'; +import { useEuiMemoizedStyles, type EuiBreakpointSize } from '../../services'; import { CommonProps } from '../common'; +import { useIsEuiTableResponsive } from './mobile/responsive_context'; import { euiTableStyles } from './table.styles'; export interface EuiTableProps extends CommonProps, TableHTMLAttributes { compressed?: boolean; + /** + * @deprecated - use `responsiveBreakpoint` instead + */ responsive?: boolean; + /** + * Named breakpoint. Below this size, the table will collapse + * into responsive cards. + * + * Pass `false` to never collapse to a mobile view, or inversely, + * `true` to always render mobile-friendly cards. + * + * @default m + */ + responsiveBreakpoint?: EuiBreakpointSize | boolean; /** * Sets the table-layout CSS property */ @@ -30,14 +44,15 @@ export const EuiTable: FunctionComponent = ({ className, compressed, tableLayout = 'fixed', + responsiveBreakpoint, // Default handled by `useIsEuiTableResponsive` responsive = true, ...rest }) => { - // TODO: Make the table responsive breakpoint customizable via prop - const isResponsive = useIsWithinMaxBreakpoint('s') && responsive; + const isResponsive = + useIsEuiTableResponsive(responsiveBreakpoint) && responsive; const classes = classNames('euiTable', className, { - 'euiTable--responsive': responsive, + 'euiTable--responsive': isResponsive, }); const styles = useEuiMemoizedStyles(euiTableStyles); From fb2cf77b2e17fa297ec863cc085290902f1e375b Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 20:44:24 -0700 Subject: [PATCH 02/11] [docs] Document new `responsiveBreakpoint` behavior NOTE: docs example will look broken until all styles are fully converted to Emotion --- src-docs/src/views/tables/mobile/mobile.tsx | 2 +- .../src/views/tables/mobile/mobile_section.js | 25 ++++++++++++++----- src/components/table/table.test.tsx | 2 +- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src-docs/src/views/tables/mobile/mobile.tsx b/src-docs/src/views/tables/mobile/mobile.tsx index 8f50d211256..ee9fa68def2 100644 --- a/src-docs/src/views/tables/mobile/mobile.tsx +++ b/src-docs/src/views/tables/mobile/mobile.tsx @@ -302,7 +302,7 @@ export default () => { selection={selection} isSelectable={true} hasActions={true} - responsive={isResponsive} + responsiveBreakpoint={isResponsive} onChange={onTableChange} /> diff --git a/src-docs/src/views/tables/mobile/mobile_section.js b/src-docs/src/views/tables/mobile/mobile_section.js index 1575e5c81da..ee5e2629c7b 100644 --- a/src-docs/src/views/tables/mobile/mobile_section.js +++ b/src-docs/src/views/tables/mobile/mobile_section.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { GuideSectionTypes } from '../../../components'; import Table from './mobile'; @@ -32,14 +33,26 @@ export const section = { text: ( <>

- Allowing a table to be responsive means breaking each row down into its - own section and individually displaying each table header above the cell - contents. There are few times when you may want to exclude this behavior - from your table, for instance, when the table has very few columns or - the table does not break down easily into this format. For these use - cases, you may set responsive=false. + Tables will be mobile-responsive by default, breaking down each row into + its own card section and individually displaying each table header above + the cell contents. The default breakpoint at which the table will + responsively shift into cards is the{' '} + + m window size + + , which can be customized with the{' '} + responsiveBreakpoint prop (e.g.,{' '} + {'responsiveBreakpoint="s"'}).

+ To never render your table responsively (e.g. for tables with very few + columns), you may set{' '} + {'responsiveBreakpoint={false}'}. + Inversely, if you always want your table to render in a mobile-friendly + manner, pass true. +

+

+ {/* TODO: This shouldn't be true by the end of the Emotion conversion */} To make your table work responsively, please make sure you add the following additional props to the top level table component (EuiBasicTable or{' '} diff --git a/src/components/table/table.test.tsx b/src/components/table/table.test.tsx index 53bf16aad3e..449f5ddb893 100644 --- a/src/components/table/table.test.tsx +++ b/src/components/table/table.test.tsx @@ -73,7 +73,7 @@ describe('EuiTable', () => { window.innerWidth = 2000; const { container } = render(); - expect(container.firstElementChild!.className).toContain('-mobile'); + expect(container.firstChild).toHaveClass('euiTable--responsive'); }); it('allows never rendering responsive tables if set to `false`', () => { From fb3ccb9a7f4318153873fbd72b0713bec21d0e81 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 16:46:09 -0700 Subject: [PATCH 03/11] [EuiTableMobileHeader] Refactor to use new `responsiveBreakpoint` API - switch to conditionally rendering JSX instead of applying `display: none` CSS + convert styles to Emotion (basic table/memory tables) + remove unnecessary `EuiFlexGroup`/`EuiFlexItem` components, just use flex CSS instead + fix InMemoryTable tests that relied on the mobile header DOM + add new test/snapshot --- .../__snapshots__/basic_table.test.tsx.snap | 75 ----------------- .../in_memory_table.test.tsx.snap | 80 ++++++++++++------- src/components/basic_table/basic_table.tsx | 13 +-- .../basic_table/in_memory_table.test.tsx | 54 +++++++++++-- .../table_header_mobile.test.tsx.snap | 4 +- src/components/table/mobile/_mobile.scss | 13 --- .../mobile/table_header_mobile.styles.ts | 20 +++++ .../table/mobile/table_header_mobile.test.tsx | 29 ++++++- .../table/mobile/table_header_mobile.tsx | 20 +++-- 9 files changed, 167 insertions(+), 141 deletions(-) create mode 100644 src/components/table/mobile/table_header_mobile.styles.ts 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 17104f245e4..40c551fee48 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -7,20 +7,6 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = ` data-test-subj="test subject string" >

-
-
-
-
-
-
-
-
-
-
- -
- -
-
-
-
-
- -
-
-
-
-
+
+ +
+ +
+
+
+ +
+
+
+`; + exports[`EuiInMemoryTable behavior pagination 1`] = `
-
-
-
-
-
-
extends Component< } = this.props; const mobileHeader = responsive ? ( - - - {this.renderSelectAll(true)} - {this.renderTableMobileSort()} - + + {this.renderSelectAll(true)} + {this.renderTableMobileSort()} ) : undefined; const caption = this.renderTableCaption(); diff --git a/src/components/basic_table/in_memory_table.test.tsx b/src/components/basic_table/in_memory_table.test.tsx index e15294c9eb4..be4af72f428 100644 --- a/src/components/basic_table/in_memory_table.test.tsx +++ b/src/components/basic_table/in_memory_table.test.tsx @@ -638,7 +638,9 @@ describe('EuiInMemoryTable', () => { onSelectionChange: () => undefined, }, }; - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText('Page 1 of 1')).toBeTruthy(); expect(getByText('Select all rows')).toBeTruthy(); @@ -667,7 +669,9 @@ describe('EuiInMemoryTable', () => { onSelectionChange: () => undefined, }, }; - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText('Page 1 of 1')).toBeTruthy(); expect(getByText('Select all rows')).toBeTruthy(); @@ -700,7 +704,9 @@ describe('EuiInMemoryTable', () => { onSelectionChange: () => undefined, }, }; - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText('Page 1 of 2')).toBeTruthy(); expect(getByText('Select all rows')).toBeTruthy(); @@ -741,7 +747,9 @@ describe('EuiInMemoryTable', () => { onSelectionChange: () => undefined, }, }; - const { getByText } = render(); + const { getByText } = render( + + ); expect(getByText('Page 1 of 1')).toBeTruthy(); expect(getByText('Select all rows')).toBeTruthy(); @@ -784,7 +792,7 @@ describe('EuiInMemoryTable', () => { }, }; const { getByText, getByPlaceholderText } = render( - + ); expect(getByText('Page 1 of 1')).toBeTruthy(); @@ -881,7 +889,9 @@ describe('EuiInMemoryTable', () => { onSelectionChange: () => undefined, }, }; - const { container, queryByText } = render(); + const { container, queryByText } = render( + + ); expect(queryByText('Page 1 of 1')).toBeTruthy(); expect(queryByText('Select all rows')).toBeTruthy(); @@ -1088,6 +1098,38 @@ describe('EuiInMemoryTable', () => { }); describe('behavior', () => { + test('mobile header', () => { + const props: EuiInMemoryTableProps = { + ...requiredProps, + items: [ + { id: '1', name: 'name1' }, + { id: '2', name: 'name2' }, + { id: '3', name: 'name3' }, + ], + itemId: 'id', + columns: [ + { + field: 'name', + name: 'Name', + description: 'description', + sortable: true, + }, + ], + pagination: true, + sorting: true, + selection: { + onSelectionChange: () => undefined, + }, + }; + const { container } = render( + + ); + + expect( + container.querySelector('.euiTableHeaderMobile') + ).toMatchSnapshot(); + }); + test('pagination', async () => { const props: EuiInMemoryTableProps = { ...requiredProps, diff --git a/src/components/table/mobile/__snapshots__/table_header_mobile.test.tsx.snap b/src/components/table/mobile/__snapshots__/table_header_mobile.test.tsx.snap index efd9e55dd1a..b8fc3b6fbc3 100644 --- a/src/components/table/mobile/__snapshots__/table_header_mobile.test.tsx.snap +++ b/src/components/table/mobile/__snapshots__/table_header_mobile.test.tsx.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiTableHeaderMobile is rendered 1`] = ` +exports[`EuiTableHeaderMobile renders when below the responsive breakpoint 1`] = `
`; diff --git a/src/components/table/mobile/_mobile.scss b/src/components/table/mobile/_mobile.scss index 81f684aab3d..621e60b9be1 100644 --- a/src/components/table/mobile/_mobile.scss +++ b/src/components/table/mobile/_mobile.scss @@ -1,17 +1,4 @@ // Hide mobile-only elements by default -.euiTableHeaderMobile, .euiTableHeaderCell--hideForDesktop { display: none; } - -@include euiBreakpoint('xs', 's') { - .euiTableHeaderMobile { - display: flex; - justify-content: flex-end; - padding: $euiTableCellContentPadding 0; - } - - .euiTableSortMobile { - display: block; - } -} diff --git a/src/components/table/mobile/table_header_mobile.styles.ts b/src/components/table/mobile/table_header_mobile.styles.ts new file mode 100644 index 00000000000..84c91134bf0 --- /dev/null +++ b/src/components/table/mobile/table_header_mobile.styles.ts @@ -0,0 +1,20 @@ +/* + * 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 { css } from '@emotion/react'; + +import { UseEuiTheme } from '../../../services'; + +export const euiTableHeaderMobileStyles = ({ euiTheme }: UseEuiTheme) => ({ + euiTableHeaderMobile: css` + display: flex; + justify-content: space-between; + align-items: baseline; + padding-block: ${euiTheme.size.s}; + `, +}); diff --git a/src/components/table/mobile/table_header_mobile.test.tsx b/src/components/table/mobile/table_header_mobile.test.tsx index ca103822515..0bf2bafcd98 100644 --- a/src/components/table/mobile/table_header_mobile.test.tsx +++ b/src/components/table/mobile/table_header_mobile.test.tsx @@ -9,13 +9,38 @@ import React from 'react'; import { requiredProps } from '../../../test'; import { render } from '../../../test/rtl'; +import { EuiProvider } from '../../provider'; import { EuiTableHeaderMobile } from './table_header_mobile'; describe('EuiTableHeaderMobile', () => { - test('is rendered', () => { - const { container } = render(); + it('does not render if window size is above the default m breakpoint', () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it('renders when below the responsive breakpoint', () => { + const { container } = render( + + ); + + expect(container).not.toBeEmptyDOMElement(); expect(container.firstChild).toMatchSnapshot(); }); + + it('respects EuiProvider.componentDefaults', () => { + const { container } = render( + + + , + { wrapper: undefined } + ); + + expect(container).not.toBeEmptyDOMElement(); + }); }); diff --git a/src/components/table/mobile/table_header_mobile.tsx b/src/components/table/mobile/table_header_mobile.tsx index 574031b7442..a04142b7016 100644 --- a/src/components/table/mobile/table_header_mobile.tsx +++ b/src/components/table/mobile/table_header_mobile.tsx @@ -8,16 +8,26 @@ import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; + +import { useEuiMemoizedStyles } from '../../../services'; import { CommonProps } from '../../common'; +import type { EuiTableProps } from '../table'; +import { useIsEuiTableResponsive } from './responsive_context'; +import { euiTableHeaderMobileStyles } from './table_header_mobile.styles'; + export const EuiTableHeaderMobile: FunctionComponent< - CommonProps & HTMLAttributes -> = ({ children, className, ...rest }) => { + CommonProps & + HTMLAttributes & + Pick +> = ({ children, className, responsiveBreakpoint, ...rest }) => { + const isResponsive = useIsEuiTableResponsive(responsiveBreakpoint); + const styles = useEuiMemoizedStyles(euiTableHeaderMobileStyles); const classes = classNames('euiTableHeaderMobile', className); - return ( -
+ return isResponsive ? ( +
{children}
- ); + ) : null; }; From b7fe610ffc762b9d003985984f7507e43ee1f927 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 16:58:13 -0700 Subject: [PATCH 04/11] [EuiTableSortMobile] Convert to Emotion - fairly straightforward, no theme tokens needed + extend types --- .../__snapshots__/in_memory_table.test.tsx.snap | 2 +- .../table_sort_mobile.test.tsx.snap | 2 +- .../table/mobile/table_sort_mobile.tsx | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) 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 f3db322df89..41abaac9b73 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 @@ -24,7 +24,7 @@ exports[`EuiInMemoryTable behavior mobile header 1`] = `
); - return
{mobileSortPopover}
; + return ( +
+ {mobileSortPopover} +
+ ); } } From 3e3a6656899c39818f8f1b893357e1ddbadfa5d7 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 17:23:17 -0700 Subject: [PATCH 05/11] Initial/basic responsive vs desktop table styles --- .../__snapshots__/basic_table.test.tsx.snap | 4 +-- .../in_memory_table.test.tsx.snap | 4 +-- .../table/__snapshots__/table.test.tsx.snap | 2 +- src/components/table/_responsive.scss | 28 ------------------- src/components/table/_table.scss | 4 --- src/components/table/mobile/_mobile.scss | 4 --- src/components/table/table.styles.ts | 23 +++++++++++++++ src/components/table/table.test.tsx | 12 ++++---- src/components/table/table.tsx | 3 +- 9 files changed, 36 insertions(+), 48 deletions(-) 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 40c551fee48..57574cef9ea 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -8,7 +8,7 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = ` >
@@ -122,7 +122,7 @@ 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 41abaac9b73..0a0580159a8 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 @@ -156,7 +156,7 @@ exports[`EuiInMemoryTable empty array 1`] = ` >
@@ -251,7 +251,7 @@ exports[`EuiInMemoryTable with items 1`] = ` >
diff --git a/src/components/table/__snapshots__/table.test.tsx.snap b/src/components/table/__snapshots__/table.test.tsx.snap index a907b7df004..8472109c2a7 100644 --- a/src/components/table/__snapshots__/table.test.tsx.snap +++ b/src/components/table/__snapshots__/table.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiTable renders 1`] = `
diff --git a/src/components/table/_responsive.scss b/src/components/table/_responsive.scss index 1cae1afb5bf..40e07303ae2 100644 --- a/src/components/table/_responsive.scss +++ b/src/components/table/_responsive.scss @@ -1,41 +1,13 @@ // TODO: Address nesting during Emotion conversion, if possible // stylelint-disable max-nesting-depth -.euiTableRowCell__mobileHeader { - // Don't display by default unless table is responsive - display: none; -} - -@include euiBreakpoint('xs', 's') { - .euiTableRowCell--hideForMobile { // must come last to override any special cases - // stylelint-disable-next-line declaration-no-important - display: none !important; - } -} - -@include euiBreakpoint('m', 'l', 'xl') { - .euiTableRowCell--hideForDesktop { // must come last to override any special cases - // stylelint-disable-next-line declaration-no-important - display: none !important; - } -} - @include euiBreakpoint('xs', 's') { .euiTable.euiTable--responsive { - thead { - display: none; // Use mobile versions of selecting and filtering instead - } - - tfoot { - display: none; // Not supporting responsive footer content - } - .euiTableRowCell__mobileHeader { // Always truncate @include euiTextTruncate; @include fontSize($euiFontSize * .6875); - display: block; color: $euiColorDarkShade; padding: $euiSizeS; padding-bottom: 0; diff --git a/src/components/table/_table.scss b/src/components/table/_table.scss index daa30ba4b2c..b3a0f56d3a5 100644 --- a/src/components/table/_table.scss +++ b/src/components/table/_table.scss @@ -101,10 +101,6 @@ &--baseline { vertical-align: baseline; } - - &.euiTableRowCell--isMobileHeader { - display: none; // Hide if not mobile breakpoint - } } .euiTableRowCellCheckbox { diff --git a/src/components/table/mobile/_mobile.scss b/src/components/table/mobile/_mobile.scss index 621e60b9be1..e69de29bb2d 100644 --- a/src/components/table/mobile/_mobile.scss +++ b/src/components/table/mobile/_mobile.scss @@ -1,4 +0,0 @@ -// Hide mobile-only elements by default -.euiTableHeaderCell--hideForDesktop { - display: none; -} diff --git a/src/components/table/table.styles.ts b/src/components/table/table.styles.ts index 0f34ddb279d..4497dd6d5c1 100644 --- a/src/components/table/table.styles.ts +++ b/src/components/table/table.styles.ts @@ -56,6 +56,29 @@ export const euiTableStyles = (euiThemeContext: UseEuiTheme) => { padding: ${compressedCellContentPadding}; } `, + /** + * Responsive/mobile vs desktop styles + */ + desktop: css` + .euiTableHeaderCell--hideForDesktop, + .euiTableRowCell--hideForDesktop, + .euiTableRowCell__mobileHeader { + display: none; + } + `, + mobile: css` + .euiTableRowCell--hideForMobile { + display: none; + } + + thead { + display: none; /* Use mobile versions of selecting and filtering instead */ + } + + tfoot { + display: none; /* Not supporting responsive footer content */ + } + `, }; }; diff --git a/src/components/table/table.test.tsx b/src/components/table/table.test.tsx index 449f5ddb893..b98b059604a 100644 --- a/src/components/table/table.test.tsx +++ b/src/components/table/table.test.tsx @@ -37,20 +37,20 @@ describe('EuiTable', () => { ); expect(container.firstChild).toMatchSnapshot(); - expect(container.firstChild).not.toHaveClass('euiTable--responsive'); }); describe('responsive/mobile context', () => { it('renders responsive styles if below the default m breakpoint', () => { window.innerWidth = 767; const { container } = render(); - expect(container.firstChild).toHaveClass('euiTable--responsive'); + + expect(container.firstElementChild!.className).toContain('-mobile'); }); it('allows customizing responsiveBreakpoint', () => { const { container } = render(); - expect(container.firstChild).toHaveClass('euiTable--responsive'); + expect(container.firstElementChild!.className).toContain('-mobile'); }); it('allows customizing responsiveBreakpoint via EuiProvider.componentDefaults', () => { @@ -65,7 +65,7 @@ describe('EuiTable', () => { { wrapper: undefined } ); - expect(container.firstChild).toHaveClass('euiTable--responsive'); + expect(container.firstElementChild!.className).toContain('-mobile'); }); }); @@ -73,13 +73,13 @@ describe('EuiTable', () => { window.innerWidth = 2000; const { container } = render(); - expect(container.firstChild).toHaveClass('euiTable--responsive'); + expect(container.firstElementChild!.className).toContain('-mobile'); }); it('allows never rendering responsive tables if set to `false`', () => { window.innerWidth = 320; const { container } = render(); - expect(container.firstChild).not.toHaveClass('euiTable--responsive'); + expect(container.firstElementChild!.className).toContain('-desktop'); }); }); diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx index 349356cfb20..9d846ef6673 100644 --- a/src/components/table/table.tsx +++ b/src/components/table/table.tsx @@ -52,7 +52,7 @@ export const EuiTable: FunctionComponent = ({ useIsEuiTableResponsive(responsiveBreakpoint) && responsive; const classes = classNames('euiTable', className, { - 'euiTable--responsive': isResponsive, + 'euiTable--responsive': responsive, }); const styles = useEuiMemoizedStyles(euiTableStyles); @@ -61,6 +61,7 @@ export const EuiTable: FunctionComponent = ({ styles.layout[tableLayout], (!compressed || isResponsive) && styles.uncompressed, compressed && !isResponsive && styles.compressed, + isResponsive ? styles.mobile : styles.desktop, ]; return ( From ff8dab7455b62935a2ab202bce825cf6b0ba2fd3 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 19:36:41 -0700 Subject: [PATCH 06/11] Set up child responsive context - so any child can pick up whether or not the table is responsive --- src/components/table/mobile/responsive_context.ts | 10 ++++++++++ src/components/table/table.tsx | 9 +++++++-- src/components/table/table_row_cell.tsx | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/components/table/mobile/responsive_context.ts b/src/components/table/mobile/responsive_context.ts index bb273b58d06..b7537a8f099 100644 --- a/src/components/table/mobile/responsive_context.ts +++ b/src/components/table/mobile/responsive_context.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { createContext, useContext } from 'react'; import { useIsWithinMinBreakpoint, type EuiBreakpointSize, @@ -32,3 +33,12 @@ export const useIsEuiTableResponsive = ( const isResponsive = !useIsWithinMinBreakpoint(isBoolean ? '' : breakpoint); return isBoolean ? breakpoint : isResponsive; }; + +/** + * Context set by parent table components + * Hook used by cells to fetch parent isResponsive state + */ +export const EuiTableIsResponsiveContext = createContext(false); + +export const useEuiTableIsResponsive = () => + useContext(EuiTableIsResponsiveContext); diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx index 9d846ef6673..53fd65b1c18 100644 --- a/src/components/table/table.tsx +++ b/src/components/table/table.tsx @@ -12,7 +12,10 @@ import classNames from 'classnames'; import { useEuiMemoizedStyles, type EuiBreakpointSize } from '../../services'; import { CommonProps } from '../common'; -import { useIsEuiTableResponsive } from './mobile/responsive_context'; +import { + useIsEuiTableResponsive, + EuiTableIsResponsiveContext, +} from './mobile/responsive_context'; import { euiTableStyles } from './table.styles'; export interface EuiTableProps @@ -66,7 +69,9 @@ export const EuiTable: FunctionComponent = ({ return (
- {children} + + {children} +
); }; diff --git a/src/components/table/table_row_cell.tsx b/src/components/table/table_row_cell.tsx index 3188d217828..8a64a79f646 100644 --- a/src/components/table/table_row_cell.tsx +++ b/src/components/table/table_row_cell.tsx @@ -22,11 +22,11 @@ import { LEFT_ALIGNMENT, RIGHT_ALIGNMENT, CENTER_ALIGNMENT, - useIsWithinBreakpoints, } from '../../services'; import { isObject } from '../../services/predicate'; import { EuiTextBlockTruncate } from '../text_truncate'; +import { useEuiTableIsResponsive } from './mobile/responsive_context'; import { resolveWidthAsStyle } from './utils'; interface EuiTableRowCellSharedPropsShape { @@ -170,7 +170,7 @@ export const EuiTableRowCell: FunctionComponent = ({ }); const widthValue = - useIsWithinBreakpoints(['xs', 's']) && mobileOptions.width + useEuiTableIsResponsive() && mobileOptions.width ? mobileOptions.width : width; From 4fdcc3f0388e5c0f5e58fcb0509660a9cb0448ee Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 19:51:29 -0700 Subject: [PATCH 07/11] Delete `_mobile.scss` Sass files --- src/components/table/_index.scss | 2 -- src/components/table/mobile/_index.scss | 1 - src/components/table/mobile/_mobile.scss | 0 3 files changed, 3 deletions(-) delete mode 100644 src/components/table/mobile/_index.scss delete mode 100644 src/components/table/mobile/_mobile.scss diff --git a/src/components/table/_index.scss b/src/components/table/_index.scss index 6851e343201..843fd0b6d53 100644 --- a/src/components/table/_index.scss +++ b/src/components/table/_index.scss @@ -3,5 +3,3 @@ @import 'table'; @import 'responsive'; - -@import 'mobile/index'; diff --git a/src/components/table/mobile/_index.scss b/src/components/table/mobile/_index.scss deleted file mode 100644 index 15a75dfe483..00000000000 --- a/src/components/table/mobile/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'mobile'; diff --git a/src/components/table/mobile/_mobile.scss b/src/components/table/mobile/_mobile.scss deleted file mode 100644 index e69de29bb2d..00000000000 From a1eaef261fc51bb16068f842f827ad586af774d7 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 19:54:30 -0700 Subject: [PATCH 08/11] [misc] Clean up unnecessary `
` wrapper around basic/memory tables + remove unnecessary variable definitions in favor of inline JSX (microperf) --- .../__snapshots__/basic_table.test.tsx.snap | 1104 ++++++++--------- .../in_memory_table.test.tsx.snap | 284 +++-- src/components/basic_table/basic_table.tsx | 30 +- 3 files changed, 703 insertions(+), 715 deletions(-) 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 57574cef9ea..d5b80dc5add 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.tsx.snap @@ -6,113 +6,111 @@ exports[`EuiBasicTable renders (bare-bones) 1`] = ` class="euiBasicTable testClass1 testClass2 emotion-euiTestCss" data-test-subj="test subject string" > -
- - - - + + - - - + + + + + - - - - + + + + + - - + + + + + - - -
-
+
+
+ - - Name - - - description - + Name + + + description -
-
- Name -
-
+
+ - - name1 - -
-
- -
- Name -
-
+
+ - - name2 - -
-
- -
- Name -
-
+
+ - - name3 - -
-
-
+ name3 + +
+ + + + `; @@ -120,569 +118,567 @@ exports[`EuiBasicTable renders (kitchen sink) with pagination, selection, sortin
-
- - - - + + - - + - + - + - - - + + + + + + - - - + + - - + - + - - + + + + + + + - + + - - + - + - - + + + + + + + - + + - - + - + - - - - - + + + + + - + - + - + - + - - -
-
+
+
+
+
- -
-
+ class="euiCheckbox__square" + />
-
- - + + + + - - Age - - - your age - + ID + + + your id - + + - - Actions - + Age + + + your age -
+ + + Actions + + +
+
- -
-
+ class="euiCheckbox__square" + />
-
+ +
+ Name +
+
+ NAME1 +
+
+
-
- Name -
-
- NAME1 -
-
+
-
- ID -
-
- - 1 - -
-
+ + +
-
- Age -
-
+
+ - - 20 - -
-
+ + +
-
- - - - + + + + - -
-
-
+
- -
-
+ class="euiCheckbox__square" + />
-
+ +
+ Name +
+
+ NAME2 +
+
+
-
- Name -
-
- NAME2 -
-
+
-
- ID -
-
- - 2 - -
-
+ + +
-
- Age -
-
+
+ - - 21 - -
-
+ + +
-
- - - - + + + + - -
-
-
+
- -
-
+ class="euiCheckbox__square" + />
-
+ +
+ Name +
+
+ NAME3 +
+
+
-
- Name -
-
- NAME3 -
-
+
-
- ID -
-
- - 3 - -
-
+ + +
-
- Age -
-
+
+ - - 22 - -
-
+ + +
-
- - - - + + + + - -
-
+ + + +
+
-
- -
-
+ + +
-
- -
-
+ + +
-
- - - Total items: - 5 - - -
-
+ Total items: + 5 + + + + +
-
- -
-
+ + +
-
- -
-
-
+ +
+ + + +
-
- - - - + + - - - + description + + + + + + + - - - - -
-
+
+
+ - - Name - - - description - + Name -
-
- - No items found - -
-
-
+ No items found + +
+ + + +
`; @@ -249,112 +247,110 @@ exports[`EuiInMemoryTable with items 1`] = ` class="euiBasicTable testClass1 testClass2 emotion-euiTestCss" data-test-subj="test subject string" > -
- - - - + + - - - + description + + + + + + + - - - - + + + + + - - + + + + + - - -
-
+
+
+ - - Name - - - description - + Name -
-
- Name -
-
+
+ - - name1 - -
-
- -
- Name -
-
+
+ - - name2 - -
-
- -
- Name -
-
+
+ - - name3 - -
-
-
+ name3 + + + + + + `; diff --git a/src/components/basic_table/basic_table.tsx b/src/components/basic_table/basic_table.tsx index acfb4aa4275..f3aeec4c963 100644 --- a/src/components/basic_table/basic_table.tsx +++ b/src/components/basic_table/basic_table.tsx @@ -562,19 +562,15 @@ export class EuiBasicTable extends Component< loading, } = this.props; - const mobileHeader = responsive ? ( - - {this.renderSelectAll(true)} - {this.renderTableMobileSort()} - - ) : undefined; - const caption = this.renderTableCaption(); - const head = this.renderTableHead(); - const body = this.renderTableBody(); - const footer = this.renderTableFooter(); return ( -
- {mobileHeader} + <> + {/* TODO: Remove conditional once `responsive` prop is deprecated */} + {responsive && ( + + {this.renderSelectAll(true)} + {this.renderTableMobileSort()} + + )} extends Component< compressed={compressed} css={loading && safariLoadingWorkaround} > - {caption} - {head} - {body} - {footer} + {this.renderTableCaption()} + {this.renderTableHead()} + {this.renderTableBody()} + {this.renderTableFooter()} -
+ ); } From 5d4271159df15bdc0edd6da02d8dcfa4dd2581f8 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Tue, 26 Mar 2024 20:00:01 -0700 Subject: [PATCH 09/11] changelog --- changelogs/upcoming/7625.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelogs/upcoming/7625.md diff --git a/changelogs/upcoming/7625.md b/changelogs/upcoming/7625.md new file mode 100644 index 00000000000..c22e06a2608 --- /dev/null +++ b/changelogs/upcoming/7625.md @@ -0,0 +1,7 @@ +- Updated `EuiTable`, `EuiBasicTable`, and `EuiInMemoryTable` with a new `responsiveBreakpoint` prop, which allows customizing the point at which the table collapses into a mobile-friendly view with cards +- Updated `EuiProvider`'s `componentDefaults` prop to allow configuring `EuiTable.responsiveBreakpoint` + +**Deprecations** + +- Deprecated the `responsive` prop from `EuiTable`, `EuiBasicTable`, and `EuiInMemoryTable`. Use the new `responsiveBreakpoint` prop instead +- `EuiTable` mobile headers no longer render in the DOM when not visible (previously rendered with `display: none`). This may affect DOM testing assertions. From ed61e7afdaceee420d01629a48499ebe71a6757d Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Wed, 27 Mar 2024 09:22:04 -0700 Subject: [PATCH 10/11] [PR feedback] wording goodly --- .../provider/component_defaults/component_defaults.tsx | 2 +- src/components/table/table.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/provider/component_defaults/component_defaults.tsx b/src/components/provider/component_defaults/component_defaults.tsx index ac20b82618f..0dc2ee6165c 100644 --- a/src/components/provider/component_defaults/component_defaults.tsx +++ b/src/components/provider/component_defaults/component_defaults.tsx @@ -40,7 +40,7 @@ export type EuiComponentDefaults = { /** * Provide a global configuration for EuiTable's `responsiveBreakpoint` prop. Defaults to `'s'`. * - * Defaults will be inherited all `EuiBasicTable`s and `EuiInMemoryTable`s. + * Defaults will be inherited by all `EuiBasicTable`s and `EuiInMemoryTable`s. */ EuiTable?: Pick; }; diff --git a/src/components/table/table.test.tsx b/src/components/table/table.test.tsx index b98b059604a..194fd002da0 100644 --- a/src/components/table/table.test.tsx +++ b/src/components/table/table.test.tsx @@ -76,7 +76,7 @@ describe('EuiTable', () => { expect(container.firstElementChild!.className).toContain('-mobile'); }); - it('allows never rendering responsive tables if set to `false`', () => { + it('never renders responsive tables if set to `false`', () => { window.innerWidth = 320; const { container } = render(); From edaf2d3b113805165ce30be2ce5fe1981beeecda Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Wed, 27 Mar 2024 11:41:13 -0700 Subject: [PATCH 11/11] [PR feedback] clarify comment to be a little less confusing --- src/components/table/mobile/responsive_context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/table/mobile/responsive_context.ts b/src/components/table/mobile/responsive_context.ts index b7537a8f099..305c318d373 100644 --- a/src/components/table/mobile/responsive_context.ts +++ b/src/components/table/mobile/responsive_context.ts @@ -29,7 +29,8 @@ export const useIsEuiTableResponsive = ( const isBoolean = typeof breakpoint === 'boolean'; - // We use ! and minBreakpoint to more accurately reflect the single point at which tables collapse + // Note: we're using `!useIsWithinMinBreakpoint` here instead of `useIsWithinMaxBreakpoint` + // because it more accurately reflects the single breakpoint at which tables collapse const isResponsive = !useIsWithinMinBreakpoint(isBoolean ? '' : breakpoint); return isBoolean ? breakpoint : isResponsive; };