DEV Community

Cover image for Inline styles in draft-js
Hosein Pouyanmehr
Hosein Pouyanmehr

Posted on • Updated on • Originally published at contenidojs.com

Inline styles in draft-js

What is this post about?

This tutorial is about inline styles in draft-js and for your. In this post, You will learn how to implement an editor with basic and custom inline styles. This tutorial is helpful if you're new to draft-js and web editors or looking for a way to implement an editor for your comment box or similar use cases.

Table of contents

Requirements

To be able to create an editor, the only requirement is to know how to set up a ReactJS (or NextJs) project. We're going to use draft-js and contenido packages in this tutorial.

Draft JS

Draft-js is a rich text editor framework for React. You can read this post to get familiar with that.

Contenido

Contenido is a library with a set of tools to help you create your rich text editor on top of draft-js. We use contenido to boost the development process and avoid repetitive processes.

Read More: A brief introduction to contenido

Installation

First, we will install React and Typescript (There will be a JS alternative for everything, so stick with your favorite one).

# typescript
npx create-react-app draft-inline-styles --template typescript

# javascript
npx create-react-app draft-inline-styles
Enter fullscreen mode Exit fullscreen mode

After the installation is done, install draft-js and contenido with this command:

# typescript
npm i draft-js @types/draft-js contenido@latest

# javascript
npm i draft-js  contenido@latest
Enter fullscreen mode Exit fullscreen mode

Create Editor component

After setting up the project, create a components folder in the src directory and then create the Editor component:

Typescript:

// src > components > Editor.tsx
import { useState } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'contenido';

// Types
import type { FC } from 'react';

// Custom Types
export interface InlineStylesEditorProps {}

