From 9956c9c51bbc4c12408725fcc868b05ae68a1533 Mon Sep 17 00:00:00 2001 From: svenadlung Date: Tue, 16 Nov 2021 18:26:28 +0100 Subject: [PATCH] Add Mention demo for React --- demos/src/Nodes/Mention/React/MentionList.jsx | 65 ++++++++++++++ .../src/Nodes/Mention/React/MentionList.scss | 25 ++++++ demos/src/Nodes/Mention/React/index.html | 15 ++++ demos/src/Nodes/Mention/React/index.jsx | 37 ++++++++ demos/src/Nodes/Mention/React/index.spec.js | 7 ++ demos/src/Nodes/Mention/React/styles.scss | 12 +++ demos/src/Nodes/Mention/React/suggestion.js | 84 +++++++++++++++++++ 7 files changed, 245 insertions(+) create mode 100644 demos/src/Nodes/Mention/React/MentionList.jsx create mode 100644 demos/src/Nodes/Mention/React/MentionList.scss create mode 100644 demos/src/Nodes/Mention/React/index.html create mode 100644 demos/src/Nodes/Mention/React/index.jsx create mode 100644 demos/src/Nodes/Mention/React/index.spec.js create mode 100644 demos/src/Nodes/Mention/React/styles.scss create mode 100644 demos/src/Nodes/Mention/React/suggestion.js diff --git a/demos/src/Nodes/Mention/React/MentionList.jsx b/demos/src/Nodes/Mention/React/MentionList.jsx new file mode 100644 index 00000000..0091b63e --- /dev/null +++ b/demos/src/Nodes/Mention/React/MentionList.jsx @@ -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 ( +
+ {props.items.map((item, index) => ( + + ))} +
+ ) +}) diff --git a/demos/src/Nodes/Mention/React/MentionList.scss b/demos/src/Nodes/Mention/React/MentionList.scss new file mode 100644 index 00000000..89e81cd2 --- /dev/null +++ b/demos/src/Nodes/Mention/React/MentionList.scss @@ -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; + } +} diff --git a/demos/src/Nodes/Mention/React/index.html b/demos/src/Nodes/Mention/React/index.html new file mode 100644 index 00000000..65a1893d --- /dev/null +++ b/demos/src/Nodes/Mention/React/index.html @@ -0,0 +1,15 @@ + + + + + + + +
+ + + diff --git a/demos/src/Nodes/Mention/React/index.jsx b/demos/src/Nodes/Mention/React/index.jsx new file mode 100644 index 00000000..38c2bef9 --- /dev/null +++ b/demos/src/Nodes/Mention/React/index.jsx @@ -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: ` +

Hi everyone! Don’t forget the daily stand up at 8 AM.

+

Would you mind to share what you’ve been working on lately? We fear not much happened since Dirty Dancing. +

Let’s go through your most important points quickly.

+

I have a meeting with and don’t want to come late.

+

– Thanks, your big boss

+ `, + }) + + if (!editor) { + return null + } + + return +} diff --git a/demos/src/Nodes/Mention/React/index.spec.js b/demos/src/Nodes/Mention/React/index.spec.js new file mode 100644 index 00000000..45be4bfd --- /dev/null +++ b/demos/src/Nodes/Mention/React/index.spec.js @@ -0,0 +1,7 @@ +context('/src/Nodes/Mention/React/', () => { + before(() => { + cy.visit('/src/Nodes/Mention/React/') + }) + + // TODO: Write tests +}) diff --git a/demos/src/Nodes/Mention/React/styles.scss b/demos/src/Nodes/Mention/React/styles.scss new file mode 100644 index 00000000..596151fc --- /dev/null +++ b/demos/src/Nodes/Mention/React/styles.scss @@ -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; +} diff --git a/demos/src/Nodes/Mention/React/suggestion.js b/demos/src/Nodes/Mention/React/suggestion.js new file mode 100644 index 00000000..911addee --- /dev/null +++ b/demos/src/Nodes/Mention/React/suggestion.js @@ -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() + }, + } + }, +}