Skip to content

Commit dffaa90

Browse files
authored
fix(Truncate, Progress): enable truncated tooltip via keyboard (#11731)
* fix(Progress): enable truncated tooltip via keyboard * fix a11y bug * update Truncate with fix * fix truncate mock for snapshot, add basic integration test for tabindex * update snapshot
1 parent dbf81e6 commit dffaa90

File tree

8 files changed

+110
-59
lines changed

8 files changed

+110
-59
lines changed

packages/react-core/src/components/Progress/ProgressContainer.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Fragment, useState } from 'react';
1+
import { Fragment, useState, useRef, useEffect } from 'react';
22
import progressStyle from '@patternfly/react-styles/css/components/Progress/progress';
33
import { css } from '@patternfly/react-styles';
44
import { Tooltip, TooltipPosition } from '../Tooltip';
@@ -80,37 +80,44 @@ export const ProgressContainer: React.FunctionComponent<ProgressContainerProps>
8080
}: ProgressContainerProps) => {
8181
const StatusIcon = variantToIcon.hasOwnProperty(variant) && variantToIcon[variant];
8282
const [tooltip, setTooltip] = useState('');
83-
const onMouseEnter = (event: any) => {
83+
const titleRef = useRef(null);
84+
const updateTooltip = (event: any) => {
8485
if (event.target.offsetWidth < event.target.scrollWidth) {
8586
setTooltip(title || event.target.innerHTML);
8687
} else {
8788
setTooltip('');
8889
}
8990
};
91+
92+
useEffect(() => {
93+
if (tooltip !== '') {
94+
titleRef.current.focus();
95+
}
96+
}, [tooltip]);
97+
98+
const _isTruncatedAndString = isTitleTruncated && typeof title === 'string';
9099
const Title = (
91100
<div
92-
className={css(
93-
progressStyle.progressDescription,
94-
isTitleTruncated && typeof title === 'string' && progressStyle.modifiers.truncate
95-
)}
101+
className={css(progressStyle.progressDescription, _isTruncatedAndString && progressStyle.modifiers.truncate)}
96102
id={`${parentId}-description`}
97-
aria-hidden="true"
98-
onMouseEnter={isTitleTruncated && typeof title === 'string' ? onMouseEnter : null}
103+
aria-hidden={_isTruncatedAndString ? null : 'true'}
104+
onMouseEnter={_isTruncatedAndString ? updateTooltip : null}
105+
onFocus={_isTruncatedAndString ? updateTooltip : null}
106+
{...(_isTruncatedAndString && { tabIndex: 0 })}
107+
ref={titleRef}
99108
>
100109
{title}
101110
</div>
102111
);
103112

104113
return (
105114
<Fragment>
106-
{title &&
107-
(tooltip ? (
108-
<Tooltip position={tooltipPosition} content={tooltip} isVisible>
109-
{Title}
110-
</Tooltip>
111-
) : (
112-
Title
113-
))}
115+
{title && (
116+
<>
117+
{tooltip && <Tooltip position={tooltipPosition} content={tooltip} isVisible triggerRef={titleRef} />}
118+
{Title}
119+
</>
120+
)}
114121
{(measureLocation !== ProgressMeasureLocation.none || StatusIcon) && (
115122
<div className={css(progressStyle.progressStatus)} aria-hidden="true">
116123
{(measureLocation === ProgressMeasureLocation.top || measureLocation === ProgressMeasureLocation.outside) && (

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,8 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
109109
setTextElement(textRef.current);
110110
}
111111

112-
if (
113-
(refToGetParent?.current || (subParentRef?.current && subParentRef.current.parentElement.parentElement)) &&
114-
!parentElement
115-
) {
116-
setParentElement(refToGetParent?.current.parentElement || subParentRef?.current.parentElement.parentElement);
112+
if ((refToGetParent?.current || (subParentRef?.current && subParentRef.current.parentElement)) && !parentElement) {
113+
setParentElement(refToGetParent?.current.parentElement || subParentRef?.current.parentElement);
117114
}
118115
}, [textRef, subParentRef, textElement, parentElement]);
119116

@@ -224,18 +221,20 @@ export const Truncate: React.FunctionComponent<TruncateProps> = ({
224221
<span
225222
ref={subParentRef}
226223
className={css(styles.truncate, shouldRenderByMaxChars && styles.modifiers.fixed, className)}
224+
{...(isTruncated && { tabIndex: 0 })}
227225
{...props}
228226
>
229227
{!shouldRenderByMaxChars ? renderResizeObserverContent() : renderMaxDisplayContent()}
230228
</span>
231229
);
232230

233-
return isTruncated ? (
234-
<Tooltip hidden={!isTruncated} position={tooltipPosition} content={content}>
231+
return (
232+
<>
233+
{isTruncated && (
234+
<Tooltip hidden={!isTruncated} position={tooltipPosition} content={content} triggerRef={subParentRef} />
235+
)}
235236
{truncateBody}
236-
</Tooltip>
237-
) : (
238-
truncateBody
237+
</>
239238
);
240239
};
241240

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import styles from '@patternfly/react-styles/css/components/Truncate/truncate';
44
import '@testing-library/jest-dom';
55

66
jest.mock('../../Tooltip', () => ({
7-
Tooltip: ({ content, position, children, ...props }) => (
7+
Tooltip: ({ content, position, children, triggerRef, ...props }) => (
88
<div data-testid="Tooltip-mock" {...props}>
99
<div data-testid="Tooltip-mock-content-container">Test {content}</div>
1010
<p>{`position: ${position}`}</p>

packages/react-core/src/components/Truncate/__tests__/__snapshots__/Truncate.test.tsx.snap

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,28 @@ exports[`Truncation with maxCharsDisplayed Matches snapshot with default positio
1313
<p>
1414
position: top
1515
</p>
16+
</div>
17+
<span
18+
class="pf-v6-c-truncate pf-m-fixed"
19+
tabindex="0"
20+
>
1621
<span
17-
class="pf-v6-c-truncate pf-m-fixed"
22+
class="pf-v6-c-truncate__text"
1823
>
19-
<span
20-
class="pf-v6-c-truncate__text"
21-
>
22-
Tes
23-
</span>
24-
<span
25-
aria-hidden="true"
26-
class="pf-v6-c-truncate__omission"
27-
>
28-
29-
</span>
30-
<span
31-
class="pf-v6-screen-reader"
32-
>
33-
t truncation content
34-
</span>
24+
Tes
3525
</span>
36-
</div>
26+
<span
27+
aria-hidden="true"
28+
class="pf-v6-c-truncate__omission"
29+
>
30+
31+
</span>
32+
<span
33+
class="pf-v6-screen-reader"
34+
>
35+
t truncation content
36+
</span>
37+
</span>
3738
</DocumentFragment>
3839
`;
3940

@@ -50,16 +51,17 @@ exports[`renders default truncation 1`] = `
5051
<p>
5152
position: top
5253
</p>
54+
</div>
55+
<span
56+
class="pf-v6-c-truncate"
57+
tabindex="0"
58+
>
5359
<span
54-
class="pf-v6-c-truncate"
60+
class="pf-v6-c-truncate__start"
5561
>
56-
<span
57-
class="pf-v6-c-truncate__start"
58-
>
59-
Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
60-
</span>
62+
Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
6163
</span>
62-
</div>
64+
</span>
6365
</DocumentFragment>
6466
`;
6567

@@ -76,15 +78,16 @@ exports[`renders start truncation with &lrm; at end 1`] = `
7678
<p>
7779
position: top
7880
</p>
81+
</div>
82+
<span
83+
class="pf-v6-c-truncate"
84+
tabindex="0"
85+
>
7986
<span
80-
class="pf-v6-c-truncate"
87+
class="pf-v6-c-truncate__end"
8188
>
82-
<span
83-
class="pf-v6-c-truncate__end"
84-
>
85-
Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.‎
86-
</span>
89+
Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.‎
8790
</span>
88-
</div>
91+
</span>
8992
</DocumentFragment>
9093
`;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
describe('Truncate Demo Test', () => {
2+
it('Navigate to demo section', () => {
3+
cy.visit('http://localhost:3000/truncate-demo-nav-link');
4+
});
5+
6+
it('Verify no tooltip renders by default', () => {
7+
cy.get('.pf-v6-c-tooltip').should('not.exist');
8+
});
9+
10+
it('Verify non-truncated content in wide container does not have tabindex', () => {
11+
cy.get('#no-truncate').should('not.have.attr', 'tabindex');
12+
});
13+
14+
it('Verify truncated content in small container does have tabindex', () => {
15+
cy.get('#basic-truncate').should('have.attr', 'tabindex');
16+
});
17+
18+
it('Verify tooltip renders when truncate is focused', () => {
19+
cy.get('#basic-truncate').should('have.attr', 'tabindex');
20+
cy.get('#basic-truncate').focus();
21+
cy.get('.pf-v6-c-tooltip').should('exist');
22+
});
23+
});

packages/react-integration/demo-app-ts/src/Demos.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,11 @@ export const Demos: DemoInterface[] = [
462462
name: 'Tree View Demo',
463463
componentType: Examples.TreeViewDemo
464464
},
465+
{
466+
id: 'truncate-demo',
467+
name: 'Truncate Demo',
468+
componentType: Examples.TruncateDemo
469+
},
465470
{
466471
id: 'wizard-demo',
467472
name: 'Wizard Demo',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Truncate } from '@patternfly/react-core';
2+
3+
export const TruncateDemo: React.FunctionComponent = () => (
4+
<>
5+
<div style={{ width: '100px' }}>
6+
<Truncate id="basic-truncate" content={'basic truncated content'} />
7+
</div>
8+
<div style={{ width: '800px' }}>
9+
<Truncate id="no-truncate" content={'no truncation in wide container'} />
10+
</div>
11+
</>
12+
);
13+
TruncateDemo.displayName = 'TruncateDemo';

packages/react-integration/demo-app-ts/src/components/demos/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,6 @@ export * from './ToolbarDemo/ToolbarDemo';
8888
export * from './ToolbarDemo/ToolbarVisibilityDemo';
8989
export * from './TooltipDemo/TooltipDemo';
9090
export * from './TreeViewDemo/TreeViewDemo';
91+
export * from './TruncateDemo/TruncateDemo';
9192
export * from './WizardDemo/WizardDemo';
9293
export * from './WizardDeprecatedDemo/WizardDeprecatedDemo';

0 commit comments

Comments
 (0)