Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
109 changes: 64 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,51 @@ 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 +619,15 @@ 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>
)}
{!hasEditorHeaderContent && editorHeaderContent}
{isLanguageLabelVisible && (
<div className={css(styles.codeEditorTab)}>
<span className={css(styles.codeEditorTabIcon)}>
Expand Down Expand Up @@ -643,14 +662,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={`${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
Original file line number Diff line number Diff line change
Expand Up @@ -6,118 +6,126 @@ exports[`Matches snapshot with control buttons enabled 1`] = `
class="pf-v5-c-code-editor"
>
<div
class="pf-v5-c-file-upload false false"
class="false"
role="presentation"
tabindex="0"
>
<div
class="pf-v5-c-code-editor__header"
>
<div
class="pf-v5-c-code-editor__controls"
class="pf-v5-c-code-editor__header-content"
>
<div
style="display: contents;"
class="pf-v5-c-code-editor__controls"
>
<button
aria-disabled="false"
aria-label="Copy code to clipboard"
class="pf-v5-c-button pf-m-control"
data-ouia-component-id="OUIA-Generated-Button-control-1"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
<div
style="display: contents;"
>
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 448 512"
width="1em"
<button
aria-disabled="false"
aria-label="Copy code to clipboard"
class="pf-v5-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-1"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
>
<path
d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"
/>
</svg>
</button>
</div>
<div
style="display: contents;"
>
<button
aria-disabled="false"
aria-label="Upload code"
class="pf-v5-c-button pf-m-control"
data-ouia-component-id="OUIA-Generated-Button-control-2"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 448 512"
width="1em"
>
<path
d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"
/>
</svg>
</button>
</div>
<div
style="display: contents;"
>
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 512 512"
width="1em"
<button
aria-disabled="false"
aria-label="Upload code"
class="pf-v5-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-2"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
>
<path
d="M296 384h-80c-13.3 0-24-10.7-24-24V192h-87.7c-17.8 0-26.7-21.5-14.1-34.1L242.3 5.7c7.5-7.5 19.8-7.5 27.3 0l152.2 152.2c12.6 12.6 3.7 34.1-14.1 34.1H320v168c0 13.3-10.7 24-24 24zm216-8v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h136v8c0 30.9 25.1 56 56 56h80c30.9 0 56-25.1 56-56v-8h136c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"
/>
</svg>
</button>
</div>
<div
style="display: contents;"
>
<button
aria-disabled="false"
aria-label="Download code"
class="pf-v5-c-button pf-m-control"
data-ouia-component-id="OUIA-Generated-Button-control-3"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M296 384h-80c-13.3 0-24-10.7-24-24V192h-87.7c-17.8 0-26.7-21.5-14.1-34.1L242.3 5.7c7.5-7.5 19.8-7.5 27.3 0l152.2 152.2c12.6 12.6 3.7 34.1-14.1 34.1H320v168c0 13.3-10.7 24-24 24zm216-8v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h136v8c0 30.9 25.1 56 56 56h80c30.9 0 56-25.1 56-56v-8h136c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"
/>
</svg>
</button>
</div>
<div
style="display: contents;"
>
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 512 512"
width="1em"
<button
aria-disabled="false"
aria-label="Download code"
class="pf-v5-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-3"
data-ouia-component-type="PF5/Button"
data-ouia-safe="true"
type="button"
>
<path
d="M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"
/>
</svg>
</button>
<svg
aria-hidden="true"
class="pf-v5-svg"
fill="currentColor"
height="1em"
role="img"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"
/>
</svg>
</button>
</div>
</div>
<div
class="pf-v5-c-code-editor__header-main"
/>
</div>
<div
class="pf-v5-c-code-editor__header-main"
/>
</div>
<div
class="pf-v5-c-code-editor__main"
>
<input
style="display: none;"
tabindex="-1"
type="file"
/>
<div
class="pf-v5-c-code-editor__code"
dir="ltr"
tabindex="0"
class="pf-v5-c-code-editor__upload"
>
<div
data-testid="mock-editor"
<input
style="display: none;"
tabindex="-1"
type="file"
/>
<div
class="pf-v5-c-code-editor__code"
dir="ltr"
tabindex="0"
>
<div
data-testid="mock-editor"
/>
</div>
</div>
</div>
</div>
Expand Down
Loading