@@ -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 ( ) . toLowerCase ( ) ;
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 . toLowerCase ( ) . 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,78 @@ 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+ testID = "search-examples"
202+ accessibilityLabel = "Search examples"
203+ autoCapitalize = "none"
204+ autoCorrect = { false }
205+ clearButtonMode = "while-editing"
206+ placeholder = "Search examples"
207+ placeholderTextColor = { COLORS . GRAY }
208+ returnKeyType = "search"
209+ selectionColor = { COLORS . DARK_PURPLE }
210+ style = { styles . searchInput }
211+ value = { searchQuery }
212+ onChangeText = { onChangeSearchQuery }
213+ />
214+ { searchQuery . length > 0 && Platform . OS !== 'ios' && (
215+ < Pressable
216+ accessibilityLabel = "Clear search"
217+ hitSlop = { 8 }
218+ onPress = { ( ) => onChangeSearchQuery ( '' ) } >
219+ < Icon name = "cross-circle" size = { 20 } color = { COLORS . GRAY } />
220+ </ Pressable >
221+ ) }
222+ </ View >
223+ < View style = { styles . settings } >
224+ < Touchable
225+ androidRipple = { { } }
226+ underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
227+ style = { styles . settingsButton }
228+ onPress = { ( ) => {
229+ updateKeepSetting ( ! openLastExample ) ;
230+ } } >
231+ < View
232+ style = { styles . settingsButtonContent }
233+ pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
234+ < Text style = { [ styles . text , styles . aligned ] } >
235+ Open last example on launch
236+ </ Text >
237+ < Switch
238+ style = { styles . aligned }
239+ value = { openLastExample }
240+ onValueChange = { ( ) => {
241+ updateKeepSetting ( ! openLastExample ) ;
242+ } }
243+ />
244+ </ View >
245+ </ Touchable >
246+ < Touchable
247+ androidRipple = { { } }
248+ underlayColor = { Platform . OS === 'android' ? 'transparent' : 'black' }
249+ style = { styles . settingsButton }
250+ onPress = { ( ) => {
251+ updateVersionSetting ( ! showLegacyVersion ) ;
252+ } } >
253+ < View
254+ style = { styles . settingsButtonContent }
255+ pointerEvents = { Platform . OS === 'web' ? 'box-only' : 'auto' } >
256+ < Text style = { [ styles . text , styles . aligned ] } >
257+ Show legacy version examples
258+ </ Text >
259+ < Switch
260+ style = { styles . aligned }
261+ value = { showLegacyVersion }
262+ onValueChange = { ( ) => {
263+ updateVersionSetting ( ! showLegacyVersion ) ;
264+ } }
265+ />
266+ </ View >
267+ </ Touchable >
268+ </ View >
205269 </ View >
206270 ) ;
207271 }
@@ -264,6 +328,12 @@ const styles = StyleSheet.create({
264328 separator : {
265329 height : 2 ,
266330 } ,
331+ emptyText : {
332+ paddingHorizontal : 24 ,
333+ paddingVertical : 32 ,
334+ color : COLORS . GRAY ,
335+ textAlign : 'center' ,
336+ } ,
267337 button : {
268338 flex : 1 ,
269339 height : 48 ,
@@ -306,6 +376,26 @@ const styles = StyleSheet.create({
306376 settings : {
307377 flexDirection : 'row' ,
308378 gap : 24 ,
379+ } ,
380+ headerControls : {
381+ gap : 16 ,
309382 margin : 24 ,
310383 } ,
384+ searchBar : {
385+ minHeight : 44 ,
386+ paddingHorizontal : 14 ,
387+ flexDirection : 'row' ,
388+ alignItems : 'center' ,
389+ gap : 10 ,
390+ borderRadius : 12 ,
391+ borderWidth : 1 ,
392+ borderColor : COLORS . headerSeparator ,
393+ backgroundColor : '#fff' ,
394+ } ,
395+ searchInput : {
396+ flex : 1 ,
397+ paddingVertical : Platform . OS === 'web' ? 10 : 8 ,
398+ color : COLORS . NAVY ,
399+ fontSize : 16 ,
400+ } ,
311401} ) ;
0 commit comments