Add Mention demo for React
This commit is contained in:
65
demos/src/Nodes/Mention/React/MentionList.jsx
Normal file
65
demos/src/Nodes/Mention/React/MentionList.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, {
|
||||
useState, useEffect, forwardRef, useImperativeHandle,
|
||||
} from 'react'
|
||||
import './MentionList.scss'
|
||||
|
||||
export default forwardRef((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
const selectItem = index => {
|
||||
const item = props.items[index]
|
||||
|
||||
if (item) {
|
||||
props.command({ id: item })
|
||||
}
|
||||
}
|
||||
|
||||
const upHandler = () => {
|
||||
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length)
|
||||
}
|
||||
|
||||
const downHandler = () => {
|
||||
setSelectedIndex((selectedIndex + 1) % props.items.length)
|
||||
}
|
||||
|
||||
const enterHandler = () => {
|
||||
selectItem(selectedIndex)
|
||||
}
|
||||
|
||||
useEffect(() => setSelectedIndex(0), [props.items])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onKeyDown: ({ event }) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
upHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
downHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
enterHandler()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="items">
|
||||
{props.items.map((item, index) => (
|
||||
<button
|
||||
className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
|
||||
key={index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
25
demos/src/Nodes/Mention/React/MentionList.scss
Normal file
25
demos/src/Nodes/Mention/React/MentionList.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
.items {
|
||||
background: #fff;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1);
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 0.9rem;
|
||||
overflow: hidden;
|
||||
padding: 0.2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.4rem;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0.2rem 0.4rem;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&.is-selected {
|
||||
border-color: #000;
|
||||
}
|
||||
}
|
||||
15
demos/src/Nodes/Mention/React/index.html
Normal file
15
demos/src/Nodes/Mention/React/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import setup from "../../../../setup/react.ts";
|
||||
import source from "@source";
|
||||
setup("Nodes/Mention", source);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
37
demos/src/Nodes/Mention/React/index.jsx
Normal file
37
demos/src/Nodes/Mention/React/index.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { useEditor, EditorContent } from '@tiptap/react'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Mention from '@tiptap/extension-mention'
|
||||
import suggestion from './suggestion'
|
||||
import './styles.scss'
|
||||
|
||||
export default () => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
},
|
||||
suggestion,
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
<p>Hi everyone! Don’t forget the daily stand up at 8 AM.</p>
|
||||
<p><span data-mention data-id="Jennifer Grey"></span> Would you mind to share what you’ve been working on lately? We fear not much happened since Dirty Dancing.
|
||||
<p><span data-mention data-id="Winona Ryder"></span> <span data-mention data-id="Axl Rose"></span> Let’s go through your most important points quickly.</p>
|
||||
<p>I have a meeting with <span data-mention data-id="Christina Applegate"></span> and don’t want to come late.</p>
|
||||
<p>– Thanks, your big boss</p>
|
||||
`,
|
||||
})
|
||||
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <EditorContent editor={editor} />
|
||||
}
|
||||
7
demos/src/Nodes/Mention/React/index.spec.js
Normal file
7
demos/src/Nodes/Mention/React/index.spec.js
Normal file
@@ -0,0 +1,7 @@
|
||||
context('/src/Nodes/Mention/React/', () => {
|
||||
before(() => {
|
||||
cy.visit('/src/Nodes/Mention/React/')
|
||||
})
|
||||
|
||||
// TODO: Write tests
|
||||
})
|
||||
12
demos/src/Nodes/Mention/React/styles.scss
Normal file
12
demos/src/Nodes/Mention/React/styles.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.ProseMirror {
|
||||
> * + * {
|
||||
margin-top: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
border: 1px solid #000;
|
||||
border-radius: 0.4rem;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
84
demos/src/Nodes/Mention/React/suggestion.js
Normal file
84
demos/src/Nodes/Mention/React/suggestion.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { ReactRenderer } from '@tiptap/react'
|
||||
import tippy from 'tippy.js'
|
||||
import MentionList from './MentionList.jsx'
|
||||
|
||||
export default {
|
||||
items: ({ query }) => {
|
||||
return [
|
||||
'Lea Thompson',
|
||||
'Cyndi Lauper',
|
||||
'Tom Cruise',
|
||||
'Madonna',
|
||||
'Jerry Hall',
|
||||
'Joan Collins',
|
||||
'Winona Ryder',
|
||||
'Christina Applegate',
|
||||
'Alyssa Milano',
|
||||
'Molly Ringwald',
|
||||
'Ally Sheedy',
|
||||
'Debbie Harry',
|
||||
'Olivia Newton-John',
|
||||
'Elton John',
|
||||
'Michael J. Fox',
|
||||
'Axl Rose',
|
||||
'Emilio Estevez',
|
||||
'Ralph Macchio',
|
||||
'Rob Lowe',
|
||||
'Jennifer Grey',
|
||||
'Mickey Rourke',
|
||||
'John Cusack',
|
||||
'Matthew Broderick',
|
||||
'Justine Bateman',
|
||||
'Lisa Bonet',
|
||||
]
|
||||
.filter(item => item.toLowerCase().startsWith(query.toLowerCase()))
|
||||
.slice(0, 5)
|
||||
},
|
||||
|
||||
render: () => {
|
||||
let component
|
||||
let popup
|
||||
|
||||
return {
|
||||
onStart: props => {
|
||||
component = new ReactRenderer(MentionList, {
|
||||
props,
|
||||
editor: props.editor,
|
||||
})
|
||||
|
||||
popup = tippy('body', {
|
||||
getReferenceClientRect: props.clientRect,
|
||||
appendTo: () => document.body,
|
||||
content: component.element,
|
||||
showOnCreate: true,
|
||||
interactive: true,
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
},
|
||||
|
||||
onUpdate(props) {
|
||||
component.updateProps(props)
|
||||
|
||||
popup[0].setProps({
|
||||
getReferenceClientRect: props.clientRect,
|
||||
})
|
||||
},
|
||||
|
||||
onKeyDown(props) {
|
||||
if (props.event.key === 'Escape') {
|
||||
popup[0].hide()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return component.ref?.onKeyDown(props)
|
||||
},
|
||||
|
||||
onExit() {
|
||||
popup[0].destroy()
|
||||
component.destroy()
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user