diff --git a/.changeset/orange-suits-notice.md b/.changeset/orange-suits-notice.md new file mode 100644 index 00000000..f3e8703f --- /dev/null +++ b/.changeset/orange-suits-notice.md @@ -0,0 +1,5 @@ +--- +"react-mentions": minor +--- + +Added support to disable suggestion selection diff --git a/README.md b/README.md index 6d2a7f2b..e15cbbf1 100755 --- a/README.md +++ b/README.md @@ -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 | diff --git a/demo/src/examples/Examples.js b/demo/src/examples/Examples.js index 1e1e7e82..dd5d69eb 100644 --- a/demo/src/examples/Examples.js +++ b/demo/src/examples/Examples.js @@ -46,6 +46,7 @@ const users = [ { id: 'jesse', display: 'Jesse Pinkman', + disabled: true, }, { id: 'gus', diff --git a/demo/src/examples/defaultStyle.js b/demo/src/examples/defaultStyle.js index 1b264aa4..fd286b11 100644 --- a/demo/src/examples/defaultStyle.js +++ b/demo/src/examples/defaultStyle.js @@ -46,6 +46,10 @@ export default { '&focused': { backgroundColor: '#cee4e5', }, + '&disabled': { + color: 'rgba(0,0,0,0.3)', + cursor: 'default', + }, }, }, } diff --git a/src/LoadingIndicator.js b/src/LoadingIndicator.js index ed9a42aa..c8278c47 100644 --- a/src/LoadingIndicator.js +++ b/src/LoadingIndicator.js @@ -5,7 +5,7 @@ function LoadingIndicator({ style, className, classNames }) { const styles = useStyles(defaultstyle, { style, className, classNames }) const spinnerStyles = styles('spinner') return ( -
+
diff --git a/src/MentionsInput.js b/src/MentionsInput.js index 68e6b73d..3c267f69 100755 --- a/src/MentionsInput.js +++ b/src/MentionsInput.js @@ -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) diff --git a/src/Suggestion.js b/src/Suggestion.js index 81274a17..957bfed4 100644 --- a/src/Suggestion.js +++ b/src/Suggestion.js @@ -67,7 +67,16 @@ function Suggestion({ } return ( -
  • +
  • {renderContent()}
  • ) @@ -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 + ), + }) ) export default styled(Suggestion) diff --git a/src/SuggestionsOverlay.js b/src/SuggestionsOverlay.js index 6c85ced9..932442b7 100644 --- a/src/SuggestionsOverlay.js +++ b/src/SuggestionsOverlay.js @@ -115,6 +115,10 @@ function SuggestionsOverlay({ } const select = (suggestion, queryInfo) => { + if (typeof suggestion !== 'string' && suggestion.disabled) { + return + } + onSelect(suggestion, queryInfo) } diff --git a/src/SuggestionsOverlay.spec.js b/src/SuggestionsOverlay.spec.js index 653866a6..055f586f 100644 --- a/src/SuggestionsOverlay.spec.js +++ b/src/SuggestionsOverlay.spec.js @@ -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( + + + + ) + 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) + }) })