├── .gitignore ├── README.md ├── lib ├── index.d.ts └── index.js ├── package.json ├── src └── index.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # react-native-outline-input 3 | 4 | This repository is react-native animated input component 5 | 6 | | ![ezgif com-video-to-gif](https://user-images.githubusercontent.com/59888174/88774626-592d7780-d18c-11ea-83a7-e0d2676dfe2c.gif) | 7 | |----| 8 | 9 | 10 | 11 | 12 | ## Installation 13 | 14 | > npm install react-native-outline-input 15 | 16 | or 17 | > yarn add react-native-outline-input 18 | 19 | ## Usage 20 | 21 | ```js 22 | import React, { useState } from 'react'; 23 | import { SafeAreaView } from 'react-native'; 24 | import OutlineInput from 'react-native-outline-input'; 25 | 26 | const App = () => { 27 | const [email, setEmail] = useState(''); 28 | const [password, setPassword] = useState(''); 29 | 30 | return ( 31 | 32 | setEmail(e)} 35 | label="Username" 36 | activeValueColor="#6c63fe" 37 | activeBorderColor="#6c63fe" 38 | activeLabelColor="#6c63fe" 39 | passiveBorderColor="#bbb7ff" 40 | passiveLabelColor="#bbb7ff" 41 | passiveValueColor="#bbb7ff" 42 | /> 43 | setPassword(e)} 46 | label="Username" 47 | activeValueColor="#6c63fe" 48 | activeBorderColor="#6c63fe" 49 | activeLabelColor="#6c63fe" 50 | passiveBorderColor="#bbb7ff" 51 | passiveLabelColor="#bbb7ff" 52 | passiveValueColor="#bbb7ff" 53 | /> 54 | 55 | ); 56 | }; 57 | 58 | export default App; 59 | ``` 60 | 61 | ## Props 62 | 63 | |Prop|Type|Default|Description| 64 | |----|----|----|--------| 65 | | label | string | any | The label that will display giving information about your input field 66 | | onChangeText | function | any |Function that works when there is a change 67 | | value | string | any | Value of input 68 | | secureTextEntry | boolean | false | If true, the text input obscures the text entered 69 | | autoCapitalize | string | none | Can tell to automatically capitalize certain characters 70 | | fontSize | number | 16 |Determines the font size 71 | | height | number | 56 | The label that will display giving information about your input field. 72 | | duration | number | 300 | Determines the animation time 73 | | easing | EasingFunction | Easing.inOut(Easing.ease) | Determines the animation type 74 | | activeValueColor | string | #51AD56 | Determines active value color 75 | | passiveValueColor | string | #757575 | Determines passive value color 76 | | activeLabelColor | string | #51AD56 | Determines active label color 77 | | passiveLabelColor | string | #757575 | Determines passive label color 78 | | activeBorderColor | string | #51AD56 | Determines active border color 79 | | passiveBorderColor | string | #EFEFEF | Determines passive border color 80 | | fontFamily | string | System | Determines value and label font family 81 | 82 | Thank you for your contribution to Burhan Yılmaz ([@burhanyilmaz](https://github.com/burhanyilmaz)) 83 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { EasingFunction } from 'react-native'; 3 | declare type secureTextEntryType = true | false; 4 | declare type autoCapitalizeType = 'characters' | 'words' | 'sentences' | 'none'; 5 | interface PropTypes { 6 | label: string; 7 | onChangeText?: any; 8 | value?: string; 9 | secureTextEntry?: secureTextEntryType; 10 | autoCapitalize?: autoCapitalizeType; 11 | fontSize?: number; 12 | height?: number; 13 | duration?: number; 14 | easing?: EasingFunction; 15 | activeValueColor?: string; 16 | passiveValueColor?: string; 17 | activeLabelColor?: string; 18 | passiveLabelColor?: string; 19 | activeBorderColor?: string; 20 | passiveBorderColor?: string; 21 | fontFamily?: string; 22 | } 23 | declare const _default: React.MemoExoticComponent<({ label, onChangeText, value, secureTextEntry, autoCapitalize, fontSize, height, duration, easing, activeValueColor, passiveValueColor, activeLabelColor, passiveLabelColor, activeBorderColor, passiveBorderColor, fontFamily, }: PropTypes) => JSX.Element>; 24 | export default _default; 25 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const React = require('react'); 4 | 5 | var __assign = (this && this.__assign) || function () { 6 | __assign = Object.assign || function(t) { 7 | for (var s, i = 1, n = arguments.length; i < n; i++) { 8 | s = arguments[i]; 9 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 10 | t[p] = s[p]; 11 | } 12 | return t; 13 | }; 14 | return __assign.apply(this, arguments); 15 | }; 16 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 17 | if (k2 === undefined) k2 = k; 18 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 19 | }) : (function(o, m, k, k2) { 20 | if (k2 === undefined) k2 = k; 21 | o[k2] = m[k]; 22 | })); 23 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 24 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 25 | }) : function(o, v) { 26 | o["default"] = v; 27 | }); 28 | var __importStar = (this && this.__importStar) || function (mod) { 29 | if (mod && mod.__esModule) return mod; 30 | var result = {}; 31 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 32 | __setModuleDefault(result, mod); 33 | return result; 34 | }; 35 | Object.defineProperty(exports, "__esModule", { value: true }); 36 | var react_1 = __importStar(require("react")); 37 | var react_native_1 = require("react-native"); 38 | var OutlineInput = function (_a) { 39 | var label = _a.label, onChangeText = _a.onChangeText, value = _a.value, _b = _a.secureTextEntry, secureTextEntry = _b === void 0 ? false : _b, _c = _a.autoCapitalize, autoCapitalize = _c === void 0 ? 'none' : _c, _d = _a.fontSize, fontSize = _d === void 0 ? 16 : _d, _e = _a.height, height = _e === void 0 ? 56 : _e, _f = _a.duration, duration = _f === void 0 ? 300 : _f, _g = _a.easing, easing = _g === void 0 ? react_native_1.Easing.inOut(react_native_1.Easing.ease) : _g, _h = _a.activeValueColor, activeValueColor = _h === void 0 ? '#51AD56' : _h, _j = _a.passiveValueColor, passiveValueColor = _j === void 0 ? '#757575' : _j, _k = _a.activeLabelColor, activeLabelColor = _k === void 0 ? '#51AD56' : _k, _l = _a.passiveLabelColor, passiveLabelColor = _l === void 0 ? '#757575' : _l, _m = _a.activeBorderColor, activeBorderColor = _m === void 0 ? '#51AD56' : _m, _o = _a.passiveBorderColor, passiveBorderColor = _o === void 0 ? '#EFEFEF' : _o, _p = _a.fontFamily, fontFamily = _p === void 0 ? 'System' : _p; 40 | var _q = react_1.useState(false), isFocused = _q[0], setIsFocused = _q[1]; 41 | var lineHeightValue = fontSize + 2; 42 | var initialTopValue = (height - lineHeightValue) / 2; 43 | var labelPositionEmptyValue = 0; 44 | var inputValueFontSize = fontSize; 45 | var padding = 8; 46 | var labelPositionFillValue = lineHeightValue / 2 + initialTopValue; 47 | var inputHeight = height; 48 | var labelPositionRef = react_1.useRef(new react_native_1.Animated.Value(value ? labelPositionFillValue : labelPositionEmptyValue)).current; 49 | var fontSizeRef = react_1.useRef(new react_native_1.Animated.Value(value ? fontSize - 2 : fontSize)).current; 50 | var lineHeightRef = react_1.useRef(new react_native_1.Animated.Value(value ? lineHeightValue - 2 : lineHeightValue)).current; 51 | var zIndexRef = react_1.useRef(new react_native_1.Animated.Value(value ? 2 : -1)).current; 52 | var commonAnimatedProps = { 53 | duration: duration, 54 | useNativeDriver: false, 55 | easing: easing, 56 | }; 57 | var onBlur = react_1.useCallback(function () { 58 | setIsFocused(false); 59 | if (!value) { 60 | react_native_1.Animated.parallel([ 61 | react_native_1.Animated.timing(labelPositionRef, __assign({ toValue: labelPositionEmptyValue }, commonAnimatedProps)), 62 | react_native_1.Animated.timing(fontSizeRef, __assign({ toValue: fontSize }, commonAnimatedProps)), 63 | react_native_1.Animated.timing(lineHeightRef, __assign({ toValue: lineHeightValue }, commonAnimatedProps)), 64 | react_native_1.Animated.timing(zIndexRef, __assign({ toValue: -1 }, commonAnimatedProps)), 65 | ]).start(); 66 | } 67 | }, [!!value]); 68 | var onFocus = react_1.useCallback(function () { 69 | setIsFocused(true); 70 | react_native_1.Animated.parallel([ 71 | react_native_1.Animated.timing(labelPositionRef, __assign({ toValue: labelPositionFillValue }, commonAnimatedProps)), 72 | react_native_1.Animated.timing(fontSizeRef, __assign({ toValue: fontSize - 2 }, commonAnimatedProps)), 73 | react_native_1.Animated.timing(lineHeightRef, __assign({ toValue: lineHeightValue - 2 }, commonAnimatedProps)), 74 | react_native_1.Animated.timing(zIndexRef, __assign({ toValue: 2 }, commonAnimatedProps)), 75 | ]).start(); 76 | }, [!!value]); 77 | var animatedViewProps = { 78 | style: { 79 | position: 'absolute', 80 | bottom: labelPositionRef, 81 | left: 10, 82 | zIndex: zIndexRef, 83 | height: height, 84 | }, 85 | }; 86 | var animatedTextProps = { 87 | style: [ 88 | LabelStyle({ 89 | isFocused: isFocused, 90 | initialTopValue: initialTopValue, 91 | activeLabelColor: activeLabelColor, 92 | passiveLabelColor: passiveLabelColor, 93 | }), 94 | { fontSize: fontSizeRef, lineHeight: lineHeightRef, fontFamily: fontFamily }, 95 | ], 96 | }; 97 | var inputProps = { 98 | secureTextEntry: secureTextEntry, 99 | value: value, 100 | onChangeText: onChangeText, 101 | onFocus: onFocus, 102 | onBlur: onBlur, 103 | autoCapitalize: autoCapitalize, 104 | isFocused: isFocused, 105 | height: inputHeight, 106 | padding: padding, 107 | fontSize: inputValueFontSize, 108 | activeBorderColor: activeBorderColor, 109 | passiveBorderColor: passiveBorderColor, 110 | style: [ 111 | { fontFamily: fontFamily }, 112 | InputStyle({ 113 | padding: padding, 114 | height: height, 115 | fontSize: fontSize, 116 | isFocused: isFocused, 117 | activeBorderColor: activeBorderColor, 118 | passiveBorderColor: passiveBorderColor, 119 | activeValueColor: activeValueColor, 120 | passiveValueColor: passiveValueColor, 121 | }), 122 | ], 123 | }; 124 | return ( 125 | 126 | {label} 127 | 128 | 129 | ); 130 | }; 131 | var LabelStyle = function (_a) { 132 | var isFocused = _a.isFocused, initialTopValue = _a.initialTopValue, activeLabelColor = _a.activeLabelColor, passiveLabelColor = _a.passiveLabelColor; 133 | return ({ 134 | fontStyle: 'normal', 135 | fontWeight: 'normal', 136 | color: isFocused ? activeLabelColor : passiveLabelColor, 137 | backgroundColor: '#FFFFFF', 138 | paddingRight: 5, 139 | paddingLeft: 5, 140 | top: initialTopValue, 141 | }); 142 | }; 143 | var styles = react_native_1.StyleSheet.create({ 144 | container: { 145 | flexDirection: 'column', 146 | marginRight: 5, 147 | backgroundColor: '#ffffff', 148 | }, 149 | }); 150 | var InputStyle = function (_a) { 151 | var padding = _a.padding, height = _a.height, fontSize = _a.fontSize, isFocused = _a.isFocused, activeBorderColor = _a.activeBorderColor, passiveBorderColor = _a.passiveBorderColor, activeValueColor = _a.activeValueColor, passiveValueColor = _a.passiveValueColor; 152 | return ({ 153 | padding: padding, 154 | height: height, 155 | fontSize: fontSize, 156 | borderWidth: 1, 157 | borderColor: isFocused ? activeBorderColor : passiveBorderColor, 158 | borderRadius: 6, 159 | color: isFocused ? activeValueColor : passiveValueColor, 160 | }); 161 | }; 162 | exports.default = react_1.memo(OutlineInput); 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-outline-input", 3 | "version": "1.0.7", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "p-patch": "npm version patch && npm publish", 9 | "p-major": "npm version major && npm publish", 10 | "p-minor": "npm version minor && npm publish", 11 | "build": "tsc" 12 | }, 13 | "peerDependencies": { 14 | "react":"^16.8.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^14.0.27", 18 | "react": "^16.8.6", 19 | "react-native": "^0.60.0", 20 | "@types/react": "^16.9.43", 21 | "@types/react-native": "^0.63.2", 22 | "typescript": "^3.8.3" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/acerezci/react-native-outline-input" 27 | }, 28 | "keywords": [ 29 | "outlineinput", 30 | "react-native-outline-input" 31 | ], 32 | "author": "Alperen Çerezci", 33 | "license": "ISC" 34 | } 35 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | memo, useRef, useState, useCallback, 3 | } from 'react'; 4 | import { 5 | Animated, 6 | Easing, 7 | EasingFunction, 8 | StyleSheet, 9 | View, 10 | TextInput, 11 | } from 'react-native'; 12 | 13 | type secureTextEntryType = true | false; 14 | type autoCapitalizeType = 'characters' | 'words' | 'sentences' | 'none'; 15 | 16 | interface PropTypes { 17 | label: string; 18 | onChangeText?: any; 19 | value?: string; 20 | secureTextEntry?: secureTextEntryType; 21 | autoCapitalize?: autoCapitalizeType; 22 | fontSize?: number; 23 | height?: number; 24 | duration?: number; 25 | easing?: EasingFunction; 26 | activeValueColor?:string; 27 | passiveValueColor?:string; 28 | activeLabelColor?: string; 29 | passiveLabelColor?: string; 30 | activeBorderColor?: string; 31 | passiveBorderColor?: string; 32 | fontFamily?: string; 33 | } 34 | 35 | interface CommonAnimatedPropsTypes { 36 | duration: number; 37 | useNativeDriver: boolean; 38 | easing: EasingFunction; 39 | } 40 | 41 | interface LabelStylePropTypes { 42 | isFocused: boolean; 43 | initialTopValue: number; 44 | activeLabelColor: string; 45 | passiveLabelColor: string; 46 | } 47 | 48 | interface InputStyleProps { 49 | padding: number; 50 | height: number; 51 | fontSize: number; 52 | isFocused: boolean; 53 | activeBorderColor: string; 54 | passiveBorderColor: string; 55 | activeValueColor:string; 56 | passiveValueColor:string; 57 | } 58 | 59 | const OutlineInput = ({ 60 | label, 61 | onChangeText, 62 | value, 63 | secureTextEntry = false, 64 | autoCapitalize = 'none', 65 | fontSize = 16, 66 | height = 56, 67 | duration = 300, 68 | easing = Easing.inOut(Easing.ease), 69 | activeValueColor = '#51AD56', 70 | passiveValueColor = '#757575', 71 | activeLabelColor = '#51AD56', 72 | passiveLabelColor = '#757575', 73 | activeBorderColor = '#51AD56', 74 | passiveBorderColor = '#EFEFEF', 75 | fontFamily = 'System', 76 | }: PropTypes) => { 77 | const [isFocused, setIsFocused] = useState(false); 78 | const lineHeightValue: number = fontSize + 2; 79 | const initialTopValue: number = (height - lineHeightValue) / 2; 80 | const labelPositionEmptyValue: number = 0; 81 | const inputValueFontSize: number = fontSize; 82 | const padding: number = 8; 83 | const labelPositionFillValue: number = lineHeightValue / 2 + initialTopValue; 84 | const inputHeight: number = height; 85 | 86 | const labelPositionRef = useRef( 87 | new Animated.Value( 88 | value ? labelPositionFillValue : labelPositionEmptyValue, 89 | ), 90 | ).current; 91 | const fontSizeRef = useRef( 92 | new Animated.Value(value ? fontSize - 2 : fontSize), 93 | ).current; 94 | const lineHeightRef = useRef( 95 | new Animated.Value(value ? lineHeightValue - 2 : lineHeightValue), 96 | ).current; 97 | const zIndexRef = useRef(new Animated.Value(value ? 2 : -1)).current; 98 | 99 | const commonAnimatedProps: CommonAnimatedPropsTypes = { 100 | duration, 101 | useNativeDriver: false, 102 | easing, 103 | }; 104 | 105 | const onBlur: () => void = useCallback(() => { 106 | setIsFocused(false); 107 | if (!value) { 108 | Animated.parallel([ 109 | Animated.timing(labelPositionRef, { 110 | toValue: labelPositionEmptyValue, 111 | ...commonAnimatedProps, 112 | }), 113 | Animated.timing(fontSizeRef, { 114 | toValue: fontSize, 115 | ...commonAnimatedProps, 116 | }), 117 | Animated.timing(lineHeightRef, { 118 | toValue: lineHeightValue, 119 | ...commonAnimatedProps, 120 | }), 121 | Animated.timing(zIndexRef, { 122 | toValue: -1, 123 | ...commonAnimatedProps, 124 | }), 125 | ]).start(); 126 | } 127 | }, [!!value]); 128 | 129 | const onFocus: () => void = useCallback(() => { 130 | setIsFocused(true); 131 | Animated.parallel([ 132 | Animated.timing(labelPositionRef, { 133 | toValue: labelPositionFillValue, 134 | ...commonAnimatedProps, 135 | }), 136 | Animated.timing(fontSizeRef, { 137 | toValue: fontSize - 2, 138 | ...commonAnimatedProps, 139 | }), 140 | Animated.timing(lineHeightRef, { 141 | toValue: lineHeightValue - 2, 142 | ...commonAnimatedProps, 143 | }), 144 | Animated.timing(zIndexRef, { 145 | toValue: 2, 146 | ...commonAnimatedProps, 147 | }), 148 | ]).start(); 149 | }, [!!value]); 150 | 151 | const animatedViewProps = { 152 | style: { 153 | position: 'absolute', 154 | bottom: labelPositionRef, 155 | left: 10, 156 | zIndex: zIndexRef, 157 | height, 158 | }, 159 | }; 160 | 161 | const animatedTextProps = { 162 | style: [ 163 | LabelStyle({ 164 | isFocused, 165 | initialTopValue, 166 | activeLabelColor, 167 | passiveLabelColor, 168 | }), 169 | { fontSize: fontSizeRef, lineHeight: lineHeightRef, fontFamily }, 170 | ], 171 | }; 172 | 173 | const inputProps = { 174 | secureTextEntry, 175 | value, 176 | onChangeText, 177 | onFocus, 178 | onBlur, 179 | autoCapitalize, 180 | isFocused, 181 | height: inputHeight, 182 | padding, 183 | fontSize: inputValueFontSize, 184 | activeBorderColor, 185 | passiveBorderColor, 186 | style: [ 187 | { fontFamily }, 188 | InputStyle({ 189 | padding, 190 | height, 191 | fontSize, 192 | isFocused, 193 | activeBorderColor, 194 | passiveBorderColor, 195 | activeValueColor, 196 | passiveValueColor, 197 | }), 198 | ], 199 | }; 200 | 201 | return ( 202 | 203 | 204 | {label} 205 | 206 | 207 | 208 | ); 209 | }; 210 | 211 | const LabelStyle = ({ 212 | isFocused, 213 | initialTopValue, 214 | activeLabelColor, 215 | passiveLabelColor, 216 | }: LabelStylePropTypes) => ({ 217 | fontStyle: 'normal', 218 | fontWeight: 'normal', 219 | color: isFocused ? activeLabelColor : passiveLabelColor, 220 | backgroundColor: '#FFFFFF', 221 | paddingRight: 5, 222 | paddingLeft: 5, 223 | top: initialTopValue, 224 | }); 225 | 226 | const styles = StyleSheet.create({ 227 | container: { 228 | flexDirection: 'column', 229 | marginRight: 5, 230 | backgroundColor: '#ffffff', 231 | }, 232 | }); 233 | 234 | const InputStyle = ({ 235 | padding, 236 | height, 237 | fontSize, 238 | isFocused, 239 | activeBorderColor, 240 | passiveBorderColor, 241 | activeValueColor, 242 | passiveValueColor, 243 | }: InputStyleProps) => ({ 244 | padding, 245 | height, 246 | fontSize, 247 | borderWidth: 1, 248 | borderColor: isFocused ? activeBorderColor : passiveBorderColor, 249 | borderRadius: 6, 250 | color: isFocused ? activeValueColor : passiveValueColor, 251 | }); 252 | 253 | export default memo(OutlineInput); 254 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "rootDir": "src", 8 | "strict": true, 9 | "jsx": "react-native", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true 12 | }, 13 | "include": ["src"], 14 | "exclude": ["node_modules"] 15 | } --------------------------------------------------------------------------------