diff --git a/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Playground.png b/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Playground.png new file mode 100644 index 00000000000..bf3ea364546 Binary files /dev/null and b/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_Playground.png differ diff --git a/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Playground.png b/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Playground.png new file mode 100644 index 00000000000..fb260487de8 Binary files /dev/null and b/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_Playground.png differ diff --git a/changelogs/upcoming/7720.md b/changelogs/upcoming/7720.md new file mode 100644 index 00000000000..faf548b583b --- /dev/null +++ b/changelogs/upcoming/7720.md @@ -0,0 +1,4 @@ +**Bug fixes** + +- Fixed missing styles on header cells of `EuiDataGrid` that prevented content text alignment styles to apply + diff --git a/src/components/datagrid/body/header/_data_grid_header_row.scss b/src/components/datagrid/body/header/_data_grid_header_row.scss index 92169af2883..a32a8a8889e 100644 --- a/src/components/datagrid/body/header/_data_grid_header_row.scss +++ b/src/components/datagrid/body/header/_data_grid_header_row.scss @@ -28,6 +28,10 @@ @include euiDataGridCellFocus; } + .euiDataGridHeaderCell__content { + flex-grow: 1; // ensures content stretches and allows for manual layout styles to apply + } + // We only truncate if the cell is not a control column. &:not(.euiDataGridHeaderCell--controlColumn) { .euiDataGridHeaderCell__button { @@ -42,6 +46,8 @@ .euiDataGridHeaderCell__content { @include euiTextTruncate; + + text-align: left; // overwrites inherited 'center' styles from button } .euiDataGridHeaderCell__sortingArrow { @@ -76,7 +82,6 @@ &.euiDataGridHeaderCell--numeric, &.euiDataGridHeaderCell--currency { .euiDataGridHeaderCell__content { - flex-grow: 1; text-align: right; } } diff --git a/src/components/datagrid/controls/data_grid_toolbar.tsx b/src/components/datagrid/controls/data_grid_toolbar.tsx index f00722d1014..2cd96ed3ecb 100644 --- a/src/components/datagrid/controls/data_grid_toolbar.tsx +++ b/src/components/datagrid/controls/data_grid_toolbar.tsx @@ -18,7 +18,7 @@ import { EuiScreenReaderOnly } from '../../accessibility'; import { IS_JEST_ENVIRONMENT } from '../../../utils'; // When below this number the grid only shows the right control icon buttons -const MINIMUM_WIDTH_FOR_GRID_CONTROLS = 479; +export const MINIMUM_WIDTH_FOR_GRID_CONTROLS = 479; export const EuiDataGridToolbar = ({ gridWidth, diff --git a/src/components/datagrid/data_grid.stories.tsx b/src/components/datagrid/data_grid.stories.tsx new file mode 100644 index 00000000000..aa2e4f04cd6 --- /dev/null +++ b/src/components/datagrid/data_grid.stories.tsx @@ -0,0 +1,353 @@ +/* + * 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, { useCallback, useEffect, useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { faker } from '@faker-js/faker'; + +import { enableFunctionToggleControls } from '../../../.storybook/utils'; +import { EuiLink } from '../link'; +import { EuiScreenReaderOnly } from '../accessibility'; +import { EuiButtonIcon } from '../button'; + +import { DEFAULT_ROW_HEIGHT } from './utils/row_heights'; +import { MINIMUM_WIDTH_FOR_GRID_CONTROLS } from './controls/data_grid_toolbar'; +import type { + EuiDataGridCellValueElementProps, + EuiDataGridColumnCellActionProps, + EuiDataGridColumnSortingConfig, + EuiDataGridProps, +} from './data_grid_types'; +import { EuiDataGrid } from './data_grid'; + +const dataKeys = [ + 'name', + 'email', + 'account', + 'location', + 'date', + 'version', +] as const; +const raw_data = Array.from({ length: 10 }).map(() => { + const email = faker.internet.email(); + const name = `${faker.person.lastName()}, ${faker.person.firstName()}`; + const suffix = faker.person.suffix(); + return { + name: { + formatted: `${name} ${suffix}`, + raw: name, + }, + email: { + formatted: {faker.internet.email()}, + raw: email, + }, + location: ( + <> + {`${faker.location.city()}, `} + {faker.location.country()} + + ), + date: `${faker.date.past()}`, + account: faker.finance.accountNumber(), + version: faker.system.semver(), + }; +}); + +const columns = [ + { + id: 'name', + displayAsText: 'Name', + defaultSortDirection: 'asc' as const, + cellActions: [ + ({ rowIndex, Component }: EuiDataGridColumnCellActionProps) => { + const data = raw_data; + const value = data[rowIndex].name.raw; + return ( + alert(`Hi ${value}`)} + iconType="heart" + aria-label={`Say hi to ${value}!`} + > + Say hi + + ); + }, + ], + }, + { + id: 'email', + displayAsText: 'Email address', + initialWidth: 130, + cellActions: [ + ({ rowIndex, Component }: EuiDataGridColumnCellActionProps) => { + const data = raw_data; + const value = data[rowIndex].email.raw; + return ( + alert(value)} + iconType="email" + aria-label={`Send email to ${value}`} + > + Send email + + ); + }, + ], + }, + { + id: 'location', + displayAsText: 'Location', + }, + { + id: 'account', + displayAsText: 'Account', + actions: { + showHide: { label: 'Custom hide label' }, + showMoveLeft: false, + showMoveRight: false, + additional: [ + { + label: 'Custom action', + onClick: () => {}, + iconType: 'cheer', + size: 'xs' as const, + color: 'text' as const, + }, + ], + }, + cellActions: [ + ({ + rowIndex, + Component, + isExpanded, + }: EuiDataGridColumnCellActionProps) => { + const data = raw_data; + const value = data[rowIndex].account; + const onClick = isExpanded + ? () => alert(`Sent money to ${value} when expanded`) + : () => alert(`Sent money to ${value} when not expanded`); + return ( + + Send money + + ); + }, + ], + }, + { + id: 'date', + displayAsText: 'Date', + defaultSortDirection: 'desc' as const, + }, + { + id: 'version', + displayAsText: 'Version', + defaultSortDirection: 'desc' as const, + initialWidth: 70, + isResizable: false, + actions: false as const, + }, +]; + +const RenderCellValue = ({ + rowIndex, + columnId, +}: EuiDataGridCellValueElementProps) => { + const data = raw_data; + const row = data[rowIndex]; + const columnName = columnId as (typeof dataKeys)[number]; + const column = row[columnName]; + + const getFormatted = () => { + if (typeof column === 'object') { + const hasFormatted = 'formatted' in column; + + return hasFormatted ? column.formatted : column; + } + + return typeof column === 'string' ? column : null; + }; + + return data.hasOwnProperty(rowIndex) ? getFormatted() : null; +}; + +const meta: Meta = { + title: 'Tabular Content/EuiDataGrid', + component: EuiDataGrid, + argTypes: { + width: { control: 'text' }, + height: { control: 'text' }, + }, + args: { + minSizeForControls: MINIMUM_WIDTH_FOR_GRID_CONTROLS, + }, +}; +enableFunctionToggleControls(meta, ['onColumnResize']); + +export default meta; +type Story = StoryObj; + +export const Playground: Story = { + args: { + columns, + rowCount: 10, + renderCellValue: RenderCellValue, + trailingControlColumns: [ + { + id: 'trailing-actions', + width: 40, + headerCellRender: () => ( + + Trailing actions + + ), + rowCellRender: () => , + }, + ], + leadingControlColumns: [ + { + id: 'leading-actions', + width: 40, + headerCellRender: () => ( + + Leading actions + + ), + rowCellRender: () => , + }, + ], + // setup for easier testing/QA + columnVisibility: { + visibleColumns: [ + 'name', + 'email', + 'account', + 'location', + 'date', + 'amount', + 'phone', + 'version', + ], + setVisibleColumns: () => {}, + }, + inMemory: { level: 'sorting' }, + pagination: { + pageIndex: 0, + pageSize: 10, + pageSizeOptions: [10, 20, 50], + onChangeItemsPerPage: () => {}, + onChangePage: () => {}, + }, + gridStyle: { + fontSize: 'm', + cellPadding: 'm', + border: 'all', + stripes: false, + header: 'shade', + footer: 'overline', + stickyFooter: true, + rowHover: 'highlight', + rowClasses: {}, + }, + width: '', + height: '', + toolbarVisibility: { + showColumnSelector: true, + showDisplaySelector: true, + showSortSelector: true, + showKeyboardShortcuts: true, + showFullScreenSelector: true, + additionalControls: null, + }, + rowHeightsOptions: { + defaultHeight: DEFAULT_ROW_HEIGHT, + rowHeights: {}, + lineHeight: undefined, + scrollAnchorRow: undefined, + }, + }, + render: (args: EuiDataGridProps) => , +}; + +const StatefulDataGrid = (props: EuiDataGridProps) => { + const { pagination, sorting, columnVisibility, ...rest } = props; + + // Pagination + const [_pagination, setPagination] = useState({ + pageIndex: pagination?.pageIndex ?? 0, + ...pagination, + }); + const onChangeItemsPerPage = useCallback( + (pageSize: number) => + setPagination((pagination) => ({ + ...pagination, + pageSize, + pageIndex: 0, + })), + [setPagination] + ); + const onChangePage = useCallback( + (pageIndex: number) => + setPagination((pagination) => ({ ...pagination, pageIndex })), + [setPagination] + ); + + useEffect(() => { + if (pagination) { + setPagination((curentPagination) => ({ + ...curentPagination, + ...pagination, + })); + } + }, [pagination]); + + // Sorting + const [sortingColumns, setSortingColumns] = useState< + EuiDataGridColumnSortingConfig[] + >(sorting?.columns ?? []); + const onSort = useCallback( + (sortingColumns: EuiDataGridColumnSortingConfig[]) => { + setSortingColumns(sortingColumns); + }, + [setSortingColumns] + ); + + useEffect(() => { + if (sorting && Array.isArray(sorting.columns)) { + setSortingColumns(sorting.columns); + } + }, [sorting]); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState( + columnVisibility?.visibleColumns ?? columns.map(({ id }) => id) // initialize to the full set of columns + ); + + useEffect(() => { + if (columnVisibility?.visibleColumns != null) { + setVisibleColumns(columnVisibility?.visibleColumns); + } + }, [columnVisibility]); + + return ( + + ); +};