Skip to content

Commit ed88595

Browse files
author
Eric Olkowski
committed
feat(Truncate): added tooltip props and anchor usage
1 parent 2aa6dc4 commit ed88595

File tree

5 files changed

+88
-18
lines changed

5 files changed

+88
-18
lines changed

packages/react-core/src/components/ClipboardCopy/ClipboardCopy.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
204204
const shouldTruncate = variant === ClipboardCopyVariant.inlineCompact && truncation;
205205
const inlineCompactContent = shouldTruncate ? (
206206
<Truncate
207-
refToGetParent={this.clipboardRef}
207+
tooltipProps={{ triggerRef: this.clipboardRef }}
208208
content={copyableText}
209209
{...(typeof truncation === 'object' && truncation)}
210210
/>
@@ -223,6 +223,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
223223
className
224224
)}
225225
ref={this.clipboardRef}
226+
{...(shouldTruncate && { tabIndex: 0 })}
226227
{...divProps}
227228
{...getOUIAProps(ClipboardCopy.displayName, ouiaId, ouiaSafe)}
228229
>

packages/react-core/src/components/Truncate/Truncate.tsx

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Fragment, useEffect, useRef, useState } from 'react';
22
import styles from '@patternfly/react-styles/css/components/Truncate/truncate';
33
import { css } from '@patternfly/react-styles';
4-
import { Tooltip, TooltipPosition } from '../Tooltip';
4+
import { Tooltip, TooltipPosition, TooltipProps } from '../Tooltip';
5+
import { getReferenceElement } from '../../helpers';
56
import { getResizeObserver } from '../../helpers/resizeObserver';
67

