Learn about our RFC process, Open RFC meetings & more.Join in the discussion! »

react-smde

1.1.0 • Public • Published

react-smde

A lightweight Simple Markdown Editor for React.

NPM

codecov Open Issues Dependencies License

Installation

Demo

Basic Usage

Props

Markdown Previewing

Package Exports

Hot Keys

Custom Commands

Custom Styling

Suggestions

Tooltips

Builds

Report Bugs

Feature Requests

License

Third Party Resources

Installation

npm i react-smde

or

yarn add react-smde

react-smde is unopinionated about how to preview Markdown, therefore you'll need to supply your own Markdown previewer (see Basic Usage for an example).

Demo

Basic Usage

import MDEditor from "react-smde";
import ReactMarkdown from "react-markdown";
 
class App extends Component {
  constructor() {
    super();
    this.state = { value: "" };
    this.handleValueChange = this.handleValueChange.bind(this);
  }
 
  handleValueChange(value) {
    this.setState({ value });
  }
 
  render() {
    return (
      <MDEditor onChange={this.handleValueChange} value={this.state.value}>
        <ReactMarkdown>{this.state.value || "(empty)"}</ReactMarkdown>
      </MDEditor>
    );
  }
}

Props

The following props are accepted by MDEditor:

prop Description
autoGrow(bool) A boolean to autogrow the textarea until the maxEditorHeight has been reached. (default: false)
classes(obj) An optional object of string classNames that will be appended to the specified className. (see Custom Styling for more info)
commands(arr) A single array with an array of grouped object commands. (see Custom Commands for more info)
debounceSuggestions(num) A number set in ms to debounce calling the loadSuggestions function. (default: 300)†
disableGrip(bool) A boolean to hide and disable the bottom textarea resizing button. (default: false)
disableHotKeys(bool) A boolean to disable the textarea hot keys. (default: true)
disablePreview(bool) A boolean to disable the preview button -- also disables the preview/write hot key. (default: false)
disableToolbar(bool) A boolean to disable the toolbar. (default: false)
hideGrip(bool) A boolean to hide the bottom textarea toolbar. (default: false)
editorRef(func) An optional callback function to hoist the MDEditor's ref.
grip(str/node) An optional custom grip node or string
loadSuggestions(func) A function that returns an array of suggestions triggered by the suggestionTriggerCharacter. (see Suggestions for more info)
maxCharacterLength(num/str) A maximum MDEditor character length as a number or string. (default: null)
maxEditorHeight(num/str) A maximum MDEditor height number that is set in px or string. (default: 500)
maxEditorWidth (num/str) A maximum MDEditor width number or string. (default: 600px)
minEditorHeight(num/str) A minimum MDEditor height number that is set in px or string. (default: 250)
onChange(func) A required callback function to handle value changes.
readOnly(bool) A boolean to disable editing the text within the textarea. (default: false)
selectedTab(str) A string (write/preview) to initialize the MDEditor's view in. (default: write)
showCharacterLength(bool) A boolean to display the MDEditor's character length. †† (default: false)
suggestionTriggerCharacter(str) A string character to trigger suggestions. (default: @)
textAreaProps(obj) An optional object of properties to apply to the textarea.
tooltipPlacement(str) The tooltip position relative to the target. (default: top -- see Tooltips for more info)
value(str) A required string value.

† Setting debounceSuggestions lower than 300ms, will disable the suggestions loading indicator. In testing, a number lower than 300ms caused unavoidable UI flashes when the returned data is static. As such, this allows you to utilize an array of static data and avoid seeing a loading indicator for each key input.

†† Setting showCharacterLength as true will only be visible if hideGrip is false. In order words, if you hide the bottom bar, it won't display the character length.

Markdown Previewing

The MDEditor is unopinionated when it comes to previewing markdown content. Therefore, you must supply your own Markdown previewer as children to the MDEditor. The demo provided in the source and the example below utilizes react-markdown.

import React, { Component } from "react";
import MDEditor from "react-smde";
import ReactMarkdown from "react-markdown";
 
