@@ -10,7 +10,15 @@ import type {
1010import { createStackNavigator } from '@react-navigation/stack' ;
1111import { Icon } from '@swmansion/icons' ;
1212import { useEffect , useState } from 'react' ;
13- import { Dimensions , Platform , StyleSheet , Text , View } from 'react-native' ;
13+ import {
14+ Dimensions ,
15+ Platform ,
16+ Pressable ,
17+ StyleSheet ,
18+ Text ,
19+ TextInput ,
20+ View ,
21+ } from 'react-native' ;
1422import {
1523 GestureHandlerRootView ,
1624 Switch ,
@@ -85,6 +93,20 @@ export default function App() {
8593
8694 function MainScreen ( { navigation } : StackScreenProps < ParamListBase > ) {
8795 const insets = useSafeAreaInsets ( ) ;
96+ const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
97+
98+ const normalizedSearchQuery = searchQuery . trim ( ) . toLocaleLowerCase ( ) ;
99+ const sections = showLegacyVersion ? OLD_EXAMPLES : NEW_EXAMPLES ;
100+ const filteredSections = normalizedSearchQuery
101+ ? sections
102+ . map ( ( section ) => ( {
103+ ...section ,
104+ data : section . data . filter ( ( { name } ) =>
105+ name . toLocaleLowerCase ( ) . includes ( normalizedSearchQuery )
106+ ) ,
107+ } ) )
108+ . filter ( ( { data } ) => data . length > 0 )
109+ : sections ;
88110
89111 useEffect ( ( ) => {
90112 void AsyncStorage . multiGet ( [
@@ -114,9 +136,19 @@ export default function App() {
114136 < View style = { styles . container } >
115137 < ListWithHeader
116138 style = { styles . list }
117- sections = { showLegacyVersion ? OLD_EXAMPLES : NEW_EXAMPLES }
139+ sections = { filteredSections }
118140 keyExtractor = { ( example ) => example . name }
119- ListHeaderComponent = { Settings }
141+ ListHeaderComponent = {
142+ < Settings
143+ searchQuery = { searchQuery }
144+ onChangeSearchQuery = { setSearchQuery }
145+ />
146+ }
147+ ListEmptyComponent = {
148+ < Text style = { styles . emptyText } >
149+ No examples match “{ searchQuery . trim ( ) } ”
150+ </ Text >
151+ }
120152 contentContainerStyle = { {
121153 paddingBottom : insets . bottom ,
122154 paddingTop : insets . top ,
@@ -137,7 +169,12 @@ export default function App() {
137169 ) ;
138170 }
139171
140- function Settings ( ) {
172+ interface SettingsProps {
173+ searchQuery : string ;
174+ onChangeSearchQuery : ( query : string ) => void ;
175+ }
176+
177+ function Settings ( { searchQuery, onChangeSearchQuery } : SettingsProps ) {
141178 const [ openLastExample , setOpenLastExample ] = useState ( false ) ;
142179
143180 useEffect ( ( ) => {
@@ -157,51 +194,77 @@ export default function App() {
157194 setShowLegacyVersion ( value ) ;
158195 }
159196 return (
160- < View style = { styles . settings } >
161- < Touchable
162- androidRipple = { { } }
163- underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
164- style = { styles . settingsButton }
165- onPress = { ( ) => {
166- updateKeepSetting ( ! openLastExample ) ;
167- } } >
168- < View
169- style = { styles . settingsButtonContent }
170- pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
171- < Text style = { [ styles . text , styles . aligned ] } >
172- Open last example on launch
173- </ Text >
174- < Switch
175- style = { styles . aligned }
176- value = { openLastExample }
177- onValueChange = { ( ) => {
178- updateKeepSetting ( ! openLastExample ) ;
179- } }
180- />
181- </ View >
182- </ Touchable >
183- < Touchable
184- androidRipple = { { } }
185- underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
186- style = { styles . settingsButton }
187- onPress = { ( ) => {
188- updateVersionSetting ( ! showLegacyVersion ) ;
189- } } >
190- < View
191- style = { styles . settingsButtonContent }
192- pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
193- < Text style = { [ styles . text , styles . aligned ] } >
194- Show legacy version examples
195- </ Text >
196- < Switch
197- style = { styles . aligned }
198- value = { showLegacyVersion }
199- onValueChange = { ( ) => {
200- updateVersionSetting ( ! showLegacyVersion ) ;
201- } }
202- />
203- </ View >
204- </ Touchable >
197+ < View style = { styles . headerControls } >
198+ < View style = { styles . searchBar } >
199+ < Icon name = "search" size = { 20 } color = { COLORS . NAVY } />
200+ < TextInput
201+ accessibilityLabel = "Search examples"
202+ autoCapitalize = "none"
203+ autoCorrect = { false }
204+ clearButtonMode = "while-editing"
205+ placeholder = "Search examples"
206+ placeholderTextColor = { COLORS . GRAY }
207+ returnKeyType = "search"
208+ selectionColor = { COLORS . DARK_PURPLE }
209+ style = { styles . searchInput }
210+ value = { searchQuery }
211+ onChangeText = { onChangeSearchQuery }
212+ />
213+ { searchQuery . length > 0 && Platform . OS !== 'ios' && (
214+ < Pressable
215+ accessibilityLabel = "Clear search"
216+ hitSlop = { 8 }
217+ onPress = { ( ) => onChangeSearchQuery ( '' ) } >
218+ < Icon name = "cross-circle" size = { 20 } color = { COLORS . GRAY } />
219+ </ Pressable >
220+ ) }
221+ </ View >
222+ < View style = { styles . settings } >
223+ < Touchable
224+ androidRipple = { { } }
225+ underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
226+ style = { styles . settingsButton }
227+ onPress = { ( ) => {
228+ updateKeepSetting ( ! openLastExample ) ;
229+ } } >
230+ < View
231+ style = { styles . settingsButtonContent }
232+ pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
233+ < Text style = { [ styles . text , styles . aligned ] } >
234+ Open last example on launch
235+ </ Text >
236+ < Switch
237+ style = { styles . aligned }
238+ value = { openLastExample }
239+ onValueChange = { ( ) => {
240+ updateKeepSetting ( ! openLastExample ) ;
241+ } }
242+ />
243+ </ View >
244+ </ Touchable >
245+ < Touchable
246+ androidRipple = { { } }
247+ underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
248+ style = { styles . settingsButton }
249+ onPress = { ( ) => {
250+ updateVersionSetting ( ! showLegacyVersion ) ;
251+ } } >
252+ < View
253+ style = { styles . settingsButtonContent }
254+ pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
255+ < Text style = { [ styles . text , styles . aligned ] } >
256+ Show legacy version examples
257+ </ Text >
258+ < Switch
259+ style = { styles . aligned }
260+ value = { showLegacyVersion }
261+ onValueChange = { ( ) => {
262+ updateVersionSetting ( ! showLegacyVersion ) ;
263+ } }
264+ />
265+ </ View >
266+ </ Touchable >
267+ </ View >
205268 </ View >
206269 ) ;
207270 }
@@ -264,6 +327,12 @@ const styles = StyleSheet.create({
264327 separator : {
265328 height : 2 ,
266329 } ,
330+ emptyText : {
331+ paddingHorizontal : 24 ,
332+ paddingVertical : 32 ,
333+ color : COLORS . GRAY ,
334+ textAlign : 'center' ,
335+ } ,
267336 button : {
268337 flex : 1 ,
269338 height : 48 ,
@@ -306,6 +375,26 @@ const styles = StyleSheet.create({
306375 settings : {
307376 flexDirection : 'row' ,
308377 gap : 24 ,
378+ } ,
379+ headerControls : {
380+ gap : 16 ,
309381 margin : 24 ,
310382 } ,
383+ searchBar : {
384+ minHeight : 44 ,
385+ paddingHorizontal : 14 ,
386+ flexDirection : 'row' ,
387+ alignItems : 'center' ,
388+ gap : 10 ,
389+ borderRadius : 12 ,
390+ borderWidth : 1 ,
391+ borderColor : COLORS . headerSeparator ,
392+ backgroundColor : '#fff' ,
393+ } ,
394+ searchInput : {
395+ flex : 1 ,
396+ paddingVertical : Platform . OS === 'web' ? 10 : 8 ,
397+ color : COLORS . NAVY ,
398+ fontSize : 16 ,
399+ } ,
311400} ) ;
0 commit comments