diff --git a/client/components/Assignments/AssignmentEditor.tsx b/client/components/Assignments/AssignmentEditor.tsx index 24436ffee..201ec76e0 100644 --- a/client/components/Assignments/AssignmentEditor.tsx +++ b/client/components/Assignments/AssignmentEditor.tsx @@ -14,9 +14,9 @@ import { Row, SelectInput, ColouredValueInput, - SelectUserInput, } from '../UI/Form'; import {ContactsPreviewList, SelectSearchContactsField} from '../Contacts'; +import {superdeskApi} from '../../superdeskApi'; export class AssignmentEditorComponent extends React.Component { constructor(props) { @@ -243,6 +243,7 @@ export class AssignmentEditorComponent extends React.Component { showPriority, className, } = this.props; + const {SelectUser} = superdeskApi.components; return (
@@ -297,15 +298,19 @@ export class AssignmentEditorComponent extends React.Component { /> ) : ( - + +
+ { + this.onUserChange(null, user); + }} + autoFocus={false} + horizontalSpacing={true} + clearable={true} + /> +
+
)} {showPriority && ( diff --git a/client/components/Assignments/EditCoverageAssignmentModal.tsx b/client/components/Assignments/EditCoverageAssignmentModal.tsx index e00d41cc0..0367175ea 100644 --- a/client/components/Assignments/EditCoverageAssignmentModal.tsx +++ b/client/components/Assignments/EditCoverageAssignmentModal.tsx @@ -70,6 +70,7 @@ export class EditCoverageAssignmentModalComponent extends React.Component { show={true} onHide={handleHide} fill={false} + removeTabIndexAttribute={true} >

{gettext('Coverage Assignment Details')}

diff --git a/client/components/Coverages/CoverageAddAdvancedModal.tsx b/client/components/Coverages/CoverageAddAdvancedModal.tsx index b2edfdff2..984c5bab8 100644 --- a/client/components/Coverages/CoverageAddAdvancedModal.tsx +++ b/client/components/Coverages/CoverageAddAdvancedModal.tsx @@ -9,7 +9,8 @@ import {getUserInterfaceLanguageFromCV} from '../../utils/users'; import {getVocabularyItemFieldTranslated} from '../../utils/vocabularies'; import Modal from '../Modal'; -import {SelectInput, SelectUserInput} from '../UI/Form'; +import {SelectInput} from '../UI/Form'; +import {superdeskApi} from '../../superdeskApi'; const isInvalid = (coverage) => coverage.user && !coverage.desk; @@ -35,7 +36,7 @@ interface ICoverageSelector { name: IG2ContentType['name']; icon: string; desk: IDesk['_id']; - user: IUser['_id']; + user: IUser; status: IPlanningNewsCoverageStatus; popupContainer: any; filteredDesks: Array; @@ -218,6 +219,7 @@ export class CoverageAddAdvancedModal extends React.Component { render() { const language = getUserInterfaceLanguageFromCV(); + const {SelectUser} = superdeskApi.components; return ( { event.stopPropagation(); this.props.close(); }} + removeTabIndexAttribute={true} >

@@ -276,18 +279,25 @@ export class CoverageAddAdvancedModal extends React.Component {

- this.onUserChange(coverage, value)} - labelField="name" - keyField="_id" - users={coverage.filteredUsers} - clearable={true} - popupContainer={() => coverage.popupContainer} - inline={true} - /> -
coverage.popupContainer = node} /> +
+ { + this.onUserChange(coverage, user); + }} + autoFocus={false} + horizontalSpacing={true} + clearable={true} + /> +
coverage.popupContainer = node} /> +
diff --git a/client/components/ModalWithForm/index.tsx b/client/components/ModalWithForm/index.tsx index 3b409eea4..361092326 100644 --- a/client/components/ModalWithForm/index.tsx +++ b/client/components/ModalWithForm/index.tsx @@ -101,6 +101,7 @@ export class ModalWithForm extends React.Component { fill={fill} fullscreen={fullscreen} white={white} + removeTabIndexAttribute={true} >

{title}

diff --git a/client/components/UI/Form/SelectUserInput/SelectUserPopup.tsx b/client/components/UI/Form/SelectUserInput/SelectUserPopup.tsx deleted file mode 100644 index 97c3a8734..000000000 --- a/client/components/UI/Form/SelectUserInput/SelectUserPopup.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import {get} from 'lodash'; - -import {KEYCODES} from '../../constants'; -import {gettext, scrollListItemIfNeeded, onEventCapture} from '../../utils'; - -import {Popup, Content} from '../../Popup'; -import {UserAvatarWithMargin} from '../../../../components/UserAvatar'; - -import './style.scss'; - -/** - * @ngdoc react - * @name SelectUserPopup - * @description Pop-up component of SelectUserList - */ -export class SelectUserPopup extends React.Component { - constructor(props) { - super(props); - this.onKeyDown = this.onKeyDown.bind(this); - this.handleOnChange = this.handleOnChange.bind(this); - this.state = {activeIndex: -1}; - this.dom = {itemList: null}; - } - - /** - * @ngdoc method - * @name SelectUserPopup#onKeyDown - * @description onKeyDown method to handle keyboard selection of options - */ - onKeyDown(event) { - if (event) { - switch (event.keyCode) { - case KEYCODES.ENTER: - onEventCapture(event); - this.handleEnterKey(event); - break; - case KEYCODES.DOWN: - onEventCapture(event); - this.handleDownArrowKey(event); - break; - case KEYCODES.UP: - onEventCapture(event); - this.handleUpArrowKey(event); - break; - } - } - } - - /** - * @ngdoc method - * @name SelectUserPopup#handleEnterKey - * @description handleEnterKey method to handle enter-key press on a selected option - */ - handleEnterKey(event) { - this.props.onChange(this.props.users[this.state.activeIndex]); - } - - /** - * @ngdoc method - * @name SelectUserPopup#handleDownArrowKey - * @description handleDownArrowKey method to handle down-key press to select options - */ - handleDownArrowKey(event) { - if (get(this.props, 'users.length', 0) - 1 > this.state.activeIndex) { - this.setState({activeIndex: this.state.activeIndex + 1}); - scrollListItemIfNeeded(this.state.activeIndex, this.dom.itemList); - } - } - - /** - * @ngdoc method - * @name SelectUserPopup#handleUpArrowKey - * @description handleUpArrowKey method to handle up-key press to select options - */ - handleUpArrowKey(event) { - if (this.state.activeIndex > 0) { - this.setState({activeIndex: this.state.activeIndex - 1}); - scrollListItemIfNeeded(this.state.activeIndex, this.dom.itemList); - } - } - - handleOnChange(user, event) { - onEventCapture(event); - this.props.onChange(user); - } - - render() { - const { - onClose, - target, - popupContainer, - users, - } = this.props; - - return ( - - -
    this.dom.itemList = ref} - > - {users.length > 0 && users.map((user, index) => ( -
  • - -
  • - ))} - - {users.length === 0 && ( -
  • - -
  • - )} -
-
-
- ); - } -} - -SelectUserPopup.propTypes = { - onClose: PropTypes.func, - target: PropTypes.string, - popupContainer: PropTypes.func, - users: PropTypes.array, - onChange: PropTypes.func, -}; diff --git a/client/components/UI/Form/SelectUserInput/index.tsx b/client/components/UI/Form/SelectUserInput/index.tsx deleted file mode 100644 index 7a6307eae..000000000 --- a/client/components/UI/Form/SelectUserInput/index.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React from 'react'; - -import {IUser} from 'superdesk-api'; -import {superdeskApi} from '../../../../superdeskApi'; -import {KEYCODES} from '../../constants'; - -import {onEventCapture} from '../../utils'; - -import {Row, LineInput, Label, Input} from '../'; -import {UserAvatarWithMargin} from '../../../../components/UserAvatar'; -import {SelectUserPopup} from './SelectUserPopup'; - -interface IProps { - field: string; - label?: string; - placeholder?: string; - value: any; - users: Array; - readOnly?: boolean; - hideInactiveDisabled?: boolean; // defaults to `true` - inline?: boolean; - onChange(field: string, value?: IUser): void; - popupContainer?(): HTMLElement; -} - -interface IState { - filteredUserList: Array; - searchText?: string; - openFilterList?: boolean; -} - -/** - * @ngdoc react - * @name SelectUserInput - * @description Component to select users from a list - */ -export class SelectUserInput extends React.Component { - constructor(props) { - super(props); - - this.state = { - filteredUserList: this.getUsersToDisplay(this.props.users), - searchText: '', - openFilterList: false, - }; - - this.openPopup = this.openPopup.bind(this); - this.closePopup = this.closePopup.bind(this); - this.filterUserList = this.filterUserList.bind(this); - this.onUserChange = this.onUserChange.bind(this); - } - - componentWillReceiveProps(nextProps) { - this.setState({filteredUserList: this.getUsersToDisplay(nextProps.users)}); - } - - openPopup() { - this.setState({openFilterList: true}); - } - - closePopup() { - this.setState({openFilterList: false}); - } - - filterUserList(field, value) { - if (!value) { - this.setState({ - filteredUserList: this.getUsersToDisplay(this.props.users), - searchText: '', - openFilterList: true, - }); - return; - } - - const filterTextNoCase = value.toLowerCase(); - const newUserList = this.props.users.filter((user) => ( - user.display_name.toLowerCase().substr(0, value.length) === filterTextNoCase || - user.display_name.toLowerCase().indexOf(filterTextNoCase) >= 0 - )); - - this.setState({ - filteredUserList: this.getUsersToDisplay(newUserList), - searchText: value, - openFilterList: true, - }); - } - - onUserChange(newUserId) { - this.props.onChange(this.props.field, newUserId); - this.setState({ - openFilterList: false, - searchText: '', - }); - } - - getUsersToDisplay(list = []) { - if (!(this.props.hideInactiveDisabled ?? true)) { - return list; - } else { - return list.filter((u) => u.is_active && u.is_enabled && !u.needs_activation); - } - } - - render() { - const {gettext} = superdeskApi.localization; - const {value, popupContainer, label, readOnly, inline, field} = this.props; - - return ( -
- {(!inline || value) && ( - - - - - )} - - {!readOnly && (!inline || !value) && ( - - - { - if (event.keyCode === KEYCODES.ENTER || - event.keyCode === KEYCODES.DOWN) { - onEventCapture(event); - this.openPopup(); - } - } - } - /> - - {this.state.openFilterList && ( - - )} - - - )} -
- ); - } -} diff --git a/client/components/UI/Form/SelectUserInput/style.scss b/client/components/UI/Form/SelectUserInput/style.scss deleted file mode 100644 index cc39f1de7..000000000 --- a/client/components/UI/Form/SelectUserInput/style.scss +++ /dev/null @@ -1,48 +0,0 @@ -@import '~superdesk-ui-framework/app/styles/_mixins.scss'; -@import '~superdesk-ui-framework/app/styles/_variables.scss'; - -.user-search__popup { - margin-top: 1px; - - &-list { - li { - margin: 5px 0; - } - - overflow-y: scroll; - max-height: 250px; - } - - &-item { - margin: 5px; - &:hover { - background: $sd-hover; - } - - button { - width: 100%; - text-align: left; - } - - &--active { - background: $sd-hover; - } - } - - &-item-label { - padding-top: 5px; - display: inline-block; - width: 200px; - text-align: left; - } - - &-user { - display: inline-block; - margin: 10px 0; - width: 100%; - - button { - float: right; - } - } -} diff --git a/client/components/UI/Form/index.ts b/client/components/UI/Form/index.ts index 03a068922..2d0a9c973 100644 --- a/client/components/UI/Form/index.ts +++ b/client/components/UI/Form/index.ts @@ -14,7 +14,6 @@ export {SelectInput} from './SelectInput'; export {FileInput} from './FileInput'; export {LinkInput} from './LinkInput'; export {SelectMetaTermsInput} from './SelectMetaTermsInput/index'; -export {SelectUserInput} from './SelectUserInput'; export {SelectTagInput} from './SelectTagInput'; export {Checkbox} from './Checkbox'; export {CheckboxGroup} from './CheckboxGroup'; diff --git a/client/components/fields/editor/EventRelatedPlannings/EmbeddedCoverageForm.tsx b/client/components/fields/editor/EventRelatedPlannings/EmbeddedCoverageForm.tsx index 2d3f34494..c4910538c 100644 --- a/client/components/fields/editor/EventRelatedPlannings/EmbeddedCoverageForm.tsx +++ b/client/components/fields/editor/EventRelatedPlannings/EmbeddedCoverageForm.tsx @@ -14,7 +14,7 @@ import {superdeskApi, planningApi} from '../../../../superdeskApi'; import {getDesksForUser, getUsersForDesk} from '../../../../utils'; import {Select, Option} from 'superdesk-ui-framework/react'; import * as List from '../../../UI/List'; -import {Row, SelectUserInput} from '../../../UI/Form'; +import {Row} from '../../../UI/Form'; import {EditorFieldNewsCoverageStatus} from '../NewsCoverageStatus'; import * as config from 'appConfig'; import {getLanguagesForTreeSelectInput} from '../../../../selectors/vocabs'; diff --git a/client/utils/testApi.ts b/client/utils/testApi.ts index b9e7afcde..6e2374ab8 100644 --- a/client/utils/testApi.ts +++ b/client/utils/testApi.ts @@ -39,6 +39,9 @@ Object.assign(superdeskApi, { success: sinon.stub().returns(undefined), error: sinon.stub().returns(undefined), } + }, + components: { + SelectUser: sinon.stub().returns('
Stubbed SelectUser Component
'), } }); diff --git a/e2e/cypress/support/common/inputs/userSelectInput.ts b/e2e/cypress/support/common/inputs/userSelectInput.ts index 79db95832..510d9bf26 100644 --- a/e2e/cypress/support/common/inputs/userSelectInput.ts +++ b/e2e/cypress/support/common/inputs/userSelectInput.ts @@ -1,5 +1,4 @@ import {Input} from './input'; -import {Popup} from '../ui'; /** * Wrapper class for a searchable user select input field @@ -8,22 +7,21 @@ import {Popup} from '../ui'; export class UserSelectInput extends Input { type(value) { cy.log('Common.SearchableSelectInput.type'); - const popup = new Popup(); - this.element - .find('input') - .type(value); + // Click on the element to open the dropdown + this.element.click(); - popup.waitTillOpen(); - popup.element - .find('li') - .first() - .click(); - popup.waitTillClosed(); + // Type into the input inside the dropdown panel + cy.get('.p-dropdown-panel input').type(value); + + // Click on the first list item in the dropdown + cy.get('.p-dropdown-panel li').first().click(); } expect(value) { cy.log('Common.SearchableSelectInput.expect'); - this.element.should('contain.text', value); + + // Ensure that the dropdown panel contains the expected text + cy.get('.p-dropdown-panel').should('contain.text', value); } }