class App extends Component {
  constructor() {
    super();
    this.state = { value: "" };
    this.handleValueChange = this.handleValueChange.bind(this);
  }
 
  handleValueChange(value) {
    this.setState({ value });
  }
 
  render() {
    return (
      <MDEditor onChange={this.handleValueChange} value={this.state.value}>
        <ReactMarkdown>{this.state.value || "(empty)"}</ReactMarkdown>
      </MDEditor>
    );
  }
}

Package Exports

Aside from the default exported MDEditor, this package also exports a few other named internals:

commands (an object of all predefined commands)
defaultCommandLayout (a chunked array of predefined commands)
replaceSelection (function to replace/insert text -- it requires two arguments: the editor ref and a string)
SvgIcon (component used for default command icons)

You can see use cases for these internals by visiting the Live Demo.

Hot Keys

The MDEditor comes pre-configured with disabled hot keys:

  • Bold (ctrl+b)
  • Italic (ctrl+i)
  • Link (ctrl+k)
  • Edit/Preview toggle (ctrl+0)

If you want to include these keys, then they can be enabled by passing the disableHotKeys=false prop to the MDEditor. By default, they're disabled because they can interfere with other key press triggered event listeners.

Custom Styling

The MDEditor was designed to be as flexible as possible when it comes to customizing the appearance of the editor.

In order to change the style, pass a classes object property to the MDEditor with a custom class name targeting the specified property.

Click to view a summary of available "mde" property overrides...

mde (applied to root)
mdedropdown (applied to header dropdown)
mdetoolbar (applied to toolbar)
mdetoolbargroup (applied to toolbar groups)
mdetoolbaritem (applied to toolbar items)
mdetoolbarseparator (applied to toolbar separators)
mdepreview (applied to preview wrapper)
mdepreviewcontent (applied to previewed content)
mdenosuggestions (applied to no suggestions result item)
mdesuggestions (applied to suggestions overlay)
mdetextarea (applied to textarea input)
mdetextareawrapper (applied to textarea wrapper)
mdetextareacharacterlength (applied to textarea character length)
mdegripcontainer (applied to editor bottom grip container)
mdegripicon (applied to editor bottom grip icon)
mdetooltiparrow (tooltip arrow) 
mdetooltippopper (tooltip container) 
mdetooltippopperarrow (tooltip arrow container) 
mdetooltip (tooltip) 
mdetooltipplacementbottom (tooltips with placement bottom) 
mdetooltipplacementleft (tooltips with placement left) 
mdetooltipplacementright (tooltips with placement right) 
mdetooltipplacementtop (tooltips with placement top) 
mdetooltiptouch (tooltip that has been activated by touch)

For example:

classes={{ mde: "custom-mde", mdetoolbar: "custom-toolbar" }}

Note: This package uses styled-components under the hood and your CSS classnames will need to have higher specificity when overriding styles. See issues with specificity for more information.


Custom Commands

You can rearrange, remove, and adjust properties and/or add your own commands! The commands property of the MDEditor expects a single array of one or many arrays of grouped object commands.

Commands are simple objects where name must either match a name from this predefined list or must be a unique string:

{
  name: "bold",
  tooltip: "Add bold text (ctrl+b)",
  buttonProps: { "aria-label": "Add bold text" },
  icon: <SvgIcon icon="bold" />
}

The icon property must be a React node or a string. You can either pass your own node or you can import the SvgIcon from this package and pass it an icon string property as shown above. For predefined icons, please see this function, which returns a predefined React SVG node based upon a string.

You can override button commands by passing in a callback function to the the buttonProps. This assumes that your button is not a menu. If it is a menu, then you can pass an onClick callback function as a property and it will override the children's button commands. For a working example, see the Custom Commands Demo.

If you wish to append to the predefined commands, then you can import the defaultCommandLayout from the package and spread it out in the commands property (see example below).

For example:

import MDEditor, { commands, defaultCommandLayout, SvgIcon } from "react-smde";
 
const { checkedList, orderedList, unorderedList } = commands;
 
