logoChisa UI

Command Palette

Search for a command to run...

Componentschevron_rightToolbarDynamic

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';
20
21interface ToolbarDynamicProps {
22 onSearch?: (query: string) => void;
23 style?: StyleProp<ViewStyle>;
24}
25
26export function ToolbarDynamic({ onSearch, style }: ToolbarDynamicProps) {
27 const [isOpen, setIsOpen] = useState(false);
28 const [query, setQuery] = useState('');
29 const inputRef = useRef<TextInput>(null);
30
31 const width = useSharedValue(60 + 60);
32
33 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]);
45
46 const animatedStyle = useAnimatedStyle(() => {
47 return {
48 width: width.value,
49 };
50 });
51
52 return (
53 <View style={[styles.centerContainer, style]}>
54 <Animated.View style={[styles.container, animatedStyle]}>
55 {!isOpen ? (
56 <Animated.View
57 entering={FadeIn.duration(200)}
58 exiting={FadeOut.duration(200)}
59 style={styles.row}
60 >
61 <TouchableOpacity
62 style={styles.iconButton}
63 activeOpacity={0.7}
64 onPress={() => { }}
65 >
66 <User size={20} color="#52525b" />
67 </TouchableOpacity>
68 <TouchableOpacity
69 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.View
78 entering={FadeIn.delay(100).duration(200)}
79 exiting={FadeOut.duration(200)}
80 style={styles.row}
81 >
82 <TouchableOpacity
83 style={styles.iconButton}
84 activeOpacity={0.7}
85 onPress={() => setIsOpen(false)}
86 >
87 <ArrowLeft size={20} color="#52525b" />
88 </TouchableOpacity>
89 <TextInput
90 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}
104
105const 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

NameTypeDefaultDescription
onSearch(query: string) => void-Callback when search is submitted
styleStyleProp<ViewStyle>-Additional styles