Let’s Create a Rich Text Editor With Draftjs and React Toolbar and Inline Styles 02
Video Tutorial
You can get the full source code from Github.
Prequesties
We will be using react styled components for putting CSS (SASS) style embedded inside javascript files which then gets rendered as a regular React Component this way we can quickly create custom containers with custom style and we simply render it in react. So make sure to install first.
npm install styled-components --save
Also, we need to render icons for the toolbar, we can use the react-fontawesome package which embeds all fontawesome icons as SVG.
npm i --save @fortawesome/fontawesome-svg-core
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/react-fontawesome
Draft Editor
Let’s try to style the editor a bit so it looks more like a text field of a Rich Text Editor.
Move editor.jsx into the root /src folder, we also going to create a custom container for the draft editor using styled-components.
import styled from "styled-components";
//Root Wrapper of the Editor
const EditorWrapper = styled.div`
min-width: 700px;
display: flex;
flex-direction: column;
height: fit-content;
margin-top: 3em;
`;
//DraftEditor Container
const EditorContainer = styled.div`
display: flex;
min-height: 9em;
border-radius: 0 0 3px 3px;
background-color: #fff;
padding: 5px;
font-size: 17px;
font-weight: 300;
box-shadow: 0px 0px 3px 1px rgba(15, 15, 15, 0.17);
`;
Then we can wrap the editor with the container to apply some custom style to it like a border and a box shadow.
render() {
const { editorState } = this.state;
// Debug your Editor and notice that the EditorState gets update on every
// character you type
console.log("EditorSTATE: ", this.state.editorState);
//Render the Draftjs Editor Component
/*
The Editor Takes the current editorState and provides
you with onChange callback to update the current EditorState being stored on your state.
*/
return (
<EditorWrapper>
{/*Render toolbar here*/}
<EditorContainer>
<DraftEditor
placeholder="Explore Your Way In..."
editorState={this.state.editorState}
onChange={this.updateEditorState.bind(this)}
customStyleFn={customStyleFn}
/>
</EditorContainer>
</EditorWrapper>
);
}
Make sure to run the Webpack building watch to compile the react code then refresh you should see an elegant editor container which looks more like a Rich Tex Editor now.
Editor Toolbar
Toolbar is what holds the different tools to manipulate the text under the text field, we will create a customizable toolbar which reads a configuration object (Array) and renders depending on what is provided with that way we can make the editor more customizable so later we may need to add the ability to configure the Toolbar from the user’s end.
Create a toolbar/ folder under containers/ and put index.jsx which is the main entry file of the toolbar component.
import React from "react";
import styled from "styled-components";
const ToolbarContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
min-height: 48px;
padding: 5px 7px;
margin-bottom: 8px;
border-radius: 2px 2px 0 0;
box-shadow: 0px 0px 3px 1px rgba(15, 15, 15, 0.17);
`;
export default class Toolbar extends React.Component {
render() {
return (
<ToolbarContainer>
{/*Render inline styles, block types and custom styles*/}
</ToolbarContainer>
);
}
}
We create the ToolbarContainer with a box shadow and a flexbox design. Under the container, we will be rendering ToolbarItems for the three different styles we have.
Inline Styles: Standard text styles that get applied inline (ex: Bold, Italic).
Block Types: Styles which get assigned on a new line (ex: unordered list, header1).
Custom Styles: Those styles are manipulated by a custom function which you need to define the behaviour of how they work.
Create a common.js file in the toolbar/ folder which is going to hold all the shared components among different renderers.
import styled from "styled-components";
//Toolbar Item styled components
export const ToolbarItem = styled.div`
width: 28px;
height: 27px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 5px;
box-shadow: 0px 1px 11px 1px rgba(15, 15, 15, 0.2);
background-color: #34495e;
color: #fff;
font-size: 16px;
font-family: Oxygen, sans-serif;
transition: all 250ms ease-in-out;
cursor: pointer;
${props =>
props.isActive &&
` transform: translateY(1px);
color: #34495e;
background-color: transparent;
box-shadow: none;
border: 1px solid #34495e;`}
&:hover {
transform: translateY(1px);
color: #34495e;
background-color: transparent;
box-shadow: none;
border: 1px solid #34495e;
}
`;
//Basic Container
export const Container = styled.div`
display: flex;
margin-right: 7px;
`;
Also, create a constants.jsx file which is going to hold the configuration objects for the toolbar.
/* common.jsx */
import React from "react";
//We Import icon components
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
//Different types of fontawesome icons
import {
faBold,
faUnderline,
faItalic,
faAnchor
} from "@fortawesome/free-solid-svg-icons";
/*Array of object each object has a label to identify the current style, standard Draftjs style to be applied and an optional icon to use when rendering the current style under the toolbar */
const inlineStyles = [
{
label: "bold",
style: "BOLD",
icon: <FontAwesomeIcon icon={faBold} />
},
{
label: "italic",
style: "ITALIC",
icon: <FontAwesomeIcon icon={faItalic} />
},
{
label: "Underline",
style: "UNDERLINE",
icon: <FontAwesomeIcon icon={faUnderline} />
}
];
export { inlineStyles };
This way we can have a declarative API so whenever we need to edit the toolbar item by adding, editing or removing we can easily change that under the inlineStyles array.
We also use Fontawesome standard free SVG icons (ex: faBold) for optionally rendering the icon under the toolbar.
Let’s create a custom renderer for the inline styles which going to take care of rendering toolbar item for each object under the inline Styles array and handle click events and showing the active state of the currently applied item to text.
/* inlineStyles.jsx */
import React from "react";
import { inlineStyles } from "./constants";
import styled from "styled-components";
import { ToolbarItem, Container } from "./common";
//Rich utils is a utility library for manipulating text (like inlineStyle, blockTypes...)
import { RichUtils } from "draft-js";
export function RenderInlineStyles(props) {
const { editorState, updateEditorState } = props;
//apply stlye using RichUtils
const applyStyle = (e, style) => {
e.preventDefault();
//Rich utils returns a new editorState with applied style
//Make sure to update the main editor state
updateEditorState(RichUtils.toggleInlineStyle(editorState, style));
};
/*Check if current style is active under text selection (the position of the cursor under text)*/
const isActive = style => {
//currentStyle is a map of currently applied style to selected text
const currentStyle = editorState.getCurrentInlineStyle();
//check if current style is among the style map.
return currentStyle.has(style);
};
/*
Loop through the array where we render each ToolbarItem passing it the active state and the react key prop for optimizing the rendering process and we handle the click event to apply style.
*/
return (
<Container>
{inlineStyles.map((item, idx) => {
return (
<ToolbarItem
isActive={isActive(item.style)}
key={`${item.label}-${idx}`}
onClick={e => applyStyle(e, item.style)}
>
{item.icon || item.label}
</ToolbarItem>
);
})}
</Container>
);
}
So we simply loop through the inline styles array and we render for each object a new ToolbarItem which takes the isActive prop to determine whether or not the current item’s style is active under the selected text (by the cursor).
The Key prop is used to uniquely identify each item rendered in an array, this way react knows how to optimize the elements without facing issues concerning performance.
the click event is used to apply style passing it the desired style we want to apply which we get from the inline styles object then we use Rich Utils to apply the standard inline style to the currently selected text.
Finally, we need to use the inline styles renderer under the toolbar, where each style type has it’s own custom renderer to know how to handle each configuration object properly.
render() {
return (
<ToolbarContainer>
<RenderInlineStyles
editorState={this.props.editorState}
updateEditorState={this.props.updateEditorState}
/>
</ToolbarContainer>
);
}
Make sure to pass-in the editorState and the updateEditorState method to be able to manipulate the Draft Editor’s main state.