├── .gitignore ├── LICENSE ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Eyal Eizenberg 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## React Native Floating Label Text Input 2 | 3 | ### What is this? 4 | This component will render an iOS styled text field with floating label animation. When there is no value, the placeholder will be centered. Once there is a value, the value will slide down and the label will fade in and slide up. 5 | 6 | Credits for the concept to Matt D. Smith ([@mds](http://www.twitter.com/mds)), and his [original design](http://dribbble.com/shots/1254439--GIF-Mobile-Form-Interaction?list=users). 7 | 8 |

9 | 10 |

11 | 12 | ### Installation 13 | ```npm install react-native-floating-label-text-input --save``` 14 | 15 | ### Usage example 16 | 17 | ```javascript 18 | import FloatLabelTextInput from 'react-native-floating-label-text-input'; 19 | 20 | class SomeComponent extends Component { 21 | render () { 22 | return ( 23 | 24 | 30 | 31 | ); 32 | } 33 | } 34 | ``` 35 | 36 | ### Component props 37 | - placeholder (String) - String that will be used as the placeholder if there is no value. It will also be the string used for the label when there is a value. 38 | - secureTextEntry (Bool) - If true, the text input obscures the text entered so that sensitive text like passwords stay secure. The default value is false. 39 | - keyboardType (Enum) - enum('default', 'email-address', 'numeric', 'phone-pad', 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad', 'name-phone-pad', 'decimal-pad', 'twitter', 'web-search'). 40 | - value (String) - Value of the text input. 41 | - onFocus (Function) - Function to be called on focus. 42 | - onBlur (Function) - Function to be called on blur. 43 | - onChangeTextValue (Function) - Function to be called when text is modified. 44 | - noBorder (Boolean) - Hide the border bottom of the field. 45 | - maxLength (Number) - Limits the maximum number of characters that can be entered. Use this instead of implementing the logic in JS to avoid flicker. 46 | - selectionColor (String) - The highlight (and cursor on ios) color of the text input. 47 | 48 | ### Questions/Bugs/Ideas? 49 | Feel free to open an issue on github, send suggestions, fork this repository or contact me at eyal.eizenberg@samanage.com 50 | 51 | This package was developed during my work at [Samanage](http://www.samanage.com/). 52 | 53 | Thanks and Enjoy! :) 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | TextInput, 7 | Animated, 8 | Platform 9 | } from 'react-native' 10 | 11 | class FloatingLabel extends Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | let initialPadding = 9; 16 | let initialOpacity = 0; 17 | 18 | if (this.props.visible) { 19 | initialPadding = 5; 20 | initialOpacity = 1; 21 | } 22 | 23 | this.state = { 24 | paddingAnim: new Animated.Value(initialPadding), 25 | opacityAnim: new Animated.Value(initialOpacity) 26 | } 27 | } 28 | 29 | componentWillReceiveProps(newProps) { 30 | Animated.timing(this.state.paddingAnim, { 31 | toValue: newProps.visible ? 5 : 9, 32 | duration: 230 33 | }).start(); 34 | 35 | return Animated.timing(this.state.opacityAnim, { 36 | toValue: newProps.visible ? 1 : 0, 37 | duration: 230 38 | }).start(); 39 | } 40 | 41 | render() { 42 | return ( 43 | 44 | {this.props.children} 45 | 46 | ); 47 | } 48 | } 49 | 50 | class TextFieldHolder extends Component { 51 | constructor(props) { 52 | super(props); 53 | this.state = { 54 | marginAnim: new Animated.Value(this.props.withValue ? 10 : 0) 55 | } 56 | } 57 | 58 | componentWillReceiveProps(newProps) { 59 | return Animated.timing(this.state.marginAnim, { 60 | toValue: newProps.withValue ? 10 : 0, 61 | duration: 230 62 | }).start(); 63 | } 64 | 65 | render() { 66 | return ( 67 | 68 | {this.props.children} 69 | 70 | ); 71 | } 72 | } 73 | 74 | class FloatLabelTextField extends Component { 75 | constructor(props) { 76 | super(props); 77 | this.state = { 78 | focused: false, 79 | text: this.props.value 80 | }; 81 | } 82 | 83 | componentWillReceiveProps(newProps) { 84 | if (newProps.hasOwnProperty('value') && newProps.value !== this.state.text) { 85 | this.setState({ text: newProps.value }) 86 | } 87 | } 88 | 89 | leftPadding() { 90 | return { width: this.props.leftPadding || 0 } 91 | } 92 | 93 | withBorder() { 94 | if (!this.props.noBorder) { 95 | return styles.withBorder; 96 | } 97 | } 98 | 99 | render() { 100 | return ( 101 | 102 | 103 | 104 | 105 | 106 | {this.placeholderValue()} 107 | 108 | 109 | this.setFocus()} 117 | onBlur={() => this.unsetFocus()} 118 | onChangeText={(value) => this.setText(value)} 119 | /> 120 | 121 | 122 | 123 | 124 | ); 125 | } 126 | 127 | inputRef() { 128 | return this.refs.input; 129 | } 130 | 131 | focus() { 132 | this.inputRef().focus(); 133 | } 134 | 135 | blur() { 136 | this.inputRef().blur(); 137 | } 138 | 139 | isFocused() { 140 | return this.inputRef().isFocused(); 141 | } 142 | 143 | clear() { 144 | this.inputRef().clear(); 145 | } 146 | 147 | setFocus() { 148 | this.setState({ 149 | focused: true 150 | }); 151 | try { 152 | return this.props.onFocus(); 153 | } catch (_error) { } 154 | } 155 | 156 | unsetFocus() { 157 | this.setState({ 158 | focused: false 159 | }); 160 | try { 161 | return this.props.onBlur(); 162 | } catch (_error) { } 163 | } 164 | 165 | labelStyle() { 166 | if (this.state.focused) { 167 | return styles.focused; 168 | } 169 | } 170 | 171 | placeholderValue() { 172 | if (this.state.text) { 173 | return this.props.placeholder; 174 | } 175 | } 176 | 177 | setText(value) { 178 | this.setState({ 179 | text: value 180 | }); 181 | try { 182 | return this.props.onChangeTextValue(value); 183 | } catch (_error) { } 184 | } 185 | } 186 | 187 | const styles = StyleSheet.create({ 188 | container: { 189 | flex: 1, 190 | height: 45, 191 | backgroundColor: 'white', 192 | justifyContent: 'center' 193 | }, 194 | viewContainer: { 195 | flex: 1, 196 | flexDirection: 'row' 197 | }, 198 | paddingView: { 199 | width: 15 200 | }, 201 | floatingLabel: { 202 | position: 'absolute', 203 | top: 0, 204 | left: 0 205 | }, 206 | fieldLabel: { 207 | height: 15, 208 | fontSize: 10, 209 | color: '#B1B1B1' 210 | }, 211 | fieldContainer: { 212 | flex: 1, 213 | justifyContent: 'center', 214 | position: 'relative' 215 | }, 216 | withBorder: { 217 | borderBottomWidth: 1 / 2, 218 | borderColor: '#C8C7CC', 219 | }, 220 | valueText: { 221 | height: (Platform.OS == 'ios' ? 20 : 60), 222 | fontSize: 16, 223 | color: '#111111' 224 | }, 225 | focused: { 226 | color: "#1482fe" 227 | } 228 | }); 229 | 230 | export default FloatLabelTextField; 231 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-floating-label-text-input", 3 | "version": "0.1.5", 4 | "description": "iOS text input with animated floating label", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:eyaleizenberg/react-native-floating-label-text-input.git" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "react-component", 16 | "ios", 17 | "animated", 18 | "floating", 19 | "text input", 20 | "text field" 21 | ], 22 | "author": "Eyal Eizenberg (https://github.com/eyaleizenberg)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/eyaleizenberg/react-native-floating-label-text-input/issues" 26 | }, 27 | "homepage": "https://github.com/eyaleizenberg/react-native-floating-label-text-input" 28 | } 29 | --------------------------------------------------------------------------------