Skip to content

Latest commit

Β 

History

History
435 lines (363 loc) Β· 12.6 KB

File metadata and controls

435 lines (363 loc) Β· 12.6 KB
keywords
EuiMarkdownEditor

Editor

EuiMarkdownEditor provides a markdown authoring experience for the user. The component consists of a toolbar, text area, and a drag-and-drop zone to accept files (if configured to do so). There are two modes: a textarea that keeps track of cursor position, and a rendered preview mode that is powered by EuiMarkdownFormat. State is maintained between the two and it is possible to pass changes from the preview area to the textarea and vice versa.

Base editor

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 several default plugins that let you add emojis, to-do lists, and tooltips, which can be configured or removed as needed.

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.

import React, { useCallback, useState } from 'react';
import {
  EuiMarkdownEditor,
  EuiSpacer,
  EuiCodeBlock,
  EuiButton,
  EuiSwitch,
  EuiFlexGroup,
  EuiFlexItem,
} from '@elastic/eui';

const initialContent = `## Hello world!

Basic "GitHub flavored" markdown will work as you'd expect.

The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.

- [ ] 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 = [
  {
    supportedFiles: ['.jpg', '.jpeg'],
    accepts: (itemType) => itemType === 'image/jpeg',
    getFormattingForItem: (item) => {
      // fake an upload
      return new Promise((resolve) => {
        setTimeout(() => {
          const url = URL.createObjectURL(item);
          resolve({
            text: `![${item.name}](${url})`,
            config: { block: true },
          });
        }, 1000);
      });
    },
  },
];

export default () => {
  const [value, setValue] = useState(initialContent);
  const [messages, setMessages] = useState([]);
  const [ast, setAst] = useState(null);
  const [isAstShowing, setIsAstShowing] = useState(false);
  const onParse = useCallback((err, { messages, ast }) => {
    setMessages(err ? [err] : messages);
    setAst(JSON.stringify(ast, null, 2));
  }, []);

  const [isReadOnly, setIsReadOnly] = useState(false);

  const onChange = (e) => {
    setIsReadOnly(e.target.checked);
  };

  return (
    <>
      <EuiMarkdownEditor
        aria-label="EUI markdown editor demo"
        placeholder="Your markdown here..."
        value={value}
        onChange={setValue}
        height={400}
        onParse={onParse}
        errors={messages}
        dropHandlers={dropHandlers}
        readOnly={isReadOnly}
        initialViewMode="viewing"
      />
      <EuiSpacer size="s" />
      <EuiFlexGroup alignItems="center">
        <EuiFlexItem grow={true}>
          <EuiSwitch
            label="Read-only"
            checked={isReadOnly}
            onChange={onChange}
          />
        </EuiFlexItem>
        <EuiFlexItem grow={false}>
          <EuiButton
            size="s"
            iconType={isAstShowing ? 'eyeClosed' : 'eye'}
            onClick={() => setIsAstShowing(!isAstShowing)}
            fill={isAstShowing}
          >
            {isAstShowing ? 'Hide editor AST' : 'Show editor AST'}
          </EuiButton>
        </EuiFlexItem>
      </EuiFlexGroup>

      <EuiSpacer size="s" />

      {isAstShowing && <EuiCodeBlock language="json">{ast}</EuiCodeBlock>}
    </>
  );
};

Error handling and feedback

The errors prop allows you to pass an array of errors if syntax is malformed. The below example starts with an incomplete tooltip tag, showing this error message by default. These errors are meant to be ephemeral and part of the editing experience. They should not be a substitute for form validation.

```tsx interactive import React, { useCallback, useState, useRef } from 'react'; import { EuiMarkdownEditor, EuiSpacer, EuiCodeBlock, EuiButton, EuiFormErrorText, EuiLink, htmlIdGenerator, } from '@elastic/eui';

const initialContent = `## Errors

The tooltip is empty and will error

!{tooltip} `;

export default () => { const errorElementId = useRef(htmlIdGenerator()()); const [value, setValue] = useState(initialContent); const [messages, setMessages] = useState([]); const [ast, setAst] = useState(null); const [isAstShowing, setIsAstShowing] = useState(false);

const onParse = useCallback((err, { messages, ast }) => { setMessages(err ? [err] : messages); setAst(JSON.stringify(ast, null, 2)); }, []);

return ( <>

  <EuiFormErrorText
    id={errorElementId.current}
    className="euiFormRow__text"
  >
    Utilize error text or{' '}
    <strong>
      <EuiLink href="/docs/forms/validation">EuiFormRow</EuiLink>
    </strong>{' '}
    for more permanent error feedback
  </EuiFormErrorText>

  <div className="eui-textRight">
    <EuiButton
      size="s"
      iconType={isAstShowing ? 'eyeClosed' : 'eye'}
      onClick={() => setIsAstShowing(!isAstShowing)}
      fill={isAstShowing}
    >
      {isAstShowing ? 'Hide editor AST' : 'Show editor AST'}
    </EuiButton>
  </div>

  {isAstShowing && <EuiCodeBlock language="json">{ast}</EuiCodeBlock>}
</>

); };

</Demo>

## Controlling the height

The `height` prop allows you to control the height of the **EuiMarkdownEditor**. You can set the `height` in pixels or pass `"full"` to allow the **EuiMarkdownEditor** to fill the height of its container. By default, the `autoExpandPreview` prop is set to `true`. This means that the preview `height` is automatically adjusted to fit all the content and avoid a scrollbar.

You can also control the `maxHeight` of the editor/preview area. This prop only works when the `height` is not set to `"full"`.

