diff --git a/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png new file mode 100644 index 00000000000..092ccb900cb Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png new file mode 100644 index 00000000000..6cef9e165a2 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Tabular_Content_EuiDataGrid_rowHeightsOptions_prop_Auto_Below_Line_Count.png differ diff --git a/packages/eui/changelogs/upcoming/8096.md b/packages/eui/changelogs/upcoming/8096.md new file mode 100644 index 00000000000..57f9615617b --- /dev/null +++ b/packages/eui/changelogs/upcoming/8096.md @@ -0,0 +1 @@ +- Updated `EuiDataGrid` with a beta `rowHeightsOptions.autoBelowLineCount` feature flag diff --git a/packages/eui/src/components/datagrid/body/cell/data_grid_cell.test.tsx b/packages/eui/src/components/datagrid/body/cell/data_grid_cell.test.tsx index b12992b8494..841f347407b 100644 --- a/packages/eui/src/components/datagrid/body/cell/data_grid_cell.test.tsx +++ b/packages/eui/src/components/datagrid/body/cell/data_grid_cell.test.tsx @@ -757,6 +757,26 @@ describe('EuiDataGridCell', () => { callMethod(component); expect(setRowHeight).not.toHaveBeenCalled(); }); + + it('does nothing if cell height is auto or autoBelowLineCount', () => { + mockRowHeightUtils.isAutoBelowLineCount.mockReturnValue(true); + + const component = mount( + + ); + + callMethod(component); + expect(setRowHeight).not.toHaveBeenCalled(); + + mockRowHeightUtils.isAutoBelowLineCount.mockRestore(); + }); }); }); @@ -816,6 +836,30 @@ describe('EuiDataGridCell', () => { expect(component.find('.eui-textBreakWord').exists()).toBe(true); expect(component.find('.euiTextBlockTruncate').exists()).toBe(true); }); + + test('autoBelowLineCount', () => { + mockRowHeightUtils.isAutoBelowLineCount.mockReturnValue(true); + + const component = mount( + + ); + + expect( + component + .find('div.euiDataGridRowCell__content--autoBelowLineCountHeight') + .hasClass(/autoHeight/) + ).toBe(true); + expect(component.find('.eui-textBreakWord').exists()).toBe(true); + expect(component.find('.euiTextBlockTruncate').exists()).toBe(true); + + mockRowHeightUtils.isAutoBelowLineCount.mockRestore(); + }); }); // Note: Tests for cell interactivity (focus, tabbing, etc) are in `focus_utils.spec.tsx` diff --git a/packages/eui/src/components/datagrid/body/cell/data_grid_cell.tsx b/packages/eui/src/components/datagrid/body/cell/data_grid_cell.tsx index 57592f9c0e8..013e4ccf17b 100644 --- a/packages/eui/src/components/datagrid/body/cell/data_grid_cell.tsx +++ b/packages/eui/src/components/datagrid/body/cell/data_grid_cell.tsx @@ -69,7 +69,7 @@ const EuiDataGridCellContent: FunctionComponent< setCellContentsRef, rowIndex, colIndex, - rowHeight, + rowHeightsOptions, rowHeightUtils, isControlColumn, ...rest @@ -78,11 +78,18 @@ const EuiDataGridCellContent: FunctionComponent< const CellElement = renderCellValue as JSXElementConstructor; - const cellHeightType = useMemo( - () => rowHeightUtils?.getHeightType(rowHeight) || 'default', - [rowHeightUtils, rowHeight] + // Cell height type + const rowHeight = rowHeightUtils?.getRowHeightOption( + rowIndex, + rowHeightsOptions ); + const cellHeightType = useMemo(() => { + return rowHeightUtils?.isAutoBelowLineCount(rowHeightsOptions, rowHeight) + ? 'autoBelowLineCount' + : rowHeightUtils?.getHeightType(rowHeight) || 'default'; + }, [rowHeightUtils, rowHeight, rowHeightsOptions]); + // Classes and styles const classes = useMemo( () => classNames( @@ -104,7 +111,7 @@ const EuiDataGridCellContent: FunctionComponent< : [ // Regular data cells should always inherit height from the row wrapper, // except for auto height - cellHeightType === 'auto' + cellHeightType === 'auto' || cellHeightType === 'autoBelowLineCount' ? styles.content.autoHeight : styles.content.defaultHeight, ]), @@ -113,7 +120,9 @@ const EuiDataGridCellContent: FunctionComponent< return ( @@ -201,6 +210,12 @@ export class EuiDataGridCell extends Component< rowIndex, rowHeightsOptions ); + if ( + rowHeightUtils?.isAutoBelowLineCount(rowHeightsOptions, rowHeightOption) + ) { + return; // Using auto height instead + } + const isSingleLine = rowHeightOption == null; // Undefined rowHeightsOptions default to a single line const lineCount = isSingleLine ? 1 @@ -585,11 +600,6 @@ export class EuiDataGridCell extends Component< ...cellPropsStyle, // apply anything from setCellProps({ style }) }; - const rowHeight = rowHeightUtils?.getRowHeightOption( - rowIndex, - rowHeightsOptions - ); - const row = rowManager && !IS_JEST_ENVIRONMENT ? rowManager.getRow({ @@ -628,7 +638,7 @@ export class EuiDataGridCell extends Component< isExpandable={isExpandable} isExpanded={popoverIsOpen} setCellContentsRef={this.setCellContentsRef} - rowHeight={rowHeight} + rowHeightsOptions={rowHeightsOptions} rowHeightUtils={rowHeightUtils} isControlColumn={isControlColumn} rowIndex={rowIndex} 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 3093b852ad5..3b93a598a35 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 @@ -84,11 +84,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent gridItemsRenderedRef: gridItemsRendered, }, rowHeightsOptions, - gridStyles, columns, }); diff --git a/packages/eui/src/components/datagrid/controls/display_selector.test.tsx b/packages/eui/src/components/datagrid/controls/display_selector.test.tsx index d6b0374ab80..964af8eb30e 100644 --- a/packages/eui/src/components/datagrid/controls/display_selector.test.tsx +++ b/packages/eui/src/components/datagrid/controls/display_selector.test.tsx @@ -266,9 +266,19 @@ describe('useDataGridDisplaySelector', () => { expect(getSelection(baseElement, 'rowHeightButtonGroup')).toEqual( 'static' ); + expect(getByTestSubject('static')).toHaveTextContent('Static'); expect(getByTestSubject('lineCountNumber')).toHaveValue(1); }); + it('renders a "Max" label instead of "Static" if autoBelowLineCount is true', async () => { + const { container, getByTestSubject } = render( + + ); + openPopover(container); + + expect(getByTestSubject('static')).toHaveTextContent('Max'); + }); + it('calls the rowHeightsOptions.onChange callback on user change', async () => { const onRowHeightChange = jest.fn(); const { container, baseElement, getByTestSubject } = render( diff --git a/packages/eui/src/components/datagrid/controls/display_selector.tsx b/packages/eui/src/components/datagrid/controls/display_selector.tsx index 9016dd7af39..34f028950b5 100644 --- a/packages/eui/src/components/datagrid/controls/display_selector.tsx +++ b/packages/eui/src/components/datagrid/controls/display_selector.tsx @@ -162,6 +162,8 @@ const RowHeightControl = ({ rowHeightsOptions: EuiDataGridRowHeightsOptions; onChange: Function; }) => { + const { autoBelowLineCount } = rowHeightsOptions; + const [lineCountInput, setLineCountInput] = useState(1); const setLineCountHeight = useCallback( (event: ChangeEvent) => { @@ -227,10 +229,11 @@ const RowHeightControl = ({ 'euiDisplaySelector.rowHeightLabel', 'euiDisplaySelector.labelAuto', 'euiDisplaySelector.labelStatic', + 'euiDisplaySelector.labelMax', ]} - defaults={['Lines per row', 'Auto', 'Static']} + defaults={['Lines per row', 'Auto', 'Static', 'Max']} > - {([rowHeightLabel, labelAuto, labelStatic]: string[]) => ( + {([rowHeightLabel, labelAuto, labelStatic, labelMax]: string[]) => ( { } /* Workaround to trim line-clamp and padding - @see https://github.com/elastic/eui/issues/7780 */ - .euiDataGridRowCell__content--lineCountHeight { + .euiDataGridRowCell__content--lineCountHeight, + .euiDataGridRowCell__content--autoBelowLineCountHeight { ${logicalCSS('padding-bottom', 0)} ${logicalCSS( 'border-bottom', diff --git a/packages/eui/src/components/datagrid/data_grid_row_heights.stories.tsx b/packages/eui/src/components/datagrid/data_grid_row_heights.stories.tsx index 4b69a84b8d3..5100b100c95 100644 --- a/packages/eui/src/components/datagrid/data_grid_row_heights.stories.tsx +++ b/packages/eui/src/components/datagrid/data_grid_row_heights.stories.tsx @@ -80,6 +80,27 @@ export const LineCount1: Story = { ), }; +import { faker } from '@faker-js/faker'; +faker.seed(42); +const loremData = Array.from({ length: 5 }).map((_, i) => + faker.lorem.lines(i % 2 === 0 ? 1 : 20) +); + +export const AutoBelowLineCount: Story = { + args: { + autoBelowLineCount: true, + defaultHeight: { lineCount: 3 }, + }, + render: (rowHeightsOptions) => ( + loremData[rowIndex]} + columns={[{ id: 'name' }, { id: 'location' }]} + /> + ), +}; + export const StaticHeight: Story = { args: { defaultHeight: { height: 48 }, diff --git a/packages/eui/src/components/datagrid/data_grid_types.ts b/packages/eui/src/components/datagrid/data_grid_types.ts index 34f7921bd54..acac707bf34 100644 --- a/packages/eui/src/components/datagrid/data_grid_types.ts +++ b/packages/eui/src/components/datagrid/data_grid_types.ts @@ -1108,6 +1108,14 @@ export interface EuiDataGridRowHeightsOptions { * Defines the default size for all rows. It can be line count or just height. */ defaultHeight?: EuiDataGridRowHeightOption; + /** + * Feature flag for custom `lineCount` behavior, where `lineCount` acts like a + * *max* number of lines (instead of a set number of lines for all rows). + * + * This functionality is in beta and has performance implications; + * we do not yet fully recommend/support it for heavy production usage. + */ + autoBelowLineCount?: boolean; /** * Defines the height for a specific row. It can be line count or just height. * diff --git a/packages/eui/src/components/datagrid/utils/__mocks__/row_heights.ts b/packages/eui/src/components/datagrid/utils/__mocks__/row_heights.ts index 2fef7caa05b..3ff7283eca0 100644 --- a/packages/eui/src/components/datagrid/utils/__mocks__/row_heights.ts +++ b/packages/eui/src/components/datagrid/utils/__mocks__/row_heights.ts @@ -24,6 +24,7 @@ export const RowHeightUtils = jest.fn().mockImplementation(() => { const rowHeightUtilsMock: RowHeightUtilsPublicAPI = { getHeightType: jest.fn(rowHeightUtils.getHeightType), + isAutoBelowLineCount: jest.fn(() => false), isAutoHeight: jest.fn(() => false), setRowHeight: jest.fn(), pruneHiddenColumnHeights: jest.fn(), diff --git a/packages/eui/src/components/datagrid/utils/grid_height_width.ts b/packages/eui/src/components/datagrid/utils/grid_height_width.ts index 7c66fe64d90..686acc38323 100644 --- a/packages/eui/src/components/datagrid/utils/grid_height_width.ts +++ b/packages/eui/src/components/datagrid/utils/grid_height_width.ts @@ -141,7 +141,7 @@ export const useUnconstrainedHeight = ({ rowHeightOption, defaultRowHeight, correctRowIndex, - rowHeightUtils.isRowHeightOverride(correctRowIndex, rowHeightsOptions) + rowHeightsOptions ); } } diff --git a/packages/eui/src/components/datagrid/utils/row_heights.test.ts b/packages/eui/src/components/datagrid/utils/row_heights.test.ts index 988c71edc78..5ee8e2b71bf 100644 --- a/packages/eui/src/components/datagrid/utils/row_heights.test.ts +++ b/packages/eui/src/components/datagrid/utils/row_heights.test.ts @@ -9,7 +9,6 @@ import type { MutableRefObject } from 'react'; import { act } from '@testing-library/react'; import { renderHook } from '../../../test/rtl'; -import { startingStyles } from '../controls'; import type { ImperativeGridApi } from '../data_grid_types'; import { RowHeightUtils, @@ -108,6 +107,23 @@ describe('RowHeightUtils', () => { }); }); + describe('autoBelowLineCount', () => { + it('uses the auto height cache', () => { + const rowIndex = 3; + const autoRowHeight = 100; + rowHeightUtils.setRowHeight(rowIndex, 'a', autoRowHeight, 0); + + expect( + rowHeightUtils.getCalculatedHeight( + { lineCount: 10 }, + 34, + rowIndex, + { rowHeights: { [rowIndex]: autoRowHeight } } + ) + ).toEqual(autoRowHeight); + }); + }); + describe('row-specific overrides', () => { it('returns the height set in the cache', () => { const rowIndex = 5; @@ -119,7 +135,7 @@ describe('RowHeightUtils', () => { { lineCount: 10 }, 34, rowIndex, - true + { rowHeights: { [rowIndex]: rowHeightOverride } } ) ).toEqual(rowHeightOverride); }); @@ -223,6 +239,56 @@ describe('RowHeightUtils', () => { ); // 5 * 24 + 6 + 6 }); }); + + describe('isAutoBelowLineCount', () => { + it('returns true when the feature flag is enabled and a lineCount above 1 exists', () => { + expect( + rowHeightUtils.isAutoBelowLineCount( + { autoBelowLineCount: true }, + { lineCount: 3 } + ) + ).toEqual(true); + }); + + it('returns false if the feature flag is not enabled', () => { + expect( + rowHeightUtils.isAutoBelowLineCount( + { autoBelowLineCount: false }, + { lineCount: 3 } + ) + ).toEqual(false); + expect( + rowHeightUtils.isAutoBelowLineCount(undefined, { lineCount: 3 }) + ).toEqual(false); + }); + + it('returns false if height type is not lineCount', () => { + expect( + rowHeightUtils.isAutoBelowLineCount({ autoBelowLineCount: true }, 50) + ).toEqual(false); + expect( + rowHeightUtils.isAutoBelowLineCount( + { autoBelowLineCount: true }, + 'auto' + ) + ).toEqual(false); + }); + + it('returns false if lineCount is 1 (treated as single line/undefined)', () => { + expect( + rowHeightUtils.isAutoBelowLineCount( + { autoBelowLineCount: true }, + { lineCount: 1 } + ) + ).toEqual(false); + expect( + rowHeightUtils.isAutoBelowLineCount( + { autoBelowLineCount: true }, + undefined + ) + ).toEqual(false); + }); + }); }); describe('auto height utils', () => { @@ -240,6 +306,15 @@ describe('RowHeightUtils', () => { ).toEqual(true); }); + it('returns true if the conditions for `.isAutoBelowLineCount` are met', () => { + expect( + rowHeightUtils.isAutoHeight(1, { + autoBelowLineCount: true, + defaultHeight: { lineCount: 2 }, + }) + ).toEqual(true); + }); + it('returns false otherwise', () => { expect( rowHeightUtils.isAutoHeight(1, { @@ -539,10 +614,8 @@ describe('RowHeightVirtualizationUtils', () => { }); describe('useRowHeightUtils', () => { - const mockArgs = { - gridStyles: startingStyles, + const mockArgs: Parameters[0] = { columns: [{ id: 'A' }, { id: 'B' }], - rowHeightOptions: undefined, }; const mockVirtualizationArgs = { ...mockArgs, diff --git a/packages/eui/src/components/datagrid/utils/row_heights.ts b/packages/eui/src/components/datagrid/utils/row_heights.ts index 57f415e11e4..aa02bd8a5ab 100644 --- a/packages/eui/src/components/datagrid/utils/row_heights.ts +++ b/packages/eui/src/components/datagrid/utils/row_heights.ts @@ -22,7 +22,6 @@ import { EuiDataGridRowHeightOption, EuiDataGridRowHeightsOptions, EuiDataGridScrollAnchorRow, - EuiDataGridStyle, ImperativeGridApi, } from '../data_grid_types'; import { DataGridSortedContext } from './sorting'; @@ -54,7 +53,7 @@ export class RowHeightUtils { heightOption: EuiDataGridRowHeightOption, defaultHeight: number, rowIndex?: number, - isRowHeightOverride?: boolean + rowHeightsOptions?: EuiDataGridRowHeightsOptions ) { if (isObject(heightOption) && heightOption.height) { return Math.max(heightOption.height, defaultHeight); @@ -65,8 +64,13 @@ export class RowHeightUtils { } if (isObject(heightOption) && heightOption.lineCount) { - if (isRowHeightOverride) { - return this.getRowHeight(rowIndex!) || defaultHeight; // lineCount overrides are stored in the heights cache + const { autoBelowLineCount } = rowHeightsOptions || {}; // uses auto height cache + const isRowHeightOverride = // lineCount overrides are stored in the heights cache + rowIndex != null && + this.isRowHeightOverride(rowIndex, rowHeightsOptions); + + if (autoBelowLineCount || isRowHeightOverride) { + return this.getRowHeight(rowIndex!) || defaultHeight; } else { return defaultHeight; // default lineCount height is set in minRowHeight state in grid_row_body } @@ -115,6 +119,15 @@ export class RowHeightUtils { return contentHeight + padding * 2; } + isAutoBelowLineCount( + options?: EuiDataGridRowHeightsOptions, + option?: EuiDataGridRowHeightOption + ) { + if (!options?.autoBelowLineCount) return false; + if ((this.getLineCount(option) ?? 0) > 1) return true; + return false; + } + /** * Auto height utils */ @@ -128,6 +141,9 @@ export class RowHeightUtils { if (height === AUTO_HEIGHT) { return true; } + if (this.isAutoBelowLineCount(rowHeightsOptions, height)) { + return true; + } return false; } @@ -310,7 +326,6 @@ export const useRowHeightUtils = ({ gridItemsRenderedRef: MutableRefObject; }; rowHeightsOptions?: EuiDataGridRowHeightsOptions; - gridStyles: EuiDataGridStyle; columns: EuiDataGridColumn[]; }) => { const forceRenderRef = useLatest(useForceRender()); @@ -393,7 +408,7 @@ export const useDefaultRowHeight = ({ rowHeightOption, minRowHeight, correctRowIndex, - rowHeightUtils.isRowHeightOverride(correctRowIndex, rowHeightsOptions) + rowHeightsOptions ); }