MM-12189 Set autocomplete max height based off available space (#2218)
* MM-12189 Set autocomplete max height based off available space * Have each autocomplete type determine its own max height since DateSuggestion needs more space * Remove unused props and ref
This commit is contained in:
parent
dd506d4c9f
commit
4e251d392a
9 changed files with 85 additions and 61 deletions
|
|
@ -26,8 +26,8 @@ export default class AtMention extends PureComponent {
|
|||
defaultChannel: PropTypes.object,
|
||||
inChannel: PropTypes.array,
|
||||
isSearch: PropTypes.bool,
|
||||
listHeight: PropTypes.number,
|
||||
matchTerm: PropTypes.string,
|
||||
maxListHeight: PropTypes.number,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
outChannel: PropTypes.array,
|
||||
|
|
@ -204,7 +204,7 @@ export default class AtMention extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {isSearch, listHeight, theme} = this.props;
|
||||
const {maxListHeight, theme} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
|
|
@ -219,7 +219,7 @@ export default class AtMention extends PureComponent {
|
|||
<SectionList
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, isSearch ? [style.search, {height: listHeight}] : null]}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
|
|
@ -235,8 +235,5 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default class Autocomplete extends PureComponent {
|
|||
cursorPosition: PropTypes.number.isRequired,
|
||||
deviceHeight: PropTypes.number,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
maxHeight: PropTypes.number,
|
||||
rootId: PropTypes.string,
|
||||
isSearch: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
|
@ -84,12 +85,21 @@ export default class Autocomplete extends PureComponent {
|
|||
this.setState({keyboardOffset: 0});
|
||||
};
|
||||
|
||||
listHeight() {
|
||||
let offset = Platform.select({ios: 65, android: 75});
|
||||
if (DeviceInfo.getModel().includes('iPhone X')) {
|
||||
offset = 90;
|
||||
maxListHeight() {
|
||||
let maxHeight;
|
||||
if (this.props.maxHeight) {
|
||||
maxHeight = this.props.maxHeight;
|
||||
} else {
|
||||
// List is expanding downwards, likely from the search box
|
||||
let offset = Platform.select({ios: 65, android: 75});
|
||||
if (DeviceInfo.getModel().includes('iPhone X')) {
|
||||
offset = 90;
|
||||
}
|
||||
|
||||
maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
}
|
||||
return this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -113,26 +123,29 @@ export default class Autocomplete extends PureComponent {
|
|||
containerStyle.push(style.borders);
|
||||
}
|
||||
}
|
||||
const listHeight = this.listHeight();
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
|
||||
return (
|
||||
<View style={wrapperStyle}>
|
||||
<View style={containerStyle}>
|
||||
<AtMention
|
||||
listHeight={listHeight}
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleAtMentionCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<ChannelMention
|
||||
listHeight={listHeight}
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleChannelMentionCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<EmojiSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleEmojiCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
<SlashSuggestion
|
||||
maxListHeight={maxListHeight}
|
||||
onResultCountChange={this.handleCommandCountChange}
|
||||
{...this.props}
|
||||
/>
|
||||
|
|
@ -167,7 +180,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
container: {
|
||||
bottom: 0,
|
||||
maxHeight: 200,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ export default class ChannelMention extends PureComponent {
|
|||
currentTeamId: PropTypes.string.isRequired,
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
listHeight: PropTypes.number,
|
||||
matchTerm: PropTypes.string,
|
||||
maxListHeight: PropTypes.number,
|
||||
myChannels: PropTypes.array,
|
||||
myMembers: PropTypes.object,
|
||||
otherChannels: PropTypes.array,
|
||||
|
|
@ -194,7 +194,7 @@ export default class ChannelMention extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {isSearch, listHeight, theme} = this.props;
|
||||
const {maxListHeight, theme} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
|
|
@ -209,7 +209,7 @@ export default class ChannelMention extends PureComponent {
|
|||
<SectionList
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.keyExtractor}
|
||||
style={[style.listView, isSearch ? [style.search, {height: listHeight}] : null]}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
|
|
@ -225,8 +225,5 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {changeOpacity} from 'app/utils/theme';
|
|||
export default class DateSuggestion extends PureComponent {
|
||||
static propTypes = {
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
listHeight: PropTypes.number,
|
||||
locale: PropTypes.string.isRequired,
|
||||
matchTerm: PropTypes.string,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export default class EmojiSuggestion extends Component {
|
|||
emojis: PropTypes.array.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
fuse: PropTypes.object.isRequired,
|
||||
maxListHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
|
|
@ -171,18 +172,20 @@ export default class EmojiSuggestion extends Component {
|
|||
getItemLayout = ({index}) => ({length: 40, offset: 40 * index, index})
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme} = this.props;
|
||||
|
||||
if (!this.state.active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps='always'
|
||||
style={style.listView}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export default class SlashSuggestion extends Component {
|
|||
commands: PropTypes.array,
|
||||
commandsRequest: PropTypes.object.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
maxListHeight: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
|
|
@ -133,18 +134,20 @@ export default class SlashSuggestion extends Component {
|
|||
)
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme} = this.props;
|
||||
|
||||
if (!this.state.active) {
|
||||
// If we are not in an active state return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps='always'
|
||||
style={style.listView}
|
||||
style={[style.listView, {maxHeight: maxListHeight}]}
|
||||
extraData={this.state}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,9 @@ export default class FileUploadPreview extends PureComponent {
|
|||
static propTypes = {
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelIsLoading: PropTypes.bool,
|
||||
createPostRequestStatus: PropTypes.string.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
files: PropTypes.array.isRequired,
|
||||
filesUploadingForCurrentChannel: PropTypes.bool.isRequired,
|
||||
inputHeight: PropTypes.number.isRequired,
|
||||
rootId: PropTypes.string,
|
||||
showFileMaxWarning: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ function mapStateToProps(state, ownProps) {
|
|||
|
||||
return {
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
createPostRequestStatus: state.requests.posts.createPost.status,
|
||||
deviceHeight,
|
||||
filesUploadingForCurrentChannel: checkForFileUploadingInChannel(state, ownProps.channelId, ownProps.rootId),
|
||||
theme: getTheme(state),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import FormattedText from 'app/components/formatted_text';
|
|||
|
||||
import Typing from './components/typing';
|
||||
|
||||
const AUTOCOMPLETE_MARGIN = 20;
|
||||
const AUTOCOMPLETE_MAX_HEIGHT = 200;
|
||||
|
||||
let PaperPlane = null;
|
||||
|
||||
export default class PostTextbox extends PureComponent {
|
||||
|
|
@ -76,6 +79,7 @@ export default class PostTextbox extends PureComponent {
|
|||
contentHeight: INITIAL_HEIGHT,
|
||||
cursorPosition: 0,
|
||||
keyboardType: 'default',
|
||||
top: 0,
|
||||
value: props.value,
|
||||
showFileMaxWarning: false,
|
||||
};
|
||||
|
|
@ -105,10 +109,6 @@ export default class PostTextbox extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
attachAutocomplete = (c) => {
|
||||
this.autocomplete = c;
|
||||
};
|
||||
|
||||
blur = () => {
|
||||
if (this.refs.input) {
|
||||
this.refs.input.blur();
|
||||
|
|
@ -487,6 +487,12 @@ export default class PostTextbox extends PureComponent {
|
|||
</View>);
|
||||
};
|
||||
|
||||
handleLayout = (e) => {
|
||||
this.setState({
|
||||
top: e.nativeEvent.layout.y,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl} = this.context;
|
||||
const {
|
||||
|
|
@ -514,7 +520,13 @@ export default class PostTextbox extends PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const {contentHeight, cursorPosition, showFileMaxWarning, value} = this.state;
|
||||
const {
|
||||
contentHeight,
|
||||
cursorPosition,
|
||||
showFileMaxWarning,
|
||||
top,
|
||||
value,
|
||||
} = this.state;
|
||||
|
||||
const textInputHeight = Math.min(contentHeight, MAX_CONTENT_HEIGHT);
|
||||
const textValue = channelIsLoading ? '' : value;
|
||||
|
|
@ -547,48 +559,52 @@ export default class PostTextbox extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<React.Fragment>
|
||||
<Typing/>
|
||||
<FileUploadPreview
|
||||
channelId={channelId}
|
||||
files={files}
|
||||
inputHeight={textInputHeight}
|
||||
rootId={rootId}
|
||||
showFileMaxWarning={showFileMaxWarning}
|
||||
/>
|
||||
<Autocomplete
|
||||
ref={this.attachAutocomplete}
|
||||
cursorPosition={cursorPosition}
|
||||
maxHeight={Math.min(top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleTextChange}
|
||||
value={this.state.value}
|
||||
rootId={rootId}
|
||||
/>
|
||||
{!channelIsArchived && <View style={style.inputWrapper}>
|
||||
{!channelIsReadOnly && attachmentButton}
|
||||
<View style={[inputContainerStyle, (channelIsReadOnly && {marginLeft: 10})]}>
|
||||
<TextInput
|
||||
ref='input'
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder)}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[style.input, Platform.OS === 'android' ? {height: textInputHeight} : {maxHeight: MAX_CONTENT_HEIGHT}]}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
{!channelIsArchived && (
|
||||
<View
|
||||
style={style.inputWrapper}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{!channelIsReadOnly && attachmentButton}
|
||||
<View style={[inputContainerStyle, (channelIsReadOnly && {marginLeft: 10})]}>
|
||||
<TextInput
|
||||
ref='input'
|
||||
value={textValue}
|
||||
onChangeText={this.handleTextChange}
|
||||
onSelectionChange={this.handlePostDraftSelectionChanged}
|
||||
placeholder={intl.formatMessage(placeholder)}
|
||||
placeholderTextColor={changeOpacity('#000', 0.5)}
|
||||
multiline={true}
|
||||
numberOfLines={5}
|
||||
blurOnSubmit={false}
|
||||
underlineColorAndroid='transparent'
|
||||
style={[style.input, Platform.OS === 'android' ? {height: textInputHeight} : {maxHeight: MAX_CONTENT_HEIGHT}]}
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
/>
|
||||
{this.renderSendButton()}
|
||||
</View>
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
{channelIsArchived && this.archivedView(theme, style)}
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue