diff --git a/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png new file mode 100644 index 00000000000..e37ce718880 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png new file mode 100644 index 00000000000..35af7967f7a Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png differ diff --git a/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Virtualization.png b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Virtualization.png new file mode 100644 index 00000000000..f5da952784a Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Virtualization.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png new file mode 100644 index 00000000000..5e60f5820aa Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Flyout.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png new file mode 100644 index 00000000000..595f3e634cb Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_FullscreenVRT_Full_Screen_With_Header.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Virtualization.png b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Virtualization.png new file mode 100644 index 00000000000..13780c65549 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Virtualization.png differ diff --git a/packages/eui/changelogs/upcoming/7998.md b/packages/eui/changelogs/upcoming/7998.md new file mode 100644 index 00000000000..4ee6491ef45 --- /dev/null +++ b/packages/eui/changelogs/upcoming/7998.md @@ -0,0 +1,5 @@ +**CSS-in-JS conversions** + +- Converted `EuiDataGrid` to Emotion + - Removed `$euiZDataGrid` + - Removed `$euiZHeaderBelowDataGrid` diff --git a/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index f197ca1bf0b..6e9976b6c13 100644 --- a/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/packages/eui/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -457,11 +457,11 @@ exports[`EuiDataGrid rendering renders additional toolbar controls 1`] = ` tabindex="-1" />
{ + return { + euiDataGridBody: css` + ${euiScrollBarStyles(euiThemeContext)} + `, + virtualized: css` + scroll-padding: 0; + `, + customRender: css` + ${logicalSizeCSS('100%')} + overflow: auto; + `, + }; +}; diff --git a/packages/eui/src/components/datagrid/body/data_grid_body.tsx b/packages/eui/src/components/datagrid/body/data_grid_body.tsx index c71f8745a94..14ecf7108cf 100644 --- a/packages/eui/src/components/datagrid/body/data_grid_body.tsx +++ b/packages/eui/src/components/datagrid/body/data_grid_body.tsx @@ -8,9 +8,11 @@ import React, { FunctionComponent } from 'react'; +import { useEuiMemoizedStyles } from '../../../services'; import { EuiDataGridBodyProps } from '../data_grid_types'; import { EuiDataGridBodyVirtualized } from './data_grid_body_virtualized'; import { EuiDataGridBodyCustomRender } from './data_grid_body_custom'; +import { euiDataGridBodyStyles } from './data_grid_body.styles'; export const EuiDataGridBody: FunctionComponent = ({ renderCustomGridBody, @@ -21,12 +23,19 @@ export const EuiDataGridBody: FunctionComponent = ({ * + virtualization library for rendering content, or if consumers have * passed their own custom renderer */ + const styles = useEuiMemoizedStyles(euiDataGridBodyStyles); + const cssStyles = [ + styles.euiDataGridBody, + renderCustomGridBody ? styles.customRender : styles.virtualized, + ]; + return renderCustomGridBody ? ( ) : ( - + ); }; diff --git a/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx b/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx index bec60b9fd59..0100c23dfcf 100644 --- a/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx +++ b/packages/eui/src/components/datagrid/body/data_grid_body_custom.tsx @@ -50,6 +50,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< rowHeightsOptions, gridWidth, gridStyles, + className, }) => { /** * Columns & widths @@ -187,6 +188,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< {...customGridBodyProps} className={classNames( 'euiDataGrid__customRenderBody', + className, customGridBodyProps?.className )} > diff --git a/packages/eui/src/components/datagrid/body/data_grid_body_virtualized.tsx b/packages/eui/src/components/datagrid/body/data_grid_body_virtualized.tsx index aeaee32047c..fdca0eea89c 100644 --- a/packages/eui/src/components/datagrid/body/data_grid_body_virtualized.tsx +++ b/packages/eui/src/components/datagrid/body/data_grid_body_virtualized.tsx @@ -140,6 +140,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent gridRef, gridItemsRendered, wrapperRef, + className, }) => { /** * Grid refs & observers @@ -367,6 +368,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent ref={gridRef} className={classNames( 'euiDataGrid__virtualized', + className, virtualizationOptions?.className )} onItemsRendered={onItemsRendered} diff --git a/packages/eui/src/components/datagrid/controls/full_screen_selector.stories.tsx b/packages/eui/src/components/datagrid/controls/full_screen_selector.stories.tsx new file mode 100644 index 00000000000..788669f6074 --- /dev/null +++ b/packages/eui/src/components/datagrid/controls/full_screen_selector.stories.tsx @@ -0,0 +1,95 @@ +/* + * 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, { useState } from 'react'; +import { fireEvent, within } from '@storybook/test'; +import type { Meta, StoryObj, ReactRenderer } from '@storybook/react'; +import type { PlayFunctionContext } from '@storybook/csf'; +import { LOKI_SELECTORS } from '../../../../.storybook/loki'; + +import { EuiHeader } from '../../header'; +import { EuiPageTemplate } from '../../page_template'; +import { EuiFlyout } from '../../flyout'; +import { EuiDataGrid } from '../data_grid'; +import { EuiDataGridProps } from '../data_grid_types'; + +const meta: Meta = { + title: 'Tabular Content/EuiDataGrid/FullscreenVRT', + component: EuiDataGrid, +}; + +export default meta; +type Story = StoryObj; + +const dataGridProps: EuiDataGridProps = { + 'aria-label': 'Test', + columns: [{ id: 'Test' }], + rowCount: 100, + pagination: { + pageIndex: 0, + pageSize: 50, + pageSizeOptions: [1], + onChangePage: () => {}, + onChangeItemsPerPage: () => {}, + }, + renderCellValue: () => 'Test', + columnVisibility: { + visibleColumns: ['Test'], + setVisibleColumns: () => {}, + }, +}; + +export const FullScreenWithHeader: Story = { + tags: ['vrt-only'], + parameters: { + loki: { chromeSelector: LOKI_SELECTORS.body }, + }, + render: () => ( + <> + + + + + + ), + play: async ({ canvasElement }: PlayFunctionContext) => { + const canvas = within(canvasElement); + await fireEvent.click(canvas.getByLabelText('Enter fullscreen')); + }, +}; + +export const FullScreenWithFlyout: Story = { + ...FullScreenWithHeader, + tags: ['vrt-only'], + render: function Render() { + const [openFlyout, setOpenFlyout] = useState(true); + return ( + <> + + + ( + + )} + /> + + {openFlyout && ( + setOpenFlyout(false)}> + Flyout should not be below header in full screen mode + + )} + + ); + }, +}; diff --git a/packages/eui/src/components/datagrid/controls/fullscreen_selector.styles.ts b/packages/eui/src/components/datagrid/controls/fullscreen_selector.styles.ts new file mode 100644 index 00000000000..c5c00246190 --- /dev/null +++ b/packages/eui/src/components/datagrid/controls/fullscreen_selector.styles.ts @@ -0,0 +1,48 @@ +/* + * 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/css'; + +import { UseEuiTheme } from '../../../services'; +import { logicalCSS } from '../../../global_styling'; + +export const euiDataGridFullScreenStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + const fullScreenZIndex = Number(euiTheme.levels.header) - 1; + + return { + 'euiDataGrid--fullScreen': css` + z-index: ${fullScreenZIndex}; + position: fixed; + inset: 0; + background-color: ${euiTheme.colors.emptyShade}; + `, + // This is a vanilla className applied to the when fullscreen is enabled. + // It removes extra scrollbars + tweaks components to account for fixed headers + euiDataGrid__restrictBody: css` + ${logicalCSS('height', '100vh')} + overflow: hidden; + + .euiHeader[data-fixed-header] { + /* !important needed to override header inline styles */ + /* stylelint-disable-next-line declaration-no-important */ + z-index: ${fullScreenZIndex - 1} !important; + } + + .euiOverlayMask[data-relative-to-header='below'] { + ${logicalCSS('top', '0')} + } + + .euiFlyout { + ${logicalCSS('top', '0')} + ${logicalCSS('height', '100%')} + } + `, + }; +}; diff --git a/packages/eui/src/components/datagrid/controls/fullscreen_selector.test.tsx b/packages/eui/src/components/datagrid/controls/fullscreen_selector.test.tsx index f0dd674df29..a5f70d84ed2 100644 --- a/packages/eui/src/components/datagrid/controls/fullscreen_selector.test.tsx +++ b/packages/eui/src/components/datagrid/controls/fullscreen_selector.test.tsx @@ -106,4 +106,14 @@ describe('useDataGridFullScreenSelector', () => { ).toBe(false); }); }); + + describe('fullScreenStyles', () => { + it('returns an Emotion fullscreen className to apply to the EuiDataGrid', () => { + const { fullScreenStyles } = renderHook(() => + useDataGridFullScreenSelector() + ).result.current; + + expect(fullScreenStyles).toContain('euiDataGrid--fullScreen'); + }); + }); }); diff --git a/packages/eui/src/components/datagrid/controls/fullscreen_selector.tsx b/packages/eui/src/components/datagrid/controls/fullscreen_selector.tsx index d151e7f2f59..29c4b5f5eda 100644 --- a/packages/eui/src/components/datagrid/controls/fullscreen_selector.tsx +++ b/packages/eui/src/components/datagrid/controls/fullscreen_selector.tsx @@ -16,11 +16,13 @@ import React, { KeyboardEventHandler, } from 'react'; -import { keys } from '../../../services'; +import { keys, useEuiMemoizedStyles } from '../../../services'; import { EuiToolTip } from '../../tool_tip'; import { EuiButtonIcon } from '../../button'; import { useEuiI18n } from '../../i18n'; +import { euiDataGridFullScreenStyles } from './fullscreen_selector.styles'; + const GRID_IS_FULLSCREEN_CLASSNAME = 'euiDataGrid__restrictBody'; export const useDataGridFullScreenSelector = (): { @@ -28,6 +30,7 @@ export const useDataGridFullScreenSelector = (): { setIsFullScreen: (isFullScreen: boolean) => void; fullScreenSelector: ReactNode; handleGridKeyDown: KeyboardEventHandler; + fullScreenStyles: string; } => { const [isFullScreen, setIsFullScreen] = useState(false); @@ -80,21 +83,30 @@ export const useDataGridFullScreenSelector = (): { [isFullScreen] ); + const styles = useEuiMemoizedStyles(euiDataGridFullScreenStyles); + useEffect(() => { // When the data grid is fullscreen, we add a class to the body to remove the extra scrollbar and stay above any fixed headers if (isFullScreen) { - document.body.classList.add(GRID_IS_FULLSCREEN_CLASSNAME); + document.body.classList.add( + GRID_IS_FULLSCREEN_CLASSNAME, + styles.euiDataGrid__restrictBody + ); return () => { - document.body.classList.remove(GRID_IS_FULLSCREEN_CLASSNAME); + document.body.classList.remove( + GRID_IS_FULLSCREEN_CLASSNAME, + styles.euiDataGrid__restrictBody + ); }; } - }, [isFullScreen]); + }, [isFullScreen, styles.euiDataGrid__restrictBody]); return { isFullScreen, setIsFullScreen, fullScreenSelector, handleGridKeyDown, + fullScreenStyles: styles['euiDataGrid--fullScreen'], }; }; diff --git a/packages/eui/src/components/datagrid/data_grid.stories.tsx b/packages/eui/src/components/datagrid/data_grid.stories.tsx index 0496a9f23e9..8eca2c46a8b 100644 --- a/packages/eui/src/components/datagrid/data_grid.stories.tsx +++ b/packages/eui/src/components/datagrid/data_grid.stories.tsx @@ -226,6 +226,7 @@ type Story = StoryObj; export const Playground: Story = { args: { + 'aria-label': 'EuiDataGrid', columns, rowCount: 10, renderCellValue: RenderCellValue, @@ -307,6 +308,15 @@ export const Playground: Story = { }; enableFunctionToggleControls(Playground, ['onColumnResize']); +export const Virtualization: Story = { + args: { + ...Playground.args, + width: 300, + height: 300, + }, + render: (args: EuiDataGridProps) => , +}; + export const HeightLineCount: Story = { parameters: { controls: { diff --git a/packages/eui/src/components/datagrid/data_grid.styles.ts b/packages/eui/src/components/datagrid/data_grid.styles.ts new file mode 100644 index 00000000000..25637454272 --- /dev/null +++ b/packages/eui/src/components/datagrid/data_grid.styles.ts @@ -0,0 +1,41 @@ +/* + * 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'; +import { logicalCSS, logicalSizeCSS } from '../../global_styling'; + +export const euiDataGridStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + return { + euiDataGrid: css` + display: flex; + flex-direction: column; + align-items: stretch; + ${logicalCSS('height', '100%')} + overflow: hidden; + `, + // Sits below the controls above it and pagination below it + euiDataGrid__content: css` + z-index: 1; + position: relative; + flex-grow: 1; + ${logicalSizeCSS('100%')} + ${logicalCSS('max-width', '100%')} + overflow: hidden; + background-color: ${euiTheme.colors.body}; + font-feature-settings: 'tnum' 1; /* Tabular numbers */ + `, + // Wrapper around EuiDataGrid + euiDataGrid__focusWrap: css` + ${logicalCSS('height', '100%')} + `, + }; +}; diff --git a/packages/eui/src/components/datagrid/data_grid.tsx b/packages/eui/src/components/datagrid/data_grid.tsx index ba5fa160971..89bd611a559 100644 --- a/packages/eui/src/components/datagrid/data_grid.tsx +++ b/packages/eui/src/components/datagrid/data_grid.tsx @@ -19,7 +19,7 @@ import { VariableSizeGrid as Grid, GridOnItemsRenderedProps, } from 'react-window'; -import { useGeneratedHtmlId } from '../../services'; +import { useGeneratedHtmlId, useEuiMemoizedStyles } from '../../services'; import { useEuiTablePaginationDefaults } from '../table/table_pagination'; import { EuiFocusTrap } from '../focus_trap'; import { EuiI18n, useEuiI18n } from '../i18n'; @@ -36,6 +36,7 @@ import { checkOrDefaultToolBarDisplayOptions, EuiDataGridToolbar, } from './controls'; +import { EuiDataGridPagination, shouldRenderPagination } from './pagination'; import { DataGridSortedContext, useSorting } from './utils/sorting'; import { DataGridFocusContext, @@ -49,7 +50,6 @@ import { } from './utils/in_memory'; import { DataGridCellPopoverContext, useCellPopover } from './body/cell'; import { computeVisibleRows } from './utils/row_count'; -import { EuiDataGridPaginationRenderer } from './utils/data_grid_pagination'; import { schemaDetectors as providedSchemaDetectors, useMergedSchema, @@ -67,6 +67,7 @@ import { EuiDataGridStyleHeader, EuiDataGridStyleRowHover, } from './data_grid_types'; +import { euiDataGridStyles } from './data_grid.styles'; // Each gridStyle object above sets a specific CSS select to .euiGrid const fontSizesToClassMap: { [size in EuiDataGridStyleFontSizes]: string } = { @@ -152,6 +153,8 @@ export const EuiDataGrid = memo( } : _pagination; }, [_pagination, paginationDefaults]); + const showPagination = + pagination && shouldRenderPagination(rowCount, pagination); const gridStyleWithDefaults = useMemo( () => ({ ...startingStyles, ...gridStyle }), @@ -317,6 +320,7 @@ export const EuiDataGrid = memo( setIsFullScreen, fullScreenSelector, handleGridKeyDown, + fullScreenStyles, } = useDataGridFullScreenSelector(); /** @@ -354,6 +358,7 @@ export const EuiDataGrid = memo( }, { 'euiDataGrid--fullScreen': isFullScreen, + [fullScreenStyles]: isFullScreen, }, { 'euiDataGrid--noControls': !toolbarVisibility, @@ -423,6 +428,8 @@ export const EuiDataGrid = memo( ] ); + const styles = useEuiMemoizedStyles(euiDataGridStyles); + return ( @@ -430,8 +437,10 @@ export const EuiDataGrid = memo(
- {pagination && props['aria-labelledby'] && ( + {showPagination && props['aria-labelledby'] && ( )} - {pagination && ( - ; gridItemsRendered: MutableRefObject; wrapperRef: MutableRefObject; + className?: string; } export interface EuiDataGridCustomBodyProps { diff --git a/packages/eui/src/components/datagrid/pagination/data_grid_pagination.styles.ts b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.styles.ts new file mode 100644 index 00000000000..006f5033efc --- /dev/null +++ b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.styles.ts @@ -0,0 +1,29 @@ +/* + * 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'; +import { logicalCSS } from '../../../global_styling'; + +export const euiDataGridPaginationStyles = ({ euiTheme }: UseEuiTheme) => ({ + euiDataGrid__pagination: css` + z-index: 2; /* Sits above the content above it */ + flex-grow: 0; + ${logicalCSS('padding-top', euiTheme.size.xs)} + + .euiDataGrid--fullScreen & { + ${logicalCSS('padding-bottom', euiTheme.size.xs)} + background-color: ${euiTheme.colors.lightestShade}; + + /* Use box-shadow instead of border-top to avoid duplicating the border-bottom on grid cells */ + box-shadow: ${euiTheme.border.width.thin} 0 0 + ${euiTheme.border.width.thin} ${euiTheme.border.color}; + } + `, +}); diff --git a/packages/eui/src/components/datagrid/utils/data_grid_pagination.test.tsx b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.test.tsx similarity index 57% rename from packages/eui/src/components/datagrid/utils/data_grid_pagination.test.tsx rename to packages/eui/src/components/datagrid/pagination/data_grid_pagination.test.tsx index 2c176b702ed..8ca16f97050 100644 --- a/packages/eui/src/components/datagrid/utils/data_grid_pagination.test.tsx +++ b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.test.tsx @@ -10,17 +10,34 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; -import { DataGridFocusContext } from './focus'; -import { mockFocusContext } from './__mocks__/focus_context'; -import { EuiDataGridPaginationRenderer } from './data_grid_pagination'; +import { DataGridFocusContext } from '../utils/focus'; +import { mockFocusContext } from '../utils/__mocks__/focus_context'; +import { + shouldRenderPagination, + EuiDataGridPagination, +} from './data_grid_pagination'; -describe('EuiDataGridPaginationRenderer', () => { +const mockPagination = { + pageIndex: 0, + pageSize: 25, + pageSizeOptions: [25], + onChangePage: () => {}, + onChangeItemsPerPage: () => {}, +}; + +describe('shouldRenderPagination', () => { + it('returns false if there are fewer rows than the smallest page size option', () => { + expect(shouldRenderPagination(2, mockPagination)).toEqual(false); + }); + + it('returns true if there are more rows than the current or smallest page size', () => { + expect(shouldRenderPagination(30, mockPagination)).toEqual(true); + }); +}); + +describe('EuiDataGridPagination', () => { const props = { - pageIndex: 0, - pageSize: 25, - pageSizeOptions: [25], - onChangePage: jest.fn(), - onChangeItemsPerPage: jest.fn(), + ...mockPagination, rowCount: 100, controls: 'data-grid-id', }; @@ -29,7 +46,7 @@ describe('EuiDataGridPaginationRenderer', () => { it('renders', () => { const { container, getByText } = render( - + ); expect(container.firstChild).toHaveClass('euiDataGrid__pagination'); fireEvent.click(getByText('Rows per page: 25')); @@ -39,7 +56,7 @@ describe('EuiDataGridPaginationRenderer', () => { it('renders a detailed aria-label', () => { const { getAllByLabelText } = render( - + ); expect( getAllByLabelText('Pagination for preceding grid: Test Grid') @@ -48,7 +65,7 @@ describe('EuiDataGridPaginationRenderer', () => { it('hides the page size selection if pageSizeOptions is empty', () => { const { queryByTestSubject, queryByText } = render( - + ); expect(queryByTestSubject('tablePaginationPopoverButton')).toBeFalsy(); expect(queryByText('Rows per page:')).toBeFalsy(); @@ -56,7 +73,7 @@ describe('EuiDataGridPaginationRenderer', () => { it('handles the "show all" page size option', () => { const { getByText } = render( - { expect(getByText('Showing all rows')).toBeTruthy(); }); - it('does not render if there are fewer rows than the smallest page size option', () => { - const { container } = render( - - ); - expect(container).toBeEmptyDOMElement(); - }); - it('focuses the first data cell on page change', () => { const { getByTestSubject } = render( - + ); fireEvent.click(getByTestSubject('pagination-button-2')); expect(mockFocusContext.setFocusedCell).toHaveBeenCalledWith([0, 0]); }); - - describe('EuiProvider component defaults', () => { - it('falls back to EuiTablePagination defaults if pageSize and pageSizeOptions are undefined', () => { - const { getByText } = render( - - ); - fireEvent.click(getByText('Rows per page: 10')); - expect(getByText('10 rows')).toBeTruthy(); - expect(getByText('25 rows')).toBeTruthy(); - expect(getByText('50 rows')).toBeTruthy(); - }); - }); }); diff --git a/packages/eui/src/components/datagrid/utils/data_grid_pagination.tsx b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.tsx similarity index 55% rename from packages/eui/src/components/datagrid/utils/data_grid_pagination.tsx rename to packages/eui/src/components/datagrid/pagination/data_grid_pagination.tsx index 25ed1771b17..1a34dfedaa9 100644 --- a/packages/eui/src/components/datagrid/utils/data_grid_pagination.tsx +++ b/packages/eui/src/components/datagrid/pagination/data_grid_pagination.tsx @@ -6,32 +6,47 @@ * Side Public License, v 1. */ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback, useContext, AriaAttributes } from 'react'; -import { useEuiI18n } from '../../i18n'; // Note: this file must be named data_grid_pagination to match i18n tokens -import { - EuiTablePagination, - useEuiTablePaginationDefaults, -} from '../../table/table_pagination'; -import { - EuiDataGridPaginationProps, - EuiDataGridPaginationRendererProps, -} from '../data_grid_types'; -import { DataGridFocusContext } from './focus'; +import { useEuiMemoizedStyles } from '../../../services'; +import { useEuiI18n } from '../../i18n'; +import { EuiTablePagination } from '../../table/table_pagination'; +import { EuiDataGridPaginationProps } from '../data_grid_types'; +import { DataGridFocusContext } from '../utils/focus'; -export const EuiDataGridPaginationRenderer = ({ +import { euiDataGridPaginationStyles } from './data_grid_pagination.styles'; + +type _EuiDataGridPaginationProps = Required & { + // Internal EUI props + rowCount: number; + controls: string; + 'aria-label'?: AriaAttributes['aria-label']; +}; + +/** + * Do not render the pagination when: + * 1. Rows count is less than min pagination option (rows per page) + * 2. Rows count is less than pageSize (the case when there are no pageSizeOptions provided) + */ +export const shouldRenderPagination = ( + rowCount: number, + { pageSize, pageSizeOptions }: Required +) => { + const minSizeOption = [...pageSizeOptions].sort((a, b) => a - b)[0]; + return !(rowCount < (minSizeOption || pageSize)); +}; + +export const EuiDataGridPagination = ({ pageIndex, - pageSize: _pageSize, - pageSizeOptions: _pageSizeOptions, + pageSize, + pageSizeOptions, onChangePage: _onChangePage, onChangeItemsPerPage, rowCount, controls, 'aria-label': ariaLabel, -}: EuiDataGridPaginationRendererProps) => { - const defaults = useEuiTablePaginationDefaults(); - const pageSize = _pageSize ?? defaults.itemsPerPage; - const pageSizeOptions = _pageSizeOptions ?? defaults.itemsPerPageOptions; +}: _EuiDataGridPaginationProps) => { + const styles = useEuiMemoizedStyles(euiDataGridPaginationStyles); const detailedPaginationLabel = useEuiI18n( 'euiDataGridPagination.detailedPaginationLabel', @@ -54,28 +69,18 @@ export const EuiDataGridPaginationRenderer = ({ ); const pageCount = pageSize ? Math.ceil(rowCount / pageSize) : 1; - const minSizeOption = [...pageSizeOptions].sort((a, b) => a - b)[0]; - - if (rowCount < (minSizeOption || pageSize)) { - /** - * Do not render the pagination when: - * 1. Rows count is less than min pagination option (rows per page) - * 2. Rows count is less than pageSize (the case when there are no pageSizeOptions provided) - */ - return null; - } - - // Hide select rows per page if pageSizeOptions is an empty array - const hidePerPageOptions = pageSizeOptions.length === 0; return ( -
+
0} pageCount={pageCount} onChangePage={onChangePage} onChangeItemsPerPage={onChangeItemsPerPage} diff --git a/packages/eui/src/components/datagrid/pagination/index.ts b/packages/eui/src/components/datagrid/pagination/index.ts new file mode 100644 index 00000000000..f143fd2926e --- /dev/null +++ b/packages/eui/src/components/datagrid/pagination/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export { + shouldRenderPagination, + EuiDataGridPagination, +} from './data_grid_pagination'; diff --git a/packages/eui/src/components/datagrid/utils/scrolling.styles.ts b/packages/eui/src/components/datagrid/utils/scrolling.styles.ts new file mode 100644 index 00000000000..640b1544c5e --- /dev/null +++ b/packages/eui/src/components/datagrid/utils/scrolling.styles.ts @@ -0,0 +1,56 @@ +/* + * 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'; +import { logicalCSS, mathWithUnits } from '../../../global_styling'; + +export const euiDataGridScrollBarStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + // Note that 'borders' *must* be rendered with inset box-shadow, because actual + // `border` CSS will affect the relative position of the child scroll bar overlays + // and cause them to be off by the width of the border + const borderWidth = euiTheme.border.width.thin; + const borderColor = euiTheme.border.color; + + return { + euiDataGrid__scrollOverlay: css` + position: absolute; + inset: 0; + ${logicalCSS('top', `-${borderWidth}`)} /* Overlaps the toolbar border */ + + /* Ensure the underlying grid is still interactable */ + pointer-events: none; + + /* Ensure the scrolling data grid body always has border edges regardless of cell position */ + box-shadow: inset 0 0 0 ${borderWidth} ${borderColor}; + + .euiDataGrid--bordersHorizontal & { + box-shadow: inset 0 -${mathWithUnits(borderWidth, (x) => x * 2)} 0 -${borderWidth} + ${borderColor}; + } + `, + // Ensure the horizontal scrollbar has a top border + euiDataGrid__scrollBarOverlayBottom: css` + position: absolute; + inset-inline: 0; + ${logicalCSS('height', borderWidth)} + background-color: ${borderColor}; + `, + // Ensure the vertical scrollbar has a left border + euiDataGrid__scrollBarOverlayRight: css` + position: absolute; + ${logicalCSS('width', borderWidth)} + background-color: ${borderColor}; + `, + // Note: Scroll bar border positions are set via JS inline style, since + // JS has access to the exact OS scrollbar width/height and CSS doesn't + }; +}; diff --git a/packages/eui/src/components/datagrid/utils/scrolling.test.tsx b/packages/eui/src/components/datagrid/utils/scrolling.test.tsx index e4c95d9583e..12b3c477003 100644 --- a/packages/eui/src/components/datagrid/utils/scrolling.test.tsx +++ b/packages/eui/src/components/datagrid/utils/scrolling.test.tsx @@ -465,7 +465,7 @@ describe('useScrollBars', () => { expect(container.firstChild).toMatchInlineSnapshot(`