AnimatedInput
Input component with 4 variants (outlined, filled, underlined, rounded), floating label animation, and error shake effect.
9:41
signal_cellular_altwifibattery_full
outlined
Email
filled
Username
underlined
Phone
rounded
Password
Installation
$
Usage
Example.tsx
import { View } from 'react-native';import { AnimatedInput } from './components/AnimatedInput';export default function App() {const [email, setEmail] = useState('');const [password, setPassword] = useState('');const [error, setError] = useState('');return (<View style={{ padding: 16 }}>{/* Outlined (default) */}<AnimatedInputlabel="Email"value={email}onChangeText={setEmail}keyboardType="email-address"/>{/* Filled variant */}<AnimatedInputlabel="Password"variant="filled"value={password}onChangeText={setPassword}secureTextEntryshowPasswordToggle/>{/* With error */}<AnimatedInputlabel="Username"variant="underlined"error={error}/>{/* Rounded variant */}<AnimatedInputlabel="Search"variant="rounded"placeholder="Search..."/></View>);}
Full Component Code
AnimatedInput.tsx
1import React, { useState, useRef } from 'react';2import { View, Text, TextInput, StyleSheet, Pressable, TextInputProps } from 'react-native';3import Animated, { useSharedValue, useAnimatedStyle, withTiming, withSpring, interpolate, interpolateColor, Easing } from 'react-native-reanimated';45type InputVariant = 'outlined' | 'filled' | 'underlined' | 'rounded';67interface AnimatedInputProps extends Omit<TextInputProps, 'style'> {8 label?: string;9 variant?: InputVariant;10 error?: string;11 helperText?: string;12 primaryColor?: string;13 disabled?: boolean;14}1516export const AnimatedInput: React.FC<AnimatedInputProps> = ({17 label,18 variant = 'outlined',19 error,20 helperText,21 primaryColor = '#cf350b',22 disabled = false,23 value,24 onChangeText,25 ...props26}) => {27 const inputRef = useRef<TextInput>(null);28 const [isFocused, setIsFocused] = useState(false);29 const focusProgress = useSharedValue(0);30 const labelPosition = useSharedValue(value ? 1 : 0);31 const shakeAnimation = useSharedValue(0);3233 const handleFocus = () => {34 setIsFocused(true);35 focusProgress.value = withTiming(1, { duration: 200 });36 labelPosition.value = withTiming(1, { duration: 200 });37 };3839 const handleBlur = () => {40 setIsFocused(false);41 focusProgress.value = withTiming(0, { duration: 200 });42 if (!value) labelPosition.value = withTiming(0, { duration: 200 });43 };4445 React.useEffect(() => {46 if (error) {47 shakeAnimation.value = withSpring(1, { damping: 2, stiffness: 400 }, () => {48 shakeAnimation.value = withTiming(0, { duration: 100 });49 });50 }51 }, [error]);5253 const animatedLabelStyle = useAnimatedStyle(() => ({54 transform: [55 { translateY: interpolate(labelPosition.value, [0, 1], [0, -24]) },56 { scale: interpolate(labelPosition.value, [0, 1], [1, 0.85]) },57 ],58 color: isFocused ? primaryColor : '#6B7280',59 }));6061 const animatedBorderStyle = useAnimatedStyle(() => ({62 borderColor: interpolateColor(focusProgress.value, [0, 1], ['#D1D5DB', error ? '#EF4444' : primaryColor]),63 borderWidth: interpolate(focusProgress.value, [0, 1], [1, 2]),64 }));6566 return (67 <View style={styles.wrapper}>68 <Pressable onPress={() => inputRef.current?.focus()}>69 <Animated.View style={[styles.container, animatedBorderStyle]}>70 {label && <Animated.Text style={[styles.label, animatedLabelStyle]}>{label}</Animated.Text>}71 <TextInput72 ref={inputRef}73 style={styles.input}74 value={value}75 onChangeText={onChangeText}76 onFocus={handleFocus}77 onBlur={handleBlur}78 editable={!disabled}79 {...props}80 />81 </Animated.View>82 </Pressable>83 {(error || helperText) && <Text style={[styles.helper, error && { color: '#EF4444' }]}>{error || helperText}</Text>}84 </View>85 );86};8788const styles = StyleSheet.create({89 wrapper: { marginBottom: 16 },90 container: { minHeight: 56, paddingHorizontal: 16, justifyContent: 'center', borderRadius: 12, backgroundColor: '#FFF' },91 label: { position: 'absolute', left: 16, fontSize: 16, color: '#6B7280' },92 input: { fontSize: 16, color: '#1F2937', paddingTop: 12 },93 helper: { marginTop: 4, marginLeft: 16, fontSize: 12, color: '#6B7280' },94});
Props
| Name | Type | Default | Description |
|---|---|---|---|
| label | string | - | Floating label text |
| variant | 'outlined' | 'filled' | 'underlined' | 'rounded' | 'outlined' | Visual variant |
| error | string | - | Error message (triggers shake) |
| helperText | string | - | Helper text below input |
| leftIcon | React.ReactNode | - | Left icon component |
| rightIcon | React.ReactNode | - | Right icon component |
| showPasswordToggle | boolean | false | Show password visibility toggle |
| primaryColor | string | '#E16C2D' | Focus/active color |
| disabled | boolean | false | Disabled state |
