Add Mention demo for React

This commit is contained in:
svenadlung
2021-11-16 18:26:28 +01:00
parent 0ad3c48435
commit 9956c9c51b
7 changed files with 245 additions and 0 deletions

View 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>
)
})

View 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;
}
}

View 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>

View 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! Dont forget the daily stand up at 8 AM.</p>
<p><span data-mention data-id="Jennifer Grey"></span> Would you mind to share what youve 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> Lets go through your most important points quickly.</p>
<p>I have a meeting with <span data-mention data-id="Christina Applegate"></span> and dont want to come late.</p>
<p> Thanks, your big boss</p>
`,
})
if (!editor) {
return null
}
return <EditorContent editor={editor} />
}

View File

@@ -0,0 +1,7 @@
context('/src/Nodes/Mention/React/', () => {
before(() => {
cy.visit('/src/Nodes/Mention/React/')
})
// TODO: Write tests
})

View 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;
}

View 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()
},
}
},
}