// manual command layout
<MDEditor
    commands={[
      [orderedList, unorderedList, checkedList],
        [
          {
            name: "bold",
            tooltip: "Add bold text (ctrl+b)",
            buttonProps: { "aria-label": "Add bold text" },
            icon: <SvgIcon icon="bold" />
          }
        ]
      ]
    }
    ...otherProps
>
  ...etc
</MDEditor>
 
// appending custom commands to the default layout
<MDEditor
    commands={[
        ...defaultCommandLayout,
        [
          {
            name: "bold",
            tooltip: "Add bold text (ctrl+b)",
            buttonProps: { "aria-label": "Add bold text" },
            icon: <SvgIcon icon="bold" />
          }
        ]
      ]
    }
    ...otherProps
>
  ...etc
</MDEditor>
 

Suggestions

In order to use suggestions, you must supply a callback function to the MDEditor as loadSuggestions. The loadSuggestions callback function must return data in the following structure:

[
  { value: "example1" },
  { value: "example2" },
  { value: "example3" },
  ...etc
]

By default, the MDEditor expects the data to be filtered server-side or by the loadSuggestions callback function.

The suggestions overlay will only be triggered by the suggestionTriggerCharacter and will only execute the loadSuggestions function as determined by the debounceSuggestions property.

Please note that setting a debounceSuggestions lower than 300ms will disable the loading indicator -- this is useful if the returned data remains static.

For a dynamic data set example, see the Demo example above, otherwise here's a static data example:

import React, { Component } from "react";
import MDEditor from "react-smde";
 
class App extends Component {
  constructor() {
    super();
    this.state = {
      value: ""
      suggestions: [
        { value: "andre" },
        { value: "angela" },
        { value: "david" },
        { value: "louise" }
      ]
    };
    this.handleValueChange = this.handleValueChange.bind(this);
    this.loadSuggestions = this.loadSuggestions.bind(this);
  }
 
  handleValueChange(value) {
    this.setState({ value });
  }
 
  loadSuggestions(searchText) {
    return this.state.suggestions.filter(({ value }) =>
      value.toLowerCase().includes(searchText.toLowerCase())
    );
  }
 
  render() {
    return(
      <div className="container">
        <MDEditor
          onChange={this.handleValueChange}
          value={this.state.value}
          debounceSuggestions={0}
          loadSuggestions={this.loadSuggestions}
        >
          <ReactMarkdown>{this.state.value}</ReactMarkdown>
        </MDEditor>
      </div>
    );
  }
}

Tooltips

You can specify the position of the tooltip in relation to its target. For example, one of the following strings can be passed to the tooltipPlacement property:

'bottom-end'
'bottom-start'
'bottom'
'left-end'
'left-start'
'left'
'right-end'
'right-start'
'right'
'top-end'
'top-start'
'top'

Please note that there must be sufficient surrounding window space for the tooltip to occupy the specified placement area; otherwise, the tooltip location may be incorrectly calculated and cause an undesirable UX.

Builds

By default, this package is compiled to a common-js (CJS - main) file with supplemental Universal Module Definition (UMD - fallback) and a ECMAScript Module (ESM - module) files. If you wish to use one of the supplemental verisons, then you can do so by either:

Option 1: Import from react-smde/dist/(esm|umd)/index.(esm.umd).js.

Option 2: If you're using webpack, then you can utilize the resolve.mainFields property and point to one of the fields listed above: main, module, or fallback. See the resolve.mainFields webpack documentation for more info.

Report bugs

If you run into any issues, please fill out a bug report.

⚠️ NOTE: Please provide a reproducible codesandbox example of the bug(s) you're experiencing. Issues that don't provide a reproducible example may be ignored.

Feature Requests

Have a feature you want included or believe the editor is missing a standard feature? You can either fork the repo, commit changes, and submit a new PR (please include relevant .tests.js files and run npm run test:cov or yarn test:cov to make sure the code is covered) or you can submit a feature request.

License

react-smde is MIT licensed.

Third Party Resources

In order to make react-smde, the following packages are referenced and used within this package:

Install

npm i react-smde

DownloadsWeekly Downloads

28

Version

1.1.0

License

MIT

Unpacked Size

261 kB

Total Files

6

Last publish

Collaborators

  • avatar