Skip to content

Commit 69d5937

Browse files
authored
fix(CodeEditor): use codeEditorControls and clean up overall (#7931)
* fix(CodeEditor): use codeEditorControls and clean up overall * fix lint errors * use better variable names * clean up per PR comments * add back useCallback * clean up * clean up * fix lint errors * add console warnings when using deprecated props * fix lint errors * update warning messages * fix lint again
1 parent 01da036 commit 69d5937

9 files changed

Lines changed: 158 additions & 130 deletions

File tree

packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
Popover,
1414
PopoverProps,
1515
Title,
16-
Tooltip,
1716
TooltipPosition
1817
} from '@patternfly/react-core';
1918
import MonacoEditor, { ChangeHandler, EditorDidMount } from 'react-monaco-editor';
@@ -25,6 +24,7 @@ import CodeIcon from '@patternfly/react-icons/dist/esm/icons/code-icon';
2524
import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon';
2625
import Dropzone from 'react-dropzone';
2726
import { CodeEditorContext } from './CodeEditorUtils';
27+
import { CodeEditorControl } from './CodeEditorControl';
2828

2929
export interface Shortcut {
3030
description: string;
@@ -440,18 +440,8 @@ export class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState
440440
};
441441

442442
copyCode = () => {
443-
if (this.timer) {
444-
window.clearTimeout(this.timer);
445-
this.setState({ copied: false });
446-
}
447-
this.editor?.focus();
448-
document.execCommand('copy');
449-
this.setState({ copied: true }, () => {
450-
this.timer = window.setTimeout(() => {
451-
this.setState({ copied: false });
452-
this.timer = null;
453-
}, 2500);
454-
});
443+
navigator.clipboard.writeText(this.state.value);
444+
this.setState({ copied: true });
455445
};
456446

457447
download = () => {
@@ -555,55 +545,51 @@ export class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState
555545
</EmptyState>
556546
));
557547

