Skip to content

Commit 48b5749

Browse files
feat(ClipboardCopy): added truncation for inlinecompact variant (#11610)
* feat(ClipboardCopy): added truncation for inlinecompact variant * Updated chaining for ref current properties * Updated import path casing * Feedback from Austin and Katie * Added new modifier class to clipboard copy * Added tests to check truncation only on inlinecompact variant * Moved all tests to describe block * Added mock tests for Truncate within Clipboard
1 parent 87709ef commit 48b5749

File tree

12 files changed

+271
-31
lines changed

12 files changed

+271
-31
lines changed

packages/react-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"tslib": "^2.8.1"
5555
},
5656
"devDependencies": {
57-
"@patternfly/patternfly": "6.2.0-prerelease.20",
57+
"@patternfly/patternfly": "6.2.0-prerelease.21",
5858
"case-anything": "^3.1.2",
5959
"css": "^3.0.0",
6060
"fs-extra": "^11.3.0"

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Component, Fragment } from 'react';
1+
import { Component, Fragment, createRef } from 'react';
22
import styles from '@patternfly/react-styles/css/components/ClipboardCopy/clipboard-copy';
33
import { css } from '@patternfly/react-styles';
44
import { PickOptional } from '../../helpers/typeUtils';
55
import { TooltipPosition } from '../Tooltip';
66
import { TextInput } from '../TextInput';
7+
import { Truncate, TruncateProps } from '../Truncate';
78
import { GenerateId } from '../../helpers/GenerateId/GenerateId';
89
import { ClipboardCopyButton } from './ClipboardCopyButton';
910
import { ClipboardCopyToggle } from './ClipboardCopyToggle';
@@ -92,6 +93,8 @@ export interface ClipboardCopyProps extends Omit<React.HTMLProps<HTMLDivElement>
9293
children: string | string[];
9394
/** Additional actions for inline clipboard copy. Should be wrapped with ClipboardCopyAction. */
9495
additionalActions?: React.ReactNode;
96+
/** Enables and customizes truncation for an inline-compact ClipboardCopy. */
97+
truncation?: boolean | Omit<TruncateProps, 'content'>;
9598
/** Value to overwrite the randomly generated data-ouia-component-id.*/
9699
ouiaId?: number | string;
97100
/** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
@@ -101,6 +104,7 @@ export interface ClipboardCopyProps extends Omit<React.HTMLProps<HTMLDivElement>
101104
class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
102105
static displayName = 'ClipboardCopy';
103106
timer = null as number;
107+
private clipboardRef: React.RefObject<any>;
104108
constructor(props: ClipboardCopyProps) {
105109
super(props);
106110
const text = Array.isArray(this.props.children) ? this.props.children.join(' ') : (this.props.children as string);
@@ -110,6 +114,8 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
110114
copied: false,
111115
textWhenExpanded: text
112116
};
117+
118+
this.clipboardRef = createRef();
113119
}
114120

115121
static defaultProps: PickOptional<ClipboardCopyProps> = {
@@ -128,6 +134,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
128134
textAriaLabel: 'Copyable input',
129135
toggleAriaLabel: 'Show content',
130136
additionalActions: null,
137+
truncation: false,
131138
ouiaSafe: true
132139
};
133140

@@ -184,37 +191,53 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
184191
position,
185192
className,
186193
additionalActions,
194+
truncation,
187195
ouiaId,
188196
ouiaSafe,
189197
...divProps
190198
} = this.props;
191199
const textIdPrefix = 'text-input-';
192200
const toggleIdPrefix = 'toggle-';
193201
const contentIdPrefix = 'content-';
202+
203+
const copyableText = this.state.text;
204+
const shouldTruncate = variant === ClipboardCopyVariant.inlineCompact && truncation;
205+
const inlineCompactContent = shouldTruncate ? (
206+
<Truncate
207+
refToGetParent={this.clipboardRef}
208+
content={copyableText}
209+
{...(typeof truncation === 'object' && truncation)}
210+
/>
211+
) : (
212+
copyableText
213+
);
214+
194215
return (
195216
<div
196217
className={css(
197218
styles.clipboardCopy,
198-
variant === 'inline-compact' && styles.modifiers.inline,
219+
variant === ClipboardCopyVariant.inlineCompact && styles.modifiers.inline,
199220
isBlock && styles.modifiers.block,
200221
this.state.expanded && styles.modifiers.expanded,
222+
shouldTruncate && styles.modifiers.truncate,
201223
className
202224
)}
225+
ref={this.clipboardRef}
203226
{...divProps}
204227
{...getOUIAProps(ClipboardCopy.displayName, ouiaId, ouiaSafe)}
205228
>
206-
{variant === 'inline-compact' && (
229+
{variant === ClipboardCopyVariant.inlineCompact && (
207230
<GenerateId prefix="">
208231
{(id) => (
209232
<Fragment>
210233
{!isCode && (
211234
<span className={css(styles.clipboardCopyText)} id={`${textIdPrefix}${id}`}>
212-
{this.state.text}
235+
{inlineCompactContent}
213236
</span>
214237
)}
215238
{isCode && (
216239
<code className={css(styles.clipboardCopyText, styles.modifiers.code)} id={`${textIdPrefix}${id}`}>
217-
{this.state.text}
240+
{inlineCompactContent}
218241
</code>
219242
)}
220243
<span className={css(styles.clipboardCopyActions)}>
@@ -229,7 +252,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
229252
textId={`text-input-${id}`}
230253
aria-label={hoverTip}
231254
onClick={(event: any) => {
232-
onCopy(event, this.state.text);
255+
onCopy(event, copyableText);
233256
this.setState({ copied: true });
234257
}}
235258
onTooltipHidden={() => this.setState({ copied: false })}
@@ -244,20 +267,20 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
244267
)}
245268
</GenerateId>
246269
)}
247-
{variant !== 'inline-compact' && (
270+
{variant !== ClipboardCopyVariant.inlineCompact && (
248271
<GenerateId prefix="">
249272
{(id) => (
250273
<Fragment>
251274
<div className={css(styles.clipboardCopyGroup)}>
252-
{variant === 'expansion' && (
275+
{variant === ClipboardCopyVariant.expansion && (
253276
<ClipboardCopyToggle
254277
isExpanded={this.state.expanded}
255278
onClick={(_event) => {
256279
this.expandContent(_event);
257280
if (this.state.expanded) {
258281
this.setState({ text: this.state.textWhenExpanded });
259282
} else {
260-
this.setState({ textWhenExpanded: this.state.text });
283+
this.setState({ textWhenExpanded: copyableText });
261284
}
262285
}}
263286
id={`${toggleIdPrefix}${id}`}
@@ -269,7 +292,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
269292
<TextInput
270293
readOnlyVariant={isReadOnly || this.state.expanded ? 'default' : undefined}
271294
onChange={this.updateText}
272-
value={this.state.expanded ? this.state.textWhenExpanded : this.state.text}
295+
value={this.state.expanded ? this.state.textWhenExpanded : copyableText}
273296
id={`text-input-${id}`}
274297
aria-label={textAriaLabel}
275298
{...(isCode && { dir: 'ltr' })}
@@ -283,7 +306,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
283306
textId={`text-input-${id}`}
284307
aria-label={hoverTip}
285308
onClick={(event: any) => {
286-
onCopy(event, this.state.expanded ? this.state.textWhenExpanded : this.state.text);
309+
onCopy(event, this.state.expanded ? this.state.textWhenExpanded : copyableText);
287310
this.setState({ copied: true });
288311
}}
289312
onTooltipHidden={() => this.setState({ copied: false })}
@@ -298,7 +321,7 @@ class ClipboardCopy extends Component<ClipboardCopyProps, ClipboardCopyState> {
298321
id={`content-${id}`}
299322
onChange={this.updateTextWhenExpanded}
300323
>
301-
{this.state.text}
324+
{copyableText}
302325
</ClipboardCopyExpanded>
303326
)}
304327
</Fragment>

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

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { screen, render } from '@testing-library/react';
2-
import { ClipboardCopy } from '../ClipboardCopy';
2+
import { ClipboardCopy, ClipboardCopyVariant } from '../ClipboardCopy';
33
import styles from '@patternfly/react-styles/css/components/ClipboardCopy/clipboard-copy';
4+
import truncateStyles from '@patternfly/react-styles/css/components/Truncate/truncate';
45
import userEvent from '@testing-library/user-event';
56

67
jest.mock('../../../helpers/GenerateId/GenerateId');
@@ -27,6 +28,19 @@ jest.mock('../ClipboardCopyToggle', () => ({
2728
)
2829
}));
2930

31+
jest.mock('../../Truncate', () => ({
32+
Truncate: ({ className, trailingNumChars, content, position, tooltipPosition, ...props }) => (
33+
<div data-testid="Truncate-mock" {...props}>
34+
<p>{`Truncate content: ${content}`}</p>
35+
<p>{`Truncate className: ${className}`}</p>
36+
<p>{`Truncate position: ${position}`}</p>
37+
<p>{`Truncate trailingNumChars: ${trailingNumChars}`}</p>
38+
<p>{`Truncate tooltipPosition: ${tooltipPosition}`}</p>
39+
{children}
40+
</div>
41+
)
42+
}));
43+
3044
const children = 'Copyable text';
3145
const testId = 'clipboard-copy';
3246

@@ -350,6 +364,178 @@ test('Can take array of strings as children', async () => {
350364
expect(onCopyMock).toHaveBeenCalledWith(expect.any(Object), children);
351365
});
352366

367+
describe('ClipboardCopy with truncation', () => {
368+
test(`Does not render ClipboardCopy component with class ${styles.modifiers.truncate} by default`, () => {
369+
render(<ClipboardCopy data-testid={testId}>{children}</ClipboardCopy>);
370+
371+
expect(screen.getByTestId(testId)).not.toHaveClass(styles.modifiers.truncate);
372+
});
373+
374+
test(`Does not render ClipboardCopy component with class ${styles.modifiers.truncate} for expansion variant`, () => {
375+
render(
376+
<ClipboardCopy variant={ClipboardCopyVariant.expansion} data-testid={testId}>
377+
{children}
378+
</ClipboardCopy>
379+
);
380+
381+
expect(screen.getByTestId(testId)).not.toHaveClass(styles.modifiers.truncate);
382+
});
383+
384+
test(`Does not render ClipboardCopy component with class ${styles.modifiers.truncate} for inlinecompact variant and truncation is false`, () => {
385+
render(
386+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} data-testid={testId}>
387+
{children}
388+
</ClipboardCopy>
389+
);
390+
391+
expect(screen.getByTestId(testId)).not.toHaveClass(styles.modifiers.truncate);
392+
});
393+
394+
test(`Renders ClipboardCopy component with class ${styles.modifiers.truncate} when truncation is true and variant is inline-compact`, () => {
395+
render(
396+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation data-testid={testId}>
397+
{children}
398+
</ClipboardCopy>
399+
);
400+
401+
expect(screen.getByTestId(testId)).toHaveClass(styles.modifiers.truncate);
402+
});
403+
404+
test(`Renders ClipboardCopy component with class ${styles.modifiers.truncate} when truncation is an object and variant is inlinecompact`, () => {
405+
render(
406+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{}} data-testid={testId}>
407+
{children}
408+
</ClipboardCopy>
409+
);
410+
411+
expect(screen.getByTestId(testId)).toHaveClass(styles.modifiers.truncate);
412+
});
413+
414+
test('Does not render with Truncate component by default', () => {
415+
render(<ClipboardCopy>{children}</ClipboardCopy>);
416+
417+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
418+
});
419+
420+
test('Does not render with Truncate component when truncation is true and variant is not inline-compact', () => {
421+
render(<ClipboardCopy truncation>{children}</ClipboardCopy>);
422+
423+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
424+
});
425+
426+
test('Does not render with Truncate component when truncation is true and variant is expansion', () => {
427+
render(
428+
<ClipboardCopy variant={ClipboardCopyVariant.expansion} truncation>
429+
{children}
430+
</ClipboardCopy>
431+
);
432+
433+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
434+
});
435+
436+
test('Does not render with Truncate component when truncation is an object and variant is not inline-compact', () => {
437+
render(<ClipboardCopy truncation={{}}>{children}</ClipboardCopy>);
438+
439+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
440+
});
441+
442+
test('Does not render with Truncate component when truncation is an object and variant is expansion', () => {
443+
render(
444+
<ClipboardCopy variant={ClipboardCopyVariant.expansion} truncation={{}}>
445+
{children}
446+
</ClipboardCopy>
447+
);
448+
449+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
450+
});
451+
452+
test('Does not render with Truncate component when variant="inline-compact" and truncation is false', () => {
453+
render(<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact}>{children}</ClipboardCopy>);
454+
455+
expect(screen.queryByTestId('Truncate-mock')).not.toBeInTheDocument();
456+
});
457+
458+
test('Renders with Truncate component when variant="inline-compact" and truncation is true', () => {
459+
render(
460+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation>
461+
{children}
462+
</ClipboardCopy>
463+
);
464+
465+
expect(screen.getByTestId('Truncate-mock')).toBeInTheDocument();
466+
});
467+
468+
test('Passes children to Truncate content prop when variant="inline-compact" and truncation is true', () => {
469+
render(
470+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation>
471+
{children}
472+
</ClipboardCopy>
473+
);
474+
475+
expect(screen.getByText(`Truncate content: ${children}`)).toBeInTheDocument();
476+
});
477+
478+
test('Renders with Truncate component when variant="inline-compact" and truncation is an object', () => {
479+
render(
480+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{}}>
481+
{children}
482+
</ClipboardCopy>
483+
);
484+
485+
expect(screen.getByTestId('Truncate-mock')).toBeInTheDocument();
486+
});
487+
488+
test('Passes children to Truncate content prop when variant="inline-compact" and truncation is an object', () => {
489+
render(
490+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{}}>
491+
{children}
492+
</ClipboardCopy>
493+
);
494+
495+
expect(screen.getByText(`Truncate content: ${children}`)).toBeInTheDocument();
496+
});
497+
498+
test('Passes className prop to Truncate when variant="inline-compact" and truncation is passed', () => {
499+
render(
500+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{ className: 'clipboard-truncate' }}>
501+
{children}
502+
</ClipboardCopy>
503+
);
504+
505+
expect(screen.getByText(`Truncate className: clipboard-truncate`)).toBeInTheDocument();
506+
});
507+
508+
test('Passes trailingNumChars prop to Truncate when variant="inline-compact" and truncation is passed', () => {
509+
render(
510+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{ trailingNumChars: 5 }}>
511+
{children}
512+
</ClipboardCopy>
513+
);
514+
515+
expect(screen.getByText(`Truncate trailingNumChars: 5`)).toBeInTheDocument();
516+
});
517+
518+
test('Passes position prop to Truncate when variant="inline-compact" and truncation is passed', () => {
519+
render(
520+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{ position: 'start' }}>
521+
{children}
522+
</ClipboardCopy>
523+
);
524+
525+
expect(screen.getByText(`Truncate position: start`)).toBeInTheDocument();
526+
});
527+
528+
test('Passes tooltipPosition prop to Truncate when variant="inline-compact" and truncation is passed', () => {
529+
render(
530+
<ClipboardCopy variant={ClipboardCopyVariant.inlineCompact} truncation={{ tooltipPosition: 'bottom' }}>
531+
{children}
532+
</ClipboardCopy>
533+
);
534+
535+
expect(screen.getByText(`Truncate tooltipPosition: bottom`)).toBeInTheDocument();
536+
});
537+
});
538+
353539
test('Matches snapshot', () => {
354540
const { asFragment } = render(
355541
<ClipboardCopy id="snapshot" ouiaId="snapshot">

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exports[`Matches snapshot 1`] = `
1818
<input
1919
aria-invalid="false"
2020
aria-label="Copyable input"
21-
data-ouia-component-id="OUIA-Generated-TextInputBase-27"
21+
data-ouia-component-id="OUIA-Generated-TextInputBase-34"
2222
data-ouia-component-type="PF6/TextInput"
2323
data-ouia-safe="true"
2424
id="text-input-generated-id"

0 commit comments

Comments
 (0)