Tiptap Editor
Tiptap Editor
is a third-party library. Please refer to its official documentation for more details.
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That's it. It's probably too much for real minimalists though.
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
- TSX
- JS
'use client'
// MUI Imports
import Divider from '@mui/material/Divider'
// Third-party imports
import { useEditor, EditorContent } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { Underline } from '@tiptap/extension-underline'
import { Placeholder } from '@tiptap/extension-placeholder'
import { TextAlign } from '@tiptap/extension-text-align'
import type { Editor } from '@tiptap/core'
// Components Imports
import CustomIconButton from '@core/components/mui/IconButton'
const EditorToolbar = ({ editor }: { editor: Editor | null }) => {
if (!editor) {
return null
}
return (
<div className='flex flex-wrap gap-x-3 gap-y-1 p-5'>
<CustomIconButton
{...(editor.isActive('bold') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleBold().run()}
>
<i className='ri-bold' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('underline') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleUnderline().run()}
>
<i className='ri-underline' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('italic') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleItalic().run()}
>
<i className='ri-italic' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('strike') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleStrike().run()}
>
<i className='ri-strikethrough' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'left' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('left').run()}
>
<i className='ri-align-left' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'center' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('center').run()}
>
<i className='ri-align-center' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'right' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('right').run()}
>
<i className='ri-align-right' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'justify' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
>
<i className='ri-align-justify' />
</CustomIconButton>
</div>
)
}
const EditorBasic = ({ content }: { content?: string }) => {
const editor = useEditor({
extensions: [
StarterKit,
Placeholder.configure({
placeholder: 'Write something here...'
}),
TextAlign.configure({
types: ['heading', 'paragraph']
}),
Underline
],
content:
content ??
`
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That's it. It's probably too much for real minimalists though.
</p>
<br />
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`
})
return (
<div className='border rounded-md'>
<EditorToolbar editor={editor} />
<Divider />
<EditorContent editor={editor} className='bs-[200px] overflow-y-auto flex' />
</div>
)
}
export default EditorBasic
'use client'
// MUI Imports
import Divider from '@mui/material/Divider'
// Third-party imports
import { useEditor, EditorContent } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { Underline } from '@tiptap/extension-underline'
import { Placeholder } from '@tiptap/extension-placeholder'
import { TextAlign } from '@tiptap/extension-text-align'
// Components Imports
import CustomIconButton from '@core/components/mui/IconButton'
const EditorToolbar = ({ editor }) => {
if (!editor) {
return null
}
return (
<div className='flex flex-wrap gap-x-3 gap-y-1 p-5'>
<CustomIconButton
{...(editor.isActive('bold') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleBold().run()}
>
<i className='ri-bold' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('underline') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleUnderline().run()}
>
<i className='ri-underline' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('italic') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleItalic().run()}
>
<i className='ri-italic' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive('strike') && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().toggleStrike().run()}
>
<i className='ri-strikethrough' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'left' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('left').run()}
>
<i className='ri-align-left' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'center' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('center').run()}
>
<i className='ri-align-center' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'right' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('right').run()}
>
<i className='ri-align-right' />
</CustomIconButton>
<CustomIconButton
{...(editor.isActive({ textAlign: 'justify' }) && { color: 'primary' })}
variant='outlined'
size='small'
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
>
<i className='ri-align-justify' />
</CustomIconButton>
</div>
)
}
const EditorBasic = ({ content }) => {
const editor = useEditor({
extensions: [
StarterKit,
Placeholder.configure({
placeholder: 'Write something here...'
}),
TextAlign.configure({
types: ['heading', 'paragraph']
}),
Underline
],
content:
content !== null && content !== void 0
? content
: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That's it. It's probably too much for real minimalists though.
</p>
<br />
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`
})
return (
<div className='border rounded-md'>
<EditorToolbar editor={editor} />
<Divider />
<EditorContent editor={editor} className='bs-[200px] overflow-y-auto flex' />
</div>
)
}
export default EditorBasic
Hi there,
this is a basic example of tiptap. Sure, there are all kind of basic text styles you'd probably expect from a text editor. But wait until you see the lists:
That's a bullet list with one …
… or two list items.
Isn't that great? And all of that is editable. But wait, there's more. Let's try a code block:
body {
display: none;
}
I know, I know, this is impressive. It's only the tip of the iceberg though. Give it a try and click a little bit around. Don't forget to check the other examples too.
Wow, that's amazing. Good work, boy! 👏
— Mom
'use client'
// MUI Imports
import Divider from '@mui/material/Divider'
import Chip from '@mui/material/Chip'
// Third-party imports
import { Color } from '@tiptap/extension-color'
import { ListItem } from '@tiptap/extension-list-item'
import { TextStyle } from '@tiptap/extension-text-style'
import { EditorProvider, useCurrentEditor } from '@tiptap/react'
import { StarterKit } from '@tiptap/starter-kit'
import { Placeholder } from '@tiptap/extension-placeholder'
const EditorToolbar = () => {
const { editor } = useCurrentEditor()
if (!editor) {
return null
}
return (
<div className='flex flex-wrap gap-x-4 gap-y-2 p-5'>
<Chip
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()}
{...(editor.isActive('bold') && { variant: 'tonal', color: 'primary' })}
label='bold'
/>
<Chip
onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().chain().focus().toggleItalic().run()}
{...(editor.isActive('italic') && { variant: 'tonal', color: 'primary' })}
label='italic'
/>
<Chip
onClick={() => editor.chain().focus().toggleStrike().run()}
disabled={!editor.can().chain().focus().toggleStrike().run()}
{...(editor.isActive('strike') && { variant: 'tonal', color: 'primary' })}
label='strike'
/>
<Chip
onClick={() => editor.chain().focus().toggleCode().run()}
disabled={!editor.can().chain().focus().toggleCode().run()}
{...(editor.isActive('code') && { variant: 'tonal', color: 'primary' })}
label='code'
/>
<Chip onClick={() => editor.chain().focus().unsetAllMarks().run()} label='clear marks' />
<Chip onClick={() => editor.chain().focus().clearNodes().run()} label='clear nodes' />
<Chip
onClick={() => editor.chain().focus().setParagraph().run()}
{...(editor.isActive('paragraph') && { variant: 'tonal', color: 'primary' })}
label='paragraph'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
{...(editor.isActive('heading', { level: 1 }) && { variant: 'tonal', color: 'primary' })}
label='h1'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
{...(editor.isActive('heading', { level: 2 }) && { variant: 'tonal', color: 'primary' })}
label='h2'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
{...(editor.isActive('heading', { level: 3 }) && { variant: 'tonal', color: 'primary' })}
label='h3'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
{...(editor.isActive('heading', { level: 4 }) && { variant: 'tonal', color: 'primary' })}
label='h4'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
{...(editor.isActive('heading', { level: 5 }) && { variant: 'tonal', color: 'primary' })}
label='h5'
/>
<Chip
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
{...(editor.isActive('heading', { level: 6 }) && { variant: 'tonal', color: 'primary' })}
label='h6'
/>
<Chip
onClick={() => editor.chain().focus().toggleBulletList().run()}
{...(editor.isActive('bulletList') && { variant: 'tonal', color: 'primary' })}
label=' bulletlist'
/>
<Chip
onClick={() => editor.chain().focus().toggleOrderedList().run()}
{...(editor.isActive('orderedList') && { variant: 'tonal', color: 'primary' })}
label=' orderedlist'
/>
<Chip
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
{...(editor.isActive('codeBlock') && { variant: 'tonal', color: 'primary' })}
label=' codeblock'
/>
<Chip
onClick={() => editor.chain().focus().toggleBlockquote().run()}
{...(editor.isActive('blockquote') && { variant: 'tonal', color: 'primary' })}
label='blockquote'
/>
<Chip onClick={() => editor.chain().focus().setHorizontalRule().run()} label='horizontal rule' />
<Chip onClick={() => editor.chain().focus().setHardBreak().run()} label='hard break' />
<Chip
onClick={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()}
label='undo'
/>
<Chip
onClick={() => editor.chain().focus().redo().run()}
disabled={!editor.can().chain().focus().redo().run()}
label='redo'
/>
<Chip onClick={() => editor.chain().focus().setColor('var(--mui-palette-primary-main)').run()} label='primary' />
</div>
)
}
const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle,
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false
},
orderedList: {
keepMarks: true,
keepAttributes: false
}
}),
Placeholder.configure({
placeholder: 'Write something here...'
})
]
const content = `
<h2>
Hi there,
</h2>
<p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles you'd probably expect from a text editor. But wait until you see the lists:
</p>
<ul>
<li>
That's a bullet list with one …
</li>
<li>
… or two list items.
</li>
</ul>
<p>
Isn't that great? And all of that is editable. But wait, there's more. Let's try a code block:
</p>
<pre><code class="language-css">body {
display: none;
}</code></pre>
<p>
I know, I know, this is impressive. It's only the tip of the iceberg though. Give it a try and click a little bit around. Don't forget to check the other examples too.
</p>
<blockquote>
Wow, that's amazing. Good work, boy! 👏
<br />
— Mom
</blockquote>
`
const EditorCustom = () => {
return (
<div className='border rounded-md'>
<EditorProvider
slotBefore={
<>
<EditorToolbar />
<Divider />
</>
}
extensions={extensions}
content={content}
/>
</div>
)
}
export default EditorCustom