Skip to content

Commit af04cd4

Browse files
j-piaseckiCopilot
andauthored
[Example] Add search bar to the main screen (#4314)
## Description Adds a seach bar that allows filtering examples by name ## Test plan https://github.com/user-attachments/assets/b0b29e57-3299-4b0f-897d-15a01ab3c63d --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 17d3df8 commit af04cd4

1 file changed

Lines changed: 139 additions & 49 deletions

File tree

apps/common-app/App.tsx

Lines changed: 139 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import type {
1010
import { createStackNavigator } from '@react-navigation/stack';
1111
import { Icon } from '@swmansion/icons';
1212
import { 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';
1422
import {
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

Comments
 (0)