move React components to a package, translate default editor example to React
This commit is contained in:
167
docs/src/demos/Examples/Default/React/index.jsx
Normal file
167
docs/src/demos/Examples/Default/React/index.jsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||||
|
import { useEditor, Editor } from '@tiptap/react'
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
// useEditor only works for child components of <Editor />
|
||||||
|
const MenuBar = () => {
|
||||||
|
const editor = useEditor()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
className={`${editor.isActive('italic') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
italic
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
className={`${editor.isActive('strike') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
strike
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
className={`${editor.isActive('code') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
code
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
|
||||||
|
clear marks
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().clearNodes().run()}>
|
||||||
|
clear nodes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().setParagraph().run()}
|
||||||
|
className={`${editor.isActive('paragraph') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
paragraph
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 1 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h1
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 2 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 3 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h3
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 4 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h4
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 5 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h5
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
|
||||||
|
className={`${editor.isActive('heading', { level: 6 }) ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
h6
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
className={`${editor.isActive('bulletList') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
bullet list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
className={`${editor.isActive('orderedList') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
ordered list
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||||
|
className={`${editor.isActive('codeBlock') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
code block
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
className={`${editor.isActive('blockquote') ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
blockquote
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
|
||||||
|
horizontal rule
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().setHardBreak().run()}>
|
||||||
|
hard break
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().undo().run()}>
|
||||||
|
undo
|
||||||
|
</button>
|
||||||
|
<button onClick={() => editor.chain().focus().redo().run()}>
|
||||||
|
redo
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
|
className={`${editor.isActive('bold') ? 'is-active' : ''}`}
|
||||||
|
>
|
||||||
|
bold
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [value, setValue] = useState(`
|
||||||
|
<h2>
|
||||||
|
Hi there,
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
this is a basic <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>
|
||||||
|
`)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Editor
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
extensions={defaultExtensions()}
|
||||||
|
>
|
||||||
|
<MenuBar />
|
||||||
|
</Editor>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
55
docs/src/demos/Examples/Default/React/styles.scss
Normal file
55
docs/src/demos/Examples/Default/React/styles.scss
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/* Basic editor styles */
|
||||||
|
.ProseMirror {
|
||||||
|
> * + * {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: rgba(#616161, 0.1);
|
||||||
|
color: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #0D0D0D;
|
||||||
|
color: #FFF;
|
||||||
|
font-family: 'JetBrainsMono', monospace;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
context('/demos/Examples/Default', () => {
|
context('/demos/Examples/Default/Vue', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.visit('/demos/Examples/Default')
|
cy.visit('/demos/Examples/Default/Vue')
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { defaultExtensions } from '@tiptap/starter-kit'
|
import { defaultExtensions } from '@tiptap/starter-kit'
|
||||||
import { useEditor, Editor } from './components/Editor'
|
import { useEditor, Editor } from '@tiptap/react'
|
||||||
|
|
||||||
// Menu bar example component
|
// Menu bar example component
|
||||||
// useEditor only works for child components of <Editor />
|
// useEditor only works for child components of <Editor />
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
# Default text editor
|
# Default text editor
|
||||||
Did we mention that you have full control over the rendering of the editor? Here is barebones example without any styling, but packed with a whole set of common extensions.
|
Did we mention that you have full control over the rendering of the editor? Here is barebones example without any styling, but packed with a whole set of common extensions.
|
||||||
|
|
||||||
<demo name="Examples/Default" />
|
## Vue
|
||||||
|
<demo name="Examples/Default/Vue" />
|
||||||
|
|
||||||
|
## React
|
||||||
|
<demo name="Examples/Default/React" />
|
||||||
|
|||||||
14
packages/react/README.md
Normal file
14
packages/react/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# @tiptap/react
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/react)
|
||||||
|
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||||
|
[](https://www.npmjs.com/package/@tiptap/react)
|
||||||
|
[](https://github.com/sponsors/ueberdosis)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
|
||||||
|
|
||||||
|
## Offical Documentation
|
||||||
|
Documentation can be found on the [tiptap website](https://tiptap.dev).
|
||||||
|
|
||||||
|
## License
|
||||||
|
tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
|
||||||
32
packages/react/package.json
Normal file
32
packages/react/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@tiptap/react",
|
||||||
|
"description": "React components for tiptap",
|
||||||
|
"version": "2.0.0-alpha.1",
|
||||||
|
"private": true,
|
||||||
|
"homepage": "https://tiptap.dev",
|
||||||
|
"keywords": [
|
||||||
|
"tiptap",
|
||||||
|
"tiptap react components"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"main": "dist/tiptap-react.cjs.js",
|
||||||
|
"umd": "dist/tiptap-react.umd.js",
|
||||||
|
"module": "dist/tiptap-react.esm.js",
|
||||||
|
"unpkg": "dist/tiptap-react.bundle.umd.min.js",
|
||||||
|
"types": "dist/packages/react/src/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^2.0.0-alpha.6",
|
||||||
|
"react": "^17.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-view": "^1.17.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/react/src/ReactNodeViewRenderer.ts
Normal file
1
packages/react/src/ReactNodeViewRenderer.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default class ReactNodeViewRenderer {}
|
||||||
1
packages/react/src/ReactRenderer.ts
Normal file
1
packages/react/src/ReactRenderer.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default class ReactRenderer {}
|
||||||
34
packages/react/src/components/Editor.jsx
Normal file
34
packages/react/src/components/Editor.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, {
|
||||||
|
useState, useRef, useEffect, createContext, useContext,
|
||||||
|
} from 'react'
|
||||||
|
import { Editor as Tiptap } from '@tiptap/core'
|
||||||
|
|
||||||
|
export const EditorContext = createContext({})
|
||||||
|
|
||||||
|
export const useEditor = () => useContext(EditorContext)
|
||||||
|
|
||||||
|
export const Editor = ({
|
||||||
|
value, onChange, children, ...props
|
||||||
|
}) => {
|
||||||
|
const [editor, setEditor] = useState(null)
|
||||||
|
const editorRef = useRef(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const e = new Tiptap({
|
||||||
|
element: editorRef.current,
|
||||||
|
content: value,
|
||||||
|
...props,
|
||||||
|
}).on('transaction', () => {
|
||||||
|
onChange(e.getJSON())
|
||||||
|
})
|
||||||
|
|
||||||
|
setEditor(e)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorContext.Provider value={editor}>
|
||||||
|
{editorRef.current && children}
|
||||||
|
<div ref={editorRef} />
|
||||||
|
</EditorContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
packages/react/src/index.ts
Normal file
7
packages/react/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from '@tiptap/core'
|
||||||
|
export { default as ReactRenderer } from './ReactRenderer'
|
||||||
|
export { default as ReactNodeViewRenderer } from './ReactNodeViewRenderer'
|
||||||
|
export {
|
||||||
|
Editor, EditorContext, useEditor,
|
||||||
|
} from './components/Editor'
|
||||||
Reference in New Issue
Block a user