import React, { useMemo, useCallback } from 'react';
import { createEditor, Editor, Transforms , Element as SlateElement } from 'slate';
import { Slate, Editable, withReact, useSlate } from 'slate-react';
import {MdFormatBold,MdFormatItalic,MdFormatUnderlined,MdFormatListBulleted ,MdFormatAlignLeft,MdFormatAlignCenter,MdFormatAlignRight,MdFormatAlignJustify,MdFormatQuote} from 'react-icons/md';
const LIST_TYPES = ['numbered-list', 'bulleted-list']
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify']
const RichTextExample = () => {
const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withReact(createEditor()), [])
return (
<Slate editor={editor} value={initialValue}>
<BlockButton format="heading-one" icon="H1" />
<BlockButton format="heading-two" icon="H2" />
<MarkButton format="bold" icon={<MdFormatBold/>} />
<MarkButton format="italic" icon={<MdFormatItalic/>} />
<MarkButton format="underline" icon={<MdFormatUnderlined/>} />
<BlockButton format="block-quote" icon={<MdFormatQuote/>} />
<BlockButton format="bulleted-list" icon={<MdFormatListBulleted/>} />
<BlockButton format="left" icon={<MdFormatAlignLeft/>} />
<BlockButton format="center" icon={<MdFormatAlignCenter/>} />
<BlockButton format="right" icon={<MdFormatAlignRight/>} />
<BlockButton format="justify" icon={<MdFormatAlignJustify />} />
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
placeholder="Enter some rich text…"
spellCheck
autoFocus
onKeyDown={event => {
}}
/>
</Slate>
)
}
const toggleBlock = (editor, format) => {
const isActive = isBlockActive(
editor,
format,
TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'
)
const isList = LIST_TYPES.includes(format)
Transforms.unwrapNodes(editor, {
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
LIST_TYPES.includes(n.type) &&
!TEXT_ALIGN_TYPES.includes(format),
split: true,
})
let newProperties
if (TEXT_ALIGN_TYPES.includes(format)) {
newProperties = {
align: isActive ? undefined : format,
}
} else {
newProperties = {
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
}
}
Transforms.setNodes(editor, newProperties);
if (!isActive && isList) {
const block = { type: format, children: [] }
Transforms.wrapNodes(editor, block)
}
}
const toggleMark = (editor, format) => {
const isActive = isMarkActive(editor, format)
if (isActive) {
Editor.removeMark(editor, format)
} else {
Editor.addMark(editor, format, true)
}
}
const isBlockActive = (editor, format, blockType = 'type') => {
const { selection } = editor
if (!selection) return false
const [match] = Array.from(
Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
n[blockType] === format,
})
)
return !!match
}
const isMarkActive = (editor, format) => {
const marks = Editor.marks(editor)
return marks ? marks[format] === true : false
}
const Element = ({ attributes, children, element }) => {
const style = { textAlign: element.align }
switch (element.type) {
case 'block-quote':
return (
<blockquote style={style} {...attributes} className='before:content-["❝"] before:pr-1 before:text-2xl before:font-semibold after:content-["❞"] after:pl-1 after:text-2xl after:font-semibold' >
<span className='bg-gray-100 p-2 rounded'>{children}</span>
</blockquote>
)
case 'bulleted-list':
return (
<ul style={style} {...attributes} className='list-disc'>
{children}
</ul>
)
case 'heading-one':
return (
<h1 style={style} {...attributes} className='font-extrabold text-3xl'>
{children}
</h1>
)
case 'heading-two':
return (
<h2 style={style} {...attributes} className='font-semibold text-xl'>
{children}
</h2>
)
case 'list-item':
return (
<li style={style} {...attributes}>
{children}
</li>
)
case 'numbered-list':
return (
<ol style={style} {...attributes}>
{children}
</ol>
)
default:
return (
<p style={style} {...attributes}>
{children}
</p>
)
}
}
const Leaf = ({ attributes, children, leaf }) => {
if (leaf.bold) {
children = <strong>{children}</strong>
}
if (leaf.code) {
children = <code>{children}</code>
}
if (leaf.italic) {
children = <em>{children}</em>
}
if (leaf.underline) {
children = <u>{children}</u>
}
return <span {...attributes}>{children}</span>
}
const BlockButton = ({ format, icon }) => {
const editor = useSlate();
const isActive = isBlockActive(editor,format,TEXT_ALIGN_TYPES.includes(format)?'align':'type');
return (
<button
className={`p-2 text-xl rounded mx-1 ${isActive ? 'bg-gray-300':''}`}
onMouseDown={event => {
event.preventDefault()
toggleBlock(editor, format)
}}
>
{icon}
</button>
)
}
const MarkButton = ({ format, icon }) => {
const editor = useSlate()
const isActive = isMarkActive(editor,format)
return (
<button
className={`p-2 text-xl rounded mx-1 ${isActive? 'bg-gray-300':'' }`}
onMouseDown={event => {
event.preventDefault()
toggleMark(editor, format)
}}
>
{icon}
</button>
)
}
const initialValue = [
{
type: 'paragraph',
children: [
{ text: 'This is editable ' },
{ text: 'rich', bold: true },
{ text: ' text, ' },
{ text: 'much', italic: true },
{ text: ' better than a ' },
{ text: '<textarea>', code: true },
{ text: '!' },
],
},
{
type: 'paragraph',
children: [
{
text:
"Since it's rich text, you can do things like turn a selection of text ",
},
{ text: 'bold', bold: true },
{
text:
', or add a semantically rendered block quote in the middle of the page, like this:',
},
],
},
{
type: 'block-quote',
children: [{ text: 'A wise quote.' }],
},
{
type: 'paragraph',
align: 'center',
children: [{ text: 'Try it out for yourself!' }],
},
]
export default RichTextExample
Finally , I got the working code . I'm answering my own question , Feel free to use my code if anyone having the similar issues .
Dependencies used :
- Tailwindcss
- React-Icons
- SlateJS