Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support to disable suggestion selection #719

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/orange-suits-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-mentions": minor
---

Added support to disable suggestion selection
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Each data source is configured using a `Mention` component, which has the follow
| Prop name | Type | Default value | Description |
| ---------------- | ------------------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| trigger | regexp or string | `'@'` | Defines the char sequence upon which to trigger querying the data source |
| data | array or function (search, callback) | `null` | An array of the mentionable data entries (objects with `id` & `display` keys, or a filtering function that returns an array based on a query parameter |
| data | array or function (search, callback) | `null` | An array of the mentionable data entries (objects with `id`, `display` & optional `disabled` keys), or a filtering function that returns an array based on a query parameter |
| renderSuggestion | function (entry, search, highlightedDisplay, index, focused) | `null` | Allows customizing how mention suggestions are rendered (optional) |
| markup | string | `'@[__display__](__id__)'` | A template string for the markup to use for mentions |
| displayTransform | function (id, display) | returns `display` | Accepts a function for customizing the string that is displayed for a mention |
Expand Down
1 change: 1 addition & 0 deletions demo/src/examples/Examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const users = [
{
id: 'jesse',
display: 'Jesse Pinkman',
disabled: true,
},
{
id: 'gus',
Expand Down
4 changes: 4 additions & 0 deletions demo/src/examples/defaultStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export default {
'&focused': {
backgroundColor: '#cee4e5',
},
'&disabled': {
color: 'rgba(0,0,0,0.3)',
cursor: 'default',
},
},
},
}
2 changes: 1 addition & 1 deletion src/LoadingIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function LoadingIndicator({ style, className, classNames }) {
const styles = useStyles(defaultstyle, { style, className, classNames })
const spinnerStyles = styles('spinner')
return (
<div {...styles}>
<div {...styles} aria-label="Loading indicator">
<div {...spinnerStyles}>
<div {...spinnerStyles(['element', 'element1'])} />
<div {...spinnerStyles(['element', 'element2'])} />
Expand Down
6 changes: 5 additions & 1 deletion src/MentionsInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -996,9 +996,13 @@ class MentionsInput extends React.Component {
}

addMention = (
{ id, display },
{ id, display, disabled },
{ childIndex, querySequenceStart, querySequenceEnd, plainTextValue }
) => {
if (disabled) {
return;
}

// Insert mention in the marked up value at the correct position
const value = this.props.value || ''
const config = readConfigFromChildren(this.props.children)
Expand Down
18 changes: 16 additions & 2 deletions src/Suggestion.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,16 @@ function Suggestion({
}

return (
<li id={id} role="option" aria-selected={focused} {...rest} {...style}>
<li
id={id}
role="option"
aria-selected={focused}
aria-disabled={Boolean(
typeof suggestion === 'string' ? false : suggestion.disabled
)}
{...rest}
{...style}
>
{renderContent()}
</li>
)
Expand Down Expand Up @@ -95,7 +104,12 @@ const styled = defaultStyle(
{
cursor: 'pointer',
},
(props) => ({ '&focused': props.focused })
(props) => ({
'&focused': props.focused,
'&disabled': Boolean(
typeof props.suggestion === 'string' ? false : props.suggestion.disabled
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you check here if 'suggestion' is string before accessing disabled properly, but on a line 74, you don't?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @crecotun, thanks - I've added 47b3c68 to be more defensive here

),
})
)

export default styled(Suggestion)
4 changes: 4 additions & 0 deletions src/SuggestionsOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ function SuggestionsOverlay({
}

const select = (suggestion, queryInfo) => {
if (typeof suggestion !== 'string' && suggestion.disabled) {
return
}

onSelect(suggestion, queryInfo)
}

Expand Down
128 changes: 121 additions & 7 deletions src/SuggestionsOverlay.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,123 @@
import { Mention } from './index'
import SuggestionsOverlay from './SuggestionsOverlay'

import React from 'react'
import { mount } from 'enzyme'

const suggestions = {
'0': {
queryInfo: {
childIndex: 0,
query: 'en',
querySequenceStart: 0,
querySequenceEnd: 3,
plainTextValue: '@en',
},
results: [
{
id: 'first',
display: 'First entry',
},
{
id: 'second',
display: 'Second entry',
disabled: true,
},
],
},
}

const data = [
{ id: 'first', value: 'First entry' },
{ id: 'second', value: 'Second entry', disabled: true },
{ id: 'third', value: 'Third' },
]

describe('SuggestionsOverlay', () => {
it.todo('should render a list of all passed suggestions.')
it.todo('should be possible to style the list.')
it.todo('should be possible to apply styles to the items in the list.')
it.todo('should notify when the user clicks on a suggestion.')
it.todo('should be possible to show a loading indicator.')
it.todo('should be possible to style the loading indicator.')
it.todo('should notify when the user enters a suggestion with his mouse.')
let wrapper
const onSelect = jest.fn()
const onMouseEnter = jest.fn()

beforeEach(() => {
wrapper = mount(
<SuggestionsOverlay
id="foo"
suggestions={suggestions}
onSelect={onSelect}
onMouseEnter={onMouseEnter}
isOpened
>
<Mention trigger="@" data={data} />
</SuggestionsOverlay>
)
jest.resetAllMocks()
})

it('should render a list of all passed suggestions.', () => {
expect(wrapper.find('li').length).toEqual(2)
})

it('should be possible to style the list.', () => {
wrapper.setProps({ style: { list: { color: 'red' } } })

expect(wrapper.find('ul').props().style.color).toEqual('red')
})

it('should be possible to apply styles to the items in the list.', () => {
wrapper.setProps({ style: { item: { color: 'green' } } })

expect(
wrapper
.find('li')
.first()
.props().style.color
).toEqual('green')
})

it('should notify when the user clicks on a suggestion.', () => {
wrapper
.find('li')
.first()
.simulate('click')

expect(onSelect).toHaveBeenCalledTimes(1)
})

it('should be possible to show a loading indicator.', () => {
wrapper.setProps({ isLoading: true })

expect(wrapper.find('div[aria-label="Loading indicator"]').length).toBe(1)
})

it('should be possible to style the loading indicator.', () => {
wrapper.setProps({
isLoading: true,
style: { loadingIndicator: { color: 'purple' } },
})

expect(
wrapper.find('div[aria-label="Loading indicator"]').props().style.color
).toBe('purple')
})

it('should notify when the user enters a suggestion with their mouse.', () => {
wrapper
.find('li')
.first()
.simulate('mouseenter')

expect(onMouseEnter).toHaveBeenCalledTimes(1)
})

it('should prevent selecting a disabled suggestion.', () => {
const results = wrapper.find('li')

expect(results.last().props()['aria-disabled']).toBe(true)
results.last().simulate('click')
expect(onSelect).toHaveBeenCalledTimes(0)

expect(results.first().props()['aria-disabled']).toBe(false)
results.first().simulate('click')
expect(onSelect).toHaveBeenCalledTimes(1)
})
})