diff --git a/src-docs/src/views/tables/auto/auto.tsx b/src-docs/src/views/tables/auto/auto.tsx index 0344bf816b0..80c861df198 100644 --- a/src-docs/src/views/tables/auto/auto.tsx +++ b/src-docs/src/views/tables/auto/auto.tsx @@ -83,11 +83,12 @@ const columns: Array> = [ { field: 'jobTitle', name: 'Job title', + truncateText: true, }, { field: 'address', name: 'Address', - truncateText: true, + truncateText: { lines: 2 }, }, ]; @@ -147,11 +148,12 @@ const alignButtons: EuiButtonGroupOptionProps[] = [ export default () => { const [tableLayout, setTableLayout] = useState('tableLayoutFixed'); - const [vAlign, setVAlign] = useState('columnVAlignTop'); + const [vAlign, setVAlign] = useState('columnVAlignMiddle'); const [align, setAlign] = useState('columnAlignLeft'); const onTableLayoutChange = (id: string, value: string) => { setTableLayout(id); + columns[4].width = value === 'custom' ? '100px' : undefined; columns[5].width = value === 'custom' ? '20%' : undefined; }; @@ -169,14 +171,16 @@ export default () => { switch (tableLayout) { case 'tableLayoutFixed': - callOutText = 'Address has truncateText set to true'; + callOutText = + 'Job title has truncateText set to true. Address is set to { lines: 2 }'; break; case 'tableLayoutAuto': callOutText = - 'Address has truncateText set to true which is not applied since tableLayout is set to auto'; + 'Job title will not wrap or truncate since tableLayout is set to auto. Address will truncate if necessary'; break; case 'tableLayoutCustom': - callOutText = 'Address has truncateText set to true and width set to 20%'; + callOutText = + 'Job title has a custom column width of 100px. Address has a custom column width of 20%'; break; } diff --git a/src/components/basic_table/table_types.ts b/src/components/basic_table/table_types.ts index 587515bbf58..0bd235f5d3c 100644 --- a/src/components/basic_table/table_types.ts +++ b/src/components/basic_table/table_types.ts @@ -12,7 +12,10 @@ import { Pagination } from './pagination_bar'; import { Action } from './action_types'; import { Primitive } from '../../services/sort/comparators'; import { CommonProps } from '../common'; -import { EuiTableRowCellMobileOptionsShape } from '../table/table_row_cell'; +import { + EuiTableRowCellProps, + EuiTableRowCellMobileOptionsShape, +} from '../table/table_row_cell'; export type ItemId = string | number | ((item: T) => string); export type ItemIdResolved = string | number; @@ -68,9 +71,12 @@ export interface EuiTableFieldDataColumnType */ align?: HorizontalAlignment; /** - * Indicates whether this column should truncate its content when it doesn't fit + * Indicates whether this column should truncate overflowing text content. + * - Set to `true` to enable single-line truncation. + * - To enable multi-line truncation, use a configuration object with `lines` + * set to a number of lines to truncate to. */ - truncateText?: boolean; + truncateText?: EuiTableRowCellProps['truncateText']; mobileOptions?: Omit & { render?: (item: T) => ReactNode; }; diff --git a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap index 5617a87cf89..794bc452819 100644 --- a/src/components/table/__snapshots__/table_row_cell.test.tsx.snap +++ b/src/components/table/__snapshots__/table_row_cell.test.tsx.snap @@ -112,7 +112,21 @@ exports[`truncateText defaults to false 1`] = ` `; -exports[`truncateText is rendered when specified 1`] = ` +exports[`truncateText renders lines configuration 1`] = ` + +
+ +
+ +`; + +exports[`truncateText renders true 1`] = ` diff --git a/src/components/table/table_row_cell.test.tsx b/src/components/table/table_row_cell.test.tsx index 105149bd5f6..1a3ef10ba43 100644 --- a/src/components/table/table_row_cell.test.tsx +++ b/src/components/table/table_row_cell.test.tsx @@ -78,15 +78,32 @@ describe('textOnly', () => { }); describe('truncateText', () => { - test('defaults to false', () => { + it('defaults to false', () => { const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); - test('is rendered when specified', () => { + it('renders true', () => { const { container } = render(); + expect( + container.querySelector('.euiTableCellContent--truncateText') + ).toBeInTheDocument(); + expect(container.firstChild).toMatchSnapshot(); + }); + + test('renders lines configuration', () => { + const { container } = render( + + ); + + expect( + container.querySelector('.euiTableCellContent--truncateText') + ).not.toBeInTheDocument(); + expect(container.querySelector('.euiTableCellContent__text')).toHaveClass( + 'euiTextBlockTruncate' + ); expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/src/components/table/table_row_cell.tsx b/src/components/table/table_row_cell.tsx index 748efd9c1e0..546fbd93d81 100644 --- a/src/components/table/table_row_cell.tsx +++ b/src/components/table/table_row_cell.tsx @@ -8,15 +8,15 @@ import React, { CSSProperties, - Fragment, FunctionComponent, ReactElement, ReactNode, TdHTMLAttributes, + useCallback, } from 'react'; import classNames from 'classnames'; -import { CommonProps } from '../common'; +import { CommonProps } from '../common'; import { HorizontalAlignment, LEFT_ALIGNMENT, @@ -24,6 +24,8 @@ import { CENTER_ALIGNMENT, useIsWithinBreakpoints, } from '../../services'; +import { isObject } from '../../services/predicate'; +import { EuiTextBlockTruncate } from '../text_truncate'; import { resolveWidthAsStyle } from './utils'; @@ -42,9 +44,12 @@ interface EuiTableRowCellSharedPropsShape { */ textOnly?: boolean; /** - * Don't allow line breaks within cells + * Indicates whether this column should truncate overflowing text content. + * - Set to `true` to enable single-line truncation. + * - To enable multi-line truncation, use a configuration object with `lines` + * set to a number of lines to truncate to. */ - truncateText?: boolean; + truncateText?: boolean | { lines: number }; width?: CSSProperties['width']; } @@ -138,7 +143,7 @@ export const EuiTableRowCell: FunctionComponent = ({ 'euiTableCellContent--alignRight': align === RIGHT_ALIGNMENT, 'euiTableCellContent--alignCenter': align === CENTER_ALIGNMENT, 'euiTableCellContent--showOnHover': showOnHover, - 'euiTableCellContent--truncateText': truncateText, + 'euiTableCellContent--truncateText': truncateText === true, // We're doing this rigamarole instead of creating `euiTableCellContent--textOnly` for BWC // purposes for the time-being. 'euiTableCellContent--overflowingContent': textOnly !== true, @@ -171,23 +176,33 @@ export const EuiTableRowCell: FunctionComponent = ({ const styleObj = resolveWidthAsStyle(style, widthValue); - function modifyChildren(children: ReactNode) { - let modifiedChildren = children; - - if (textOnly === true) { - modifiedChildren = {children}; - } else if (React.isValidElement(children)) { - modifiedChildren = React.Children.map( - children, - (child: ReactElement) => - React.cloneElement(child, { - className: classNames(child.props.className, childClasses), - }) - ); - } - - return modifiedChildren; - } + const modifyChildren = useCallback( + (children: ReactNode) => { + let modifiedChildren = children; + + if (textOnly === true) { + modifiedChildren = {children}; + } else if (React.isValidElement(children)) { + modifiedChildren = React.Children.map( + children, + (child: ReactElement) => + React.cloneElement(child, { + className: classNames(child.props.className, childClasses), + }) + ); + } + if (isObject(truncateText) && truncateText.lines) { + modifiedChildren = ( + + {modifiedChildren} + + ); + } + + return modifiedChildren; + }, + [childClasses, textOnly, truncateText] + ); const childrenNode = modifyChildren(children); @@ -223,14 +238,14 @@ export const EuiTableRowCell: FunctionComponent = ({ {/* Content depending on mobile render existing */} {mobileOptions.render ? ( - + <>
{modifyChildren(mobileOptions.render)}
{childrenNode}
-
+ ) : (
{childrenNode}
)} diff --git a/upcoming_changelogs/7254.md b/upcoming_changelogs/7254.md new file mode 100644 index 00000000000..f381cbf3119 --- /dev/null +++ b/upcoming_changelogs/7254.md @@ -0,0 +1 @@ +- Updated `EuiBasicTable` and `EuiInMemoryTable` to support multi-line truncation. This can be set via `truncateText.lines` in the `columns` prop.