// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {intlShape} from 'react-intl';
import {
Animated,
InteractionManager,
Keyboard,
TouchableWithoutFeedback,
StyleSheet,
View,
Platform,
} from 'react-native';
import {SearchBar} from 'react-native-elements';
import CompassIcon from '@components/compass_icon';
import {memoizeResult} from '@mm-redux/utils/helpers';
const LEFT_COMPONENT_INITIAL_POSITION = Platform.OS === 'ios' ? 7 : 0;
export default class Search extends PureComponent {
static propTypes = {
testID: PropTypes.string,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
onSearchButtonPress: PropTypes.func,
onChangeText: PropTypes.func,
onCancelButtonPress: PropTypes.func,
onSelectionChange: PropTypes.func,
backgroundColor: PropTypes.string,
placeholderTextColor: PropTypes.string,
titleCancelColor: PropTypes.string,
tintColorSearch: PropTypes.string,
tintColorDelete: PropTypes.string,
selectionColor: PropTypes.string,
inputStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
cancelButtonStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
autoFocus: PropTypes.bool,
placeholder: PropTypes.string,
cancelTitle: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object,
]),
returnKeyType: PropTypes.string,
keyboardType: PropTypes.string,
autoCapitalize: PropTypes.string,
inputHeight: PropTypes.number,
editable: PropTypes.bool,
blurOnSubmit: PropTypes.bool,
keyboardShouldPersist: PropTypes.bool,
value: PropTypes.string,
keyboardAppearance: PropTypes.string,
showArrow: PropTypes.bool,
searchBarRightMargin: PropTypes.number,
leftComponent: PropTypes.element,
searchIconSize: PropTypes.number,
backArrowSize: PropTypes.number,
deleteIconSize: PropTypes.number,
showCancel: PropTypes.bool,
containerHeight: PropTypes.number,
};
static contextTypes = {
intl: intlShape,
};
static defaultProps = {
onSelectionChange: () => true,
onBlur: () => true,
editable: true,
blurOnSubmit: false,
keyboardShouldPersist: false,
placeholderTextColor: 'grey',
value: '',
showArrow: false,
showCancel: true,
searchIconSize: 24,
backArrowSize: 24,
deleteIconSize: 20,
searchBarRightMargin: 0,
returnKeyType: 'search',
keyboardType: 'default',
containerHeight: 40,
};
constructor(props) {
super(props);
this.state = {
leftComponentWidth: 0,
};
this.leftComponentAnimated = new Animated.Value(LEFT_COMPONENT_INITIAL_POSITION);
this.searchContainerAnimated = new Animated.Value(0);
}
setSearchContainerRef = (ref) => {
this.searchContainerRef = ref;
}
setInputKeywordRef = (ref) => {
this.inputKeywordRef = ref;
}
blur = () => {
this.inputKeywordRef.blur();
};
focus = () => {
this.inputKeywordRef.focus();
};
onBlur = async () => {
this.props.onBlur();
if (this.props.leftComponent) {
await this.collapseAnimation();
}
};
onLeftComponentLayout = (event) => {
const leftComponentWidth = event.nativeEvent.layout.width;
this.setState({leftComponentWidth});
};
onSearch = async () => {
if (this.props.keyboardShouldPersist === false) {
await Keyboard.dismiss();
}
this.props.onSearchButtonPress(this.props.value);
};
onChangeText = (text) => {
if (this.props.onChangeText) {
this.props.onChangeText(text);
}
};
onFocus = () => {
InteractionManager.runAfterInteractions(async () => {
if (this.props.onFocus) {
this.props.onFocus();
}
if (this.props.leftComponent) {
await this.expandAnimation();
}
});
};
onClear = () => {
this.focus();
this.props.onChangeText('', true);
};
onCancel = () => {
Keyboard.dismiss();
InteractionManager.runAfterInteractions(() => {
if (this.props.onCancelButtonPress) {
this.props.onCancelButtonPress();
}
});
};
onSelectionChange = (event) => {
this.props.onSelectionChange(event);
};
expandAnimation = () => {
return new Promise((resolve) => {
Animated.parallel([
Animated.timing(
this.leftComponentAnimated,
{
toValue: -115,
duration: 200,
},
),
Animated.timing(
this.searchContainerAnimated,
{
toValue: this.state.leftComponentWidth * -1,
duration: 200,
},
),
]).start(resolve);
});
}
collapseAnimation = () => {
return new Promise((resolve) => {
Animated.parallel([
Animated.timing(
this.leftComponentAnimated,
{
toValue: LEFT_COMPONENT_INITIAL_POSITION,
duration: 200,
},
),
Animated.timing(
this.searchContainerAnimated,
{
toValue: 0,
duration: 200,
},
),
]).start(resolve);
});
}
render() {
const {testID, backgroundColor, inputHeight, inputStyle, placeholderTextColor, tintColorSearch, cancelButtonStyle, tintColorDelete, titleCancelColor, searchBarRightMargin, containerHeight} = this.props;
const searchClearButtonTestID = `${testID}.search.clear.button`;
const searchCancelButtonTestID = `${testID}.search.cancel.button`;
const searchInputTestID = `${testID}.search.input`;
const searchBarStyle = getSearchBarStyle(
backgroundColor,
cancelButtonStyle,
inputHeight,
inputStyle,
placeholderTextColor,
tintColorDelete,
tintColorSearch,
titleCancelColor,
searchBarRightMargin,
containerHeight,
);
const {intl} = this.context;
let clearIcon = null;
let searchIcon = null;
let cancelIcon = null;
if (Platform.OS === 'ios') {
clearIcon = (
);
searchIcon = (
);
} else {
searchIcon = this.props.showArrow ?
(
) :
(
);
// Making sure the icon won't change depending on whether the input is in focus on Android devices
cancelIcon = (
);
clearIcon = (
);
}
return (
{((this.props.leftComponent) ?
{this.props.leftComponent}
:
null
)}
);
}
}
const getSearchBarStyle = memoizeResult((
backgroundColor,
cancelButtonStyle,
inputHeight,
inputStyle,
placeholderTextColor,
tintColorDelete,
tintColorSearch,
titleCancelColor,
searchBarRightMargin,
containerHeight,
) => ({
cancelButtonText: {
...cancelButtonStyle,
color: titleCancelColor,
},
container: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
height: containerHeight,
overflow: 'hidden',
},
clearIconColorIos: tintColorDelete || styles.defaultColor.color,
clearIconColorAndroid: titleCancelColor || placeholderTextColor,
inputStyle: {
...inputStyle,
backgroundColor: 'transparent',
height: inputHeight,
},
inputContainer: {
backgroundColor: inputStyle.backgroundColor,
height: inputHeight,
},
searchBarWrapper: {
marginRight: searchBarRightMargin,
height: Platform.select({
ios: inputHeight || containerHeight - 10,
android: inputHeight,
}),
},
searchBarContainer: {
backgroundColor,
},
searchIcon: {
color: tintColorSearch || placeholderTextColor,
top: 8,
},
searchIconColor: tintColorSearch || placeholderTextColor,
}));
const styles = StyleSheet.create({
defaultColor: {
color: 'grey',
},
fullWidth: {
flex: 1,
},
inputContainer: {
borderRadius: Platform.select({
ios: 2,
android: 0,
}),
},
inputMargin: {
marginLeft: 4,
paddingTop: 0,
marginTop: Platform.select({
ios: 0,
android: 8,
}),
},
leftIcon: {
marginLeft: 4,
width: 30,
},
searchContainer: {
paddingTop: 0,
paddingBottom: 0,
},
text: {
fontSize: Platform.select({
ios: 14,
android: 15,
}),
color: '#fff',
},
leftComponent: {
position: 'relative',
marginLeft: 2,
},
});