Components
Hooks
Utils
Text-Editor
Overview
The TextEditor
component is a rich text editor built using the Lexical Composer library. It provides a user-friendly interface for editing and formatting text, supporting features such as mentions, links, tables, images and more.
Installation
To use the TextEditor
component in your project, you can install it via npm or yarn:
npm install @locoworks/reusejs-react-text-editor
# or
yarn add @locoworks/reusejs-react-text-editor
External Dependencies
The TextEditor
component relies on external dependencies - tailwind-merge
, lexical
and @lexical/react
.
npm install lexical @lexical/react tailwind-merge
# or
yarn add lexical @lexical/react tailwind-merge
Importing
Import the TextEditor
component into your React application as follows:
import { TextEditor } from "@locoworks/reusejs-react-text-editor";
Types/ Exported Components
Props
The TextEditor
component accepts the following props:
mentionsData
: An optional array of objects representing mentions data. It indicates enabling or disabling the mentions.Its value is "false" by default which means disabled mentions.useMentionLookupService
: An optional function to handle mention lookup service and return results for search.convertFilesToImageUrl
: A function to convert files to image URLs and return Array of imageUrls.onChangeCallback
: An optional callback for editor reference changes and payload.wrapperClass
: An optional class for styling the editor wrapper.editable
: A boolean indicating the edit state of the editor.setEditable
: An optional function to set the editable state of the editor. It is a React state setter function that accepts a boolean value.htmlData
: An optional string to set the intialState of the editor. You can pass the html data directly to the editor as its initial state.placeholderText
: An optional string for the placeholder inside the editor. Its value is "Start Typing..." by default.hideToolbar
: A boolean value eithertrue
orfalse
, by default value isfalse
.
Exported properties
The onChange function within the TextEditor
component serves as a callback, providing access to the editorRef and a payload object. This payload encapsulates various properties derived from the editor's state, including HTML content, JSON representation, text content, and, if mentions are enabled, a reference to the mentions array.
For example:
You can access the HTML content specifically using payload["html"] in the callback function.
Usage/Examples
TextEditor demo
import React, { useState } from "react";
import { LexicalEditor } from "lexical";
import { TextEditor } from "@locoworks/reusejs-react-text-editor";
import "@locoworks/reusejs-react-text-editor/css";
const Example = () => {
const [editable, setEditable] = useState<boolean>(false);
const [data, setData] = useState<string | TrustedHTML>("Here");
function convertFilesToImageUrl(files: FileList | null) {
if (!files || files.length === 0) {
return null;
}
const imageUrls = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const imageUrl = URL.createObjectURL(file);
imageUrls.push(imageUrl);
}
return imageUrls.length > 0 ? imageUrls : null;
}
function onChange(_editorRef: LexicalEditor | null, payload: any) {
setData(payload["html"]);
}
return (
<div className="flex flex-col items-center justify-center py-10 mt-10 bg-gray-100 border rounded gap-x-3">
<TextEditor
editable={editable}
setEditable={setEditable}
convertFilesToImageUrl={convertFilesToImageUrl}
onChangeCallback={onChange}
hideToolbar={false}
showToolbarText={false}
/>
{!editable && (
<div
className="w-full min-h-[50px] cursor-text bg-white"
dangerouslySetInnerHTML={{ __html: data }}
onClick={() => {
setEditable(true);
}}
/>
)}
</div>
);
};
export default Example;
PrepopulatedTextEditor
Here hello
import React, { useEffect, useState } from "react";
import { LexicalEditor } from "lexical";
import { TextEditor } from "@locoworks/reusejs-react-text-editor";
import "@locoworks/reusejs-react-text-editor/css";
const PrepopulatedTextEditor = () => {
const payload: string = `<table>
<tr>
<th>Company</th>
<th>Contact</th>
<th>Country</th>
</tr>
<tr>
<td>Alfreds Futterkiste</td>
<td>Maria Anders</td>
<td>Germany</td>
</tr>
<tr>
<td>Centro comercial Moctezuma</td>
<td>Francisco Chang</td>
<td>Mexico</td>
</tr>
<tr>
<td>Ernst Handel</td>
<td>Roland Mendel</td>
<td>Austria</td>
</tr>
<tr>
<td>Island Trading</td>
<td>Helen Bennett</td>
<td>UK</td>
</tr>
<tr>
<td>Laughing Bacchus Winecellars</td>
<td>Yoshi Tannamuri</td>
<td>Canada</td>
</tr>
<tr>
<td>Magazzini Alimentari Riuniti</td>
<td>Giovanni Rovelli</td>
<td>Italy</td>
</tr>
</table>
`;
const [htmlData, setHtmlData] = useState(`<p>Here hello</p>`);
setTimeout(() => {
setHtmlData(payload);
}, 5000);
useEffect(() => {
setData(htmlData);
}, [htmlData]);
const [editable, setEditable] = useState<boolean>(false);
const [data, setData] = useState<string | TrustedHTML>(htmlData);
function convertFilesToImageUrl(files: FileList | null) {
if (!files || files.length === 0) {
return null;
}
const imageUrls = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const imageUrl = URL.createObjectURL(file);
imageUrls.push(imageUrl);
}
return imageUrls.length > 0 ? imageUrls : null;
}
function onChange(_editorRef: LexicalEditor | null, payload: any) {
setData(payload["html"]);
}
return (
<div className="flex flex-col items-center justify-center py-10 mt-10 bg-gray-100 border rounded gap-x-3">
<TextEditor
htmlData={htmlData}
editable={editable}
setEditable={setEditable}
convertFilesToImageUrl={convertFilesToImageUrl}
onChangeCallback={onChange}
/>
{!editable && (
<div
className="w-full min-h-[50px] cursor-text bg-white"
dangerouslySetInnerHTML={{ __html: data }}
onClick={() => {
setEditable(true);
}}
/>
)}
</div>
);
};
export default PrepopulatedTextEditor;
Enabled Mentions
import React, { useState } from "react";
import { LexicalEditor } from "lexical";
import { TextEditor } from "@locoworks/reusejs-react-text-editor";
import "@locoworks/reusejs-react-text-editor/css";
const EnabledMentions = () => {
const [editable, setEditable] = useState<boolean>(false);
const [data, setData] = useState<string | TrustedHTML>("Here");
const dummyMentionsData = [
"Aayla Secura",
"Adi Gallia",
"Niima the Hutt",
"Nines",
"Norra Wexley",
"Nute Gunray",
"Val Beckett",
"Vanden Willard",
"Vice Admiral Amilyn Holdo",
"Vober Dand",
"WAC-47",
"Wedge Antilles",
"Wes Janson",
"Wicket W. Warrick",
"Wilhuff Tarkin",
"Wollivan",
"Wuher",
"Wullf Yularen",
"Xamuel Lennox",
"Yaddle",
"Yarael Poof",
"Yoda",
"Zam Wesell",
"Zev Senesca",
"Ziro the Hutt",
"Zuckuss",
].map((name) => ({
mentionName: `user_${name}`,
label: name,
}));
function convertFilesToImageUrl(files: FileList | null) {
if (!files || files.length === 0) {
return null;
}
const imageUrls = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const imageUrl = URL.createObjectURL(file);
imageUrls.push(imageUrl);
}
return imageUrls.length > 0 ? imageUrls : null;
}
function onChange(_editorRef: LexicalEditor | null, payload: any) {
setData(payload["html"]);
}
return (
<div className="flex flex-col items-center justify-center py-10 mt-10 bg-gray-100 border rounded gap-x-3">
<TextEditor
editable={editable}
setEditable={setEditable}
mentionsData={dummyMentionsData}
convertFilesToImageUrl={convertFilesToImageUrl}
onChangeCallback={onChange}
/>
{!editable && (
<div
className="w-full min-h-[50px] cursor-text bg-white"
dangerouslySetInnerHTML={{ __html: data }}
onClick={() => {
setEditable(true);
}}
/>
)}
</div>
);
};
export default EnabledMentions;
Enabled Mentions with Custom lookup function
import React, { useEffect, useState } from "react";
import { LexicalEditor } from "lexical";
import { TextEditor } from "@locoworks/reusejs-react-text-editor";
import "@locoworks/reusejs-react-text-editor/css";
const EnabledMentionsWithCustomLookupService = () => {
const [editable, setEditable] = useState<boolean>(false);
const [data, setData] = useState<string | TrustedHTML>("Here");
const dummyMentionsData = [
"Aayla Secura",
"Adi Gallia",
"Niima the Hutt",
"Nines",
"Norra Wexley",
"Nute Gunray",
"Val Beckett",
"Vanden Willard",
"Vice Admiral Amilyn Holdo",
"Vober Dand",
"WAC-47",
"Wedge Antilles",
"Wes Janson",
"Wicket W. Warrick",
"Wilhuff Tarkin",
"Wollivan",
"Wuher",
"Wullf Yularen",
"Xamuel Lennox",
"Yaddle",
"Yarael Poof",
"Yoda",
"Zam Wesell",
"Zev Senesca",
"Ziro the Hutt",
"Zuckuss",
].map((name) => ({
mentionName: `user_${name}`,
label: name,
}));
function useMentionLookupService(
mentionString: string | null,
mentionsData: Array<{
mentionName: string;
label: string;
}>,
) {
const [results, setResults] = useState<
Array<{ mentionName: string; label: string }>
>([]);
useEffect(() => {
if (mentionString === null) {
setResults([]);
} else {
setResults(
mentionsData.filter((mention) =>
mention.label.toLowerCase().includes(mentionString.toLowerCase()),
),
);
}
}, [mentionString]);
return results;
}
function convertFilesToImageUrl(files: FileList | null) {
if (!files || files.length === 0) {
return null;
}
const imageUrls = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const imageUrl = URL.createObjectURL(file);
imageUrls.push(imageUrl);
}
return imageUrls.length > 0 ? imageUrls : null;
}
function onChange(_editorRef: LexicalEditor | null, payload: any) {
setData(payload["html"]);
}
return (
<div className="flex flex-col items-center justify-center py-10 mt-10 bg-gray-100 border rounded gap-x-3">
<TextEditor
editable={editable}
setEditable={setEditable}
mentionsData={dummyMentionsData}
useMentionLookupService={useMentionLookupService}
convertFilesToImageUrl={convertFilesToImageUrl}
onChangeCallback={onChange}
/>
{!editable && (
<div
className="w-full min-h-[50px] cursor-text bg-white"
dangerouslySetInnerHTML={{ __html: data }}
onClick={() => {
setEditable(true);
}}
/>
)}
</div>
);
};
export default EnabledMentionsWithCustomLookupService;
Styling
The styling of the editorWrapper can be customized using Tailwind CSS .
Note: To use the styling of the editor component , you must import the StyleSheet in your application.
import "@locoworks/reusejs-react-text-editor/css";
Best Practices
- Ensure that necessary dependencies are installed.
- Handle editor reference changes and payload using the onChangeCallback prop.
Props Table
Prop | Type | Description |
---|---|---|
mentionsData | Array<{ mentionName: string; label: string; }> | false | An optional array of objects representing mentions data. Each object should have mentionName and label properties. Its false by default. |
useMentionLookupService | (mentionString: string | null, mentionsData) => Array<{ mentionName: string; label: string; }> | An optional function to handle mention lookup and return results. |
convertFilesToImageUrl | (files: FileList | null) => Array<string> | null | A function to convert files to image URLs and return image URLs. |
onChangeCallback | (editorRef: LexicalEditor | null, payload: any) => void | An optional callback for editor reference changes and payload. |
wrapperClass | string | An optional class for styling the editor wrapper. |
editable | boolean | A boolean indicating the edit state of the editor. |
setEditable | React.Dispatch<React.SetStateAction<boolean>> | An optional function to set the editable state of the editor. |
htmlData | string | An optional string to set the initial state of the editor. |
placeholderText | string | An optional string for the placeholder inside the editor. Its value is "Start Typing..." by default |
hideToolbar | boolean | It accepts either true or false by default it is false . |
Notes
The TextEditor component uses the Lexical library for rich text editing. Check the Lexical Composer documentation for advanced configuration options.
To enable the mentions plugin , you have to pass the mentionsData which is false by default and thus disabled.