ToolbarDynamic
An animated toolbar that smoothly expands into a search bar.
9:41
signal_cellular_altwifibattery_full
Installation
$
Usage
Example.tsx
import { ToolbarDynamic } from './components/ToolbarDynamic';export default function App() {const handleSearch = (query: string) => {console.log('Searching for:', query);};return (<ToolbarDynamic onSearch={handleSearch} />);}
Full Component Code
ToolbarDynamic.tsx
1import React, { useState, useRef, useEffect } from 'react';2import {3 StyleSheet,4 View,5 Text,6 TouchableOpacity,7 TextInput,8 ViewStyle,9 StyleProp,10} from 'react-native';11import Animated, {12 useSharedValue,13 useAnimatedStyle,14 withTiming,15 Easing,16 FadeIn,17 FadeOut,18} from 'react-native-reanimated';19import { ArrowLeft, Search, User } from 'lucide-react-native';2021interface ToolbarDynamicProps {22 onSearch?: (query: string) => void;23 style?: StyleProp<ViewStyle>;24}2526export function ToolbarDynamic({ onSearch, style }: ToolbarDynamicProps) {27 const [isOpen, setIsOpen] = useState(false);28 const [query, setQuery] = useState('');29 const inputRef = useRef<TextInput>(null);3031 const width = useSharedValue(60 + 60);3233 useEffect(() => {34 width.value = withTiming(isOpen ? 300 : 120, {35 duration: 300,36 easing: Easing.bezier(0.25, 1, 0.5, 1),37 });38 if (isOpen) {39 setTimeout(() => inputRef.current?.focus(), 100);40 } else {41 inputRef.current?.blur();42 setQuery('');43 }44 }, [isOpen]);4546 const animatedStyle = useAnimatedStyle(() => {47 return {48 width: width.value,49 };50 });5152 return (53 <View style={[styles.centerContainer, style]}>54 <Animated.View style={[styles.container, animatedStyle]}>55 {!isOpen ? (56 <Animated.View57 entering={FadeIn.duration(200)}58 exiting={FadeOut.duration(200)}59 style={styles.row}60 >61 <TouchableOpacity62 style={styles.iconButton}63 activeOpacity={0.7}64 onPress={() => { }}65 >66 <User size={20} color="#52525b" />67 </TouchableOpacity>68 <TouchableOpacity69 style={styles.iconButton}70 activeOpacity={0.7}71 onPress={() => setIsOpen(true)}72 >73 <Search size={20} color="#52525b" />74 </TouchableOpacity>75 </Animated.View>76 ) : (77 <Animated.View78 entering={FadeIn.delay(100).duration(200)}79 exiting={FadeOut.duration(200)}80 style={styles.row}81 >82 <TouchableOpacity83 style={styles.iconButton}84 activeOpacity={0.7}85 onPress={() => setIsOpen(false)}86 >87 <ArrowLeft size={20} color="#52525b" />88 </TouchableOpacity>89 <TextInput90 ref={inputRef}91 style={styles.input}92 placeholder="Search notes"93 placeholderTextColor="#71717a"94 value={query}95 onChangeText={setQuery}96 onSubmitEditing={() => onSearch?.(query)}97 />98 </Animated.View>99 )}100 </Animated.View>101 </View>102 );103}104105const styles = StyleSheet.create({106 centerContainer: {107 alignItems: 'center',108 justifyContent: 'center',109 },110 container: {111 height: 60,112 backgroundColor: '#ffffff',113 borderRadius: 16,114 borderWidth: 1,115 borderColor: '#e4e4e7',116 overflow: 'hidden',117 shadowColor: '#000',118 shadowOffset: {119 width: 0,120 height: 2,121 },122 shadowOpacity: 0.05,123 shadowRadius: 8,124 elevation: 2,125 },126 row: {127 flexDirection: 'row',128 alignItems: 'center',129 height: '100%',130 paddingHorizontal: 8,131 width: '100%',132 },133 iconButton: {134 width: 44,135 height: 44,136 justifyContent: 'center',137 alignItems: 'center',138 borderRadius: 12,139 },140 input: {141 flex: 1,142 height: '100%',143 paddingHorizontal: 12,144 fontSize: 16,145 color: '#18181b',146 },147});
Props
| Name | Type | Default | Description |
|---|---|---|---|
| onSearch | (query: string) => void | - | Callback when search is submitted |
| style | StyleProp<ViewStyle> | - | Additional styles |
