From 3a91d4e5e419a43ff19a0736ce697f8d611d36e3 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 2 Mar 2017 17:48:56 -0500 Subject: PLT-3077 Add group messaging (#5489) * Implement server changes for group messaging * Majority of client-side implementation * Some server updates * Added new React multiselect component * Fix style issues * Add custom renderer for options * Fix model test * Update ENTER functionality for multiselect control * Remove buttons from multiselect UI control * Updating group messaging UI (#5524) * Move filter controls up a component level * Scroll with arrow keys * Updating mobile layout for multiselect (#5534) * Fix race condition when backspacing quickly * Hidden or new GMs show up for regular messages * Add overriding of number remaining text * Add UI filtering for team if config setting set * Add icon to channel switcher and class prop to status icon * Minor updates per feedback * Improving group messaging UI (#5563) * UX changes per feedback * Update email for group messages * UI fixes for group messaging (#5587) * Fix missing localization string * Add maximum users message when adding members to GM * Fix input clearing on Android * Updating group messaging UI (#5603) * Updating UI for group messaging (#5604) --- webapp/components/multiselect/multiselect_list.jsx | 169 +++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 webapp/components/multiselect/multiselect_list.jsx (limited to 'webapp/components/multiselect/multiselect_list.jsx') diff --git a/webapp/components/multiselect/multiselect_list.jsx b/webapp/components/multiselect/multiselect_list.jsx new file mode 100644 index 000000000..ff9f68bf8 --- /dev/null +++ b/webapp/components/multiselect/multiselect_list.jsx @@ -0,0 +1,169 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {cmdOrCtrlPressed} from 'utils/utils.jsx'; +import Constants from 'utils/constants.jsx'; +const KeyCodes = Constants.KeyCodes; + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +export default class MultiSelectList extends React.Component { + constructor(props) { + super(props); + + this.defaultOptionRenderer = this.defaultOptionRenderer.bind(this); + this.handleArrowPress = this.handleArrowPress.bind(this); + this.setSelected = this.setSelected.bind(this); + + this.toSelect = -1; + + this.state = { + selected: -1 + }; + } + + componentDidMount() { + document.addEventListener('keydown', this.handleArrowPress); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleArrowPress); + } + + componentWillReceiveProps(nextProps) { + this.setState({selected: this.toSelect}); + + const options = nextProps.options; + + if (options && options.length > 0 && this.toSelect >= 0) { + this.props.onSelect(options[this.toSelect]); + } + } + + componentDidUpdate() { + if (this.refs.list && this.refs.selected) { + const elemTop = this.refs.selected.getBoundingClientRect().top; + const elemBottom = this.refs.selected.getBoundingClientRect().bottom; + const listTop = this.refs.list.getBoundingClientRect().top; + const listBottom = this.refs.list.getBoundingClientRect().bottom; + if (elemBottom > listBottom) { + this.refs.selected.scrollIntoView(false); + } else if (elemTop < listTop) { + this.refs.selected.scrollIntoView(true); + } + } + } + + setSelected(selected) { + this.toSelect = selected; + } + + handleArrowPress(e) { + if (cmdOrCtrlPressed(e) && e.shiftKey) { + return; + } + + const options = this.props.options; + if (options.length === 0) { + return; + } + + let selected; + switch (e.keyCode) { + case KeyCodes.DOWN: + if (this.state.selected === -1) { + selected = 0; + break; + } + selected = Math.min(this.state.selected + 1, options.length - 1); + break; + case KeyCodes.UP: + if (this.state.selected === -1) { + selected = 0; + break; + } + selected = Math.max(this.state.selected - 1, 0); + break; + default: + return; + } + + e.preventDefault(); + this.setState({selected}); + this.props.onSelect(options[selected]); + } + + defaultOptionRenderer(option, isSelected, onAdd) { + var rowSelected = ''; + if (isSelected) { + rowSelected = 'more-modal__row--selected'; + } + + return ( +
onAdd(option)} + > + {option.label} +
+ ); + } + + render() { + const options = this.props.options; + + if (options == null || options.length === 0) { + return ( +
+

+ +

+
+ ); + } + + let renderer; + if (this.props.optionRenderer) { + renderer = this.props.optionRenderer; + } else { + renderer = this.defaultOptionRenderer; + } + + const optionControls = options.map((o, i) => renderer(o, this.state.selected === i, this.props.onAdd)); + + return ( +
+
+ {optionControls} +
+
+ ); + } +} + +MultiSelectList.defaultProps = { + options: [], + perPage: 50, + onAction: () => null +}; + +MultiSelectList.propTypes = { + options: React.PropTypes.arrayOf(React.PropTypes.object), + optionRenderer: React.PropTypes.func, + page: React.PropTypes.number, + perPage: React.PropTypes.number, + onPageChange: React.PropTypes.func, + onAdd: React.PropTypes.func, + onSelect: React.PropTypes.func +}; -- cgit v1.2.3-1-g7c22