548+
const tooltipProps = {
549+
position: toolTipPosition,
550+
exitDelay: toolTipDelay,
551+
entryDelay: toolTipDelay,
552+
maxWidth: toolTipMaxWidth,
553+
trigger: 'mouseenter focus'
554+
};
555+
558556
const editorHeader = (
559557
<div className={css(styles.codeEditorHeader)}>
560558
{
561559
<div className={css(styles.codeEditorControls)}>
562-
{isCopyEnabled && (!showEmptyState || !!value) && (
563-
<Tooltip
564-
trigger="mouseenter"
565-
content={<div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>}
566-
exitDelay={copied ? toolTipCopyExitDelay : toolTipDelay}
567-
entryDelay={toolTipDelay}
568-
maxWidth={toolTipMaxWidth}
569-
position={toolTipPosition}
570-
>
571-
<Button onClick={this.copyCode} variant="control" aria-label={copyButtonAriaLabel}>
572-
<CopyIcon />
573-
</Button>
574-
</Tooltip>
575-
)}
576-
{isUploadEnabled && (
577-
<Tooltip
578-
trigger="mouseenter focus click"
579-
content={<div>{uploadButtonToolTipText}</div>}
580-
entryDelay={toolTipDelay}
581-
exitDelay={toolTipDelay}
582-
maxWidth={toolTipMaxWidth}
583-
position={toolTipPosition}
584-
>
585-
<Button onClick={open} variant="control" aria-label={uploadButtonAriaLabel}>
586-
<UploadIcon />
587-
</Button>
588-
</Tooltip>
589-
)}
590-
{isDownloadEnabled && (!showEmptyState || !!value) && (
591-
<Tooltip
592-
trigger="mouseenter focus click"
593-
content={<div>{downloadButtonToolTipText}</div>}
594-
entryDelay={toolTipDelay}
595-
exitDelay={toolTipDelay}
596-
maxWidth={toolTipMaxWidth}
597-
position={toolTipPosition}
598-
>
599-
<Button onClick={this.download} variant="control" aria-label={downloadButtonAriaLabel}>
600-
<DownloadIcon />
601-
</Button>
602-
</Tooltip>
603-
)}
604-
{customControls && (
605-
<CodeEditorContext.Provider value={{ code: value }}>{customControls}</CodeEditorContext.Provider>
606-
)}
560+
<CodeEditorContext.Provider value={{ code: value }}>
561+
{isCopyEnabled && (!showEmptyState || !!value) && (
562+
<CodeEditorControl
563+
icon={<CopyIcon />}
564+
aria-label={copyButtonAriaLabel}
565+
tooltipProps={{
566+
...tooltipProps,
567+
'aria-live': 'polite',
568+
content: <div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>,
569+
exitDelay: copied ? toolTipCopyExitDelay : toolTipDelay,
570+
onTooltipHidden: () => this.setState({ copied: false })
571+
}}
572+
onClick={this.copyCode}
573+
/>
574+
)}
575+
{isUploadEnabled && (
576+
<CodeEditorControl
577+
icon={<UploadIcon />}
578+
aria-label={uploadButtonAriaLabel}
579+
tooltipProps={{ content: <div>{uploadButtonToolTipText}</div>, ...tooltipProps }}
580+
onClick={open}
581+
/>
582+
)}
583+
{isDownloadEnabled && (!showEmptyState || !!value) && (
584+
<CodeEditorControl
585+
icon={<DownloadIcon />}
586+
aria-label={downloadButtonAriaLabel}
587+
tooltipProps={{ content: <div>{downloadButtonToolTipText}</div>, ...tooltipProps }}
588+
onClick={this.download}
589+
/>
590+
)}
591+
{customControls && customControls}
592+
</CodeEditorContext.Provider>
607593
</div>
608594
}
609595
{<div className={css(styles.codeEditorHeaderMain)}>{headerMainContent}</div>}

packages/react-code-editor/src/components/CodeEditor/CodeEditorControl.tsx

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ import { CodeEditorContext } from './CodeEditorUtils';
77
*/
88

99
export interface CodeEditorControlProps extends Omit<ButtonProps, 'onClick'> {
10-
/** Accessible label for the code editor control. */
10+
/** Accessible label for the code editor control */
1111
'aria-label'?: string;
1212
/** Additional classes added to the code editor control. */
1313
className?: string;
14-
/** Delay in ms before the tooltip appears. */
14+
/** @deprecated Delay in ms before the tooltip appears. */
1515
entryDelay?: number;
16-
/** Delay in ms before the tooltip disappears. */
16+
/** @deprecated Delay in ms before the tooltip disappears. */
1717
exitDelay?: number;
1818
/** Icon rendered inside the code editor control. */
1919
icon: React.ReactNode;
20-
/** Maximum width of the tooltip (default 150px). */
20+
/** @deprecated Maximum width of the tooltip (default 150px). */
2121
maxWidth?: string;
22-
/** Copy button popover position. */
22+
/** @deprecated Copy button popover position. */
2323
position?:
2424
| PopoverPosition
2525
| 'auto'
@@ -35,41 +35,82 @@ export interface CodeEditorControlProps extends Omit<ButtonProps, 'onClick'> {
3535
| 'left-end'
3636
| 'right-start'
3737
| 'right-end';
38-
/** Text to display in the tooltip. */
39-
toolTipText: React.ReactNode;
40-
/** Event handler for the click of the button. */
38+
/** @deprecated Text to display in the tooltip*/
39+
toolTipText?: React.ReactNode;
40+
/** Event handler for the click of the button */
4141
onClick: (code: string, event?: any) => void;
4242
/** Flag indicating that the button is visible above the code editor. */
4343
isVisible?: boolean;
44+
/** Additional tooltip props forwarded to the Tooltip component */
45+
tooltipProps?: any;
4446
}
4547

4648
export const CodeEditorControl: React.FunctionComponent<CodeEditorControlProps> = ({
4749
icon,
4850
className,
4951
'aria-label': ariaLabel,
5052
toolTipText,
51-
exitDelay = 0,
52-
entryDelay = 300,
53-
maxWidth = '100px',
54-
position = 'top',
53+
exitDelay,
54+
entryDelay,
55+
maxWidth,
56+
position,
5557
onClick = () => {},
5658
isVisible = true,
59+
tooltipProps = {},
5760
...props
5861
}: CodeEditorControlProps) => {
5962
const context = React.useContext(CodeEditorContext);
6063

64+
if (entryDelay !== undefined) {
65+
// eslint-disable-next-line no-console
66+
console.warn(
67+
'CodeEditorControl: entryDelay prop has been deprecated. ' +
68+
'Pass the entryDelay via the tooltipProps prop instead.'
69+
);
70+
}
71+
72+
if (exitDelay !== undefined) {
73+
// eslint-disable-next-line no-console
74+
console.warn(
75+
'CodeEditorControl: exitDelay prop has been deprecated. ' +
76+
'Pass the exitDelay via the tooltipProps prop instead.'
77+
);
78+
}
79+
80+
if (maxWidth !== undefined) {
81+
// eslint-disable-next-line no-console
82+
console.warn(
83+
'CodeEditorControl: maxWidth prop has been deprecated. ' + 'Pass the maxWidth via the tooltipProps prop instead.'
84+
);
85+
}
86+
87+
if (position !== undefined) {
88+
// eslint-disable-next-line no-console
89+
console.warn(
90+
'CodeEditorControl: position prop has been deprecated. ' + 'Pass the position via the tooltipProps prop instead.'
91+
);
92+
}
93+
94+
if (toolTipText !== undefined) {
95+
// eslint-disable-next-line no-console
96+
console.warn(
97+
'CodeEditorControl: toolTipText prop has been deprecated. ' +
98+
'Pass the toolTipText by setting the content field in tooltipProps prop instead.'
99+
);
100+
}
101+
61102
const onCustomClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
62103
onClick(context.code, event);
63104
};
64105

65106
return isVisible ? (
66107
<Tooltip
67-
trigger="mouseenter focus click"
68108
exitDelay={exitDelay}
69109
entryDelay={entryDelay}
70110
maxWidth={maxWidth}
71111
position={position}
72112
content={<div>{toolTipText}</div>}
113+
{...tooltipProps}
73114
>
74115
<Button className={className} onClick={onCustomClick} variant="control" aria-label={ariaLabel} {...props}>
75116
{icon}

packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorCustomControl.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const CodeEditorCustomControl: React.FunctionComponent = () => {
1818
<CodeEditorControl
1919
icon={<PlayIcon />}
2020
aria-label="Execute code"
21-
toolTipText="Execute code"
21+
tooltipProps={{ content: 'Execute code' }}
2222
onClick={onExecuteCode}
2323
isVisible={code !== ''}
2424
/>

packages/react-code-editor/src/components/CodeEditor/examples/CodeEditorShortcutMainHeader.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ export const CodeEditorShortcutMainHeader: React.FunctionComponent = () => {
3636
];
3737
const shortcutsPopoverProps = {
3838
bodyContent: (
39-
<Grid span={6} hasGutter>
40-
{shortcuts.map(s => (
41-
<>
39+
<Grid span={6} hasGutter key="grid">
40+
{shortcuts.map((shortcut, index) => (
41+
<React.Fragment key={index}>
4242
<GridItem style={{ textAlign: 'right', marginRight: '1em' }}>
43-
{s.keys
44-
.map(k => (
45-
<Chip key={k} isReadOnly>
46-
{k}
43+
{shortcut.keys
44+
.map(key => (
45+
<Chip key={key} isReadOnly>
46+
{key}
4747
</Chip>
4848
))
4949
.reduce((prev, curr) => (
5050
<>{[prev, ' + ', curr]}</>
5151
))}
5252
</GridItem>
53-
<GridItem>{s.description}</GridItem>
54-
</>
53+
<GridItem>{shortcut.description}</GridItem>
54+
</React.Fragment>
5555
))}
5656
</Grid>
5757
),
@@ -60,7 +60,6 @@ export const CodeEditorShortcutMainHeader: React.FunctionComponent = () => {
6060

6161
return (
6262
<CodeEditor
63-
headerMainContent="Shortcut Example"
6463
shortcutsPopoverProps={shortcutsPopoverProps}
6564
isLanguageLabelVisible
6665
code="Some example content"

0 commit comments

Comments
 (0)