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:
parent
e98e22600e
commit
70a1a92d06
13 changed files with 216 additions and 120 deletions
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
123
app/components/autocomplete/autocomplete.js
Normal file
123
app/components/autocomplete/autocomplete.js
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
});
|
||||
16
app/components/autocomplete/autocomplete_divider/index.js
Normal file
16
app/components/autocomplete/autocomplete_divider/index.js
Normal 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);
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ class Search extends PureComponent {
|
|||
|
||||
handleSelectionChange = (event) => {
|
||||
if (this.autocomplete) {
|
||||
this.autocomplete.handleSelectionChange(event);
|
||||
this.autocomplete.getWrappedInstance().handleSelectionChange(event);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue