add react suggestion example

This commit is contained in:
Philipp Kühn
2021-03-16 19:12:26 +01:00
parent 6841408e0d
commit 9201a7dff2
11 changed files with 288 additions and 10 deletions

View File

@@ -86,7 +86,7 @@ export default {
.filter(item => { .filter(item => {
return ['vue', 'ts', 'js', 'jsx', 'scss'].includes(item.extension) return ['vue', 'ts', 'js', 'jsx', 'scss'].includes(item.extension)
}) })
.sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue')) .sortBy(item => item.path.split('/').length && !item.path.endsWith('index.vue') && !item.path.endsWith('index.jsx'))
.toArray() .toArray()
}, },
} }

View File

@@ -0,0 +1,79 @@
import React from 'react'
import './MentionList.scss'
export class MentionList extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedIndex: 0,
}
}
componentDidUpdate(oldProps) {
if (this.props.items !== oldProps.items) {
this.setState({
selectedIndex: 0
})
}
}
onKeyDown({ event }) {
if (event.key === 'ArrowUp') {
this.upHandler()
return true
}
if (event.key === 'ArrowDown') {
this.downHandler()
return true
}
if (event.key === 'Enter') {
this.enterHandler()
return true
}
return false
}
upHandler() {
this.setState({
selectedIndex: ((this.state.selectedIndex + this.props.items.length) - 1) % this.props.items.length
})
}
downHandler() {
this.setState({
selectedIndex: (this.state.selectedIndex + 1) % this.props.items.length
})
}
enterHandler() {
this.selectItem(this.state.selectedIndex)
}
selectItem(index) {
const item = this.props.items[index]
if (item) {
this.props.command({ id: item })
}
}
render() {
return (
<div className="items">
{this.props.items.map((item, index) => (
<button
className={`item ${index === this.state.selectedIndex ? 'is-selected' : ''}`}
key={index}
onClick={() => this.selectItem(index)}
>
{item}
</button>
))}
</div>
)
}
}

View File

@@ -0,0 +1,27 @@
.items {
position: relative;
border-radius: 0.25rem;
background: white;
color: rgba(black, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.1),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item {
display: block;
width: 100%;
text-align: left;
background: transparent;
border: none;
padding: 0.2rem 0.5rem;
&.is-selected,
&:hover {
color: #A975FF;
background: rgba(#A975FF, 0.1);
}
}

View File

@@ -0,0 +1,126 @@
import React from 'react'
import tippy from 'tippy.js'
import { useEditor, EditorContent, ReactRenderer } from '@tiptap/react'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import CharacterCount from '@tiptap/extension-character-count'
import Mention from '@tiptap/extension-mention'
import { MentionList } from './MentionList'
import './styles.scss'
export default () => {
const limit = 280
const editor = useEditor({
extensions: [
Document,
Paragraph,
Text,
CharacterCount.configure({
limit,
}),
Mention.configure({
HTMLAttributes: {
class: 'mention',
},
suggestion: {
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 reactRenderer
let popup
return {
onStart: props => {
reactRenderer = new ReactRenderer(MentionList, {
props,
editor: props.editor,
})
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: reactRenderer.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},
onUpdate(props) {
reactRenderer.updateProps(props)
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},
onKeyDown(props) {
return reactRenderer.ref.onKeyDown(props)
},
onExit() {
popup[0].destroy()
reactRenderer.destroy()
},
}
}
},
})
],
content: `
<p>
What do you all think about the new <span data-mention="Winona Ryder"></span> movie?
</p>
`,
})
const percentage = editor
? Math.round((100 / limit) * editor.getCharacterCount())
: 0
return (
<div>
<EditorContent editor={editor} />
{editor &&
<div className={`character-count ${editor.getCharacterCount() === limit ? 'character-count--warning' : ''}`}>
<svg
height="20"
width="20"
viewBox="0 0 20 20"
className="character-count__graph"
>
<circle
r="10"
cx="10"
cy="10"
fill="#e9ecef"
/>
<circle
r="5"
cx="10"
cy="10"
fill="transparent"
stroke="currentColor"
strokeWidth="10"
strokeDasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
transform="rotate(-90) translate(-20)"
/>
<circle
r="6"
cx="10"
cy="10"
fill="white"
/>
</svg>
<div className="character-count__text">
{editor.getCharacterCount()}/{limit} characters
</div>
</div>
}
</div>
)
}

View File

@@ -0,0 +1,41 @@
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
}
.mention {
color: #A975FF;
background-color: rgba(#A975FF, 0.1);
border-radius: 0.3rem;
padding: 0.1rem 0.3rem;
}
.character-count {
margin-top: 1rem;
display: flex;
align-items: center;
color: #68CEF8;
&--warning {
color: #FB5151;
}
&__graph {
margin-right: 0.5rem;
}
&__text {
color: #868e96;
}
}

View File

@@ -0,0 +1,7 @@
context('/demos/Examples/Community/Vue', () => {
before(() => {
cy.visit('/demos/Examples/Community/Vue')
})
// TODO: Write tests
})

View File

@@ -79,7 +79,7 @@ export default {
items: query => { items: query => {
return [ 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', '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, 10) ].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
}, },
render: () => { render: () => {
let component let component

View File

@@ -1,7 +0,0 @@
context('/demos/Examples/Community', () => {
before(() => {
cy.visit('/demos/Examples/Community')
})
// TODO: Write tests
})

View File

@@ -1,3 +1,7 @@
# Suggestions # Suggestions
<demo name="Examples/Community" /> ## Vue
<demo name="Examples/Community/Vue" />
## React
<demo name="Examples/Community/React" />

View File

@@ -1,3 +1,4 @@
.demo-page { .demo-page {
padding: 1.25rem; padding: 1.25rem;
min-height: 12rem;
} }