diff --git a/CHANGELOG.md b/CHANGELOG.md
index 307da5eb404..8a8df73537f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
## [`main`](https://github.com/elastic/eui/tree/main)
-No public interface changes since `49.0.0`.
+- Added `readOnly` prop to `EuiMarkdownEditor` ([#5627](https://github.com/elastic/eui/pull/5627))
## [`49.0.0`](https://github.com/elastic/eui/tree/v49.0.0)
diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js
index e37a1417c5d..c36d88a7d68 100644
--- a/src-docs/src/views/markdown_editor/mardown_editor_example.js
+++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js
@@ -102,10 +102,21 @@ export const MarkdownEditorExample = {
],
title: 'Base editor',
text: (
-
- The base editor can render basic markdown along with some built-in
- plugins.
-
+ <>
+
+ Use the base editor to produce technical content in markdown which
+ can contain text, code, and images. Besides this default markdown
+ content, the base editor comes with built-in plugins that let you
+ add emojis, to-do lists, and tooltips.
+
+
+ Consider applying the readOnly prop to restrict
+ editing during asynchronous submit events, like when submitting a{' '}
+ comment. This will ensure
+ users understand that the content cannot be changed while the
+ comment is being submitted.
+
+ >
),
props: {
EuiMarkdownEditor,
diff --git a/src-docs/src/views/markdown_editor/markdown_editor.js b/src-docs/src/views/markdown_editor/markdown_editor.js
index 48d32275af2..3e6b98a12c0 100644
--- a/src-docs/src/views/markdown_editor/markdown_editor.js
+++ b/src-docs/src/views/markdown_editor/markdown_editor.js
@@ -5,6 +5,9 @@ import {
EuiSpacer,
EuiCodeBlock,
EuiButton,
+ EuiSwitch,
+ EuiFlexGroup,
+ EuiFlexItem,
} from '../../../../src/components';
const initialContent = `## Hello world!
@@ -16,6 +19,10 @@ The editor also ships with some built in plugins. For example it can handle chec
- [ ] Checkboxes
- [x] Can be filled
- [ ] Or empty
+
+It can also handle emojis! :smile:
+
+And it can render !{tooltip[tooltips like this](Look! I'm a very helpful tooltip content!)}
`;
const dropHandlers = [
@@ -46,6 +53,13 @@ export default () => {
setMessages(err ? [err] : messages);
setAst(JSON.stringify(ast, null, 2));
}, []);
+
+ const [isReadOnly, setIsReadOnly] = useState(false);
+
+ const onChange = (e) => {
+ setIsReadOnly(e.target.checked);
+ };
+
return (
<>
{
onParse={onParse}
errors={messages}
dropHandlers={dropHandlers}
+ readOnly={isReadOnly}
+ initialViewMode="viewing"
/>
-
- setIsAstShowing(!isAstShowing)}
- fill={isAstShowing}
- >
- {isAstShowing ? 'Hide editor AST' : 'Show editor AST'}
-
-
+
+
+
+
+
+ setIsAstShowing(!isAstShowing)}
+ fill={isAstShowing}
+ >
+ {isAstShowing ? 'Hide editor AST' : 'Show editor AST'}
+
+
+
+
+
+
{isAstShowing && {ast} }
>
);
diff --git a/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap b/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap
index 42a0588187b..20d02c30e21 100644
--- a/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap
+++ b/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap
@@ -4385,3 +4385,249 @@ exports[`EuiMarkdownEditor props maxHeight is rendered with a custom size 1`] =
`;
+
+exports[`EuiMarkdownEditor props readOnly is set to true 1`] = `
+
+`;
diff --git a/src/components/markdown_editor/_markdown_editor_preview.scss b/src/components/markdown_editor/_markdown_editor_preview.scss
index 32749abf0cb..741798dd549 100644
--- a/src/components/markdown_editor/_markdown_editor_preview.scss
+++ b/src/components/markdown_editor/_markdown_editor_preview.scss
@@ -6,3 +6,15 @@
border: $euiBorderThin;
padding: $euiSizeM;
}
+
+.euiMarkdownEditorPreview-isReadOnly {
+ // overrides cursor pointer to better indicate that checkboxes are not clickable
+ .euiCheckbox__input ~ .euiCheckbox__label {
+ cursor: default;
+ }
+
+ // maintains the initial color on click to enforce that clicks are not doing anything
+ .euiCheckbox__input:focus:not(:checked) + .euiCheckbox__square {
+ border-color: $euiFormCustomControlBorderColor;
+ }
+}
\ No newline at end of file
diff --git a/src/components/markdown_editor/_markdown_editor_text_area.scss b/src/components/markdown_editor/_markdown_editor_text_area.scss
index 1af68c17d85..a0827ed8798 100644
--- a/src/components/markdown_editor/_markdown_editor_text_area.scss
+++ b/src/components/markdown_editor/_markdown_editor_text_area.scss
@@ -32,3 +32,13 @@
background-size: 100% 100%;
}
}
+
+.euiMarkdownEditorTextArea-isReadOnly {
+ background: $euiFormBackgroundReadOnlyColor;
+ cursor: unset;
+
+ &:focus,
+ .euiMarkdownEditor:focus-within & {
+ background: none;
+ }
+}
\ No newline at end of file
diff --git a/src/components/markdown_editor/markdown_context.ts b/src/components/markdown_editor/markdown_context.ts
index 4ea7310f158..8ec0fae3173 100644
--- a/src/components/markdown_editor/markdown_context.ts
+++ b/src/components/markdown_editor/markdown_context.ts
@@ -17,9 +17,11 @@ interface MarkdownPosition {
export interface ContextShape {
openPluginEditor: (plugin: EuiMarkdownEditorUiPlugin) => void;
replaceNode(position: MarkdownPosition, next: string): void;
+ readOnly?: boolean;
}
export const EuiMarkdownContext = createContext({
openPluginEditor: () => {},
replaceNode() {},
+ readOnly: false,
});
diff --git a/src/components/markdown_editor/markdown_editor.test.tsx b/src/components/markdown_editor/markdown_editor.test.tsx
index 87fac5f7e78..72f262257c8 100644
--- a/src/components/markdown_editor/markdown_editor.test.tsx
+++ b/src/components/markdown_editor/markdown_editor.test.tsx
@@ -91,6 +91,23 @@ describe('EuiMarkdownEditor', () => {
expect(component).toMatchSnapshot();
});
});
+
+ describe('readOnly', () => {
+ test('is set to true', () => {
+ const component = render(
+ null}
+ readOnly
+ {...requiredProps}
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
});
test('is preview rendered', () => {
diff --git a/src/components/markdown_editor/markdown_editor.tsx b/src/components/markdown_editor/markdown_editor.tsx
index fd94fa5e3f4..1a82f8fc1d8 100644
--- a/src/components/markdown_editor/markdown_editor.tsx
+++ b/src/components/markdown_editor/markdown_editor.tsx
@@ -77,6 +77,11 @@ type CommonMarkdownEditorProps = Omit<
/** callback function when markdown content is modified */
onChange: (value: string) => void;
+ /**
+ * Sets the current display mode to a read-only state. All editing gets resctricted.
+ */
+ readOnly?: ContextShape['readOnly'];
+
/**
* Sets the `height` in pixels of the editor/preview area or pass `full` to allow
* the EuiMarkdownEditor to fill the height of its container.
@@ -209,6 +214,7 @@ export const EuiMarkdownEditor = forwardRef<
dropHandlers = [],
markdownFormatProps,
placeholder,
+ readOnly,
...rest
},
ref
@@ -272,11 +278,14 @@ export const EuiMarkdownEditor = forwardRef<
const contextValue = useMemo(
() => ({
- openPluginEditor: (plugin: EuiMarkdownEditorUiPlugin) =>
- setPluginEditorPlugin(() => plugin),
- replaceNode,
+ openPluginEditor: readOnly
+ ? () => {}
+ : (plugin: EuiMarkdownEditorUiPlugin) =>
+ setPluginEditorPlugin(() => plugin),
+ replaceNode: readOnly ? () => {} : replaceNode,
+ readOnly: readOnly,
}),
- [replaceNode]
+ [replaceNode, readOnly]
);
const [selectedNode, setSelectedNode] = useState();
@@ -354,6 +363,10 @@ export const EuiMarkdownEditor = forwardRef<
className
);
+ const classesPreview = classNames('euiMarkdownEditorPreview', {
+ 'euiMarkdownEditorPreview-isReadOnly': readOnly,
+ });
+
const onResize = () => {
if (textarea && isEditing && height !== 'full') {
const resizedTextareaHeight =
@@ -423,7 +436,7 @@ export const EuiMarkdownEditor = forwardRef<
{isPreviewing && (
setHasUnacceptedItems(false)}
placeholder={placeholder}
+ readOnly={readOnly}
{...{
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
diff --git a/src/components/markdown_editor/markdown_editor_footer.tsx b/src/components/markdown_editor/markdown_editor_footer.tsx
index e800eb3dc8a..e5b5b9f8b9f 100644
--- a/src/components/markdown_editor/markdown_editor_footer.tsx
+++ b/src/components/markdown_editor/markdown_editor_footer.tsx
@@ -12,6 +12,7 @@ import React, {
Fragment,
ReactChild,
forwardRef,
+ useContext,
} from 'react';
import { EuiLoadingSpinner } from '../loading';
import { EuiButton, EuiButtonEmpty, EuiButtonIcon } from '../button';
@@ -38,6 +39,8 @@ import { EuiHorizontalRule } from '../horizontal_rule';
import { EuiLink } from '../link';
+import { EuiMarkdownContext } from './markdown_context';
+
interface EuiMarkdownEditorFooterProps {
uiPlugins: EuiMarkdownEditorUiPlugin[];
isUploadingFiles: boolean;
@@ -110,12 +113,15 @@ export const EuiMarkdownEditorFooter = forwardRef<
'Syntax help'
);
+ const { readOnly } = useContext(EuiMarkdownContext);
+
if (isUploadingFiles) {
uploadButton = (
);
} else if (dropHandlers.length > 0 && hasUnacceptedItems) {
@@ -129,6 +135,7 @@ export const EuiMarkdownEditorFooter = forwardRef<
color="danger"
aria-label={`${ariaLabels.unsupportedFileType}. ${ariaLabels.supportedFileTypes}. ${ariaLabels.uploadingFiles}`}
onClick={openFiles}
+ isDisabled={readOnly}
>
{ariaLabels.unsupportedFileType}
@@ -142,6 +149,7 @@ export const EuiMarkdownEditorFooter = forwardRef<
color="text"
aria-label={ariaLabels.openUploadModal}
onClick={openFiles}
+ isDisabled={readOnly}
/>
);
}
@@ -157,6 +165,7 @@ export const EuiMarkdownEditorFooter = forwardRef<
color="danger"
aria-label={ariaLabels.showSyntaxErrors}
onClick={onButtonClick}
+ isDisabled={readOnly}
>
{errors.length}
@@ -211,6 +220,7 @@ export const EuiMarkdownEditorFooter = forwardRef<
color="text"
aria-label={ariaLabels.showMarkdownHelp}
onClick={() => setIsShowingHelpModal(!isShowingHelpModal)}
+ isDisabled={readOnly}
/>
diff --git a/src/components/markdown_editor/markdown_editor_text_area.tsx b/src/components/markdown_editor/markdown_editor_text_area.tsx
index b5f78e4aa57..94328675ef0 100644
--- a/src/components/markdown_editor/markdown_editor_text_area.tsx
+++ b/src/components/markdown_editor/markdown_editor_text_area.tsx
@@ -6,9 +6,10 @@
* Side Public License, v 1.
*/
-import React, { TextareaHTMLAttributes, forwardRef } from 'react';
-
+import React, { TextareaHTMLAttributes, forwardRef, useContext } from 'react';
+import classNames from 'classnames';
import { CommonProps } from '../common';
+import { EuiMarkdownContext } from './markdown_context';
export type EuiMarkdownEditorTextAreaProps = TextareaHTMLAttributes<
HTMLTextAreaElement
@@ -40,16 +41,22 @@ export const EuiMarkdownEditorTextArea = forwardRef<
},
ref
) => {
+ const { readOnly } = useContext(EuiMarkdownContext);
+ const classes = classNames('euiMarkdownEditorTextArea', {
+ 'euiMarkdownEditorTextArea-isReadOnly': readOnly,
+ });
+
return (
diff --git a/src/components/markdown_editor/markdown_editor_toolbar.tsx b/src/components/markdown_editor/markdown_editor_toolbar.tsx
index 56f21434bd6..28257cfb53b 100644
--- a/src/components/markdown_editor/markdown_editor_toolbar.tsx
+++ b/src/components/markdown_editor/markdown_editor_toolbar.tsx
@@ -98,7 +98,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
{ markdownActions, viewMode, onClickPreview, uiPlugins, selectedNode },
ref: Ref
) => {
- const { openPluginEditor } = useContext(EuiMarkdownContext);
+ const { openPluginEditor, readOnly } = useContext(EuiMarkdownContext);
const handleMdButtonClick = (mdButtonId: string) => {
const actionResult = markdownActions.do(mdButtonId);
@@ -107,6 +107,8 @@ export const EuiMarkdownEditorToolbar = forwardRef<
const isPreviewing = viewMode === MODE_VIEWING;
+ const isEditable = !isPreviewing && !readOnly;
+
return (
@@ -117,7 +119,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
onClick={() => handleMdButtonClick(item.id)}
iconType={item.iconType}
aria-label={item.label}
- isDisabled={isPreviewing}
+ isDisabled={!isEditable}
/>
))}
@@ -129,7 +131,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
onClick={() => handleMdButtonClick(item.id)}
iconType={item.iconType}
aria-label={item.label}
- isDisabled={isPreviewing}
+ isDisabled={!isEditable}
/>
))}
@@ -141,7 +143,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
onClick={() => handleMdButtonClick(item.id)}
iconType={item.iconType}
aria-label={item.label}
- isDisabled={isPreviewing}
+ isDisabled={!isEditable}
/>
))}
@@ -164,7 +166,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
onClick={() => handleMdButtonClick(name)}
iconType={button.iconType}
aria-label={button.label}
- isDisabled={isPreviewing}
+ isDisabled={!isEditable}
/>
);
@@ -179,6 +181,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
color="text"
size="s"
onClick={onClickPreview}
+ isDisabled={readOnly}
>
@@ -188,6 +191,7 @@ export const EuiMarkdownEditorToolbar = forwardRef<
color="text"
size="s"
onClick={onClickPreview}
+ isDisabled={readOnly}
>