@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,568 +1,361 @@
|
|
|
1
1
|
# List
|
|
2
2
|
|
|
3
|
-
List
|
|
3
|
+
List is a responsive FlatList wrapper component with pull-to-refresh support, content padding, and theme-aware colors.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Import & Usage
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- 📏 **Content Padding**: Responsive içerik padding'i
|
|
10
|
-
- 🎨 **Theme-Aware**: Design token uyumlu
|
|
11
|
-
- ⚡ **Performanslı**: Optimize edilmiş liste performansı
|
|
12
|
-
- ♿ **Erişilebilir**: Tam erişilebilirlik desteği
|
|
13
|
-
|
|
14
|
-
## Kurulum
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
import { List } from 'react-native-design-system';
|
|
7
|
+
```typescript
|
|
8
|
+
import { List } from 'react-native-design-system/src/molecules/List';
|
|
18
9
|
```
|
|
19
10
|
|
|
20
|
-
|
|
11
|
+
**Location:** `src/molecules/List/List.tsx`
|
|
21
12
|
|
|
22
|
-
|
|
23
|
-
import React from 'react';
|
|
24
|
-
import { View } from 'react-native';
|
|
25
|
-
import { List } from 'react-native-design-system';
|
|
26
|
-
|
|
27
|
-
export const BasicExample = () => {
|
|
28
|
-
const data = [
|
|
29
|
-
{ id: '1', title: 'Öğe 1' },
|
|
30
|
-
{ id: '2', title: 'Öğe 2' },
|
|
31
|
-
{ id: '3', title: 'Öğe 3' },
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<List
|
|
36
|
-
data={data}
|
|
37
|
-
renderItem={({ item }) => (
|
|
38
|
-
<View style={{ padding: 16 }}>
|
|
39
|
-
<AtomicText>{item.title}</AtomicText>
|
|
40
|
-
</View>
|
|
41
|
-
)}
|
|
42
|
-
keyExtractor={(item) => item.id}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Basit Liste
|
|
13
|
+
## Basic Usage
|
|
49
14
|
|
|
50
15
|
```tsx
|
|
51
|
-
const users = [
|
|
52
|
-
{ id: '1', name: 'Ahmet Yılmaz' },
|
|
53
|
-
{ id: '2', name: 'Ayşe Demir' },
|
|
54
|
-
{ id: '3', name: 'Mehmet Kaya' },
|
|
55
|
-
];
|
|
56
|
-
|
|
57
16
|
<List
|
|
58
|
-
data={
|
|
59
|
-
renderItem={({ item }) =>
|
|
60
|
-
<ListItem title={item.name} />
|
|
61
|
-
)}
|
|
17
|
+
data={items}
|
|
18
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
62
19
|
keyExtractor={(item) => item.id}
|
|
63
20
|
/>
|
|
64
21
|
```
|
|
65
22
|
|
|
66
|
-
##
|
|
23
|
+
## Strategy
|
|
67
24
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<List
|
|
82
|
-
data={data}
|
|
83
|
-
renderItem={({ item }) => <ItemCard item={item} />}
|
|
84
|
-
keyExtractor={(item) => item.id}
|
|
85
|
-
onRefresh={onRefresh}
|
|
86
|
-
refreshing={refreshing}
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
```
|
|
25
|
+
**Purpose**: Provide a performant, theme-aware list component that wraps React Native's FlatList with sensible defaults.
|
|
26
|
+
|
|
27
|
+
**When to Use**:
|
|
28
|
+
- Displaying scrolling lists of data
|
|
29
|
+
- Rendering large datasets efficiently
|
|
30
|
+
- Implementing pull-to-refresh functionality
|
|
31
|
+
- Creating infinite scroll lists
|
|
32
|
+
|
|
33
|
+
**When NOT to Use**:
|
|
34
|
+
- For small static lists - use ScrollView or map
|
|
35
|
+
- For simple layouts - use FlatList directly
|
|
36
|
+
- For grid layouts - use FlatList with numColumns
|
|
91
37
|
|
|
92
|
-
##
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
### Required
|
|
41
|
+
|
|
42
|
+
1. **ALWAYS** provide `data` array
|
|
43
|
+
2. **MUST** provide `renderItem` function
|
|
44
|
+
3. **ALWAYS** provide `keyExtractor` function
|
|
45
|
+
4. **NEVER** use array index as key
|
|
46
|
+
5. **MUST** handle empty state
|
|
47
|
+
|
|
48
|
+
### Performance
|
|
49
|
+
|
|
50
|
+
1. **ALWAYS** use stable keys from data
|
|
51
|
+
2. **MUST** memoize renderItem with useCallback
|
|
52
|
+
3. **SHOULD** use `getItemLayout` for fixed-size items
|
|
53
|
+
4. **NEVER** inline renderItem function
|
|
54
|
+
|
|
55
|
+
### Key Extraction
|
|
56
|
+
|
|
57
|
+
1. **ALWAYS** use unique IDs from data
|
|
58
|
+
2. **NEVER** use array index as key
|
|
59
|
+
3. **MUST** be stable across re-renders
|
|
60
|
+
4. **SHOULD** be unique across all items
|
|
61
|
+
|
|
62
|
+
## Forbidden
|
|
63
|
+
|
|
64
|
+
❌ **NEVER** do these:
|
|
93
65
|
|
|
94
66
|
```tsx
|
|
67
|
+
// ❌ Missing required props
|
|
68
|
+
<List /> {/* Missing data, renderItem, keyExtractor */}
|
|
69
|
+
|
|
70
|
+
// ❌ Using index as key
|
|
95
71
|
<List
|
|
96
72
|
data={items}
|
|
97
73
|
renderItem={({ item }) => <ItemCard item={item} />}
|
|
74
|
+
keyExtractor={(item, index) => index.toString()} // ❌ Bad
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
// ❌ Inline renderItem
|
|
78
|
+
<List
|
|
79
|
+
data={items}
|
|
80
|
+
renderItem={({ item }) => ( // ❌ Not memoized
|
|
81
|
+
<ItemCard item={item} />
|
|
82
|
+
)}
|
|
98
83
|
keyExtractor={(item) => item.id}
|
|
99
|
-
contentPadding
|
|
100
84
|
/>
|
|
101
|
-
```
|
|
102
85
|
|
|
103
|
-
|
|
86
|
+
// ❌ No keyExtractor
|
|
87
|
+
<List
|
|
88
|
+
data={items}
|
|
89
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
90
|
+
// Missing keyExtractor ❌
|
|
91
|
+
/>
|
|
104
92
|
|
|
105
|
-
|
|
93
|
+
// ❌ No empty state
|
|
94
|
+
<List
|
|
95
|
+
data={[]} // Empty but no empty state shown
|
|
96
|
+
renderItem={renderItem}
|
|
97
|
+
keyExtractor={keyExtractor}
|
|
98
|
+
/>
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
const renderUser = ({ item }) => (
|
|
116
|
-
<ListItem
|
|
117
|
-
title={item.name}
|
|
118
|
-
subtitle={item.email}
|
|
119
|
-
left={() => (
|
|
120
|
-
<Avatar
|
|
121
|
-
uri={item.avatar}
|
|
122
|
-
name={item.name}
|
|
123
|
-
size="md"
|
|
124
|
-
/>
|
|
125
|
-
)}
|
|
126
|
-
onPress={() => navigation.navigate('UserProfile', { userId: item.id })}
|
|
127
|
-
/>
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<List
|
|
132
|
-
data={users}
|
|
133
|
-
renderItem={renderUser}
|
|
134
|
-
keyExtractor={(item) => item.id}
|
|
135
|
-
contentPadding
|
|
136
|
-
/>
|
|
137
|
-
);
|
|
138
|
-
};
|
|
100
|
+
// ❌ Wrong data type
|
|
101
|
+
<List
|
|
102
|
+
data="not an array" // ❌ Should be array
|
|
103
|
+
renderItem={renderItem}
|
|
104
|
+
keyExtractor={keyExtractor}
|
|
105
|
+
/>
|
|
139
106
|
```
|
|
140
107
|
|
|
141
|
-
|
|
108
|
+
## Best Practices
|
|
109
|
+
|
|
110
|
+
### Key Extraction
|
|
142
111
|
|
|
112
|
+
✅ **DO**:
|
|
143
113
|
```tsx
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const [loading, setLoading] = useState(false);
|
|
147
|
-
const [page, setPage] = useState(1);
|
|
148
|
-
|
|
149
|
-
const loadMore = async () => {
|
|
150
|
-
if (loading) return;
|
|
151
|
-
setLoading(true);
|
|
152
|
-
const newData = await fetchItems(page);
|
|
153
|
-
setData([...data, ...newData]);
|
|
154
|
-
setPage(page + 1);
|
|
155
|
-
setLoading(false);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const renderFooter = () => {
|
|
159
|
-
if (!loading) return null;
|
|
160
|
-
return (
|
|
161
|
-
<View style={{ padding: 16 }}>
|
|
162
|
-
<AtomicSpinner size="md" />
|
|
163
|
-
</View>
|
|
164
|
-
);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<List
|
|
169
|
-
data={data}
|
|
170
|
-
renderItem={({ item }) => <ItemCard item={item} />}
|
|
171
|
-
keyExtractor={(item) => item.id}
|
|
172
|
-
onEndReached={loadMore}
|
|
173
|
-
onEndReachedThreshold={0.5}
|
|
174
|
-
ListFooterComponent={renderFooter}
|
|
175
|
-
contentPadding
|
|
176
|
-
/>
|
|
177
|
-
);
|
|
178
|
-
};
|
|
179
|
-
```
|
|
114
|
+
// Use unique ID
|
|
115
|
+
keyExtractor={(item) => item.id}
|
|
180
116
|
|
|
181
|
-
|
|
117
|
+
// Use composite key
|
|
118
|
+
keyExtractor={(item) => `${item.type}-${item.id}`}
|
|
182
119
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const [products, setProducts] = useState([]);
|
|
186
|
-
const [refreshing, setRefreshing] = useState(false);
|
|
187
|
-
|
|
188
|
-
const onRefresh = async () => {
|
|
189
|
-
setRefreshing(true);
|
|
190
|
-
const data = await fetchProducts();
|
|
191
|
-
setProducts(data);
|
|
192
|
-
setRefreshing(false);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const renderProduct = ({ item }) => (
|
|
196
|
-
<MediaCard
|
|
197
|
-
uri={item.image}
|
|
198
|
-
title={item.name}
|
|
199
|
-
subtitle={`${item.price} TL`}
|
|
200
|
-
onPress={() => navigation.navigate('ProductDetail', { productId: item.id })}
|
|
201
|
-
/>
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
return (
|
|
205
|
-
<List
|
|
206
|
-
data={products}
|
|
207
|
-
renderItem={renderProduct}
|
|
208
|
-
keyExtractor={(item) => item.id}
|
|
209
|
-
onRefresh={onRefresh}
|
|
210
|
-
refreshing={refreshing}
|
|
211
|
-
numColumns={2}
|
|
212
|
-
columnWrapperStyle={{ gap: 8 }}
|
|
213
|
-
contentPadding
|
|
214
|
-
/>
|
|
215
|
-
);
|
|
216
|
-
};
|
|
120
|
+
// Use slug/UUID
|
|
121
|
+
keyExtractor={(item) => item.slug}
|
|
217
122
|
```
|
|
218
123
|
|
|
219
|
-
|
|
220
|
-
|
|
124
|
+
❌ **DON'T**:
|
|
221
125
|
```tsx
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
variant="outlined"
|
|
228
|
-
style={{ marginBottom: 16 }}
|
|
229
|
-
onPress={() => navigation.navigate('Article', { articleId: item.id })}
|
|
230
|
-
>
|
|
231
|
-
<View style={{ padding: 16 }}>
|
|
232
|
-
<AtomicText type="titleMedium" style={{ marginBottom: 8 }}>
|
|
233
|
-
{item.title}
|
|
234
|
-
</AtomicText>
|
|
235
|
-
<AtomicText type="bodyMedium" color="secondary" numberOfLines={2}>
|
|
236
|
-
{item.excerpt}
|
|
237
|
-
</AtomicText>
|
|
238
|
-
<View style={{ flexDirection: 'row', marginTop: 8 }}>
|
|
239
|
-
<AtomicText type="labelSmall" color="tertiary">
|
|
240
|
-
{item.category}
|
|
241
|
-
</AtomicText>
|
|
242
|
-
<AtomicText type="labelSmall" color="tertiary" style={{ marginLeft: 16 }}>
|
|
243
|
-
{formatDate(item.publishedAt)}
|
|
244
|
-
</AtomicText>
|
|
245
|
-
</View>
|
|
246
|
-
</View>
|
|
247
|
-
</AtomicCard>
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
return (
|
|
251
|
-
<List
|
|
252
|
-
data={articles}
|
|
253
|
-
renderItem={renderArticle}
|
|
254
|
-
keyExtractor={(item) => item.id}
|
|
255
|
-
contentPadding
|
|
256
|
-
/>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
126
|
+
// Don't use index
|
|
127
|
+
keyExtractor={(item, index) => index.toString()} // ❌
|
|
128
|
+
|
|
129
|
+
// Don't use mutable values
|
|
130
|
+
keyExtractor={(item) => item.name} // May change ❌
|
|
259
131
|
```
|
|
260
132
|
|
|
261
|
-
###
|
|
133
|
+
### Performance
|
|
262
134
|
|
|
135
|
+
✅ **DO**:
|
|
263
136
|
```tsx
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
<Pressable
|
|
269
|
-
style={{ flexDirection: 'row', alignItems: 'center', padding: 16 }}
|
|
270
|
-
onPress={() => navigation.navigate('Chat', { chatId: item.id })}
|
|
271
|
-
>
|
|
272
|
-
<Avatar
|
|
273
|
-
uri={item.avatar}
|
|
274
|
-
name={item.name}
|
|
275
|
-
showStatus
|
|
276
|
-
status={item.online ? 'online' : 'offline'}
|
|
277
|
-
size="md"
|
|
278
|
-
/>
|
|
279
|
-
|
|
280
|
-
<View style={{ flex: 1, marginLeft: 12 }}>
|
|
281
|
-
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
|
282
|
-
<AtomicText type="bodyLarge" fontWeight="600">
|
|
283
|
-
{item.name}
|
|
284
|
-
</AtomicText>
|
|
285
|
-
<AtomicText type="bodySmall" color="tertiary">
|
|
286
|
-
{formatTime(item.lastMessageAt)}
|
|
287
|
-
</AtomicText>
|
|
288
|
-
</View>
|
|
289
|
-
|
|
290
|
-
<AtomicText
|
|
291
|
-
type="bodyMedium"
|
|
292
|
-
color="secondary"
|
|
293
|
-
numberOfLines={1}
|
|
294
|
-
style={{ marginTop: 4 }}
|
|
295
|
-
>
|
|
296
|
-
{item.lastMessage}
|
|
297
|
-
</AtomicText>
|
|
298
|
-
</View>
|
|
299
|
-
</Pressable>
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
return (
|
|
303
|
-
<List
|
|
304
|
-
data={conversations}
|
|
305
|
-
renderItem={renderConversation}
|
|
306
|
-
keyExtractor={(item) => item.id}
|
|
307
|
-
contentPadding
|
|
308
|
-
/>
|
|
309
|
-
);
|
|
310
|
-
};
|
|
311
|
-
```
|
|
137
|
+
// Memoize renderItem
|
|
138
|
+
const renderItem = useCallback(({ item }) => (
|
|
139
|
+
<ItemCard item={item} />
|
|
140
|
+
), []);
|
|
312
141
|
|
|
313
|
-
|
|
142
|
+
// Use getItemLayout for fixed sizes
|
|
143
|
+
getItemLayout={(data, index) => ({
|
|
144
|
+
length: ITEM_HEIGHT,
|
|
145
|
+
offset: ITEM_HEIGHT * index,
|
|
146
|
+
index,
|
|
147
|
+
})}
|
|
148
|
+
```
|
|
314
149
|
|
|
150
|
+
❌ **DON'T**:
|
|
315
151
|
```tsx
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
useEffect(() => {
|
|
321
|
-
const search = async () => {
|
|
322
|
-
setLoading(true);
|
|
323
|
-
const data = await searchItems(query);
|
|
324
|
-
setResults(data);
|
|
325
|
-
setLoading(false);
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
if (query.length > 2) {
|
|
329
|
-
search();
|
|
330
|
-
}
|
|
331
|
-
}, [query]);
|
|
332
|
-
|
|
333
|
-
const renderResult = ({ item }) => (
|
|
334
|
-
<ListItem
|
|
335
|
-
title={item.title}
|
|
336
|
-
subtitle={item.description}
|
|
337
|
-
left={() => <AtomicIcon name="search-outline" size="md" />}
|
|
338
|
-
onPress={() => navigation.navigate('Detail', { id: item.id })}
|
|
339
|
-
/>
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
if (loading) {
|
|
343
|
-
return <AtomicSpinner fullContainer />;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (results.length === 0) {
|
|
347
|
-
return (
|
|
348
|
-
<EmptyState
|
|
349
|
-
icon="search-outline"
|
|
350
|
-
title="Sonuç bulunamadı"
|
|
351
|
-
message={`"${query}" için sonuç bulunamadı`}
|
|
352
|
-
/>
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<List
|
|
358
|
-
data={results}
|
|
359
|
-
renderItem={renderResult}
|
|
360
|
-
keyExtractor={(item) => item.id}
|
|
361
|
-
contentPadding
|
|
362
|
-
/>
|
|
363
|
-
);
|
|
364
|
-
};
|
|
152
|
+
// Don't create functions in render
|
|
153
|
+
<List
|
|
154
|
+
renderItem={({ item }) => <ItemCard item={item} />} // ❌ New function each render
|
|
155
|
+
/>
|
|
365
156
|
```
|
|
366
157
|
|
|
367
|
-
###
|
|
158
|
+
### Content Padding
|
|
368
159
|
|
|
160
|
+
✅ **DO**:
|
|
369
161
|
```tsx
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const renderNotification = ({ item }) => (
|
|
374
|
-
<View
|
|
375
|
-
style={{
|
|
376
|
-
flexDirection: 'row',
|
|
377
|
-
padding: 16,
|
|
378
|
-
backgroundColor: item.read ? 'transparent' : `${tokens.colors.primary}10`,
|
|
379
|
-
borderBottomWidth: 1,
|
|
380
|
-
borderBottomColor: tokens.colors.border,
|
|
381
|
-
}}
|
|
382
|
-
>
|
|
383
|
-
<View style={{ marginRight: 12 }}>
|
|
384
|
-
<AtomicIcon
|
|
385
|
-
name={item.type === 'success' ? 'checkmark-circle' : 'information-circle'}
|
|
386
|
-
size="lg"
|
|
387
|
-
color={item.type === 'success' ? 'success' : 'primary'}
|
|
388
|
-
/>
|
|
389
|
-
</View>
|
|
390
|
-
|
|
391
|
-
<View style={{ flex: 1 }}>
|
|
392
|
-
<AtomicText type="bodyLarge" fontWeight="600">
|
|
393
|
-
{item.title}
|
|
394
|
-
</AtomicText>
|
|
395
|
-
<AtomicText type="bodyMedium" color="secondary" style={{ marginTop: 4 }}>
|
|
396
|
-
{item.message}
|
|
397
|
-
</AtomicText>
|
|
398
|
-
<AtomicText type="labelSmall" color="tertiary" style={{ marginTop: 8 }}>
|
|
399
|
-
{formatRelativeTime(item.createdAt)}
|
|
400
|
-
</AtomicText>
|
|
401
|
-
</View>
|
|
402
|
-
</View>
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
return (
|
|
406
|
-
<List
|
|
407
|
-
data={notifications}
|
|
408
|
-
renderItem={renderNotification}
|
|
409
|
-
keyExtractor={(item) => item.id}
|
|
410
|
-
contentPadding
|
|
411
|
-
/>
|
|
412
|
-
);
|
|
413
|
-
};
|
|
414
|
-
```
|
|
162
|
+
// Use built-in contentPadding
|
|
163
|
+
<List contentPadding />
|
|
415
164
|
|
|
416
|
-
|
|
165
|
+
// Manual padding for custom layouts
|
|
166
|
+
<List
|
|
167
|
+
contentContainerStyle={{ padding: 16 }}
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
417
170
|
|
|
171
|
+
❌ **DON'T**:
|
|
418
172
|
```tsx
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
setTasks(tasks.map(task =>
|
|
424
|
-
task.id === taskId ? { ...task, completed: !task.completed } : task
|
|
425
|
-
));
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
const renderTask = ({ item }) => (
|
|
429
|
-
<View
|
|
430
|
-
style={{
|
|
431
|
-
flexDirection: 'row',
|
|
432
|
-
alignItems: 'center',
|
|
433
|
-
padding: 16,
|
|
434
|
-
borderBottomWidth: 1,
|
|
435
|
-
borderBottomColor: tokens.colors.border,
|
|
436
|
-
}}
|
|
437
|
-
>
|
|
438
|
-
<Pressable onPress={() => toggleTask(item.id)}>
|
|
439
|
-
<AtomicIcon
|
|
440
|
-
name={item.completed ? 'checkmark-circle' : 'ellipse-outline'}
|
|
441
|
-
size="md"
|
|
442
|
-
color={item.completed ? 'success' : 'secondary'}
|
|
443
|
-
/>
|
|
444
|
-
</Pressable>
|
|
445
|
-
|
|
446
|
-
<View style={{ flex: 1, marginLeft: 12 }}>
|
|
447
|
-
<AtomicText
|
|
448
|
-
type="bodyLarge"
|
|
449
|
-
style={{
|
|
450
|
-
textDecorationLine: item.completed ? 'line-through' : 'none',
|
|
451
|
-
opacity: item.completed ? 0.6 : 1,
|
|
452
|
-
}}
|
|
453
|
-
>
|
|
454
|
-
{item.title}
|
|
455
|
-
</AtomicText>
|
|
456
|
-
|
|
457
|
-
{item.dueDate && (
|
|
458
|
-
<AtomicText type="labelSmall" color="tertiary" style={{ marginTop: 4 }}>
|
|
459
|
-
{formatDate(item.dueDate)}
|
|
460
|
-
</AtomicText>
|
|
461
|
-
)}
|
|
462
|
-
</View>
|
|
463
|
-
|
|
464
|
-
{item.priority === 'high' && (
|
|
465
|
-
<AtomicIcon name="alert-circle" size="sm" color="error" />
|
|
466
|
-
)}
|
|
467
|
-
</View>
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
return (
|
|
471
|
-
<List
|
|
472
|
-
data={tasks}
|
|
473
|
-
renderItem={renderTask}
|
|
474
|
-
keyExtractor={(item) => item.id}
|
|
475
|
-
contentPadding
|
|
476
|
-
/>
|
|
477
|
-
);
|
|
478
|
-
};
|
|
173
|
+
// Don't add unnecessary wrapper
|
|
174
|
+
<View style={{ padding: 16 }}>
|
|
175
|
+
<List /> {/* Extra wrapper ❌ */}
|
|
176
|
+
</View>
|
|
479
177
|
```
|
|
480
178
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
### ListProps
|
|
484
|
-
|
|
485
|
-
| Prop | Tip | Varsayılan | Açıklama |
|
|
486
|
-
|------|-----|------------|----------|
|
|
487
|
-
| `data` | `T[]` | - **(Zorunlu)** | Liste verisi |
|
|
488
|
-
| `renderItem` | `ListRenderItem<T>` | - **(Zorunlu)** | Render fonksiyonu |
|
|
489
|
-
| `keyExtractor` | `(item, index) => string` | - **(Zorunlu)** | Key extractor |
|
|
490
|
-
| `onRefresh` | `() => void` | - | Yenileme callback'i |
|
|
491
|
-
| `refreshing` | `boolean` | `false` | Yeneleniyor durumunda |
|
|
492
|
-
| `contentPadding` | `boolean` | `false` | İçerik padding'i |
|
|
493
|
-
|
|
494
|
-
**Not:** List, FlatList'in tüm props'larını destekler.
|
|
495
|
-
|
|
496
|
-
## Best Practices
|
|
497
|
-
|
|
498
|
-
### 1. Key Extractor
|
|
179
|
+
### Empty State
|
|
499
180
|
|
|
181
|
+
✅ **DO**:
|
|
500
182
|
```tsx
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
183
|
+
{data.length === 0 ? (
|
|
184
|
+
<EmptyState
|
|
185
|
+
icon="inbox-outline"
|
|
186
|
+
title="No items"
|
|
187
|
+
message="Get started by adding your first item"
|
|
188
|
+
/>
|
|
189
|
+
) : (
|
|
190
|
+
<List data={data} />
|
|
191
|
+
)}
|
|
506
192
|
```
|
|
507
193
|
|
|
508
|
-
|
|
194
|
+
❌ **DON'T**:
|
|
195
|
+
```tsx
|
|
196
|
+
// Don't show empty list
|
|
197
|
+
<List data={[]} /> {/* Shows nothing ❌ */}
|
|
198
|
+
```
|
|
509
199
|
|
|
200
|
+
## AI Coding Guidelines
|
|
201
|
+
|
|
202
|
+
### For AI Agents
|
|
203
|
+
|
|
204
|
+
When generating List components, follow these rules:
|
|
205
|
+
|
|
206
|
+
1. **Always import from correct path**:
|
|
207
|
+
```typescript
|
|
208
|
+
import { List } from 'react-native-design-system/src/molecules/List';
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
2. **Always provide required props**:
|
|
212
|
+
```tsx
|
|
213
|
+
<List
|
|
214
|
+
data={dataArray}
|
|
215
|
+
renderItem={renderItemFunction}
|
|
216
|
+
keyExtractor={keyExtractorFunction}
|
|
217
|
+
/>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
3. **Always use unique keys**:
|
|
221
|
+
```tsx
|
|
222
|
+
// ✅ Good
|
|
223
|
+
keyExtractor={(item) => item.id}
|
|
224
|
+
|
|
225
|
+
// ❌ Bad
|
|
226
|
+
keyExtractor={(item, index) => index.toString()}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
4. **Always memoize renderItem**:
|
|
230
|
+
```tsx
|
|
231
|
+
const renderItem = useCallback(({ item }) => (
|
|
232
|
+
<ItemCard item={item} />
|
|
233
|
+
), []);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
5. **Never use inline functions**:
|
|
237
|
+
```tsx
|
|
238
|
+
// ❌ Bad
|
|
239
|
+
<List
|
|
240
|
+
renderItem={({ item }) => <ItemCard item={item} />}
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
// ✅ Good
|
|
244
|
+
const renderItem = useCallback(({ item }) => (
|
|
245
|
+
<ItemCard item={item} />
|
|
246
|
+
), []);
|
|
247
|
+
<List renderItem={renderItem} />
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Common Patterns
|
|
251
|
+
|
|
252
|
+
#### Basic List
|
|
510
253
|
```tsx
|
|
511
|
-
// ✅ Memo kullan
|
|
512
254
|
const renderItem = useCallback(({ item }) => (
|
|
513
|
-
<
|
|
255
|
+
<ListItem
|
|
256
|
+
title={item.title}
|
|
257
|
+
onPress={() => navigateToDetail(item.id)}
|
|
258
|
+
/>
|
|
514
259
|
), []);
|
|
515
260
|
|
|
516
|
-
|
|
517
|
-
|
|
261
|
+
<List
|
|
262
|
+
data={items}
|
|
263
|
+
renderItem={renderItem}
|
|
264
|
+
keyExtractor={(item) => item.id}
|
|
265
|
+
contentPadding
|
|
266
|
+
/>
|
|
518
267
|
```
|
|
519
268
|
|
|
520
|
-
|
|
521
|
-
|
|
269
|
+
#### Pull-to-Refresh
|
|
522
270
|
```tsx
|
|
523
|
-
|
|
524
|
-
<List contentPadding />
|
|
271
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
525
272
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
273
|
+
const onRefresh = useCallback(async () => {
|
|
274
|
+
setRefreshing(true);
|
|
275
|
+
await loadData();
|
|
276
|
+
setRefreshing(false);
|
|
277
|
+
}, []);
|
|
278
|
+
|
|
279
|
+
<List
|
|
280
|
+
data={items}
|
|
281
|
+
renderItem={renderItem}
|
|
282
|
+
keyExtractor={(item) => item.id}
|
|
283
|
+
onRefresh={onRefresh}
|
|
284
|
+
refreshing={refreshing}
|
|
285
|
+
/>
|
|
530
286
|
```
|
|
531
287
|
|
|
532
|
-
|
|
288
|
+
#### Infinite Scroll
|
|
289
|
+
```tsx
|
|
290
|
+
const [loading, setLoading] = useState(false);
|
|
291
|
+
|
|
292
|
+
const loadMore = useCallback(async () => {
|
|
293
|
+
if (loading) return;
|
|
294
|
+
setLoading(true);
|
|
295
|
+
const newItems = await fetchMore();
|
|
296
|
+
setItems([...items, ...newItems]);
|
|
297
|
+
setLoading(false);
|
|
298
|
+
}, [loading, items]);
|
|
299
|
+
|
|
300
|
+
<List
|
|
301
|
+
data={items}
|
|
302
|
+
renderItem={renderItem}
|
|
303
|
+
keyExtractor={(item) => item.id}
|
|
304
|
+
onEndReached={loadMore}
|
|
305
|
+
onEndReachedThreshold={0.5}
|
|
306
|
+
ListFooterComponent={loading ? <LoadingSpinner /> : null}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
533
309
|
|
|
310
|
+
#### Grid Layout
|
|
534
311
|
```tsx
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
312
|
+
<List
|
|
313
|
+
data={items}
|
|
314
|
+
renderItem={renderItem}
|
|
315
|
+
keyExtractor={(item) => item.id}
|
|
316
|
+
numColumns={2}
|
|
317
|
+
columnWrapperStyle={{ gap: 8 }}
|
|
318
|
+
contentPadding
|
|
319
|
+
/>
|
|
540
320
|
```
|
|
541
321
|
|
|
542
|
-
##
|
|
322
|
+
## Props Reference
|
|
323
|
+
|
|
324
|
+
| Prop | Type | Required | Default | Description |
|
|
325
|
+
|------|------|----------|---------|-------------|
|
|
326
|
+
| `data` | `T[]` | Yes | - | List data array |
|
|
327
|
+
| `renderItem` | `ListRenderItem<T>` | Yes | - | Render function |
|
|
328
|
+
| `keyExtractor` | `(item, index) => string` | Yes | - | Key extractor |
|
|
329
|
+
| `onRefresh` | `() => void` | No | - | Refresh callback |
|
|
330
|
+
| `refreshing` | `boolean` | No | `false` | Refreshing state |
|
|
331
|
+
| `contentPadding` | `boolean` | No | `false` | Add content padding |
|
|
332
|
+
|
|
333
|
+
**Note:** List supports all FlatList props.
|
|
543
334
|
|
|
544
|
-
|
|
335
|
+
## Accessibility
|
|
545
336
|
|
|
546
|
-
- ✅ Screen reader
|
|
547
|
-
- ✅ Semantic list
|
|
548
|
-
- ✅ Focus management
|
|
549
|
-
- ✅
|
|
337
|
+
- ✅ Screen reader announces list items
|
|
338
|
+
- ✅ Semantic list structure
|
|
339
|
+
- ✅ Focus management for keyboard navigation
|
|
340
|
+
- ✅ Accessibility labels supported
|
|
341
|
+
- ✅ Proper list semantics
|
|
550
342
|
|
|
551
|
-
##
|
|
343
|
+
## Performance
|
|
552
344
|
|
|
553
|
-
1. **
|
|
554
|
-
2. **Memoization**: renderItem
|
|
555
|
-
3. **
|
|
556
|
-
4. **
|
|
557
|
-
5. **
|
|
345
|
+
1. **Key extraction**: Use unique, stable keys
|
|
346
|
+
2. **Memoization**: Memo renderItem function
|
|
347
|
+
3. **Windowing**: Uses FlatList windowing
|
|
348
|
+
4. **Optimization**: Consider `removeClippedSubviews` for large lists
|
|
349
|
+
5. **Layout**: Use `getItemLayout` for fixed-size items
|
|
350
|
+
6. **Batching**: Batch updates to data
|
|
558
351
|
|
|
559
|
-
##
|
|
352
|
+
## Related Components
|
|
560
353
|
|
|
561
354
|
- [`FlatList`](https://reactnative.dev/docs/flatlist) - React Native FlatList
|
|
562
|
-
- [`ListItem`](../listitem/README.md) -
|
|
563
|
-
- [`MediaCard`](../media-card/README.md) -
|
|
564
|
-
- [`Avatar`](../avatar/README.md) - Avatar
|
|
355
|
+
- [`ListItem`](../listitem/README.md) - List item component
|
|
356
|
+
- [`MediaCard`](../media-card/README.md) - Media card component
|
|
357
|
+
- [`Avatar`](../avatar/README.md) - Avatar component
|
|
565
358
|
|
|
566
|
-
##
|
|
359
|
+
## License
|
|
567
360
|
|
|
568
361
|
MIT
|