diff --git a/packages/react-core/src/components/Truncate/Truncate.tsx b/packages/react-core/src/components/Truncate/Truncate.tsx index 2cb441bd8f8..46bf057557a 100644 --- a/packages/react-core/src/components/Truncate/Truncate.tsx +++ b/packages/react-core/src/components/Truncate/Truncate.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import styles from '@patternfly/react-styles/css/components/Truncate/truncate'; import { css } from '@patternfly/react-styles'; import { Tooltip, TooltipPosition } from '../Tooltip'; +import { getResizeObserver } from '../../helpers/resizeObserver'; export enum TruncatePosition { start = 'start', @@ -52,11 +53,65 @@ export const Truncate: React.FunctionComponent = ({ trailingNumChars = 7, content, ...props -}: TruncateProps) => ( - - +}: TruncateProps) => { + const [isTruncated, setIsTruncated] = React.useState(true); + const [parentElement, setParentElement] = React.useState(null); + const [textElement, setTextElement] = React.useState(null); + + const textRef = React.useRef(null); + const subParentRef = React.useRef(null); + const observer = React.useRef(null); + + const getActualWidth = (element: Element) => { + const computedStyle = getComputedStyle(element); + + return ( + parseFloat(computedStyle.width) - + parseFloat(computedStyle.paddingLeft) - + parseFloat(computedStyle.paddingRight) - + parseFloat(computedStyle.borderRight) - + parseFloat(computedStyle.borderLeft) + ); + }; + + const calculateTotalTextWidth = (element: Element, trailingNumChars: number, content: string) => { + const firstTextWidth = element.scrollWidth; + const firstTextLength = content.length; + return (firstTextWidth / firstTextLength) * trailingNumChars + firstTextWidth; + }; + + React.useEffect(() => { + if (textRef && textRef.current && !textElement) { + setTextElement(textRef.current); + } + + if (subParentRef && subParentRef.current.parentElement.parentElement && !parentElement) { + setParentElement(subParentRef.current.parentElement.parentElement); + } + }, [textRef, subParentRef]); + + React.useEffect(() => { + if (textElement && parentElement && !observer.current) { + const totalTextWidth = calculateTotalTextWidth(textElement, trailingNumChars, content); + const textWidth = position === 'middle' ? totalTextWidth : textElement.scrollWidth; + + const handleResize = () => { + const parentWidth = getActualWidth(parentElement); + setIsTruncated(textWidth >= parentWidth); + }; + + const observer = getResizeObserver(parentElement, handleResize); + + return () => { + observer(); + }; + } + }, [textElement, parentElement]); + + const truncateBody = ( + {(position === TruncatePosition.end || position === TruncatePosition.start) && ( - + {content} {position === TruncatePosition.start && } @@ -64,7 +119,9 @@ export const Truncate: React.FunctionComponent = ({ {position === TruncatePosition.middle && content.slice(0, content.length - trailingNumChars).length > minWidthCharacters && ( - {sliceContent(content, trailingNumChars)[0]} + + {sliceContent(content, trailingNumChars)[0]} + {sliceContent(content, trailingNumChars)[1]} )} @@ -72,6 +129,15 @@ export const Truncate: React.FunctionComponent = ({ content.slice(0, content.length - trailingNumChars).length <= minWidthCharacters && content} - -); + ); + + return isTruncated ? ( + + ) : ( + truncateBody + ); +}; + Truncate.displayName = 'Truncate'; diff --git a/packages/react-core/src/components/Truncate/__tests__/Truncate.test.tsx b/packages/react-core/src/components/Truncate/__tests__/Truncate.test.tsx index d61733afb06..fc258822452 100644 --- a/packages/react-core/src/components/Truncate/__tests__/Truncate.test.tsx +++ b/packages/react-core/src/components/Truncate/__tests__/Truncate.test.tsx @@ -13,6 +13,12 @@ jest.mock('../../Tooltip', () => ({ ) })); +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn() +})); + test(`renders with class ${styles.truncate}`, () => { render();