// 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, }, });