Updated borders on autocomplete to wrap entire list (#1149)

* Updated borders on autocomplete to wrap entire list

* Reverted accidental changes to search autocomplete position
This commit is contained in:
Harrison Healey 2017-11-14 20:13:03 -05:00 committed by Jarred Witt
parent e98e22600e
commit 70a1a92d06
13 changed files with 216 additions and 120 deletions

View file

@ -9,6 +9,7 @@ import {RequestStatus} from 'mattermost-redux/constants';
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@ -26,6 +27,7 @@ export default class AtMention extends PureComponent {
isSearch: PropTypes.bool,
matchTerm: PropTypes.string,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
outChannel: PropTypes.array,
requestStatus: PropTypes.string.isRequired,
teamMembers: PropTypes.array,
@ -55,6 +57,9 @@ export default class AtMention extends PureComponent {
mentionComplete: false,
sections: []
});
this.props.onResultCountChange(0);
return;
} else if (matchTerm === null) {
// if the terms did not change but is null then we don't need to do anything
@ -113,6 +118,8 @@ export default class AtMention extends PureComponent {
this.setState({
sections
});
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
}
}
@ -214,6 +221,7 @@ export default class AtMention extends PureComponent {
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
ItemSeparatorComponent={AutocompleteDivider}
initialNumToRender={10}
/>
);

View file

@ -10,7 +10,7 @@ import {
} from 'react-native';
import ProfilePicture from 'app/components/profile_picture';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class AtMentionItem extends PureComponent {
static propTypes = {
@ -60,19 +60,14 @@ export default class AtMentionItem extends PureComponent {
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
row: {
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
backgroundColor: theme.centerChannelBg
},
rowPicture: {
marginHorizontal: 8,

View file

@ -0,0 +1,123 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
View
} from 'react-native';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import AtMention from './at_mention';
import ChannelMention from './channel_mention';
import EmojiSuggestion from './emoji_suggestion';
export default class Autocomplete extends PureComponent {
static propTypes = {
onChangeText: PropTypes.func.isRequired,
rootId: PropTypes.string,
isSearch: PropTypes.bool,
theme: PropTypes.object.isRequired,
value: PropTypes.string
};
static defaultProps = {
isSearch: false
};
state = {
cursorPosition: 0,
atMentionCount: 0,
channelMentionCount: 0,
emojiCount: 0
};
handleSelectionChange = (event) => {
this.setState({
cursorPosition: event.nativeEvent.selection.end
});
};
handleAtMentionCountChange = (atMentionCount) => {
this.setState({atMentionCount});
};
handleChannelMentionCountChange = (channelMentionCount) => {
this.setState({channelMentionCount});
};
handleEmojiCountChange = (emojiCount) => {
this.setState({emojiCount});
};
render() {
const style = getStyleFromTheme(this.props.theme);
const containerStyle = [style.base];
if (this.props.isSearch) {
containerStyle.push(style.searchContainer);
} else {
containerStyle.push(style.container);
}
// We always need to render something, but we only draw the borders when we have results to show
if (this.state.atMentionCount + this.state.channelMentionCount + this.state.emojiCount > 0) {
containerStyle.push(style.borders);
}
return (
<View style={containerStyle}>
<AtMention
cursorPosition={this.state.cursorPosition}
onResultCountChange={this.handleAtMentionCountChange}
{...this.props}
/>
<ChannelMention
cursorPosition={this.state.cursorPosition}
onResultCountChange={this.handleChannelMentionCountChange}
{...this.props}
/>
<EmojiSuggestion
cursorPosition={this.state.cursorPosition}
onResultCountChange={this.handleEmojiCountChange}
{...this.props}
/>
</View>
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
base: {
left: 0,
overflow: 'hidden',
position: 'absolute',
right: 0
},
borders: {
borderWidth: 1,
borderColor: changeOpacity(theme.centerChannelColor, 0.2)
},
container: {
bottom: 0,
maxHeight: 200
},
searchContainer: {
elevation: 5,
flex: 1,
maxHeight: 250,
zIndex: 5,
...Platform.select({
android: {
top: 47
},
ios: {
top: 64
}
})
}
};
});

View file

@ -0,0 +1,32 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
export default class AutocompleteDivider extends PureComponent {
static propTypes = {
theme: PropTypes.object.isRequired
};
render() {
const {theme} = this.props;
const style = getStyleFromTheme(theme);
return (
<View style={style.divider}/>
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
divider: {
height: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2)
}
};
});

View file

@ -0,0 +1,16 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import AutocompleteDivider from './autocomplete_divider';
function mapStateToProps(state) {
return {
theme: getTheme(state)
};
}
export default connect(mapStateToProps)(AutocompleteDivider);

View file

@ -40,11 +40,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
paddingLeft: 8,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2)
},
sectionText: {
fontSize: 12,

View file

@ -8,6 +8,7 @@ import {SectionList} from 'react-native';
import {RequestStatus} from 'mattermost-redux/constants';
import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
import ChannelMentionItem from 'app/components/autocomplete/channel_mention_item';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@ -24,6 +25,7 @@ export default class ChannelMention extends PureComponent {
myChannels: PropTypes.array,
otherChannels: PropTypes.array,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
privateChannels: PropTypes.array,
publicChannels: PropTypes.array,
requestStatus: PropTypes.string.isRequired,
@ -53,6 +55,9 @@ export default class ChannelMention extends PureComponent {
mentionComplete: false,
sections: []
});
this.props.onResultCountChange(0);
return;
} else if (matchTerm === null) {
// if the terms did not change but is null then we don't need to do anything
@ -112,6 +117,8 @@ export default class ChannelMention extends PureComponent {
this.setState({
sections
});
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
}
}
@ -178,6 +185,7 @@ export default class ChannelMention extends PureComponent {
sections={sections}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
ItemSeparatorComponent={AutocompleteDivider}
initialNumToRender={10}
/>
);

