Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiTable] New responsiveBreakpoint prop + initial setup for mobile vs desktop styles #7625

Merged
merged 11 commits into from
Mar 27, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const EuiComponentDefaultsProps: FunctionComponent<
// Exported in one place for DRYness
export const euiProviderComponentDefaultsSnippet = `<EuiProvider
componentDefaults={{
EuiTable: { responsiveBreakpoint: 's', },
EuiTablePagination: { itemsPerPage: 20, },
EuiFocusTrap: { crossFrame: true },
EuiPortal: { insert },
Expand Down
14 changes: 9 additions & 5 deletions src/components/basic_table/basic_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,6 @@ interface BasicTableProps<T extends object>
* Configures #Pagination
*/
pagination?: undefined;
/**
* If true, will convert table to cards in mobile view
*/
responsive?: boolean;
/**
* Applied to `EuiTableRow`
*/
Expand Down Expand Up @@ -529,6 +525,7 @@ export class EuiBasicTable<T extends object = any> extends Component<
compressed,
itemIdToExpandedRowMap,
responsive,
responsiveBreakpoint,
isSelectable,
isExpandable,
hasActions,
Expand Down Expand Up @@ -558,7 +555,13 @@ export class EuiBasicTable<T extends object = any> extends Component<
}

renderTable() {
const { compressed, responsive, tableLayout, loading } = this.props;
const {
compressed,
responsive,
responsiveBreakpoint,
tableLayout,
loading,
} = this.props;

const mobileHeader = responsive ? (
<EuiTableHeaderMobile>
Expand All @@ -582,6 +585,7 @@ export class EuiBasicTable<T extends object = any> extends Component<
<EuiTable
id={this.tableId}
tableLayout={tableLayout}
responsiveBreakpoint={responsiveBreakpoint}
responsive={responsive}
compressed={compressed}
css={loading && safariLoadingWorkaround}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import React, {

import type { EuiPortalProps } from '../../portal';
import type { EuiFocusTrapProps } from '../../focus_trap';
import type { EuiTablePaginationProps } from '../../table';
import type { EuiTablePaginationProps, EuiTableProps } from '../../table';

export type EuiComponentDefaults = {
/**
Expand All @@ -37,6 +37,12 @@ export type EuiComponentDefaults = {
EuiTablePaginationProps,
'itemsPerPage' | 'itemsPerPageOptions' | 'showPerPageOptions'
>;
/**
* Provide a global configuration for EuiTable's `responsiveBreakpoint` prop. Defaults to `'s'`.
*
* Defaults will be inherited all `EuiBasicTable`s and `EuiInMemoryTable`s.
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
*/
EuiTable?: Pick<EuiTableProps, 'responsiveBreakpoint'>;
};

// Declaring as a static const for reference integrity/reducing rerenders
Expand Down
2 changes: 1 addition & 1 deletion src/components/table/__snapshots__/table.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders EuiTable 1`] = `
exports[`EuiTable renders 1`] = `
<table
aria-label="aria-label"
class="euiTable testClass1 testClass2 euiTable--responsive emotion-euiTable-fixed-uncompressed-euiTestCss"
Expand Down
34 changes: 34 additions & 0 deletions src/components/table/mobile/responsive_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 {
useIsWithinMinBreakpoint,
type EuiBreakpointSize,
} from '../../../services';
import { useComponentDefaults } from '../../provider/component_defaults';

export const DEFAULT_TABLE_BREAKPOINT: EuiBreakpointSize = 'm';

/**
* Used by parent/top-level table components to determine isResponsive state
* based on the passed breakpoint
*/
export const useIsEuiTableResponsive = (
componentProp?: EuiBreakpointSize | boolean
): boolean => {
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);
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
return isBoolean ? breakpoint : isResponsive;
};
83 changes: 65 additions & 18 deletions src/components/table/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(
<EuiTable {...requiredProps}>
<EuiTableHeader>
<EuiTableHeaderCell>Hi Title</EuiTableHeaderCell>
<EuiTableHeaderCell>Bye Title</EuiTableHeaderCell>
</EuiTableHeader>
<EuiTableBody>
<EuiTableRow>
<EuiTableRowCell>Hi</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Bye</EuiTableRowCell>
</EuiTableRow>
</EuiTableBody>
</EuiTable>
);
expect(container.firstChild).toMatchSnapshot();
describe('EuiTable', () => {
it('renders', () => {
const { container } = render(
<EuiTable {...requiredProps}>
<EuiTableHeader>
<EuiTableHeaderCell>Hi Title</EuiTableHeaderCell>
<EuiTableHeaderCell>Bye Title</EuiTableHeaderCell>
</EuiTableHeader>
<EuiTableBody>
<EuiTableRow>
<EuiTableRowCell>Hi</EuiTableRowCell>
</EuiTableRow>
<EuiTableRow>
<EuiTableRowCell>Bye</EuiTableRowCell>
</EuiTableRow>
</EuiTableBody>
</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(<EuiTable />);
expect(container.firstChild).toHaveClass('euiTable--responsive');
});

it('allows customizing responsiveBreakpoint', () => {
const { container } = render(<EuiTable responsiveBreakpoint="xl" />);

expect(container.firstChild).toHaveClass('euiTable--responsive');
});

it('allows customizing responsiveBreakpoint via EuiProvider.componentDefaults', () => {
const { container } = render(
<EuiProvider
componentDefaults={{
EuiTable: { responsiveBreakpoint: 'xl' },
}}
>
<EuiTable />
</EuiProvider>,
{ wrapper: undefined }
);

expect(container.firstChild).toHaveClass('euiTable--responsive');
});
});

it('always renders responsive tables styles if set to `true`', () => {
window.innerWidth = 2000;
const { container } = render(<EuiTable responsiveBreakpoint={true} />);

expect(container.firstElementChild!.className).toContain('-mobile');
});

it('allows never rendering responsive tables if set to `false`', () => {
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
window.innerWidth = 320;
const { container } = render(<EuiTable responsiveBreakpoint={false} />);

expect(container.firstChild).not.toHaveClass('euiTable--responsive');
});
});
23 changes: 19 additions & 4 deletions src/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLTableElement> {
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
*/
Expand All @@ -30,14 +44,15 @@ export const EuiTable: FunctionComponent<EuiTableProps> = ({
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);
Expand Down