Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 62 additions & 45 deletions packages/react-code-editor/src/components/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
isCopyEnabled?: boolean;
/** Flag indicating the editor is styled using monaco's dark theme. */
isDarkTheme?: boolean;
/** Flag indicating the editor has a plain header. */
isHeaderPlain?: boolean;
/** Flag to add download button to code editor actions. */
isDownloadEnabled?: boolean;
/** Flag to include a label indicating the currently configured editor language. */
Expand Down Expand Up @@ -261,6 +263,7 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
isUploadEnabled: false,
isDownloadEnabled: false,
isCopyEnabled: false,
isHeaderPlain: false,
copyButtonAriaLabel: 'Copy code to clipboard',
uploadButtonAriaLabel: 'Upload code',
downloadButtonAriaLabel: 'Download code',
Expand Down Expand Up @@ -492,6 +495,7 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
emptyStateLink,
customControls,
isMinimapVisible,
isHeaderPlain,
headerMainContent,
shortcutsPopoverButtonText,
shortcutsPopoverProps: shortcutsPopoverPropsProp,
Expand Down Expand Up @@ -560,45 +564,50 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
trigger: 'mouseenter focus'
};

const editorHeader = (
<div className={css(styles.codeEditorHeader)}>
{
<div className={css(styles.codeEditorControls)}>
<CodeEditorContext.Provider value={{ code: value }}>
{isCopyEnabled && (!showEmptyState || !!value) && (
<CodeEditorControl
icon={<CopyIcon />}
aria-label={copyButtonAriaLabel}
tooltipProps={{
...tooltipProps,
'aria-live': 'polite',
content: <div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>,
exitDelay: copied ? toolTipCopyExitDelay : toolTipDelay,
onTooltipHidden: () => this.setState({ copied: false })
}}
onClick={this.copyCode}
/>
)}
{isUploadEnabled && (
<CodeEditorControl
icon={<UploadIcon />}
aria-label={uploadButtonAriaLabel}
tooltipProps={{ content: <div>{uploadButtonToolTipText}</div>, ...tooltipProps }}
onClick={open}
/>
)}
{isDownloadEnabled && (!showEmptyState || !!value) && (
<CodeEditorControl
icon={<DownloadIcon />}
aria-label={downloadButtonAriaLabel}
tooltipProps={{ content: <div>{downloadButtonToolTipText}</div>, ...tooltipProps }}
onClick={this.download}
/>
)}
{customControls && customControls}
</CodeEditorContext.Provider>
</div>
}
const hasEditorHeaderContent =
((isCopyEnabled || isDownloadEnabled) && (!showEmptyState || !!value)) ||
isUploadEnabled ||
customControls ||
headerMainContent ||
!!shortcutsPopoverProps.bodyContent;

const editorHeaderContent = (
<React.Fragment>
<div className={css(styles.codeEditorControls)}>
<CodeEditorContext.Provider value={{ code: value }}>
{isCopyEnabled && (!showEmptyState || !!value) && (
<CodeEditorControl
icon={<CopyIcon />}
aria-label={copyButtonAriaLabel}
tooltipProps={{
...tooltipProps,
'aria-live': 'polite',
content: <div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>,
exitDelay: copied ? toolTipCopyExitDelay : toolTipDelay,
onTooltipHidden: () => this.setState({ copied: false })
}}
onClick={this.copyCode}
/>
)}
{isUploadEnabled && (
<CodeEditorControl
icon={<UploadIcon />}
aria-label={uploadButtonAriaLabel}
tooltipProps={{ content: <div>{uploadButtonToolTipText}</div>, ...tooltipProps }}
onClick={open}
/>
)}
{isDownloadEnabled && (!showEmptyState || !!value) && (
<CodeEditorControl
icon={<DownloadIcon />}
aria-label={downloadButtonAriaLabel}
tooltipProps={{ content: <div>{downloadButtonToolTipText}</div>, ...tooltipProps }}
onClick={this.download}
/>
)}
{customControls && customControls}
</CodeEditorContext.Provider>
</div>
{<div className={css(styles.codeEditorHeaderMain)}>{headerMainContent}</div>}
{!!shortcutsPopoverProps.bodyContent && (
<div className={`${styles.codeEditor}__keyboard-shortcuts`}>
Expand All @@ -609,6 +618,14 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
</Popover>
</div>
)}
</React.Fragment>
);

const editorHeader = (
<div className={css(styles.codeEditorHeader, isHeaderPlain && styles.modifiers.plain)}>
{hasEditorHeaderContent && (
<div className={css(styles.codeEditorHeaderContent)}>{editorHeaderContent}</div>
)}
{isLanguageLabelVisible && (
<div className={css(styles.codeEditorTab)}>
<span className={css(styles.codeEditorTabIcon)}>
Expand Down Expand Up @@ -643,14 +660,14 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
{...getRootProps({
onClick: (event) => event.stopPropagation() // Prevents clicking TextArea from opening file dialog
})}
className={`${fileUploadStyles.fileUpload} ${isDragActive && fileUploadStyles.modifiers.dragHover} ${
isLoading && fileUploadStyles.modifiers.loading
}`}
className={css(isLoading && fileUploadStyles.modifiers.loading)}
>
{editorHeader}
<div className={css(styles.codeEditorMain)}>
<input {...getInputProps()} /* hidden, necessary for react-dropzone */ />
{(showEmptyState || providedEmptyState) && !value ? emptyState : editor}
<div className={css(styles.codeEditorMain, isDragActive && styles.modifiers.dragHover)}>
<div className={css(styles.codeEditorUpload)}>
<input {...getInputProps()} /* hidden, necessary for react-dropzone */ />
{(showEmptyState || providedEmptyState) && !value ? emptyState : editor}
</div>
</div>
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const CodeEditorControl: React.FunctionComponent<CodeEditorControlProps>

return isVisible ? (
<Tooltip {...tooltipProps}>
<Button className={className} onClick={onCustomClick} variant="control" aria-label={ariaLabel} {...props}>
<Button className={className} onClick={onCustomClick} variant="plain" aria-label={ariaLabel} {...props}>
{icon}
</Button>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import { render, screen, act } from '@testing-library/react';
import { CodeEditor, Language } from '../CodeEditor';
import styles from '@patternfly/react-styles/css/components/CodeEditor/code-editor';
import fileUploadStyles from '@patternfly/react-styles/css/components/FileUpload/file-upload';

jest.mock('@monaco-editor/react', () => jest.fn(() => <div data-testid="mock-editor"></div>));

Expand Down Expand Up @@ -35,11 +34,9 @@ test(`Renders with ${styles.modifiers.readOnly} when isReadOnly = true`, () => {
);
});

test(`Renders with ${fileUploadStyles.fileUpload} when isUploadEnabled = true`, () => {
test(`Renders with ${styles.codeEditorUpload} when isUploadEnabled = true`, () => {
render(<CodeEditor isUploadEnabled code="test" />);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement?.parentElement).toHaveClass(
fileUploadStyles.fileUpload
);
expect(screen.getByTestId('mock-editor').parentElement?.parentElement).toHaveClass(styles.codeEditorUpload);
});

test(`Renders with empty state when code = undefined`, () => {
Expand Down
Loading