Skip to content

Commit

Permalink
Platform specific SearchBar (react-native-elements#837) 🚀
Browse files Browse the repository at this point in the history
* Add SearchBar wrapper

* Split SearchBar iOS and Android into separate files

* Add back default SearchBar

* Update root import

* Add back Input right/left icon

* Rename loadingIcon to loadingProps

* Specify `platform` PropType

* Add a fallback

* Move tests to `searchbar`

* Remove old Search component

* Fix loading props spreading overriding whole style

* Remove useless things

* Add tests for SearchBar

* Generate snapshots

* Add more tests

* Update tests

* Update documentation

* Fix typo
  • Loading branch information
xavier-villelegier committed Jan 28, 2018
1 parent 52e7117 commit 7ee34bb
Show file tree
Hide file tree
Showing 15 changed files with 1,562 additions and 41 deletions.
30 changes: 28 additions & 2 deletions docs/API/searchbar.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
## Default SearchBar
<img src="https://i.imgur.com/mvPgPfg.png" width="300" >

## Platform specific SearchBar

**iOS**

<img src="https://user-images.githubusercontent.com/17592779/31585176-b124cdae-b1bd-11e7-809f-ba966cebc663.gif" width="300">

**Android**

<img src="https://user-images.githubusercontent.com/17592779/31586716-f6e8ff9c-b1d4-11e7-918f-2a7e11d51b08.gif" width="300">


```js
import { SearchBar } from 'react-native-elements'

Expand Down Expand Up @@ -33,6 +45,17 @@ import { SearchBar } from 'react-native-elements'
icon={{ type: 'font-awesome', name: 'search' }}
placeholder='Type Here...' />

<SearchBar
showLoading
platform="ios"
cancelButtonTitle="Cancel"
placeholder='Search' />

<SearchBar
showLoading
platform="android"
placeholder='Search' />

```

#### SearchBar props
Expand All @@ -41,19 +64,22 @@ import { SearchBar } from 'react-native-elements'

| prop | default | type | description |
| ---- | ---- | ----| ---- |
|platform|"default"|string| choose the look and feel of the search bar. One of "default", "ios", "android"|
|cancelButtonTitle|"Cancel"|string| **(iOS only)** title of the cancel button on the right side|
| containerStyle | inherited styling | object (style) | style the container of the TextInput |
| inputStyle | inherited styling | object (style) | style the TextInput |
| icon | { type: 'material', color: '#86939e', name: 'search' } | object {type (string), name (string), color (string), style (object)} | specify type, name, color, and styling of the icon |
| noIcon | false | boolean | remove icon from textinput |
| lightTheme | false | boolean | change theme to light theme |
| round | false | boolean | change TextInput styling to rounded corners |
| underlineColorAndroid | transparent | string (color) | specify other than the default transparent underline color |
| loadingIcon | { color: '#86939e' } | object {color (string), style (object)} | specify color, styling of the loading ActivityIndicator effect |
| showLoadingIcon | false | boolean | show the loading ActivityIndicator effect |
| loadingProps | { } | object | props passed to ActivityIndicator |
| showLoading | false | boolean | show the loading ActivityIndicator effect |
| placeholder | '' | string | set the placeholder text |
| placeholderTextColor | '#86939e' | string | set the color of the placeholder text |
| onChangeText | none | function | method to fire when text is changed |
| onClearText | none | function | method to fire when text is cleared |
|onCancel| null | function | callback fired when pressing the cancel button (iOS) or the back icon (Android)|
| clearIcon | { color: '#86939e', name: 'search' } | object {name (string), color (string), style (object)} | specify color, styling, or another [Material Icon Name](https://design.google.com/icons/) (Note: pressing on this icon clears text inside the searchbar) |

##### Interaction methods
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SocialIcon from './social/SocialIcon';
import Overlay from './overlay/Overlay';

// Utilities
import SearchBar from './search/Search';
import SearchBar from './searchbar/SearchBar';
import Badge from './badge/badge';
import CheckBox from './checkbox/CheckBox';
import Divider from './divider/Divider';
Expand Down
43 changes: 29 additions & 14 deletions src/input/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import ViewPropTypes from '../config/ViewPropTypes';
const SCREEN_WIDTH = Dimensions.get('window').width;

class Input extends Component {

componentWillMount() {
this.shake = this.shake.bind(this);
this.shakeAnimationValue = new Animated.Value(0);
Expand Down Expand Up @@ -51,8 +50,10 @@ class Input extends Component {
render() {
const {
containerStyle,
icon,
iconContainerStyle,
leftIcon,
leftIconContainerStyle,
rightIcon,
rightIconContainerStyle,
inputStyle,
displayError,
errorStyle,
Expand All @@ -74,12 +75,17 @@ class Input extends Component {
{ transform: [{ translateX }] },
]}
>
{icon &&
{leftIcon && (
<View
style={[styles.iconContainer, { height: 40 }, iconContainerStyle]}
style={[
styles.iconContainer,
{ marginLeft: 15 },
leftIconContainerStyle,
]}
>
{icon}
</View>}
{leftIcon}
</View>
)}
<TextInput
ref={input => (this.input = input)}
underlineColorAndroid="transparent"
Expand All @@ -90,11 +96,17 @@ class Input extends Component {
]}
{...attributes}
/>
{rightIcon && (
<View style={[styles.iconContainer, rightIconContainerStyle]}>
{rightIcon}
</View>
)}
</Animated.View>
{displayError &&
{displayError && (
<Text style={[styles.error, errorStyle && errorStyle]}>
{errorMessage || 'Error!'}
</Text>}
</Text>
)}
</View>
);
}
Expand All @@ -103,14 +115,17 @@ class Input extends Component {
Input.propTypes = {
containerStyle: ViewPropTypes.style,

icon: PropTypes.object,
iconContainerStyle: ViewPropTypes.style,
leftIcon: PropTypes.object,
leftIconContainerStyle: ViewPropTypes.style,

rightIcon: PropTypes.object,
rightIconContainerStyle: ViewPropTypes.style,

inputStyle: PropTypes.object,
inputStyle: Text.propTypes.style,

shake: PropTypes.any,
displayError: PropTypes.bool,
errorStyle: PropTypes.object,
errorStyle: Text.propTypes.style,
errorMessage: PropTypes.string,
};

Expand All @@ -122,9 +137,9 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
iconContainer: {
height: 40,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 15,
},
input: {
alignSelf: 'center',
Expand Down
170 changes: 170 additions & 0 deletions src/searchbar/SearchBar-android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
Dimensions,
StyleSheet,
View,
ActivityIndicator,
} from 'react-native';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';

import Input from '../input/Input';

const SCREEN_WIDTH = Dimensions.get('window').width;
const ANDROID_GRAY = 'rgba(0, 0, 0, 0.54)';

class SearchBar extends Component {
focus = () => {
this.input.focus();
};

blur = () => {
this.input.blur();
};

clear = () => {
this.input.clear();
this.onChangeText('');
this.props.onClearText && this.props.onClearText();
};

cancel = () => {
this.blur();
this.props.onCancel && this.props.onCancel();
};

onFocus = () => {
this.props.onFocus && this.props.onFocus();
this.setState({ hasFocus: true });
};

onBlur = () => {
this.props.onBlur && this.props.onBlur();
this.setState({ hasFocus: false });
};

onChangeText = text => {
this.props.onChangeText && this.props.onChangeText(text);
this.setState({ isEmpty: text === '' });
};

constructor(props) {
super(props);
this.state = {
hasFocus: false,
isEmpty: true,
};
}

render() {
const {
clearIcon,
containerStyle,
leftIcon,
leftIconContainerStyle,
rightIconContainerStyle,
inputStyle,
noIcon,
showLoading,
loadingProps,
...attributes
} = this.props;
const { hasFocus, isEmpty } = this.state;
const { style: loadingStyle, ...otherLoadingProps } = loadingProps;
const searchIcon = (
<MaterialIcon
size={25}
color={ANDROID_GRAY}
name={hasFocus ? 'arrow-left' : 'magnify'}
onPress={hasFocus && this.cancel}
/>
);
return (
<View style={styles.container}>
<Input
onFocus={this.onFocus}
onBlur={this.onBlur}
onChangeText={this.onChangeText}
ref={input => (this.input = input)}
inputStyle={[styles.input, inputStyle]}
containerStyle={[styles.inputContainer, containerStyle]}
leftIcon={noIcon ? undefined : leftIcon ? leftIcon : searchIcon}
leftIconContainerStyle={[
styles.leftIconContainerStyle,
leftIconContainerStyle,
]}
rightIcon={
<View style={{ flexDirection: 'row' }}>
{showLoading && (
<ActivityIndicator
style={[
clearIcon && !isEmpty && { marginRight: 10 },
loadingStyle,
]}
{...otherLoadingProps}
/>
)}
{clearIcon &&
!isEmpty && (
<MaterialIcon
name={'close'}
size={25}
color={ANDROID_GRAY}
onPress={() => this.clear()}
/>
)}
</View>
}
rightIconContainerStyle={[
styles.rightIconContainerStyle,
rightIconContainerStyle,
]}
{...attributes}
/>
</View>
);
}
}

SearchBar.propTypes = {
clearIcon: PropTypes.bool,
loadingProps: PropTypes.object,
noIcon: PropTypes.bool,
showLoading: PropTypes.bool,
onClearText: PropTypes.func,
onCancel: PropTypes.func,
};

SearchBar.defaultProps = {
clearIcon: true,
loadingProps: {},
noIcon: false,
showLoading: false,
onClearText: null,
onCancel: null,
};

const styles = StyleSheet.create({
container: {
width: SCREEN_WIDTH,
paddingTop: 8,
paddingBottom: 8,
},
input: {
flex: 1,
marginLeft: 24,
marginRight: 8,
},
inputContainer: {
borderBottomWidth: 0,
width: SCREEN_WIDTH,
},
rightIconContainerStyle: {
marginRight: 8,
},
leftIconContainerStyle: {
marginLeft: 8,
},
});

export default SearchBar;

0 comments on commit 7ee34bb

Please sign in to comment.