├── .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 |
--------------------------------------------------------------------------------