78
export enum TruncatePosition {
@@ -17,11 +18,15 @@ const truncateStyles = {
1718

1819
const minWidthCharacters: number = 12;
1920

20-
export interface TruncateProps extends React.HTMLProps<HTMLSpanElement> {
21+
export interface TruncateProps extends Omit<React.HTMLProps<HTMLSpanElement | HTMLAnchorElement>, 'ref'> {
2122
/** Class to add to outer span */
2223
className?: string;
2324
/** Text to truncate */
2425
content: string;
26+
/** An HREF to turn the truncate wrapper into an anchor element. For more custom control, use the
27+
* tooltipProps with a triggerRef property passed in.
28+
*/
29+
href?: string;
2530
/** The number of characters displayed in the second half of a middle truncation. This will be overridden by
2631
* the maxCharsDisplayed prop.
2732
*/
@@ -52,24 +57,22 @@ export interface TruncateProps extends React.HTMLProps<HTMLSpanElement> {
5257
| 'left-end'
5358
| 'right-start'
5459
| 'right-end';
55-
/** @hide The element whose parent to reference when calculating whether truncation should occur. This must be an ancestor
56-
* of the ClipboardCopy, and must have a valid width value. For internal use only, do not use as it is not part of the public API
57-
* and is subject to change.
58-
*/
59-
refToGetParent?: React.RefObject<any>;
60+
/** Additional props to pass to the tooltip. */
61+
tooltipProps?: Omit<TooltipProps, 'content'>;
6062
}
6163

6264
const sliceTrailingContent = (str: string, slice: number) => [str.slice(0, str.length - slice), str.slice(-slice)];
6365

6466
export const Truncate: React.FunctionComponent<TruncateProps> = ({
6567
className,
68+
href,
6669
position = 'end',
6770
tooltipPosition = 'top',
71+
tooltipProps,
6872
trailingNumChars = 7,
6973
maxCharsDisplayed,
7074
omissionContent = '\u2026',
7175
content,
72-
refToGetParent,
7376
...props
7477
}: TruncateProps) => {
7578
const [isTruncated, setIsTruncated] = useState(true);
@@ -78,7 +81,8 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
7881
const [shouldRenderByMaxChars, setShouldRenderByMaxChars] = useState(maxCharsDisplayed > 0);
7982

8083
const textRef = useRef<HTMLElement>(null);
81-
const subParentRef = useRef<HTMLDivElement>(null);
84+
const defaultSubParentRef = useRef<any>(null);
85+
const subParentRef = tooltipProps?.triggerRef || defaultSubParentRef;
8286
const observer = useRef(null);
8387

8488
if (maxCharsDisplayed <= 0) {
@@ -108,11 +112,14 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
108112
if (textRef && textRef.current && !textElement) {
109113
setTextElement(textRef.current);
110114
}
115+
}, [textRef, textElement]);
111116

112-
if ((refToGetParent?.current || (subParentRef?.current && subParentRef.current.parentElement)) && !parentElement) {
113-
setParentElement(refToGetParent?.current.parentElement || subParentRef?.current.parentElement);
117+
useEffect(() => {
118+
const refElement = getReferenceElement(subParentRef);
119+
if (refElement?.parentElement && !parentElement) {
120+
setParentElement(refElement.parentElement);
114121
}
115-
}, [textRef, subParentRef, textElement, parentElement]);
122+
}, [subParentRef, parentElement]);
116123

117124
useEffect(() => {
118125
if (textElement && parentElement && !observer.current && !shouldRenderByMaxChars) {
@@ -217,21 +224,29 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
217224
);
218225
};
219226

227+
const TruncateWrapper = href ? 'a' : 'span';
220228
const truncateBody = (
221-
<span
222-
ref={subParentRef}
229+
<TruncateWrapper
230+
ref={!tooltipProps?.triggerRef ? (subParentRef as React.MutableRefObject<any>) : null}
231+
href={href}
223232
className={css(styles.truncate, shouldRenderByMaxChars && styles.modifiers.fixed, className)}
224-
{...(isTruncated && { tabIndex: 0 })}
233+
{...(isTruncated && !href && !tooltipProps?.triggerRef && { tabIndex: 0 })}
225234
{...props}
226235
>
227236
{!shouldRenderByMaxChars ? renderResizeObserverContent() : renderMaxDisplayContent()}
228-
</span>
237+
</TruncateWrapper>
229238
);
230239

231240
return (
232241
<>
233242
{isTruncated && (
234-
<Tooltip hidden={!isTruncated} position={tooltipPosition} content={content} triggerRef={subParentRef} />
243+
<Tooltip
244+
hidden={!isTruncated}
245+
position={tooltipPosition}
246+
content={content}
247+
triggerRef={subParentRef}
248+
{...tooltipProps}
249+
/>
235250
)}
236251
{truncateBody}
237252
</>

packages/react-core/src/components/Truncate/examples/Truncate.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ cssPrefix: pf-v6-c-truncate
55
propComponents: [Truncate]
66
---
77

8+
import { useRef } from 'react';
89
import './TruncateExamples.css';
910

1011
## Examples
@@ -52,3 +53,9 @@ Truncating based on a maximum amount of characters will truncate the content at
5253
```ts file="./TruncateMaxChars.tsx"
5354

5455
```
56+
57+
### With links
58+
59+
```ts file="./TruncateLinks.tsx"
60+
61+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useRef } from 'react';
2+
import { Truncate, Button } from '@patternfly/react-core';
3+
4+
export const TruncateLinks: React.FunctionComponent = () => {
5+
const anchorRef = useRef(null);
6+
const btnRef = useRef(null);
7+
const content = 'A very lengthy anchor text content to trigger truncation';
8+
return (
9+
<>
10+
<div>With default width-observing truncation:</div>
11+
<div className="truncate-example-resize">
12+
<a ref={anchorRef} href="#">
13+
<Truncate tooltipProps={{ triggerRef: anchorRef }} content={content} />
14+
</a>
15+
<Button variant="control" ref={btnRef}>
16+
<Truncate tooltipProps={{ triggerRef: btnRef }} content={content} />
17+
</Button>
18+
<Truncate position="start" href="#" content={content} />
19+
<Truncate position="middle" href="#" content={content} />
20+
</div>
21+
<br />
22+
<div>With max characters truncation:</div>
23+
<Truncate maxCharsDisplayed={15} href="#" content={content} />
24+
<br />
25+
<Truncate maxCharsDisplayed={15} position="start" href="#" content={content} />
26+
<br />
27+
<Truncate maxCharsDisplayed={15} position="middle" href="#" content={content} />
28+
</>
29+
);
30+
};

packages/react-core/src/helpers/util.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,20 @@ export const getLanguageDirection = (targetElement: HTMLElement, defaultDirectio
545545

546546
return defaultDirection;
547547
};
548+
549+
/**
550+
* Gets a reference element based on a ref property, which can typically be 1 of several types.
551+
*
552+
* @param {HTMLElement | (() => HTMLElement) | React.RefObject<any>} refProp The ref property to get a reference element from.
553+
* @returns The reference element if one is found.
554+
*/
555+
export const getReferenceElement = (refProp: HTMLElement | (() => HTMLElement) | React.RefObject<any>) => {
556+
if (refProp instanceof HTMLElement) {
557+
return refProp;
558+
}
559+
if (typeof refProp === 'function') {
560+
return refProp();
561+
}
562+
563+
return refProp?.current;
564+
};

0 commit comments

Comments
 (0)