@umituz/react-native-design-system 2.6.93 → 2.6.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/atoms/AtomicAvatar.README.md +284 -397
- package/src/atoms/AtomicBadge.README.md +123 -358
- package/src/atoms/AtomicCard.README.md +358 -247
- package/src/atoms/AtomicDatePicker.README.md +127 -332
- package/src/atoms/AtomicFab.README.md +194 -352
- package/src/atoms/AtomicIcon.README.md +241 -274
- package/src/atoms/AtomicProgress.README.md +100 -338
- package/src/atoms/AtomicSpinner.README.md +304 -337
- package/src/atoms/AtomicText.README.md +153 -389
- package/src/atoms/AtomicTextArea.README.md +267 -268
- package/src/atoms/EmptyState.README.md +247 -292
- package/src/atoms/GlassView/README.md +313 -444
- package/src/atoms/button/README.md +186 -297
- package/src/atoms/button/STRATEGY.md +252 -0
- package/src/atoms/chip/README.md +242 -290
- package/src/atoms/input/README.md +296 -290
- package/src/atoms/picker/README.md +278 -309
- package/src/atoms/skeleton/AtomicSkeleton.README.md +394 -252
- package/src/exports/theme.ts +0 -1
- package/src/molecules/BaseModal/README.md +356 -0
- package/src/molecules/BaseModal.README.md +324 -200
- package/src/molecules/ConfirmationModal.README.md +349 -302
- package/src/molecules/Divider/README.md +293 -376
- package/src/molecules/FormField.README.md +321 -534
- package/src/molecules/GlowingCard/GlowingCard.tsx +1 -1
- package/src/molecules/GlowingCard/README.md +230 -372
- package/src/molecules/IconContainer.tsx +1 -1
- package/src/molecules/List/README.md +281 -488
- package/src/molecules/ListItem.README.md +320 -315
- package/src/molecules/SearchBar/README.md +332 -430
- package/src/molecules/StepHeader/README.md +311 -411
- package/src/molecules/StepProgress/README.md +281 -448
- package/src/molecules/alerts/README.md +272 -355
- package/src/molecules/avatar/README.md +295 -356
- package/src/molecules/bottom-sheet/README.md +303 -340
- package/src/molecules/calendar/README.md +301 -265
- package/src/molecules/countdown/README.md +347 -456
- package/src/molecules/emoji/README.md +281 -514
- package/src/molecules/listitem/README.md +307 -399
- package/src/molecules/media-card/MediaCard.tsx +31 -34
- package/src/molecules/media-card/README.md +217 -319
- package/src/molecules/navigation/README.md +263 -284
- package/src/molecules/splash/README.md +76 -92
- package/src/molecules/swipe-actions/README.md +376 -588
|
@@ -1,533 +1,435 @@
|
|
|
1
1
|
# SearchBar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modern and customizable search input component for React Native with built-in loading state, clear button, and theme support.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Import & Usage
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- ♿ **Erişilebilir**: Tam erişilebilirlik desteği
|
|
7
|
+
```typescript
|
|
8
|
+
import { SearchBar } from 'react-native-design-system/src/molecules/SearchBar';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Location:** `src/molecules/SearchBar/SearchBar.tsx`
|
|
13
12
|
|
|
14
|
-
##
|
|
13
|
+
## Basic Usage
|
|
15
14
|
|
|
16
15
|
```tsx
|
|
17
|
-
|
|
16
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
17
|
+
|
|
18
|
+
<SearchBar
|
|
19
|
+
value={searchQuery}
|
|
20
|
+
onChangeText={setSearchQuery}
|
|
21
|
+
placeholder="Search..."
|
|
22
|
+
/>
|
|
18
23
|
```
|
|
19
24
|
|
|
20
|
-
##
|
|
25
|
+
## Strategy
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
import React, { useState } from 'react';
|
|
24
|
-
import { View } from 'react-native';
|
|
25
|
-
import { SearchBar } from 'react-native-design-system';
|
|
26
|
-
|
|
27
|
-
export const BasicExample = () => {
|
|
28
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<View style={{ padding: 16 }}>
|
|
32
|
-
<SearchBar
|
|
33
|
-
value={searchQuery}
|
|
34
|
-
onChangeText={setSearchQuery}
|
|
35
|
-
placeholder="Ara..."
|
|
36
|
-
/>
|
|
37
|
-
</View>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
```
|
|
27
|
+
**Purpose**: Provide a consistent, accessible, and performant search interface for filtering and finding content.
|
|
41
28
|
|
|
42
|
-
|
|
29
|
+
**When to Use**:
|
|
30
|
+
- Searching through lists or datasets
|
|
31
|
+
- Filtering content by keywords
|
|
32
|
+
- Finding specific items (users, products, posts)
|
|
33
|
+
- Global search across multiple content types
|
|
34
|
+
- Autocomplete and suggestion inputs
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
**When NOT to Use**:
|
|
37
|
+
- For simple filtering (use Filter controls instead)
|
|
38
|
+
- For form inputs (use FormField instead)
|
|
39
|
+
- For single-choice selection (use Dropdown/Select instead)
|
|
40
|
+
- For URL navigation (use navigation components instead)
|
|
41
|
+
|
|
42
|
+
## Rules
|
|
43
|
+
|
|
44
|
+
### Required
|
|
45
|
+
|
|
46
|
+
1. **MUST** have `value` and `onChangeText` props
|
|
47
|
+
2. **ALWAYS** provide meaningful placeholder text
|
|
48
|
+
3. **MUST** debounce search input (500ms recommended)
|
|
49
|
+
4. **SHOULD** require minimum query length (2-3 chars)
|
|
50
|
+
5. **ALWAYS** show loading state during search
|
|
51
|
+
6. **MUST** handle empty results gracefully
|
|
52
|
+
7. **SHOULD** provide clear button when input has value
|
|
53
|
+
|
|
54
|
+
### Debouncing
|
|
55
|
+
|
|
56
|
+
1. **MUST** debounce search input to avoid excessive API calls
|
|
57
|
+
2. **Recommended delay**: 500ms
|
|
58
|
+
3. **MUST** cancel pending requests on new input
|
|
59
|
+
4. **ALWAYS** cleanup debounced timers
|
|
60
|
+
|
|
61
|
+
### Minimum Query Length
|
|
62
|
+
|
|
63
|
+
1. **Recommended**: 2-3 characters minimum
|
|
64
|
+
2. **MUST** show feedback if query too short
|
|
65
|
+
3. **SHOULD** not search with 1 character (too many results)
|
|
66
|
+
4. **MUST** handle empty query (clear results)
|
|
46
67
|
|
|
68
|
+
### Loading State
|
|
69
|
+
|
|
70
|
+
1. **MUST** show loading indicator during search
|
|
71
|
+
2. **SHOULD** disable input during search if needed
|
|
72
|
+
3. **MUST** handle search errors gracefully
|
|
73
|
+
4. **ALWAYS** reset loading state after search completes
|
|
74
|
+
|
|
75
|
+
## Forbidden
|
|
76
|
+
|
|
77
|
+
❌ **NEVER** do these:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// ❌ No debouncing
|
|
47
81
|
<SearchBar
|
|
48
82
|
value={query}
|
|
49
|
-
onChangeText={
|
|
50
|
-
|
|
83
|
+
onChangeText={(text) => {
|
|
84
|
+
setQuery(text);
|
|
85
|
+
searchAPI(text); // ❌ API call on every keystroke
|
|
86
|
+
}}
|
|
51
87
|
/>
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## With Submit Handler
|
|
55
88
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
// ❌ No minimum length check
|
|
90
|
+
<SearchBar
|
|
91
|
+
value={query}
|
|
92
|
+
onChangeText={(text) => {
|
|
93
|
+
if (text.length > 0) { // ❌ Searches with 1 character
|
|
94
|
+
performSearch(text);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
61
98
|
|
|
99
|
+
// ❌ No loading state
|
|
62
100
|
<SearchBar
|
|
63
101
|
value={query}
|
|
64
|
-
onChangeText={
|
|
65
|
-
|
|
66
|
-
placeholder="Ara..."
|
|
67
|
-
returnKeyType="search"
|
|
102
|
+
onChangeText={handleSearch}
|
|
103
|
+
// ❌ No loading indicator
|
|
68
104
|
/>
|
|
69
|
-
```
|
|
70
105
|
|
|
71
|
-
|
|
106
|
+
// ❌ Not handling empty results
|
|
107
|
+
const results = await searchAPI(query);
|
|
108
|
+
setResults(results); // ❌ Could be empty array
|
|
72
109
|
|
|
73
|
-
|
|
74
|
-
const
|
|
110
|
+
// ❌ Not clearing results
|
|
111
|
+
const handleClear = () => {
|
|
112
|
+
setQuery('');
|
|
113
|
+
// ❌ Results still showing
|
|
114
|
+
};
|
|
75
115
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
116
|
+
// ❌ No error handling
|
|
117
|
+
const handleSearch = async (query) => {
|
|
118
|
+
setLoading(true);
|
|
119
|
+
const results = await searchAPI(query); // ❌ No try/catch
|
|
120
|
+
setResults(results);
|
|
121
|
+
setLoading(false);
|
|
80
122
|
};
|
|
81
123
|
|
|
124
|
+
// ❌ Auto-focus without context
|
|
82
125
|
<SearchBar
|
|
83
126
|
value={query}
|
|
84
127
|
onChangeText={setQuery}
|
|
85
|
-
|
|
86
|
-
loading={isSearching}
|
|
87
|
-
placeholder="Ara..."
|
|
128
|
+
autoFocus // ❌ Auto-focuses on every render
|
|
88
129
|
/>
|
|
89
130
|
```
|
|
90
131
|
|
|
91
|
-
##
|
|
132
|
+
## Best Practices
|
|
92
133
|
|
|
134
|
+
### Debounced Search
|
|
135
|
+
|
|
136
|
+
✅ **DO**:
|
|
93
137
|
```tsx
|
|
94
|
-
const
|
|
95
|
-
setSearchQuery('');
|
|
96
|
-
// Ek işlemler (örn: sonuçları sıfırla)
|
|
97
|
-
};
|
|
138
|
+
const [query, setQuery] = useState('');
|
|
98
139
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
140
|
+
// Debounce search input
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const timer = setTimeout(() => {
|
|
143
|
+
if (query.length >= 2) {
|
|
144
|
+
performSearch(query);
|
|
145
|
+
}
|
|
146
|
+
}, 500);
|
|
106
147
|
|
|
107
|
-
|
|
148
|
+
return () => clearTimeout(timer);
|
|
149
|
+
}, [query]);
|
|
108
150
|
|
|
109
|
-
```tsx
|
|
110
151
|
<SearchBar
|
|
111
152
|
value={query}
|
|
112
153
|
onChangeText={setQuery}
|
|
113
|
-
|
|
114
|
-
placeholder="Arama devre dışı..."
|
|
154
|
+
placeholder="Search..."
|
|
115
155
|
/>
|
|
116
156
|
```
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
❌ **DON'T**:
|
|
120
159
|
```tsx
|
|
160
|
+
// ❌ No debouncing
|
|
121
161
|
<SearchBar
|
|
122
162
|
value={query}
|
|
123
|
-
onChangeText={
|
|
124
|
-
|
|
125
|
-
|
|
163
|
+
onChangeText={(text) => {
|
|
164
|
+
setQuery(text);
|
|
165
|
+
performSearch(text); // API call on every keystroke
|
|
166
|
+
}}
|
|
126
167
|
/>
|
|
127
168
|
```
|
|
128
169
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
### Ürün Arama
|
|
170
|
+
### Minimum Query Length
|
|
132
171
|
|
|
172
|
+
✅ **DO**:
|
|
133
173
|
```tsx
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
import { SearchBar } from 'react-native-design-system';
|
|
137
|
-
|
|
138
|
-
export const ProductSearch = () => {
|
|
139
|
-
const [query, setQuery] = useState('');
|
|
140
|
-
const [loading, setLoading] = useState(false);
|
|
141
|
-
const [results, setResults] = useState([]);
|
|
142
|
-
|
|
143
|
-
const handleSearch = async () => {
|
|
144
|
-
if (!query.trim()) return;
|
|
145
|
-
|
|
146
|
-
setLoading(true);
|
|
147
|
-
try {
|
|
148
|
-
const response = await fetchProducts(query);
|
|
149
|
-
setResults(response);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.error('Search error:', error);
|
|
152
|
-
} finally {
|
|
153
|
-
setLoading(false);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const handleClear = () => {
|
|
158
|
-
setQuery('');
|
|
174
|
+
const handleSearch = (text) => {
|
|
175
|
+
if (text.length < 2) {
|
|
159
176
|
setResults([]);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<View style={{ flex: 1, padding: 16 }}>
|
|
164
|
-
<SearchBar
|
|
165
|
-
value={query}
|
|
166
|
-
onChangeText={setQuery}
|
|
167
|
-
onSubmit={handleSearch}
|
|
168
|
-
onClear={handleClear}
|
|
169
|
-
loading={loading}
|
|
170
|
-
placeholder="Ürün ara..."
|
|
171
|
-
/>
|
|
172
|
-
|
|
173
|
-
<FlatList
|
|
174
|
-
data={results}
|
|
175
|
-
keyExtractor={(item) => item.id}
|
|
176
|
-
renderItem={({ item }) => (
|
|
177
|
-
<Pressable style={{ padding: 16, borderBottomWidth: 1 }}>
|
|
178
|
-
<Text>{item.name}</Text>
|
|
179
|
-
</Pressable>
|
|
180
|
-
)}
|
|
181
|
-
/>
|
|
182
|
-
</View>
|
|
183
|
-
);
|
|
184
|
-
};
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Kullanıcı Arama
|
|
188
|
-
|
|
189
|
-
```tsx
|
|
190
|
-
export const UserSearch = () => {
|
|
191
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
192
|
-
const [users, setUsers] = useState([]);
|
|
193
|
-
const [loading, setLoading] = useState(false);
|
|
194
|
-
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
if (searchQuery.length > 2) {
|
|
197
|
-
searchUsers(searchQuery);
|
|
198
|
-
} else {
|
|
199
|
-
setUsers([]);
|
|
200
|
-
}
|
|
201
|
-
}, [searchQuery]);
|
|
202
|
-
|
|
203
|
-
const searchUsers = async (query) => {
|
|
204
|
-
setLoading(true);
|
|
205
|
-
const results = await fetchUsers(query);
|
|
206
|
-
setUsers(results);
|
|
207
|
-
setLoading(false);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<View style={{ padding: 16 }}>
|
|
212
|
-
<SearchBar
|
|
213
|
-
value={searchQuery}
|
|
214
|
-
onChangeText={setSearchQuery}
|
|
215
|
-
loading={loading}
|
|
216
|
-
placeholder="Kullanıcı ara..."
|
|
217
|
-
/>
|
|
218
|
-
|
|
219
|
-
{users.map((user) => (
|
|
220
|
-
<View key={user.id} style={{ padding: 16 }}>
|
|
221
|
-
<Text>{user.name}</Text>
|
|
222
|
-
</View>
|
|
223
|
-
))}
|
|
224
|
-
</View>
|
|
225
|
-
);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
performSearch(text);
|
|
226
180
|
};
|
|
227
181
|
```
|
|
228
182
|
|
|
229
|
-
|
|
230
|
-
|
|
183
|
+
❌ **DON'T**:
|
|
231
184
|
```tsx
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Seçili filtreye göre arama
|
|
238
|
-
console.log(`Searching for "${query}" in ${selectedFilter}`);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<View style={{ padding: 16 }}>
|
|
243
|
-
<SearchBar
|
|
244
|
-
value={query}
|
|
245
|
-
onChangeText={setQuery}
|
|
246
|
-
onSubmit={handleSearch}
|
|
247
|
-
placeholder={`${selectedFilter === 'all' ? 'Tümü' : selectedFilter} ara...`}
|
|
248
|
-
/>
|
|
249
|
-
|
|
250
|
-
{/* Filtre seçimi */}
|
|
251
|
-
<View style={{ flexDirection: 'row', marginTop: 16, gap: 8 }}>
|
|
252
|
-
<Pressable onPress={() => setSelectedFilter('all')}>
|
|
253
|
-
<Text>Tümü</Text>
|
|
254
|
-
</Pressable>
|
|
255
|
-
<Pressable onPress={() => setSelectedFilter('users')}>
|
|
256
|
-
<Text>Kullanıcılar</Text>
|
|
257
|
-
</Pressable>
|
|
258
|
-
<Pressable onPress={() => setSelectedFilter('products')}>
|
|
259
|
-
<Text>Ürünler</Text>
|
|
260
|
-
</Pressable>
|
|
261
|
-
</View>
|
|
262
|
-
</View>
|
|
263
|
-
);
|
|
185
|
+
// ❌ Searches with 1 character
|
|
186
|
+
const handleSearch = (text) => {
|
|
187
|
+
if (text.length > 0) {
|
|
188
|
+
performSearch(text); // Too many results
|
|
189
|
+
}
|
|
264
190
|
};
|
|
265
191
|
```
|
|
266
192
|
|
|
267
|
-
###
|
|
193
|
+
### Clear Handler
|
|
268
194
|
|
|
195
|
+
✅ **DO**:
|
|
269
196
|
```tsx
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const [debouncedQuery, setDebouncedQuery] = useState('');
|
|
275
|
-
const [loading, setLoading] = useState(false);
|
|
276
|
-
|
|
277
|
-
// Debounce
|
|
278
|
-
useEffect(() => {
|
|
279
|
-
const timer = setTimeout(() => {
|
|
280
|
-
setDebouncedQuery(query);
|
|
281
|
-
}, 500);
|
|
282
|
-
|
|
283
|
-
return () => clearTimeout(timer);
|
|
284
|
-
}, [query]);
|
|
285
|
-
|
|
286
|
-
// Arama
|
|
287
|
-
useEffect(() => {
|
|
288
|
-
if (debouncedQuery) {
|
|
289
|
-
performSearch(debouncedQuery);
|
|
290
|
-
}
|
|
291
|
-
}, [debouncedQuery]);
|
|
292
|
-
|
|
293
|
-
const performSearch = async (searchQuery) => {
|
|
294
|
-
setLoading(true);
|
|
295
|
-
await fetch(`/api/search?q=${searchQuery}`);
|
|
296
|
-
setLoading(false);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<View style={{ padding: 16 }}>
|
|
301
|
-
<SearchBar
|
|
302
|
-
value={query}
|
|
303
|
-
onChangeText={setQuery}
|
|
304
|
-
loading={loading}
|
|
305
|
-
placeholder="Ara..."
|
|
306
|
-
/>
|
|
307
|
-
</View>
|
|
308
|
-
);
|
|
197
|
+
const handleClear = () => {
|
|
198
|
+
setQuery('');
|
|
199
|
+
setResults([]);
|
|
200
|
+
onClear?.();
|
|
309
201
|
};
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
## Props
|
|
313
|
-
|
|
314
|
-
### SearchBarProps
|
|
315
202
|
|
|
316
|
-
| Prop | Tip | Varsayılan | Açıklama |
|
|
317
|
-
|------|-----|------------|----------|
|
|
318
|
-
| `value` | `string` | - **(Zorunlu)** | Arama sorgusu |
|
|
319
|
-
| `onChangeText` | `(text: string) => void` | - **(Zorunlu)** | Değişiklik olayı |
|
|
320
|
-
| `onSubmit` | `() => void` | - | Submit olayı |
|
|
321
|
-
| `onClear` | `() => void` | - | Temizleme olayı |
|
|
322
|
-
| `onFocus` | `() => void` | - | Focus olayı |
|
|
323
|
-
| `onBlur` | `() => void` | - | Blur olayı |
|
|
324
|
-
| `placeholder` | `string` | `'Search...'` | Placeholder metni |
|
|
325
|
-
| `autoFocus` | `boolean` | `false` | Otomatik odak |
|
|
326
|
-
| `loading` | `boolean` | `false` | Yükleme durumu |
|
|
327
|
-
| `disabled` | `boolean` | `false` | Devre dışı |
|
|
328
|
-
| `containerStyle` | `ViewStyle` | - | Container stil |
|
|
329
|
-
| `inputStyle` | `TextStyle` | - | Input stil |
|
|
330
|
-
| `testID` | `string` | - | Test ID'si |
|
|
331
|
-
|
|
332
|
-
## Stil Özelleştirme
|
|
333
|
-
|
|
334
|
-
```tsx
|
|
335
203
|
<SearchBar
|
|
336
204
|
value={query}
|
|
337
205
|
onChangeText={setQuery}
|
|
338
|
-
|
|
339
|
-
backgroundColor: '#f5f5f5',
|
|
340
|
-
borderWidth: 2,
|
|
341
|
-
borderColor: '#e0e0e0',
|
|
342
|
-
}}
|
|
343
|
-
inputStyle={{
|
|
344
|
-
fontSize: 16,
|
|
345
|
-
fontWeight: '500',
|
|
346
|
-
}}
|
|
206
|
+
onClear={handleClear}
|
|
347
207
|
/>
|
|
348
208
|
```
|
|
349
209
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
210
|
+
❌ **DON'T**:
|
|
211
|
+
```tsx
|
|
212
|
+
// ❌ Doesn't clear results
|
|
213
|
+
const handleClear = () => {
|
|
214
|
+
setQuery('');
|
|
215
|
+
// Results still showing
|
|
216
|
+
};
|
|
217
|
+
```
|
|
353
218
|
|
|
219
|
+
## AI Coding Guidelines
|
|
220
|
+
|
|
221
|
+
### For AI Agents
|
|
222
|
+
|
|
223
|
+
When generating SearchBar components, follow these rules:
|
|
224
|
+
|
|
225
|
+
1. **Always import from correct path**:
|
|
226
|
+
```typescript
|
|
227
|
+
import { SearchBar } from 'react-native-design-system/src/molecules/SearchBar';
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
2. **Always debounce search input**:
|
|
231
|
+
```tsx
|
|
232
|
+
// ✅ Good - debounced search
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
const timer = setTimeout(() => {
|
|
235
|
+
if (query.length >= 2) {
|
|
236
|
+
performSearch(query);
|
|
237
|
+
}
|
|
238
|
+
}, 500);
|
|
239
|
+
|
|
240
|
+
return () => clearTimeout(timer);
|
|
241
|
+
}, [query]);
|
|
242
|
+
|
|
243
|
+
// ❌ Bad - no debouncing
|
|
244
|
+
const handleChange = (text) => {
|
|
245
|
+
setQuery(text);
|
|
246
|
+
performSearch(text); // API call on every keystroke
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
3. **Always require minimum query length**:
|
|
251
|
+
```tsx
|
|
252
|
+
// ✅ Good - minimum length check
|
|
253
|
+
if (query.length < 2) {
|
|
254
|
+
setResults([]);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ❌ Bad - searches immediately
|
|
259
|
+
if (query.length > 0) {
|
|
260
|
+
performSearch(query);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
4. **Always show loading state**:
|
|
265
|
+
```tsx
|
|
266
|
+
// ✅ Good - loading state
|
|
267
|
+
const [loading, setLoading] = useState(false);
|
|
268
|
+
|
|
269
|
+
const handleSearch = async (query) => {
|
|
270
|
+
if (query.length < 2) return;
|
|
271
|
+
|
|
272
|
+
setLoading(true);
|
|
273
|
+
try {
|
|
274
|
+
const results = await searchAPI(query);
|
|
275
|
+
setResults(results);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error('Search failed:', error);
|
|
278
|
+
} finally {
|
|
279
|
+
setLoading(false);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
<SearchBar
|
|
284
|
+
value={query}
|
|
285
|
+
onChangeText={setQuery}
|
|
286
|
+
loading={loading}
|
|
287
|
+
/>;
|
|
288
|
+
|
|
289
|
+
// ❌ Bad - no loading state
|
|
290
|
+
const handleSearch = async (query) => {
|
|
291
|
+
const results = await searchAPI(query);
|
|
292
|
+
setResults(results);
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
5. **Always handle clear properly**:
|
|
297
|
+
```tsx
|
|
298
|
+
// ✅ Good - clears everything
|
|
299
|
+
const handleClear = () => {
|
|
300
|
+
setQuery('');
|
|
301
|
+
setResults([]);
|
|
302
|
+
setError(null);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// ❌ Bad - only clears input
|
|
306
|
+
const handleClear = () => {
|
|
307
|
+
setQuery('');
|
|
308
|
+
// Results still showing
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Common Patterns
|
|
313
|
+
|
|
314
|
+
#### Basic Search
|
|
354
315
|
```tsx
|
|
355
|
-
|
|
316
|
+
const [query, setQuery] = useState('');
|
|
317
|
+
|
|
356
318
|
useEffect(() => {
|
|
357
319
|
const timer = setTimeout(() => {
|
|
358
|
-
if (query.length
|
|
320
|
+
if (query.length >= 2) {
|
|
359
321
|
performSearch(query);
|
|
360
322
|
}
|
|
361
323
|
}, 500);
|
|
362
324
|
|
|
363
325
|
return () => clearTimeout(timer);
|
|
364
326
|
}, [query]);
|
|
365
|
-
```
|
|
366
327
|
|
|
367
|
-
|
|
328
|
+
<SearchBar
|
|
329
|
+
value={query}
|
|
330
|
+
onChangeText={setQuery}
|
|
331
|
+
placeholder="Search..."
|
|
332
|
+
/>
|
|
333
|
+
```
|
|
368
334
|
|
|
335
|
+
#### Search with Loading
|
|
369
336
|
```tsx
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
337
|
+
const [query, setQuery] = useState('');
|
|
338
|
+
const [loading, setLoading] = useState(false);
|
|
339
|
+
|
|
340
|
+
const handleSearch = async (text) => {
|
|
341
|
+
setQuery(text);
|
|
342
|
+
|
|
343
|
+
if (text.length < 2) {
|
|
375
344
|
setResults([]);
|
|
345
|
+
return;
|
|
376
346
|
}
|
|
377
|
-
}, [query]);
|
|
378
|
-
```
|
|
379
347
|
|
|
380
|
-
|
|
348
|
+
setLoading(true);
|
|
349
|
+
try {
|
|
350
|
+
const results = await searchAPI(text);
|
|
351
|
+
setResults(results);
|
|
352
|
+
} finally {
|
|
353
|
+
setLoading(false);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
381
356
|
|
|
382
|
-
```tsx
|
|
383
|
-
// Kullanıcıya geri bildirim verin
|
|
384
357
|
<SearchBar
|
|
385
358
|
value={query}
|
|
386
|
-
onChangeText={
|
|
387
|
-
loading={
|
|
388
|
-
|
|
359
|
+
onChangeText={handleSearch}
|
|
360
|
+
loading={loading}
|
|
361
|
+
placeholder="Search..."
|
|
389
362
|
/>
|
|
390
363
|
```
|
|
391
364
|
|
|
392
|
-
|
|
393
|
-
|
|
365
|
+
#### Search with Clear Handler
|
|
394
366
|
```tsx
|
|
395
|
-
|
|
367
|
+
const [query, setQuery] = useState('');
|
|
368
|
+
const [results, setResults] = useState([]);
|
|
369
|
+
|
|
396
370
|
const handleClear = () => {
|
|
397
371
|
setQuery('');
|
|
398
372
|
setResults([]);
|
|
399
|
-
setFilters({});
|
|
400
373
|
};
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
## Erişilebilirlik
|
|
404
|
-
|
|
405
|
-
SearchBar, tam erişilebilirlik desteği sunar:
|
|
406
|
-
|
|
407
|
-
- ✅ Screen reader desteği
|
|
408
|
-
- ✅ Accessibility label
|
|
409
|
-
- ✅ Touch uygun boyut
|
|
410
|
-
- ✅ Keyboard navigation
|
|
411
|
-
- ✅ Test ID desteği
|
|
412
|
-
|
|
413
|
-
## Performans İpuçları
|
|
414
374
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
- [`AtomicInput`](../../atoms/input/README.md) - Input bileşeni
|
|
423
|
-
- [`BaseModal`](../BaseModal/README.md) - Modal arama sonuçları
|
|
424
|
-
- [`AtomicIcon`](../../atoms/AtomicIcon/README.md) - İkon bileşeni
|
|
425
|
-
|
|
426
|
-
## Örnek Proje
|
|
375
|
+
<SearchBar
|
|
376
|
+
value={query}
|
|
377
|
+
onChangeText={setQuery}
|
|
378
|
+
onClear={handleClear}
|
|
379
|
+
placeholder="Search..."
|
|
380
|
+
/>
|
|
381
|
+
```
|
|
427
382
|
|
|
383
|
+
#### Auto-Focus Search
|
|
428
384
|
```tsx
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const [loading, setLoading] = useState(false);
|
|
436
|
-
const [results, setResults] = useState([]);
|
|
437
|
-
const [history, setHistory] = useState([]);
|
|
438
|
-
|
|
439
|
-
useEffect(() => {
|
|
440
|
-
const timer = setTimeout(() => {
|
|
441
|
-
if (query.length > 2) {
|
|
442
|
-
performSearch(query);
|
|
443
|
-
}
|
|
444
|
-
}, 500);
|
|
445
|
-
|
|
446
|
-
return () => clearTimeout(timer);
|
|
447
|
-
}, [query]);
|
|
448
|
-
|
|
449
|
-
const performSearch = async (searchQuery) => {
|
|
450
|
-
setLoading(true);
|
|
451
|
-
try {
|
|
452
|
-
const response = await fetch(`/api/search?q=${searchQuery}`);
|
|
453
|
-
const data = await response.json();
|
|
454
|
-
setResults(data);
|
|
455
|
-
|
|
456
|
-
// Geçmişe ekle
|
|
457
|
-
setHistory(prev => [searchQuery, ...prev.slice(0, 9)]);
|
|
458
|
-
} catch (error) {
|
|
459
|
-
console.error('Search error:', error);
|
|
460
|
-
} finally {
|
|
461
|
-
setLoading(false);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const handleClear = () => {
|
|
466
|
-
setQuery('');
|
|
467
|
-
setResults([]);
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
return (
|
|
471
|
-
<View style={{ flex: 1, padding: 16 }}>
|
|
472
|
-
<SearchBar
|
|
473
|
-
value={query}
|
|
474
|
-
onChangeText={setQuery}
|
|
475
|
-
onClear={handleClear}
|
|
476
|
-
loading={loading}
|
|
477
|
-
placeholder="Ara..."
|
|
478
|
-
/>
|
|
479
|
-
|
|
480
|
-
{/* Arama Geçmişi */}
|
|
481
|
-
{query.length === 0 && history.length > 0 && (
|
|
482
|
-
<View style={{ marginTop: 16 }}>
|
|
483
|
-
<Text style={{ marginBottom: 8, fontWeight: '600' }}>
|
|
484
|
-
Son Aramalar
|
|
485
|
-
</Text>
|
|
486
|
-
{history.map((item, index) => (
|
|
487
|
-
<Pressable
|
|
488
|
-
key={index}
|
|
489
|
-
onPress={() => setQuery(item)}
|
|
490
|
-
style={{ padding: 12 }}
|
|
491
|
-
>
|
|
492
|
-
<Text>{item}</Text>
|
|
493
|
-
</Pressable>
|
|
494
|
-
))}
|
|
495
|
-
</View>
|
|
496
|
-
)}
|
|
497
|
-
|
|
498
|
-
{/* Arama Sonuçları */}
|
|
499
|
-
<FlatList
|
|
500
|
-
data={results}
|
|
501
|
-
keyExtractor={(item) => item.id}
|
|
502
|
-
renderItem={({ item }) => (
|
|
503
|
-
<Pressable
|
|
504
|
-
style={{
|
|
505
|
-
flexDirection: 'row',
|
|
506
|
-
padding: 16,
|
|
507
|
-
borderBottomWidth: 1,
|
|
508
|
-
borderBottomColor: '#e0e0e0',
|
|
509
|
-
}}
|
|
510
|
-
>
|
|
511
|
-
<Image
|
|
512
|
-
source={{ uri: item.image }}
|
|
513
|
-
style={{ width: 50, height: 50, borderRadius: 8 }}
|
|
514
|
-
/>
|
|
515
|
-
<View style={{ marginLeft: 12, flex: 1 }}>
|
|
516
|
-
<Text style={{ fontSize: 16, fontWeight: '600' }}>
|
|
517
|
-
{item.title}
|
|
518
|
-
</Text>
|
|
519
|
-
<Text style={{ color: 'gray', marginTop: 4 }}>
|
|
520
|
-
{item.description}
|
|
521
|
-
</Text>
|
|
522
|
-
</View>
|
|
523
|
-
</Pressable>
|
|
524
|
-
)}
|
|
525
|
-
/>
|
|
526
|
-
</View>
|
|
527
|
-
);
|
|
528
|
-
};
|
|
385
|
+
<SearchBar
|
|
386
|
+
value={query}
|
|
387
|
+
onChangeText={setQuery}
|
|
388
|
+
autoFocus
|
|
389
|
+
placeholder="Search..."
|
|
390
|
+
/>
|
|
529
391
|
```
|
|
530
392
|
|
|
531
|
-
##
|
|
393
|
+
## Props Reference
|
|
394
|
+
|
|
395
|
+
| Prop | Type | Required | Default | Description |
|
|
396
|
+
|------|------|----------|---------|-------------|
|
|
397
|
+
| `value` | `string` | Yes | - | Search query value |
|
|
398
|
+
| `onChangeText` | `(text: string) => void` | Yes | - | Change callback |
|
|
399
|
+
| `placeholder` | `string` | No | `'Search...'` | Placeholder text |
|
|
400
|
+
| `onSubmit` | `() => void` | No | - | Submit callback |
|
|
401
|
+
| `onClear` | `() => void` | No | - | Clear callback |
|
|
402
|
+
| `onFocus` | `() => void` | No | - | Focus callback |
|
|
403
|
+
| `onBlur` | `() => void` | No | - | Blur callback |
|
|
404
|
+
| `autoFocus` | `boolean` | No | `false` | Auto focus input |
|
|
405
|
+
| `loading` | `boolean` | No | `false` | Show loading indicator |
|
|
406
|
+
| `disabled` | `boolean` | No | `false` | Disable input |
|
|
407
|
+
| `containerStyle` | `ViewStyle` | No | - | Custom container style |
|
|
408
|
+
| `inputStyle` | `TextStyle` | No | - | Custom input style |
|
|
409
|
+
|
|
410
|
+
## Accessibility
|
|
411
|
+
|
|
412
|
+
- ✅ Screen reader announces search input and placeholder
|
|
413
|
+
- ✅ Touch target size maintained (min 44x44pt)
|
|
414
|
+
- ✅ Keyboard navigation (web)
|
|
415
|
+
- ✅ Focus management
|
|
416
|
+
- ✅ Semantic search role
|
|
417
|
+
- ✅ Loading state announced to screen readers
|
|
418
|
+
|
|
419
|
+
## Performance Tips
|
|
420
|
+
|
|
421
|
+
1. **Debounce**: Always debounce with 500ms delay
|
|
422
|
+
2. **Minimum length**: Require 2-3 characters minimum
|
|
423
|
+
3. **Cancel requests**: Cancel pending requests on new input
|
|
424
|
+
4. **Memo results**: Memo search results to prevent re-renders
|
|
425
|
+
5. **Virtualization**: Use FlatList for large result sets
|
|
426
|
+
|
|
427
|
+
## Related Components
|
|
428
|
+
|
|
429
|
+
- [`FormField`](../FormField/README.md) - Form field component
|
|
430
|
+
- [`ListItem`](../ListItem/README.md) - List item for search results
|
|
431
|
+
- [`Button`](../../atoms/button/README.md) - Button component
|
|
432
|
+
|
|
433
|
+
## License
|
|
532
434
|
|
|
533
435
|
MIT
|