```tsx interactive
import React, { useState } from 'react';
import {
  EuiMarkdownEditor,
  EuiFlexGroup,
  EuiFlexItem,
  EuiSpacer,
  EuiPanel,
} from '@elastic/eui';

const initialContent1 = `## πŸ‘‹ Hello there!

I'm a **EuiMarkdownEditor** with:

- a \`height\` set to \`200\`
- my parent container is a flex item

### Things you should know

When my content is very long πŸ˜…

The preview height is automatically adjusted πŸ˜‰

To avoid a scrollbar 😌

### That's why I look good 😍

`;

const initialContent2 = `## πŸ‘‹ Hello again!

I'm a **EuiMarkdownEditor** with:
- a \`height\` set to \`"full"\`
- my parent container is a flex item with a \`height\` set to \`600\`
`;

const initialContent3 = `## πŸ‘‹ Hi!

I'm a **EuiMarkdownEditor** with:
- a \`height\` set to \`200\`
- my parent container is a flex item.
- the \`autoExpandPreview\` is set to \`false\`

### Things you should know

When the content grows the preview height is not automatically adjusted. Just because the \`autoExpandPreview\` is set to \`false\` πŸ˜‰
`;

const initialContent4 = `## πŸ‘‹ Hello again!

I'm just a **EuiMarkdownEditor** with:
- a \`height\` set to \`200\`
- a \`maxHeight\` set to \`300\`
`;

export default () => {
  const [value1, setValue1] = useState(initialContent1);
  const [value2, setValue2] = useState(initialContent2);
  const [value3, setValue3] = useState(initialContent3);
  const [value4, setValue4] = useState(initialContent4);

  return (
    <>
      <EuiFlexGroup>
        <EuiFlexItem>
          <EuiPanel color="primary">
            <EuiMarkdownEditor
              aria-label="EUI markdown editor with fixed height"
              placeholder="Your markdown here..."
              initialViewMode="viewing"
              value={value1}
              onChange={setValue1}
              height={200}
            />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem style={{ height: '600px' }}>
          <EuiPanel color="primary">
            <EuiMarkdownEditor
              aria-label="EUI markdown editor with full height "
              initialViewMode="viewing"
              value={value2}
              onChange={setValue2}
              height="full"
            />
          </EuiPanel>
        </EuiFlexItem>
      </EuiFlexGroup>

      <EuiSpacer />

      <EuiFlexGroup>
        <EuiFlexItem>
          <EuiPanel color="primary">
            <EuiMarkdownEditor
              aria-label="EUI markdown editor with no auto expand"
              placeholder="Your markdown here..."
              initialViewMode="viewing"
              value={value3}
              onChange={setValue3}
              height={200}
              autoExpandPreview={false}
            />
          </EuiPanel>
        </EuiFlexItem>
        <EuiFlexItem>
          <EuiPanel color="primary">
            <EuiMarkdownEditor
              aria-label="EUI markdown editor with fixed and max height"
              placeholder="Your markdown here..."
              initialViewMode="viewing"
              value={value4}
              onChange={setValue4}
              height={200}
              maxHeight={300}
            />
          </EuiPanel>
        </EuiFlexItem>
      </EuiFlexGroup>
    </>
  );
};

Customization

You can customize the editor toolbar by passing a custom toolbarProps object. toolbarProps.right allows you to add custom content (like buttons) in the right slot of the toolbar.

import React, { useCallback, useState } from 'react';
import {
  EuiMarkdownEditor,
  EuiMarkdownEditorHelpButton,
  EuiSpacer,
  EuiCodeBlock,
  EuiButton,
  EuiSwitch,
  EuiFlexGroup,
  EuiFlexItem,
} from '@elastic/eui';

const initialContent = `## Hello world!

Basic "GitHub flavored" markdown will work as you'd expect.

The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.

- [ ] 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 = [
  {
    supportedFiles: ['.jpg', '.jpeg'],
    accepts: (itemType) => itemType === 'image/jpeg',
    getFormattingForItem: (item) => {
      // fake an upload
      return new Promise((resolve) => {
        setTimeout(() => {
          const url = URL.createObjectURL(item);
          resolve({
            text: `![${item.name}](${url})`,
            config: { block: true },
          });
        }, 1000);
      });
    },
  },
];

export default () => {
  const [value, setValue] = useState(initialContent);
  const [messages, setMessages] = useState([]);
  const [ast, setAst] = useState(null);
  const [isAstShowing, setIsAstShowing] = useState(false);
  const onParse = useCallback((err, { messages, ast }) => {
    setMessages(err ? [err] : messages);
    setAst(JSON.stringify(ast, null, 2));
  }, []);

  return (
    <>
      <EuiMarkdownEditor
        aria-label="EUI markdown editor demo"
        placeholder="Your markdown here..."
        value={value}
        onChange={setValue}
        height={400}
        onParse={onParse}
        errors={messages}
        dropHandlers={dropHandlers}
        initialViewMode="viewing"
        toolbarProps={{
          right: (
            <EuiButton iconType="check" size="s" onClick={() => {}}>
              Validate
            </EuiButton>
          )
        }}
      />
      <EuiSpacer size="s" />
      <EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
        <EuiFlexItem grow={false}>
          <EuiButton
            size="s"
            iconType={isAstShowing ? 'eyeClosed' : 'eye'}
            onClick={() => setIsAstShowing(!isAstShowing)}
            fill={isAstShowing}
          >
            {isAstShowing ? 'Hide editor AST' : 'Show editor AST'}
          </EuiButton>
        </EuiFlexItem>
      </EuiFlexGroup>

      <EuiSpacer size="s" />

      {isAstShowing && <EuiCodeBlock language="json">{ast}</EuiCodeBlock>}
    </>
  );
};

Props

import docgen from '@elastic/eui-docgen/dist/components/markdown_editor';