@umituz/react-native-design-system 2.6.85 → 2.6.86
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 -3
- package/src/atoms/AtomicCard.README.md +337 -0
- package/src/atoms/AtomicIcon.README.md +349 -0
- package/src/atoms/GlassView/GlassView.tsx +28 -17
- package/src/atoms/GlassView/README.md +521 -0
- package/src/atoms/button/README.md +363 -0
- package/src/atoms/chip/README.md +376 -0
- package/src/atoms/input/README.md +342 -0
- package/src/atoms/picker/README.md +412 -0
- package/src/molecules/BaseModal.README.md +435 -0
- package/src/molecules/FormField.README.md +486 -0
- package/src/molecules/GlowingCard/README.md +448 -0
- package/src/molecules/SearchBar/README.md +533 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
# SearchBar
|
|
2
|
+
|
|
3
|
+
SearchBar, React Native için modern ve özelleştirilebilir bir arama çubuğu bileşenidir. Material Design prensiplerine uygun olarak tasarlanmıştır.
|
|
4
|
+
|
|
5
|
+
## Özellikler
|
|
6
|
+
|
|
7
|
+
- 🔍 **Arama İkonu**: Sol tarafta arama ikonu
|
|
8
|
+
- ❌ **Clear Button**: Sağ tarafta temizleme butonu
|
|
9
|
+
- ⏳ **Loading State**: Yükleme göstergesi
|
|
10
|
+
- 🎨 **Tema Bilinci**: Tam tema entegrasyonu
|
|
11
|
+
- ⌨️ **Klavye Desteği**: Return key olarak "search"
|
|
12
|
+
- ♿ **Erişilebilir**: Tam erişilebilirlik desteği
|
|
13
|
+
|
|
14
|
+
## Kurulum
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { SearchBar } from 'react-native-design-system';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Temel Kullanım
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
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
|
+
```
|
|
41
|
+
|
|
42
|
+
## Basic Search
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
const [query, setQuery] = useState('');
|
|
46
|
+
|
|
47
|
+
<SearchBar
|
|
48
|
+
value={query}
|
|
49
|
+
onChangeText={setQuery}
|
|
50
|
+
placeholder="Ürün ara..."
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## With Submit Handler
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
const handleSearch = () => {
|
|
58
|
+
console.log('Searching for:', query);
|
|
59
|
+
// Arama yap
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
<SearchBar
|
|
63
|
+
value={query}
|
|
64
|
+
onChangeText={setQuery}
|
|
65
|
+
onSubmit={handleSearch}
|
|
66
|
+
placeholder="Ara..."
|
|
67
|
+
returnKeyType="search"
|
|
68
|
+
/>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Loading State
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
75
|
+
|
|
76
|
+
const handleSearch = async () => {
|
|
77
|
+
setIsSearching(true);
|
|
78
|
+
await performSearch(query);
|
|
79
|
+
setIsSearching(false);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
<SearchBar
|
|
83
|
+
value={query}
|
|
84
|
+
onChangeText={setQuery}
|
|
85
|
+
onSubmit={handleSearch}
|
|
86
|
+
loading={isSearching}
|
|
87
|
+
placeholder="Ara..."
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## With Clear Handler
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
const handleClear = () => {
|
|
95
|
+
setSearchQuery('');
|
|
96
|
+
// Ek işlemler (örn: sonuçları sıfırla)
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
<SearchBar
|
|
100
|
+
value={searchQuery}
|
|
101
|
+
onChangeText={setSearchQuery}
|
|
102
|
+
onClear={handleClear}
|
|
103
|
+
placeholder="Ara..."
|
|
104
|
+
/>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Disabled State
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<SearchBar
|
|
111
|
+
value={query}
|
|
112
|
+
onChangeText={setQuery}
|
|
113
|
+
disabled
|
|
114
|
+
placeholder="Arama devre dışı..."
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Auto Focus
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<SearchBar
|
|
122
|
+
value={query}
|
|
123
|
+
onChangeText={setQuery}
|
|
124
|
+
autoFocus
|
|
125
|
+
placeholder="Ara..."
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Örnek Kullanımlar
|
|
130
|
+
|
|
131
|
+
### Ürün Arama
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import React, { useState, useEffect } from 'react';
|
|
135
|
+
import { View, FlatList, Pressable, Text } from 'react-native';
|
|
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('');
|
|
159
|
+
setResults([]);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return (
|
|
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
|
+
);
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Filtreleme ile Arama
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
export const FilterableSearch = () => {
|
|
233
|
+
const [query, setQuery] = useState('');
|
|
234
|
+
const [selectedFilter, setSelectedFilter] = useState('all');
|
|
235
|
+
|
|
236
|
+
const handleSearch = () => {
|
|
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
|
+
);
|
|
264
|
+
};
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Debounce ile Arama
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { useCallback, useEffect } from 'react';
|
|
271
|
+
|
|
272
|
+
export const DebouncedSearch = () => {
|
|
273
|
+
const [query, setQuery] = useState('');
|
|
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
|
+
);
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Props
|
|
313
|
+
|
|
314
|
+
### SearchBarProps
|
|
315
|
+
|
|
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
|
+
<SearchBar
|
|
336
|
+
value={query}
|
|
337
|
+
onChangeText={setQuery}
|
|
338
|
+
containerStyle={{
|
|
339
|
+
backgroundColor: '#f5f5f5',
|
|
340
|
+
borderWidth: 2,
|
|
341
|
+
borderColor: '#e0e0e0',
|
|
342
|
+
}}
|
|
343
|
+
inputStyle={{
|
|
344
|
+
fontSize: 16,
|
|
345
|
+
fontWeight: '500',
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Best Practices
|
|
351
|
+
|
|
352
|
+
### 1. Debounce Kullanımı
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
// API çağrılarını azaltmak için debounce kullanın
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
const timer = setTimeout(() => {
|
|
358
|
+
if (query.length > 2) {
|
|
359
|
+
performSearch(query);
|
|
360
|
+
}
|
|
361
|
+
}, 500);
|
|
362
|
+
|
|
363
|
+
return () => clearTimeout(timer);
|
|
364
|
+
}, [query]);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 2. Minimum Karakter
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
// En az 3 karakter sonra ara
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
if (query.length > 2) {
|
|
373
|
+
performSearch(query);
|
|
374
|
+
} else {
|
|
375
|
+
setResults([]);
|
|
376
|
+
}
|
|
377
|
+
}, [query]);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 3. Loading State
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
// Kullanıcıya geri bildirim verin
|
|
384
|
+
<SearchBar
|
|
385
|
+
value={query}
|
|
386
|
+
onChangeText={setQuery}
|
|
387
|
+
loading={isSearching}
|
|
388
|
+
onSubmit={handleSearch}
|
|
389
|
+
/>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 4. Clear Handler
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
// Temizleme ile sonuçları sıfırlayın
|
|
396
|
+
const handleClear = () => {
|
|
397
|
+
setQuery('');
|
|
398
|
+
setResults([]);
|
|
399
|
+
setFilters({});
|
|
400
|
+
};
|
|
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
|
+
|
|
415
|
+
1. **Debouncing**: API çağrılarını azaltın
|
|
416
|
+
2. **Minimum Length**: Gereksiz aramaları önleyin
|
|
417
|
+
3. **Cancellation**: Async işlemleri iptal edin
|
|
418
|
+
4. **Memoization**: Sonuçları memoize edin
|
|
419
|
+
|
|
420
|
+
## İlgili Bileşenler
|
|
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
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import React, { useState, useEffect } from 'react';
|
|
430
|
+
import { View, FlatList, Pressable, Text, Image } from 'react-native';
|
|
431
|
+
import { SearchBar } from 'react-native-design-system';
|
|
432
|
+
|
|
433
|
+
export const AdvancedSearch = () => {
|
|
434
|
+
const [query, setQuery] = useState('');
|
|
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
|
+
};
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Lisans
|
|
532
|
+
|
|
533
|
+
MIT
|