View file

@ -8,7 +8,7 @@ import {
TouchableOpacity
} from 'react-native';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class ChannelMentionItem extends PureComponent {
static propTypes = {
@ -46,19 +46,14 @@ export default class ChannelMentionItem extends PureComponent {
);
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
row: {
padding: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
backgroundColor: theme.centerChannelBg
},
rowDisplayName: {
fontSize: 13,

View file

@ -10,8 +10,9 @@ import {
View
} from 'react-native';
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import Emoji from 'app/components/emoji';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
@ -25,6 +26,7 @@ export default class EmojiSuggestion extends Component {
emojis: PropTypes.array.isRequired,
theme: PropTypes.object.isRequired,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
rootId: PropTypes.string,
value: PropTypes.string
};
@ -49,6 +51,9 @@ export default class EmojiSuggestion extends Component {
matchTerm: null,
emojiComplete: false
});
this.props.onResultCountChange(0);
return;
}
@ -69,9 +74,11 @@ export default class EmojiSuggestion extends Component {
}
this.setState({
active: data.length,
active: data.length > 0,
dataSource: data
});
this.props.onResultCountChange(data.length);
}
completeSuggestion = (emoji) => {
@ -137,6 +144,7 @@ export default class EmojiSuggestion extends Component {
data={this.state.dataSource}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
ItemSeparatorComponent={AutocompleteDivider}
pageSize={10}
initialListSize={10}
/>
@ -162,13 +170,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
backgroundColor: theme.centerChannelBg,
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
backgroundColor: theme.centerChannelBg
}
};
});

View file

@ -1,89 +1,16 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
StyleSheet,
View
} from 'react-native';
import {connect} from 'react-redux';
import AtMention from './at_mention';
import ChannelMention from './channel_mention';
import EmojiSuggestion from './emoji_suggestion';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
export default class Autocomplete extends PureComponent {
static propTypes = {
onChangeText: PropTypes.func.isRequired,
rootId: PropTypes.string,
isSearch: PropTypes.bool,
value: PropTypes.string
import Autocomplete from './autocomplete';
function mapStateToProps(state) {
return {
theme: getTheme(state)
};
static defaultProps = {
isSearch: false
};
state = {
cursorPosition: 0
};
handleSelectionChange = (event) => {
this.setState({
cursorPosition: event.nativeEvent.selection.end
});
};
render() {
const searchContainer = this.props.isSearch ? style.searchContainer : null;
const container = this.props.isSearch ? null : style.container;
return (
<View style={searchContainer}>
<View style={container}>
<AtMention
cursorPosition={this.state.cursorPosition}
{...this.props}
/>
<ChannelMention
cursorPosition={this.state.cursorPosition}
{...this.props}
/>
<EmojiSuggestion
cursorPosition={this.state.cursorPosition}
{...this.props}
/>
</View>
</View>
);
}
}
const style = StyleSheet.create({
container: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
maxHeight: 200,
overflow: 'hidden'
},
searchContainer: {
elevation: 5,
flex: 1,
left: 0,
maxHeight: 250,
overflow: 'hidden',
position: 'absolute',
right: 0,
zIndex: 5,
...Platform.select({
android: {
top: 47
},
ios: {
top: 64
}
})
}
});
export default connect(mapStateToProps, null, null, {withRef: true})(Autocomplete);

View file

@ -70,13 +70,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
paddingVertical: 8,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.centerChannelBg,
borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
borderLeftWidth: 1,
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
borderRightWidth: 1,
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
backgroundColor: theme.centerChannelBg
},
rowPicture: {
marginHorizontal: 8,

View file

@ -199,7 +199,7 @@ class PostTextbox extends PureComponent {
this.props.actions.handlePostDraftSelectionChanged(this.props.channelId, cursorPosition);
}
this.autocomplete.handleSelectionChange(event);
this.autocomplete.getWrappedInstance().handleSelectionChange(event);
};
handleSendMessage = () => {

View file

@ -150,7 +150,7 @@ class Search extends PureComponent {
handleSelectionChange = (event) => {
if (this.autocomplete) {
this.autocomplete.handleSelectionChange(event);
this.autocomplete.getWrappedInstance().handleSelectionChange(event);
}
};