const InlineStylesEditor: FC<InlineStylesEditorProps> = (props) => {
  // States
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  return (
    <div>
      <div>// We'll add the toolbar here</div>
      <div>
        <Editor editorState={editorState} onChange={setEditorState} />
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Javascript:

// src > components > Editor.jsx
import { useState } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'contenido';

const InlineStylesEditor = (props) => {
  // States
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  return (
    <div>
      <div>// We'll add the toolbar here</div>
      <div>
        <Editor editorState={editorState} onChange={setEditorState} />
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

to make the editor much more visible add the border styles to the div:

...
      <div style={{
         border: '1px solid #252525',
         borderRadius: '0.5rem',
         padding: '0.5rem 1rem',
        }}
      >
        <Editor editorState={editorState} onChange={setEditorState} />
      </div>
...
Enter fullscreen mode Exit fullscreen mode

Add Placeholder

Also adding a placeholder can be useful, so:

...
        <Editor
          editorState={editorState}
          onChange={setEditorState}
         placeholder='Write here...'
        />
...
Enter fullscreen mode Exit fullscreen mode

Add Toolbar Buttons

Now, it's time to add the buttons to the toolbar. First we will create an array of buttons to map them in the toolbar. After that, we use JS map to render the buttons.

Added lines are the same for both Javascript and Typescript:

// src > components > Editor.tsx
import { useState } from 'react';
import { EditorState } from 'draft-js';
import { Editor } from 'contenido';

// Types
import type { FC } from 'react';

// Custom Types
export interface InlineStylesEditorProps {}

const buttons = [
  { title: 'Bold' },
  { title: 'Italic' },
  { title: 'Underline' },
  { title: 'Line Through' },
  { title: 'Overline' },
  { title: 'Sup' },
  { title: 'Sub' },
];

const InlineStylesEditor: FC<InlineStylesEditorProps> = (props) => {
  // States
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  return (
    <div>
      <div>
        {buttons.map((button) => (
          <button
            key={button.title}
            style={{
              minWidth: '2rem',
              padding: '0.5rem',
              borderRadius: '0.5rem',
              border: 'none',
              cursor: 'pointer',
            }}
          >
            {button.title}
          </button>
        ))}
      </div>
      <div>
        <Editor
          editorState={editorState}
          onChange={setEditorState}
          placeholder='Write here...'
        />
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

For all of these buttons, there is a utility in contenido so we just need to import and add the handler for each button.

// src > components > Editor.tsx
import { useState } from 'react';
import { EditorState } from 'draft-js';
import {
  Editor,
  initialStyleMap,
  toggleBold,
  toggleItalic,
  toggleLineThrough,
  toggleOverline,
  toggleSub,
  toggleSup,
  toggleUnderline,
} from 'contenido';

// Types
import type { FC } from 'react';

// Custom Types
export interface InlineStylesEditorProps {}

const buttons = [
  { title: 'Bold', handler: toggleBold },
  { title: 'Italic', handler: toggleItalic },
  { title: 'Underline', handler: toggleUnderline },
  { title: 'Line Through', handler: toggleLineThrough },
  { title: 'Overline', handler: toggleOverline },
  { title: 'Sup', handler: toggleSup },
  { title: 'Sub', handler: toggleSub },
];

const InlineStylesEditor: FC<InlineStylesEditorProps> = (props) => {
  // States
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  return (
    <div>
      <div>
        {buttons.map((button) => (
          <button
            key={button.title}
            style={{
              minWidth: '2rem',
              padding: '0.5rem',
              borderRadius: '0.5rem',
              border: 'none',
              cursor: 'pointer',
            }}
            onMouseDown={(e) => {
              e.preventDefault();
              button.handler(editorState, setEditorState);
            }}
          >
            {button.title}
          </button>
        ))}
      </div>
      <div>
        <Editor
          editorState={editorState}
          onChange={setEditorState}
          customStyleMap={initialStyleMap}
          placeholder='Write here...'
        />
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

The editor basic inline style tools should work properly up to this point, but as an improvement we can change the style of selected button to illustrate the difference. There is also a function for each of this inline styles to find out if it is the selected style or not.

// src > components > Editor.tsx
import { useState } from 'react';
import { EditorState } from 'draft-js';
import {
  Editor,
  initialStyleMap,
  isBold,
  isItalic,
  isLineThrough,
  isOverline,
  isSub,
  isSup,
  isUnderline,
  toggleBold,
  toggleItalic,
  toggleLineThrough,
  toggleOverline,
  toggleSub,
  toggleSup,
  toggleUnderline,
} from 'contenido';

// Types
import type { FC } from 'react';

// Custom Types
export interface InlineStylesEditorProps {}

const buttons = [
  { title: 'Bold', handler: toggleBold, checker: isBold },
  { title: 'Italic', handler: toggleItalic, checker: isItalic },
  { title: 'Underline', handler: toggleUnderline, checker: isUnderline },
  { title: 'Line Through', handler: toggleLineThrough, checker: isLineThrough },
  { title: 'Overline', handler: toggleOverline, checker: isOverline },
  { title: 'Sup', handler: toggleSup, checker: isSup },
  { title: 'Sub', handler: toggleSub, checker: isSub },
];

const InlineStylesEditor: FC<InlineStylesEditorProps> = (props) => {
  // States
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  return (
    <div>
      <div>
        {buttons.map((button) => (
          <button
            key={button.title}
            style={{
              minWidth: '2rem',
              padding: '0.5rem',
              backgroundColor: button.checker(editorState)
                ? '#4cb5f5'
                : 'rgba(125, 125, 125, 0.25)',
              color: button.checker(editorState) ? '#fff' : 'inherit',
              borderRadius: '0.5rem',
              border: 'none',
              cursor: 'pointer',
            }}
            onMouseDown={(e) => {
              e.preventDefault();
              button.handler(editorState, setEditorState);
            }}
          >
            {button.title}
          </button>
        ))}
      </div>
      <div>
        <Editor
          editorState={editorState}
          onChange={setEditorState}
          customStyleMap={initialStyleMap}
          placeholder='Write here...'
        />
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Custom Inline Styles

Although, contenido have a utility for the most common use cases but you can also have your custom inline styles. As an example, You can have buttons to change the text transformations or to change the text font or background color. We will add a support for the background color to our editor to have an example of inline style customization for draft-js as well. Approach will be the same for all valid CSS styles.

First we set a key for each of our colors:

...
const HIGHLIGHT = 'HIGHLIGHT';
...
Enter fullscreen mode Exit fullscreen mode

Then update the buttons array by importing toggleInlineStyle and hasInlineStyleOf from contenido (for Typescript we import State and StateHandler types as well):

Typescript:

const buttons = [
  { title: 'Bold', handler: toggleBold, checker: isBold },
  { title: 'Italic', handler: toggleItalic, checker: isItalic },
  { title: 'Underline', handler: toggleUnderline, checker: isUnderline },
  { title: 'Line Through', handler: toggleLineThrough, checker: isLineThrough },
  { title: 'Overline', handler: toggleOverline, checker: isOverline },
  { title: 'Sup', handler: toggleSup, checker: isSup },
  { title: 'Sub', handler: toggleSub, checker: isSub },
  {
    title: 'Highlight',
    handler: (editorState: State, stateHandler: StateHandler) =>
      toggleInlineStyle(editorState, stateHandler, HIGHLIGHT),
    checker: (editorState: State) => hasInlineStyleOf(editorState, HIGHLIGHT),
  },
];
Enter fullscreen mode Exit fullscreen mode

Javascript:

const buttons = [
  { title: 'Bold', handler: toggleBold, checker: isBold },
  { title: 'Italic', handler: toggleItalic, checker: isItalic },
  { title: 'Underline', handler: toggleUnderline, checker: isUnderline },
  { title: 'Line Through', handler: toggleLineThrough, checker: isLineThrough },
  { title: 'Overline', handler: toggleOverline, checker: isOverline },
  { title: 'Sup', handler: toggleSup, checker: isSup },
  { title: 'Sub', handler: toggleSub, checker: isSub },
  {
    title: 'Highlight',
    handler: (editorState, stateHandler) =>
      toggleInlineStyle(editorState, stateHandler, HIGHLIGHT),
    checker: (editorState) => hasInlineStyleOf(editorState, HIGHLIGHT),
  },
];
Enter fullscreen mode Exit fullscreen mode

The final step is to create a style map object and use it instead of the initialStyleMap. It's important to spread the initial one in order to keep the other styles functionality:

...
  const styleMap = {
    ...initialStyleMap,
    HIGHLIGHT: { backgroundColor: '#fdffb4' },
  };
...
Enter fullscreen mode Exit fullscreen mode

and then replace it like this:

...
  <Editor
    editorState={editorState}
    onChange={setEditorState}
    customStyleMap={styleMap}
    placeholder='Write here...'
  />
...
Enter fullscreen mode Exit fullscreen mode

Result

You can see the demo of this post on this link.


A banner of become a backer

Hi! I'm Hosein Pouyanmehr. I enjoy sharing what I learn and what I find interesting. Let's connect on LinkedIn.

See my code interests on GitHub.

Top comments (0)