From d7b6534522cd3583b4cca47b82f8c2605a8cf019 Mon Sep 17 00:00:00 2001 From: Jack Lewin <14926880+jack-lewin@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:39:23 +0000 Subject: [PATCH 1/5] Added support to disable suggestion selection If a suggestion is disabled... - Set aria-disabled=true - Apply style - Don't handle onSelect Co-authored-by: Maks --- demo/src/examples/Examples.js | 1 + demo/src/examples/defaultStyle.js | 4 ++++ src/MentionsInput.js | 6 +++++- src/Suggestion.js | 16 ++++++++++++++-- src/SuggestionsOverlay.js | 4 ++++ 5 files changed, 28 insertions(+), 3 deletions(-) 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/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..249f035f 100644 --- a/src/Suggestion.js +++ b/src/Suggestion.js @@ -67,7 +67,14 @@ function Suggestion({ } return ( -
  • +
  • {renderContent()}
  • ) @@ -95,7 +102,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..09bff893 100644 --- a/src/SuggestionsOverlay.js +++ b/src/SuggestionsOverlay.js @@ -115,6 +115,10 @@ function SuggestionsOverlay({ } const select = (suggestion, queryInfo) => { + if (suggestion.disabled) { + return + } + onSelect(suggestion, queryInfo) } From eb2192a95f21fed09b15c4acf9942e19d9aeb202 Mon Sep 17 00:00:00 2001 From: Jack Lewin <14926880+jack-lewin@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:39:39 +0000 Subject: [PATCH 2/5] Added tests for SuggestionsOverlay component --- src/LoadingIndicator.js | 2 +- src/SuggestionsOverlay.spec.js | 128 +++++++++++++++++++++++++++++++-- 2 files changed, 122 insertions(+), 8 deletions(-) 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/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) + }) }) From fd5aedfd30a9b174f2de46aed2b5e45dd91fefd7 Mon Sep 17 00:00:00 2001 From: Jack Lewin <14926880+jack-lewin@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:44:20 +0000 Subject: [PATCH 3/5] Update docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 | From 247672b552b186c95dfb788503899d4b25b3a370 Mon Sep 17 00:00:00 2001 From: Jack Lewin <14926880+jack-lewin@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:53:02 +0000 Subject: [PATCH 4/5] Added changeset --- .changeset/orange-suits-notice.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-suits-notice.md 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 From 47b3c6861ce90a490e384815d1d12ebc8b3bd760 Mon Sep 17 00:00:00 2001 From: Jack Lewin <14926880+jack-lewin@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:16:08 +0100 Subject: [PATCH 5/5] Handle case where suggestion is a string --- src/Suggestion.js | 4 +++- src/SuggestionsOverlay.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Suggestion.js b/src/Suggestion.js index 249f035f..957bfed4 100644 --- a/src/Suggestion.js +++ b/src/Suggestion.js @@ -71,7 +71,9 @@ function Suggestion({ id={id} role="option" aria-selected={focused} - aria-disabled={Boolean(suggestion.disabled)} + aria-disabled={Boolean( + typeof suggestion === 'string' ? false : suggestion.disabled + )} {...rest} {...style} > diff --git a/src/SuggestionsOverlay.js b/src/SuggestionsOverlay.js index 09bff893..932442b7 100644 --- a/src/SuggestionsOverlay.js +++ b/src/SuggestionsOverlay.js @@ -115,7 +115,7 @@ function SuggestionsOverlay({ } const select = (suggestion, queryInfo) => { - if (suggestion.disabled) { + if (typeof suggestion !== 'string' && suggestion.disabled) { return }