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
- Installation
- Create Editor Component
- Add Placeholder
- Add Toolbar Buttons
- Custom Inline Styles
- Result and Demo
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
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
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>
);
};
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>
);
};
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>
...
Add Placeholder
Also adding a placeholder can be useful, so:
...
<Editor
editorState={editorState}
onChange={setEditorState}
placeholder='Write here...'
/>
...
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>
);
};
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>
);
};
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>
);
};
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';
...
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),
},
];
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),
},
];
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' },
};
...
and then replace it like this:
...
<Editor
editorState={editorState}
onChange={setEditorState}
customStyleMap={styleMap}
placeholder='Write here...'
/>
...
Result
You can see the demo of this post on this link.
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)