├── .expo ├── packager-info.json └── settings.json ├── .gitignore ├── .npmignore ├── README.md ├── example ├── .babelrc ├── .gitignore ├── .watchmanconfig ├── App.js ├── app.json ├── assets │ └── icons │ │ ├── app.png │ │ └── loading.png ├── package-lock.json └── package.json ├── index.js ├── license.md ├── package.json ├── react-native-progressive-input-1.1.0.tgz └── screenshot.gif /.expo/packager-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "expoServerPort": null, 3 | "expoServerNgrokUrl": null, 4 | "packagerNgrokUrl": null, 5 | "ngrokPid": null, 6 | "packagerPort": null, 7 | "packagerPid": null 8 | } -------------------------------------------------------------------------------- /.expo/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostType": "tunnel", 3 | "lanType": "ip", 4 | "dev": true, 5 | "strict": false, 6 | "minify": false, 7 | "urlType": "exp", 8 | "urlRandomness": null 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn-error.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | screenshot.gif 2 | example/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Progressive Input 3 | ![Screenshot](https://github.com/khaiql/react-native-progressive-input/blob/master/screenshot.gif) 4 | 5 | [Progressive Input](https://github.com/khaiql/react-native-progressive-input) is used as a part of autocomplete solution. The control has clear button to clear text and activity indicator to show that background job is being performed. 6 | 7 | ## Getting started 8 | 9 | `$ npm install react-native-progressive-input --save` 10 | 11 | If you dont own the dependency `react-native-vector-icons`, please make sure you also run: 12 | 13 | * `npm install react-native-vector-icons --save` 14 | 15 | * `react-native link` 16 | 17 | on the terminal. This will add some necessary fonts and Info.plist updates on your xcode project. 18 | 19 | ## Usage 20 | ```javascript 21 | import ProgressiveInput from 'react-native-progressive-input'; 22 | 23 | class Screen extends Component { 24 | constructor(props) { 25 | super(props); 26 | 27 | this.state = { 28 | value: '', 29 | isLoading: false 30 | }; 31 | } 32 | 33 | _onChangeText(text) { 34 | this.setState({isLoading: true, value: text}); 35 | 36 | fetch("YOUR_URL_FOR_GETTING_SUGGESTION") 37 | .then((result) => { 38 | // Process list of suggestions 39 | 40 | this.setState({isLoading: false}); 41 | }); 42 | } 43 | 44 | render() { 45 | 50 | } 51 | } 52 | 53 | export default Screen; 54 | ``` 55 | 56 | ## Properties 57 | 58 | | Name | Type | 59 | |------------------------|-----------------------------------| 60 | | autoCorrect | PropTypes.bool | 61 | | keyboardType | TextInput.propTypes.keyboardType | 62 | | multiline | PropTypes.bool | 63 | | placeholderTextColor | PropTypes.string | 64 | | returnKeyType | TextInput.propTypes.returnKeyType | 65 | | selectTextOnFocus | PropTypes.bool | 66 | | placeholder | PropTypes.string | 67 | | editable | PropTypes.bool | 68 | | autoCapitalize | PropTypes.bool | 69 | | maxLength | PropTypes.number | 70 | | multiline | PropTypes.bool | 71 | | onEndEditing | PropTypes.func | 72 | | onChange | PropTypes.func | 73 | | value | PropTypes.string | 74 | | isLoading | PropTypes.bool | 75 | | textInputStyle | TextInput.propTypes.style | 76 | | clearButtonIcon | PropTypes.string | 77 | | clearButtonColor | PropTypes.string | 78 | | clearButtonSize | PropTypes.number | 79 | | clearButtonStyle | PropTypes.object | 80 | | activityIndicatorStyle | ActivityIndicator.propTypes.style | 81 | | onBlur | PropTypes.func | 82 | | onChangeText | PropTypes.func | 83 | | onFocus | PropTypes.func | 84 | | onInputCleared | PropTypes.func | 85 | 86 | ## Author 87 | - Khai Le (Scott) 88 | - Blog: [lequangkhai.wordpress.com](https://lequangkhai.wordpress.com) 89 | 90 | ## License 91 | MIT 92 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | import Expo, { Components } from 'expo'; 2 | import React from 'react'; 3 | import ProgressiveInput from 'react-native-progressive-input'; 4 | import { 5 | StyleSheet, 6 | Text, 7 | View, 8 | ListView, 9 | TouchableOpacity, 10 | } from 'react-native'; 11 | 12 | const GOOGLE_API_KEY = 'AIzaSyB7-8qph-zszuxivIm7cwT5b37D22bm1A4'; 13 | const ds = new ListView.DataSource({ 14 | rowHasChanged: (r1, r2) => r1.id !== r2.id, 15 | }); 16 | const latitudeDelta = 0.0922; 17 | const longitudeDelta = 0.0421; 18 | 19 | export default class App extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | isLoading: false, 24 | dataSource: ds.cloneWithRows([]), 25 | value: '', 26 | }; 27 | this.searchLocation = this.searchLocation.bind(this); 28 | this.renderRow = this.renderRow.bind(this); 29 | this.renderSeparator = this.renderSeparator.bind(this); 30 | this.onInputCleared = this.onInputCleared.bind(this); 31 | } 32 | 33 | async searchLocation(query) { 34 | const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${ 35 | GOOGLE_API_KEY 36 | }&input=${query}`; 37 | this.setState({ isLoading: true, value: query }); 38 | const response = await fetch(url); 39 | const jsonResponse = await response.json(); 40 | this.setState({ 41 | isLoading: false, 42 | dataSource: ds.cloneWithRows(jsonResponse.predictions), 43 | }); 44 | } 45 | 46 | renderRow(prediction) { 47 | return ( 48 | this.onListItemClicked(prediction)} 50 | style={styles.listItem} 51 | > 52 | {prediction.description} 53 | 54 | ); 55 | } 56 | 57 | renderSeparator() { 58 | return ; 59 | } 60 | 61 | onInputCleared() { 62 | this.setState({ 63 | value: '', 64 | isLoading: false, 65 | dataSource: ds.cloneWithRows([]), 66 | }); 67 | } 68 | 69 | async onListItemClicked(prediction) { 70 | this.setState({ 71 | value: prediction.description, 72 | dataSource: ds.cloneWithRows([]), 73 | isLoading: true, 74 | }); 75 | const url = `https://maps.googleapis.com/maps/api/place/details/json?placeid=${ 76 | prediction.place_id 77 | }&key=${GOOGLE_API_KEY}`; 78 | const response = await fetch(url); 79 | const jsonResponse = await response.json(); 80 | const { lat, lng } = jsonResponse.result.geometry.location; 81 | this.mapView.animateToRegion({ 82 | longitude: lng, 83 | latitude: lat, 84 | latitudeDelta, 85 | longitudeDelta, 86 | }); 87 | this.setState({ isLoading: false }); 88 | } 89 | 90 | render() { 91 | return ( 92 | 93 | (this.mapView = m)} 96 | initialRegion={{ 97 | latitude: 37.78825, 98 | longitude: -122.4324, 99 | latitudeDelta, 100 | longitudeDelta, 101 | }} 102 | /> 103 | 110 | 111 | 118 | 119 | 120 | ); 121 | } 122 | } 123 | 124 | const styles = StyleSheet.create({ 125 | container: { 126 | flex: 1, 127 | backgroundColor: 'grey', 128 | flexDirection: 'column', 129 | justifyContent: 'flex-start', 130 | }, 131 | map: { 132 | position: 'absolute', 133 | top: 0, 134 | right: 0, 135 | left: 0, 136 | bottom: 0, 137 | }, 138 | progressiveInput: { 139 | marginTop: 20, 140 | marginLeft: 10, 141 | marginRight: 10, 142 | }, 143 | listViewContainer: { 144 | flex: 0, 145 | }, 146 | listView: { 147 | backgroundColor: 'white', 148 | margin: 10, 149 | }, 150 | listItem: { 151 | padding: 10, 152 | }, 153 | listItemSeparator: { 154 | borderWidth: 0.5, 155 | borderColor: 'lightgrey', 156 | }, 157 | }); 158 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Progressive Input Example", 4 | "description": "A simple demo for 'react-native-progressive-input'", 5 | "slug": "progressive-input-example", 6 | "privacy": "public", 7 | "sdkVersion": "23.0.0", 8 | "version": "1.0.0", 9 | "orientation": "portrait", 10 | "primaryColor": "#cccccc", 11 | "icon": "./assets/icons/app.png", 12 | "loading": { 13 | "icon": "./assets/icons/loading.png", 14 | "hideExponentText": false 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/assets/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khaiql/react-native-progressive-input/14377e5a54f209c48c06f3d8a76a162634d0a1b8/example/assets/icons/app.png -------------------------------------------------------------------------------- /example/assets/icons/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khaiql/react-native-progressive-input/14377e5a54f209c48c06f3d8a76a162634d0a1b8/example/assets/icons/loading.png -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "progressive-input-example", 3 | "version": "0.0.0", 4 | "description": "A simple demo for 'react-native-progressive-input'", 5 | "author": null, 6 | "private": true, 7 | "main": "node_modules/expo/AppEntry.js", 8 | "dependencies": { 9 | "expo": "23.0.1", 10 | "react": "16.1.1", 11 | "react-native": "https://github.com/expo/react-native/archive/sdk-23.0.0.tar.gz", 12 | "react-native-progressive-input": "../react-native-progressive-input-1.1.0.tgz", 13 | "react-native-vector-icons": "^4.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; // 15.6.0 3 | import { 4 | TextInput, 5 | View, 6 | ActivityIndicator, 7 | TouchableOpacity, 8 | StyleSheet, 9 | } from 'react-native'; 10 | import { Ionicons } from '@expo/vector-icons'; // 6.2.2 11 | 12 | class ProgressiveInput extends Component { 13 | static propTypes = { 14 | ...TextInput.propTypes, 15 | value: PropTypes.string, 16 | isLoading: PropTypes.bool, 17 | textInputStyle: TextInput.propTypes.style, 18 | clearButtonIcon: PropTypes.string, 19 | clearButtonColor: PropTypes.string, 20 | clearButtonSize: PropTypes.number, 21 | clearButtonStyle: PropTypes.object, 22 | activityIndicatorStyle: ActivityIndicator.propTypes.style, 23 | onBlur: PropTypes.func, 24 | onChangeText: PropTypes.func, 25 | onFocus: PropTypes.func, 26 | onInputCleared: PropTypes.func, 27 | underlineColorAndroid: PropTypes.string, 28 | }; 29 | 30 | static defaultProps = { 31 | editable: true, 32 | clearButtonIcon: 'ios-close-circle', 33 | clearButtonColor: 'lightgrey', 34 | clearButtonSize: 20, 35 | underlineColorAndroid: 'transparent', 36 | }; 37 | 38 | constructor(props) { 39 | super(props); 40 | 41 | this.state = { 42 | showClearButton: false, 43 | value: this.props.value, 44 | }; 45 | } 46 | 47 | componentWillReceiveProps(nextProps) { 48 | this.setState({ value: nextProps.value }); 49 | } 50 | 51 | clearInput() { 52 | this.setState({ value: '', focus: false }); 53 | if (this.props.onInputCleared) { 54 | this.props.onInputCleared(); 55 | } 56 | } 57 | 58 | isFocused() { 59 | return this.input.isFocused(); 60 | } 61 | 62 | render() { 63 | return ( 64 | 65 | {this._renderClearButton()} 66 | (this.input = input)} 68 | style={[styles.textInput, this.props.textInputStyle]} 69 | focus={this.state.focus} 70 | value={this.state.value} 71 | editable={this.props.editable} 72 | onFocus={this._onFocus} 73 | placeholder={this.props.placeholder} 74 | onChangeText={this._onChangeText} 75 | selectTextOnFocus={this.props.selectTextOnFocus} 76 | onBlur={this._onBlur} 77 | autoCorrect={this.props.autoCorrect} 78 | keyboardType={this.props.keyboardType} 79 | multiline={this.props.multiline} 80 | placeholderTextColor={this.props.placeholderTextColor} 81 | returnKeyType={this.props.returnKeyType} 82 | autoCapitalize={this.props.autoCapitalize} 83 | maxLength={this.props.maxLength} 84 | onEndEditing={this.props.onEndEditing} 85 | onChange={this.props.onChange} 86 | underlineColorAndroid={this.props.underlineColorAndroid} 87 | /> 88 | {this._renderActivityIndicator()} 89 | 90 | ); 91 | } 92 | 93 | _renderActivityIndicator = () => { 94 | let size = this.props.isLoading ? {} : { width: 0, height: 0 }; 95 | return ( 96 | 104 | ); 105 | }; 106 | 107 | _renderClearButton = () => { 108 | if (this.state.showClearButton) { 109 | return ( 110 | this.clearInput()}> 111 | 117 | 118 | ); 119 | } 120 | }; 121 | 122 | _onFocus = () => { 123 | this._shouldShowClearButton(); 124 | if (this.props.onFocus) { 125 | this.props.onFocus(); 126 | } 127 | }; 128 | 129 | _onChangeText = text => { 130 | this.setState({ value: text }); 131 | this._shouldShowClearButton(text); 132 | if (this.props.onChangeText) { 133 | this.props.onChangeText(text); 134 | } 135 | }; 136 | 137 | _shouldShowClearButton = value => { 138 | const v = value || this.state.value; 139 | const showClearButton = v ? true : false; 140 | this.setState({ showClearButton }); 141 | }; 142 | 143 | _onBlur = () => this.setState({ showClearButton: false }); 144 | } 145 | 146 | const styles = StyleSheet.create({ 147 | container: { 148 | flexDirection: 'row', 149 | alignItems: 'center', 150 | borderWidth: 1, 151 | borderColor: 'lightgrey', 152 | backgroundColor: 'white', 153 | }, 154 | clearIcon: { 155 | marginLeft: 5, 156 | }, 157 | textInput: { 158 | flex: 1, 159 | height: 40, 160 | marginLeft: 10, 161 | }, 162 | activityIndicator: { 163 | marginLeft: 5, 164 | marginRight: 5, 165 | }, 166 | }); 167 | 168 | export default ProgressiveInput; 169 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Scott Le 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-progressive-input", 3 | "version": "1.1.0", 4 | "description": 5 | "Use this to create autocomplete text input which the text box can have both clear button and an activity indicator to show the background job", 6 | "main": "index.js", 7 | "repository": "https://github.com/khaiql/react-native-progressive-input", 8 | "keywords": [ 9 | "react-native", 10 | "uber-text-input", 11 | "autocomplete", 12 | "progressive-input", 13 | "clearable-input" 14 | ], 15 | "author": "scott ", 16 | "license": "MIT", 17 | "peerDependencies": { 18 | "react-native": "0.x", 19 | "prop-types": "^15.6.0" 20 | }, 21 | "dependencies": { 22 | "@expo/vector-icons": "^6.2.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /react-native-progressive-input-1.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khaiql/react-native-progressive-input/14377e5a54f209c48c06f3d8a76a162634d0a1b8/react-native-progressive-input-1.1.0.tgz -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khaiql/react-native-progressive-input/14377e5a54f209c48c06f3d8a76a162634d0a1b8/screenshot.gif --------------------------------------------------------------------------------