SideToolbar

Supported Environment

  • Desktop: Yes
  • Mobile: No
  • Screen-reader: No

Getting Started

npm install @draft-js-plugins/editor
npm install @draft-js-plugins/side-toolbar
gettingStarted.js
// It is important to import the Editor which accepts plugins.
import Editor from '@draft-js-plugins/editor';
import createSideToolbarPlugin from '@draft-js-plugins/side-toolbar';
import React from 'react';

// Creates an Instance. At this step, a configuration object can be passed in
// as an argument.
const sideToolbarPlugin = createSideToolbarPlugin();

// The Editor accepts an array of plugins. In this case, only the sideToolbarPlugin
// is passed in, although it is possible to pass in multiple plugins.
const MyEditor = ({ editorState, onChange }) => (
  <Editor
    editorState={editorState}
    onChange={onChange}
    plugins={[sideToolbarPlugin]}
  />
);

export default MyEditor;

Importing the default styles

The plugin ships with a default styling available at this location in the installed package:  node_modules/@draft-js-plugins/side-toolbar/lib/plugin.css

Webpack Usage

  • 1. Install Webpack loaders:  npm i style-loader css-loader --save-dev
  • 2. Add the below section to Webpack config (if your config already has a loaders array, simply add the below loader object to your existing list.
    module.exports = {
      module: {
        loaders: [
          {
            test: /plugin\.css$/,
            loaders: ['style-loader', 'css'],
          },
        ],
      },
    };
    
  • 3. Add the below import line to your component to tell Webpack to inject the style to your component.
    import '@draft-js-plugins/side-toolbar/lib/plugin.css';
  • 4. Restart Webpack.

Configuration Parameters

themeObject of CSS classes with the following keys.
buttonStyles: CSS class for the buttons.
toolbarStyles: CSS class for toolbar.
blockTypeSelectStyles: CSS class for the dot.
positionString for the position to be rendered.(Default is left)
popperOptionsThis options will be used to initialize popper.js. Read in detail about it here.The position option is ignored when the this option is used.
This is the popover for the 3 dots button component.
sideToolbarButtonComponentButton component to be used when a the Side Toolbar is visible. Default to 3 dots.
createBlockTypeSelectPopperOptionsThis function will be used create the options for popper.js. As a first parameter it will get a reference to the arrow DOM node.
This is the popover for the block type selection components.

Simple Side Toolbar Example

Once you click into the text field the sidebar plugin will show up …
SimpleSideToolbarEditor.js
import React, { Component } from 'react';
import Editor, { createEditorStateWithText } from '@draft-js-plugins/editor';
import createSideToolbarPlugin from '@draft-js-plugins/side-toolbar';
import editorStyles from './editorStyles.module.css';

const sideToolbarPlugin = createSideToolbarPlugin();
const { SideToolbar } = sideToolbarPlugin;
const plugins = [sideToolbarPlugin];
const text =
  'Once you click into the text field the sidebar plugin will show up …';

export default class SimpleSideToolbarEditor extends Component {
  state = {
    editorState: createEditorStateWithText(text),
  };

  componentDidMount() {
    // fixing issue with SSR https://github.com/facebook/draft-js/issues/2332#issuecomment-761573306
    // eslint-disable-next-line react/no-did-mount-set-state
    this.setState({
      editorState: createEditorStateWithText(text),
    });
  }

  onChange = (editorState) => {
    this.setState({
      editorState,
    });
  };

  focus = () => {
    this.editor.focus();
  };

  render() {
    return (
      <div className={editorStyles.editor} onClick={this.focus}>
        <Editor
          editorState={this.state.editorState}
          onChange={this.onChange}
          plugins={plugins}
          ref={(element) => {
            this.editor = element;
          }}
        />
        <SideToolbar />
      </div>
    );
  }
}
editorStyles.css
.editor {
  box-sizing: border-box;
  border: 1px solid #ddd;
  cursor: text;
  padding: 16px;
  border-radius: 2px;
  margin-bottom: 2em;
  box-shadow: inset 0px 1px 8px -3px #ABABAB;
  background: #fefefe;
}

.editor :global(.public-DraftEditor-content) {
  min-height: 140px;
}

Custom Side Toolbar Example

Once you click into the text field the sidebar plugin will show up …
CustomSideToolbarEditor.js
import React, { Component } from 'react';

import Editor, { createEditorStateWithText } from '@draft-js-plugins/editor';
import {
  HeadlineOneButton,
  HeadlineTwoButton,
  BlockquoteButton,
  CodeBlockButton,
} from '@draft-js-plugins/buttons';

import createSideToolbarPlugin from '@draft-js-plugins/side-toolbar';
import editorStyles from './editorStyles.module.css';
import buttonStyles from './buttonStyles.module.css';
import toolbarStyles from './toolbarStyles.module.css';
import blockTypeSelectStyles from './blockTypeSelectStyles.module.css';

// Setting the side Toolbar at right position(default is left) and styling with custom theme
const sideToolbarPlugin = createSideToolbarPlugin({
  position: 'right',
  theme: { buttonStyles, toolbarStyles, blockTypeSelectStyles },
});
const { SideToolbar } = sideToolbarPlugin;
const plugins = [sideToolbarPlugin];
const text =
  'Once you click into the text field the sidebar plugin will show up …';

export default class CustomSideToolbarEditor extends Component {
  state = {
    editorState: createEditorStateWithText(text),
  };

  onChange = (editorState) => {
    this.setState({
      editorState,
    });
  };

  componentDidMount() {
    // fixing issue with SSR https://github.com/facebook/draft-js/issues/2332#issuecomment-761573306
    // eslint-disable-next-line react/no-did-mount-set-state
    this.setState({
      editorState: createEditorStateWithText(text),
    });
  }

  focus = () => {
    this.editor.focus();
  };

  render() {
    return (
      <div className={editorStyles.editor} onClick={this.focus}>
        <Editor
          editorState={this.state.editorState}
          onChange={this.onChange}
          plugins={plugins}
          ref={(element) => {
            this.editor = element;
          }}
        />
        <SideToolbar>
          {
            // may be use React.Fragment instead of div to improve perfomance after React 16
            (externalProps) => (
              <div>
                <HeadlineOneButton {...externalProps} />
                <HeadlineTwoButton {...externalProps} />
                <BlockquoteButton {...externalProps} />
                <CodeBlockButton {...externalProps} />
              </div>
            )
          }
        </SideToolbar>
      </div>
    );
  }
}
editorStyles.css
.editor {
  box-sizing: border-box;
  border: 1px solid #ddd;
  cursor: text;
  padding: 16px;
  border-radius: 2px;
  margin-bottom: 2em;
  box-shadow: inset 0px 1px 8px -3px #ABABAB;
  background: #fefefe;
}

.editor :global(.public-DraftEditor-content) {
  min-height: 140px;
}
buttonStyles.css
.wrapper {
  position: absolute;
}

.buttonWrapper {
  display: inline-block;
}

.button {
  background: #333;
  color: #ddd;
  font-size: 18px;
  border: 0;
  padding-top: 5px;
  vertical-align: bottom;
  height: 34px;
  width: 36px;
  border-radius: 4px;
}

.button svg {
  fill: #ddd;
}

.button:hover, .button:focus {
  background: #444;
  outline: 0; /* reset for :focus */
}

.active {
  color: #6a9cc9;
}

.active svg {
  fill: #6a9cc9;
}
toolbarStyles.css
.wrapper {
  position: absolute;
}
blockTypeSelectStyles.css
.blockType {
  box-sizing: border-box;
  border: 1px solid #111;
  background: #333;
  padding: 5px;
  margin: 0;
  border-radius: 18px;
  cursor: pointer;
  height: 36px;
  width: 36px;
  line-height: 36px;
  text-align: center;
}

.blockType svg {
  fill: #ddd;
}

.spacer {
  position: absolute;
  left: 50%;
  transform: translate(-50%);
  width: 74px;
  height: 8px;
}

.popup {
  position: absolute;
  left: 50%;
  transform: translate(-50%);
  border: 1px solid #111;
  background: #333;
  border-radius: 2px;
  box-shadow: 0px 1px 3px 0px rgba(220,220,220,1);
  z-index: 3;
  box-sizing: border-box;
  width: 74px;
  margin-top: 8px;
}

.popup:after, .popup:before {
  bottom: 100%;
  left: 50%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}

.popup:after {
  border-color: rgba(251, 251, 251, 0);
  border-bottom-color: #333;
  border-width: 4px;
  margin-left: -4px;
}
.popup:before {
  border-color: rgba(221, 221, 221, 0);
  border-bottom-color: #111;
  border-width: 6px;
  margin-left: -6px;
}