@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,558 +1,449 @@
|
|
|
1
1
|
# Countdown
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A countdown timer component that displays remaining time to a specific date/target with support for multiple targets and custom time units.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Import & Usage
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- 📊 **Time Unit**: Gün, saat, dakika, saniye gösterimi
|
|
10
|
-
- 🎨 **Özelleştirilebilir**: Boyut, etiket, görünüm
|
|
11
|
-
- 🔄 **Hook**: useCountdown hook ile kontrol
|
|
12
|
-
- 🎭 **Tema Bilinci**: Design token uyumlu
|
|
13
|
-
- ♿ **Erişilebilir**: Screen reader desteği
|
|
14
|
-
|
|
15
|
-
## Kurulum
|
|
16
|
-
|
|
17
|
-
```tsx
|
|
18
|
-
import { Countdown, useCountdown } from 'react-native-design-system';
|
|
7
|
+
```typescript
|
|
8
|
+
import { Countdown, useCountdown } from 'react-native-design-system/src/molecules/countdown';
|
|
19
9
|
```
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import React from 'react';
|
|
25
|
-
import { View } from 'react-native';
|
|
26
|
-
import { Countdown } from 'react-native-design-system';
|
|
27
|
-
|
|
28
|
-
export const BasicExample = () => {
|
|
29
|
-
const targetDate = new Date('2025-12-31T23:59:59');
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<View style={{ padding: 16 }}>
|
|
33
|
-
<Countdown
|
|
34
|
-
target={{
|
|
35
|
-
date: targetDate,
|
|
36
|
-
label: 'Yılbaşı',
|
|
37
|
-
}}
|
|
38
|
-
/>
|
|
39
|
-
</View>
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
```
|
|
11
|
+
**Location:** `src/molecules/countdown/Countdown.tsx`
|
|
43
12
|
|
|
44
|
-
##
|
|
13
|
+
## Basic Usage
|
|
45
14
|
|
|
46
15
|
```tsx
|
|
47
16
|
<Countdown
|
|
48
17
|
target={{
|
|
49
18
|
date: new Date('2025-12-31'),
|
|
50
|
-
label: '
|
|
19
|
+
label: 'New Year',
|
|
51
20
|
}}
|
|
52
21
|
/>
|
|
53
22
|
```
|
|
54
23
|
|
|
55
|
-
##
|
|
24
|
+
## Strategy
|
|
25
|
+
|
|
26
|
+
**Purpose**: Display time remaining to specific events or deadlines with real-time updates.
|
|
27
|
+
|
|
28
|
+
**When to Use**:
|
|
29
|
+
- Flash sales and limited-time offers
|
|
30
|
+
- Event countdowns (concerts, conferences)
|
|
31
|
+
- Competition deadlines
|
|
32
|
+
- Daily reset timers
|
|
33
|
+
- Game timers
|
|
34
|
+
- Auction end times
|
|
35
|
+
|
|
36
|
+
**When NOT to Use**:
|
|
37
|
+
- For static time display (use regular text instead)
|
|
38
|
+
- For past dates (validate before use)
|
|
39
|
+
- For very short durations (<1 minute, use progress bar)
|
|
40
|
+
- For simple clocks (use time display instead)
|
|
41
|
+
|
|
42
|
+
## Rules
|
|
43
|
+
|
|
44
|
+
### Required
|
|
45
|
+
|
|
46
|
+
1. **MUST** provide a valid future `date` in target
|
|
47
|
+
2. **MUST** provide a `label` for the target
|
|
48
|
+
3. **SHOULD** set appropriate `interval` (1000ms recommended)
|
|
49
|
+
4. **MUST** handle `onExpire` callback when needed
|
|
50
|
+
5. **ALWAYS** validate date before passing to component
|
|
51
|
+
6. **SHOULD** use memoization for performance
|
|
52
|
+
7. **NEVER** use past dates
|
|
53
|
+
|
|
54
|
+
### Time Display
|
|
55
|
+
|
|
56
|
+
1. **Days**: Show for durations >24 hours
|
|
57
|
+
2. **Hours**: Always show for durations <1 week
|
|
58
|
+
3. **Minutes**: Always show
|
|
59
|
+
4. **Seconds**: Optional, hide for long durations
|
|
60
|
+
|
|
61
|
+
### Performance
|
|
62
|
+
|
|
63
|
+
1. **Interval**: 1000ms (1 second) recommended
|
|
64
|
+
2. **Cleanup**: Always cleanup in useEffect
|
|
65
|
+
3. **Memoization**: Memo callback functions
|
|
66
|
+
4. **Throttle**: Throttle onTick callbacks
|
|
67
|
+
|
|
68
|
+
### Memory Management
|
|
69
|
+
|
|
70
|
+
1. **Cleanup**: Clear intervals on unmount
|
|
71
|
+
2. **Unmount**: Stop countdown when not visible
|
|
72
|
+
3. **Throttle**: Don't update too frequently
|
|
73
|
+
4. **Memo**: Memoize target objects
|
|
74
|
+
|
|
75
|
+
## Forbidden
|
|
76
|
+
|
|
77
|
+
❌ **NEVER** do these:
|
|
56
78
|
|
|
57
79
|
```tsx
|
|
80
|
+
// ❌ Past date
|
|
58
81
|
<Countdown
|
|
59
82
|
target={{
|
|
60
|
-
date: new Date('
|
|
61
|
-
label: '
|
|
62
|
-
icon: 'sunny-outline',
|
|
63
|
-
}}
|
|
64
|
-
displayConfig={{
|
|
65
|
-
size: 'large',
|
|
66
|
-
showLabel: true,
|
|
67
|
-
showToggle: false,
|
|
83
|
+
date: new Date('2020-01-01'), // ❌ Past date
|
|
84
|
+
label: 'Expired Event',
|
|
68
85
|
}}
|
|
69
86
|
/>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Sadece Saat/Dakika/Saniye
|
|
73
87
|
|
|
74
|
-
|
|
88
|
+
// ❌ Too frequent updates
|
|
75
89
|
<Countdown
|
|
76
|
-
target={{
|
|
77
|
-
|
|
78
|
-
label: 'Teklif Bitişi',
|
|
79
|
-
}}
|
|
80
|
-
displayConfig={{
|
|
81
|
-
showDays: false,
|
|
82
|
-
showHours: true,
|
|
83
|
-
showMinutes: true,
|
|
84
|
-
showSeconds: true,
|
|
85
|
-
}}
|
|
90
|
+
target={{ date: futureDate, label: 'Event' }}
|
|
91
|
+
interval={100} // ❌ Too frequent (100ms)
|
|
86
92
|
/>
|
|
87
|
-
```
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
// ❌ No cleanup
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const { start } = useCountdown(target);
|
|
97
|
+
start(); // ❌ No cleanup function
|
|
98
|
+
}, []);
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
// ❌ Missing onExpire for critical actions
|
|
101
|
+
<Countdown
|
|
102
|
+
target={{ date: deadline, label: 'Sale Ends' }}
|
|
103
|
+
// Missing onExpire - user won't know when it ends
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
// ❌ Too many countdowns on screen
|
|
107
|
+
<View>
|
|
108
|
+
<Countdown target={target1} />
|
|
109
|
+
<Countdown target={target2} />
|
|
110
|
+
<Countdown target={target3} />
|
|
111
|
+
<Countdown target={target4} />
|
|
112
|
+
<Countdown target={target5} />
|
|
113
|
+
{/* ❌ Too many, causes performance issues */}
|
|
114
|
+
</View>
|
|
115
|
+
|
|
116
|
+
// ❌ Invalid date format
|
|
92
117
|
<Countdown
|
|
93
118
|
target={{
|
|
94
|
-
date:
|
|
95
|
-
label: '
|
|
96
|
-
icon: 'calendar-outline',
|
|
119
|
+
date: '2025-12-31', // ❌ String instead of Date
|
|
120
|
+
label: 'Event',
|
|
97
121
|
}}
|
|
98
|
-
alternateTargets={[
|
|
99
|
-
{
|
|
100
|
-
date: new Date('2025-06-30'),
|
|
101
|
-
label: 'Yaz Başlangıcı',
|
|
102
|
-
icon: 'sunny-outline',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
date: new Date('2025-03-20'),
|
|
106
|
-
label: 'İlk Bahar',
|
|
107
|
-
icon: 'flower-outline',
|
|
108
|
-
},
|
|
109
|
-
]}
|
|
110
122
|
/>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Custom Label Format
|
|
114
123
|
|
|
115
|
-
|
|
124
|
+
// ❌ Not handling timezone
|
|
116
125
|
<Countdown
|
|
117
126
|
target={{
|
|
118
|
-
date: new Date('2025-12-31'),
|
|
119
|
-
label: '
|
|
120
|
-
}}
|
|
121
|
-
formatLabel={(unit, value) => {
|
|
122
|
-
const labels = {
|
|
123
|
-
days: 'gün',
|
|
124
|
-
hours: 'saat',
|
|
125
|
-
minutes: 'dakika',
|
|
126
|
-
seconds: 'saniye',
|
|
127
|
-
};
|
|
128
|
-
return labels[unit];
|
|
127
|
+
date: new Date('2025-12-31'), // ❌ Ambiguous timezone
|
|
128
|
+
label: 'Global Event',
|
|
129
129
|
}}
|
|
130
130
|
/>
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
##
|
|
133
|
+
## Best Practices
|
|
134
|
+
|
|
135
|
+
### Flash Sale Countdown
|
|
134
136
|
|
|
137
|
+
✅ **DO**:
|
|
135
138
|
```tsx
|
|
136
139
|
<Countdown
|
|
137
140
|
target={{
|
|
138
|
-
date:
|
|
139
|
-
label: '
|
|
141
|
+
date: endDate,
|
|
142
|
+
label: 'Flash Sale Ends',
|
|
143
|
+
icon: 'flash-outline',
|
|
144
|
+
}}
|
|
145
|
+
displayConfig={{
|
|
146
|
+
showDays: false,
|
|
147
|
+
size: 'large',
|
|
140
148
|
}}
|
|
141
149
|
onExpire={() => {
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
Alert.alert('Sale ended!');
|
|
151
|
+
// Refresh UI or redirect
|
|
144
152
|
}}
|
|
145
153
|
/>
|
|
146
154
|
```
|
|
147
155
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
### Temel Kullanım
|
|
151
|
-
|
|
156
|
+
❌ **DON'T**:
|
|
152
157
|
```tsx
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
date: new Date('2025-12-31'),
|
|
159
|
-
label: 'Yılbaşı',
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
interval: 1000,
|
|
163
|
-
autoStart: true,
|
|
164
|
-
}
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<View>
|
|
169
|
-
<Text>{timeRemaining.days} gün {timeRemaining.hours} saat</Text>
|
|
170
|
-
<Button title={isActive ? 'Durdur' : 'Başlat'} onPress={isActive ? stop : start} />
|
|
171
|
-
</View>
|
|
172
|
-
);
|
|
173
|
-
};
|
|
158
|
+
// ❌ No expire handler
|
|
159
|
+
<Countdown
|
|
160
|
+
target={{ date: endDate, label: 'Sale Ends' }}
|
|
161
|
+
// User doesn't know what happens when it ends
|
|
162
|
+
/>
|
|
174
163
|
```
|
|
175
164
|
|
|
176
|
-
###
|
|
165
|
+
### Timezone Handling
|
|
177
166
|
|
|
167
|
+
✅ **DO**:
|
|
178
168
|
```tsx
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{ autoStart: false }
|
|
182
|
-
);
|
|
169
|
+
// ✅ Good - explicit UTC timezone
|
|
170
|
+
const targetDate = new Date('2025-12-31T23:59:59Z');
|
|
183
171
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<Button title="Başlat" onPress={start} />
|
|
188
|
-
<Button title="Durdur" onPress={stop} />
|
|
189
|
-
<Button title="Sıfırla" onPress={reset} />
|
|
190
|
-
</View>
|
|
191
|
-
);
|
|
172
|
+
<Countdown
|
|
173
|
+
target={{ date: targetDate, label: 'New Year' }}
|
|
174
|
+
/>
|
|
192
175
|
```
|
|
193
176
|
|
|
194
|
-
|
|
195
|
-
|
|
177
|
+
❌ **DON'T**:
|
|
196
178
|
```tsx
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
console.log('Kalan süre:', time.totalSeconds);
|
|
200
|
-
},
|
|
201
|
-
});
|
|
179
|
+
// ❌ Bad - ambiguous timezone
|
|
180
|
+
const targetDate = new Date('2025-12-31');
|
|
202
181
|
```
|
|
203
182
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
### Flash Sale
|
|
183
|
+
### Performance
|
|
207
184
|
|
|
185
|
+
✅ **DO**:
|
|
208
186
|
```tsx
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
<Countdown
|
|
215
|
-
target={{
|
|
216
|
-
date: endDate,
|
|
217
|
-
label: 'Flash Sale Bitiş',
|
|
218
|
-
icon: 'flash-outline',
|
|
219
|
-
}}
|
|
220
|
-
displayConfig={{
|
|
221
|
-
showDays: false,
|
|
222
|
-
size: 'large',
|
|
223
|
-
}}
|
|
224
|
-
onExpire={() => {
|
|
225
|
-
Alert.alert('Satış bitti!');
|
|
226
|
-
}}
|
|
227
|
-
/>
|
|
228
|
-
</AtomicCard>
|
|
229
|
-
);
|
|
230
|
-
};
|
|
187
|
+
// ✅ Good - appropriate interval
|
|
188
|
+
<Countdown
|
|
189
|
+
target={{ date: futureDate, label: 'Event' }}
|
|
190
|
+
interval={1000} // 1 second
|
|
191
|
+
/>
|
|
231
192
|
```
|
|
232
193
|
|
|
233
|
-
|
|
234
|
-
|
|
194
|
+
❌ **DON'T**:
|
|
235
195
|
```tsx
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
icon: 'musical-notes-outline',
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
date: new Date('2025-09-15'),
|
|
245
|
-
label: 'Teknoloji Zirvesi',
|
|
246
|
-
icon: 'laptop-outline',
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
date: new Date('2025-12-25'),
|
|
250
|
-
label: 'Yılbaşı Partisi',
|
|
251
|
-
icon: 'gift-outline',
|
|
252
|
-
},
|
|
253
|
-
]);
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<View style={{ padding: 16 }}>
|
|
257
|
-
<Countdown
|
|
258
|
-
target={events[0]}
|
|
259
|
-
alternateTargets={events.slice(1)}
|
|
260
|
-
displayConfig={{
|
|
261
|
-
size: 'medium',
|
|
262
|
-
showToggle: true,
|
|
263
|
-
}}
|
|
264
|
-
onTargetChange={(target) => {
|
|
265
|
-
console.log('Hedef değişti:', target.label);
|
|
266
|
-
}}
|
|
267
|
-
/>
|
|
268
|
-
</View>
|
|
269
|
-
);
|
|
270
|
-
};
|
|
196
|
+
// ❌ Bad - too frequent
|
|
197
|
+
<Countdown
|
|
198
|
+
target={{ date: futureDate, label: 'Event' }}
|
|
199
|
+
interval={100} // 100ms - causes performance issues
|
|
200
|
+
/>
|
|
271
201
|
```
|
|
272
202
|
|
|
273
|
-
###
|
|
203
|
+
### Memory Cleanup
|
|
274
204
|
|
|
205
|
+
✅ **DO**:
|
|
275
206
|
```tsx
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
<View style={{ padding: 16 }}>
|
|
281
|
-
<AtomicText type="headlineMedium" style={{ textAlign: 'center', marginBottom: 16 }}>
|
|
282
|
-
Yarışma Katılım Süresi
|
|
283
|
-
</AtomicText>
|
|
284
|
-
|
|
285
|
-
<Countdown
|
|
286
|
-
target={{
|
|
287
|
-
date: deadline,
|
|
288
|
-
label: 'Son Katılım Tarihi',
|
|
289
|
-
icon: 'trophy-outline',
|
|
290
|
-
}}
|
|
291
|
-
displayConfig={{
|
|
292
|
-
size: 'large',
|
|
293
|
-
showLabel: true,
|
|
294
|
-
}}
|
|
295
|
-
formatLabel={(unit) => {
|
|
296
|
-
const labels = {
|
|
297
|
-
days: 'GÜN',
|
|
298
|
-
hours: 'SAAT',
|
|
299
|
-
minutes: 'DAKİKA',
|
|
300
|
-
seconds: 'SANİYE',
|
|
301
|
-
};
|
|
302
|
-
return labels[unit];
|
|
303
|
-
}}
|
|
304
|
-
onExpire={() => {
|
|
305
|
-
Alert.alert('Yarışma sona erdi!');
|
|
306
|
-
}}
|
|
307
|
-
/>
|
|
308
|
-
</View>
|
|
309
|
-
);
|
|
310
|
-
};
|
|
311
|
-
```
|
|
207
|
+
const countdown = useCountdown(target, {
|
|
208
|
+
interval: 1000,
|
|
209
|
+
autoStart: true,
|
|
210
|
+
});
|
|
312
211
|
|
|
313
|
-
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
return () => {
|
|
214
|
+
countdown.stop(); // ✅ Cleanup
|
|
215
|
+
};
|
|
216
|
+
}, []);
|
|
217
|
+
```
|
|
314
218
|
|
|
219
|
+
## AI Coding Guidelines
|
|
220
|
+
|
|
221
|
+
### For AI Agents
|
|
222
|
+
|
|
223
|
+
When generating Countdown components, follow these rules:
|
|
224
|
+
|
|
225
|
+
1. **Always import from correct path**:
|
|
226
|
+
```typescript
|
|
227
|
+
import { Countdown, useCountdown } from 'react-native-design-system/src/molecules/countdown';
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
2. **Always validate date before use**:
|
|
231
|
+
```tsx
|
|
232
|
+
// ✅ Good - validate date
|
|
233
|
+
const targetDate = new Date('2025-12-31');
|
|
234
|
+
if (targetDate <= new Date()) {
|
|
235
|
+
throw new Error('Target date must be in the future');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
<Countdown
|
|
239
|
+
target={{ date: targetDate, label: 'Event' }}
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
// ❌ Bad - no validation
|
|
243
|
+
<Countdown
|
|
244
|
+
target={{ date: new Date('2020-01-01'), label: 'Event' }}
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
3. **Always use appropriate interval**:
|
|
249
|
+
```tsx
|
|
250
|
+
// ✅ Good - 1 second interval
|
|
251
|
+
<Countdown
|
|
252
|
+
target={{ date: futureDate, label: 'Event' }}
|
|
253
|
+
interval={1000}
|
|
254
|
+
/>
|
|
255
|
+
|
|
256
|
+
// ❌ Bad - too frequent
|
|
257
|
+
<Countdown
|
|
258
|
+
target={{ date: futureDate, label: 'Event' }}
|
|
259
|
+
interval={100}
|
|
260
|
+
/>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
4. **Always handle onExpire for critical events**:
|
|
264
|
+
```tsx
|
|
265
|
+
// ✅ Good - handle expiry
|
|
266
|
+
<Countdown
|
|
267
|
+
target={{ date: deadline, label: 'Sale Ends' }}
|
|
268
|
+
onExpire={() => {
|
|
269
|
+
Alert.alert('Sale ended!', 'The flash sale has ended.');
|
|
270
|
+
// Refresh data or redirect
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
|
|
274
|
+
// ❌ Bad - no expire handler
|
|
275
|
+
<Countdown
|
|
276
|
+
target={{ date: deadline, label: 'Sale Ends' }}
|
|
277
|
+
/>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
5. **Always use UTC for global events**:
|
|
281
|
+
```tsx
|
|
282
|
+
// ✅ Good - explicit UTC
|
|
283
|
+
const targetDate = new Date('2025-12-31T23:59:59Z');
|
|
284
|
+
|
|
285
|
+
// ❌ Bad - local timezone
|
|
286
|
+
const targetDate = new Date('2025-12-31');
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Common Patterns
|
|
290
|
+
|
|
291
|
+
#### Flash Sale Countdown
|
|
315
292
|
```tsx
|
|
316
|
-
|
|
317
|
-
return (
|
|
318
|
-
<View style={{ backgroundColor: '#ff6b6b', padding: 16, borderRadius: 8 }}>
|
|
319
|
-
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
|
|
320
|
-
<AtomicIcon name="pricetag-outline" size="sm" color="#fff" />
|
|
321
|
-
<AtomicText type="titleMedium" style={{ color: '#fff', marginLeft: 8 }}>
|
|
322
|
-
%{discountPercentage} İndirim
|
|
323
|
-
</AtomicText>
|
|
324
|
-
</View>
|
|
325
|
-
|
|
326
|
-
<Countdown
|
|
327
|
-
target={{
|
|
328
|
-
date: validUntil,
|
|
329
|
-
label: 'Teklif Bitişi',
|
|
330
|
-
}}
|
|
331
|
-
displayConfig={{
|
|
332
|
-
showDays: false,
|
|
333
|
-
size: 'medium',
|
|
334
|
-
showLabel: false,
|
|
335
|
-
}}
|
|
336
|
-
/>
|
|
337
|
-
</View>
|
|
338
|
-
);
|
|
339
|
-
};
|
|
340
|
-
```
|
|
293
|
+
const endDate = new Date(Date.now() + 3600000); // 1 hour from now
|
|
341
294
|
|
|
342
|
-
|
|
295
|
+
<Countdown
|
|
296
|
+
target={{
|
|
297
|
+
date: endDate,
|
|
298
|
+
label: 'Flash Sale Ends',
|
|
299
|
+
icon: 'flash-outline',
|
|
300
|
+
}}
|
|
301
|
+
displayConfig={{
|
|
302
|
+
showDays: false,
|
|
303
|
+
showSeconds: true,
|
|
304
|
+
size: 'large',
|
|
305
|
+
}}
|
|
306
|
+
onExpire={() => {
|
|
307
|
+
Alert.alert('Sale ended!');
|
|
308
|
+
refreshProducts();
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
343
312
|
|
|
313
|
+
#### Event Countdown
|
|
344
314
|
```tsx
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
onTimeUp?.();
|
|
357
|
-
},
|
|
358
|
-
}
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
return (
|
|
362
|
-
<View style={{ alignItems: 'center' }}>
|
|
363
|
-
<Countdown
|
|
364
|
-
target={{ date: targetDate }}
|
|
365
|
-
displayConfig={{
|
|
366
|
-
showDays: false,
|
|
367
|
-
showHours: false,
|
|
368
|
-
showMinutes: true,
|
|
369
|
-
showSeconds: true,
|
|
370
|
-
showLabel: false,
|
|
371
|
-
size: 'large',
|
|
372
|
-
}}
|
|
373
|
-
/>
|
|
374
|
-
|
|
375
|
-
{isExpired && (
|
|
376
|
-
<AtomicText type="headlineLarge" style={{ color: 'red' }}>
|
|
377
|
-
Süre Doldu!
|
|
378
|
-
</AtomicText>
|
|
379
|
-
)}
|
|
380
|
-
</View>
|
|
381
|
-
);
|
|
382
|
-
};
|
|
315
|
+
<Countdown
|
|
316
|
+
target={{
|
|
317
|
+
date: new Date('2025-06-30T20:00:00Z'),
|
|
318
|
+
label: 'Summer Concert',
|
|
319
|
+
icon: 'musical-notes-outline',
|
|
320
|
+
}}
|
|
321
|
+
displayConfig={{
|
|
322
|
+
size: 'medium',
|
|
323
|
+
showLabel: true,
|
|
324
|
+
}}
|
|
325
|
+
/>
|
|
383
326
|
```
|
|
384
327
|
|
|
385
|
-
|
|
386
|
-
|
|
328
|
+
#### Game Timer
|
|
387
329
|
```tsx
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
330
|
+
const [timeLeft, setTimeLeft] = useState(0);
|
|
331
|
+
|
|
332
|
+
useCountdown(
|
|
333
|
+
{ date: gameEndTime },
|
|
334
|
+
{
|
|
335
|
+
interval: 1000,
|
|
336
|
+
onTick: (time) => {
|
|
337
|
+
setTimeLeft(time.totalSeconds);
|
|
338
|
+
},
|
|
339
|
+
onExpire: () => {
|
|
340
|
+
handleTimeUp();
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
```
|
|
396
345
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
<Countdown
|
|
406
|
-
target={{
|
|
407
|
-
date: target,
|
|
408
|
-
label: 'Yarın',
|
|
409
|
-
}}
|
|
410
|
-
displayConfig={{
|
|
411
|
-
showDays: false,
|
|
412
|
-
size: 'medium',
|
|
413
|
-
showLabel: false,
|
|
414
|
-
}}
|
|
415
|
-
onExpire={() => {
|
|
416
|
-
// Refresh targets
|
|
417
|
-
window.location.reload();
|
|
418
|
-
}}
|
|
419
|
-
/>
|
|
420
|
-
</View>
|
|
421
|
-
);
|
|
346
|
+
#### Daily Reset Timer
|
|
347
|
+
```tsx
|
|
348
|
+
const getNextMidnight = () => {
|
|
349
|
+
const now = new Date();
|
|
350
|
+
const tomorrow = new Date(now);
|
|
351
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
352
|
+
tomorrow.setHours(0, 0, 0, 0);
|
|
353
|
+
return tomorrow;
|
|
422
354
|
};
|
|
355
|
+
|
|
356
|
+
<Countdown
|
|
357
|
+
target={{
|
|
358
|
+
date: getNextMidnight(),
|
|
359
|
+
label: 'Daily Reset',
|
|
360
|
+
}}
|
|
361
|
+
displayConfig={{
|
|
362
|
+
showDays: false,
|
|
363
|
+
showLabel: false,
|
|
364
|
+
}}
|
|
365
|
+
onExpire={() => {
|
|
366
|
+
window.location.reload();
|
|
367
|
+
}}
|
|
368
|
+
/>
|
|
423
369
|
```
|
|
424
370
|
|
|
425
|
-
## Props
|
|
371
|
+
## Props Reference
|
|
426
372
|
|
|
427
373
|
### CountdownProps
|
|
428
374
|
|
|
429
|
-
| Prop |
|
|
430
|
-
|
|
431
|
-
| `target` | `CountdownTarget` |
|
|
432
|
-
| `alternateTargets` | `CountdownTarget[]` | `[]` |
|
|
433
|
-
| `displayConfig` | `CountdownDisplayConfig` | `{}` |
|
|
434
|
-
| `interval` | `number` | `1000` |
|
|
435
|
-
| `onExpire` | `() => void` | - |
|
|
436
|
-
| `onTargetChange` | `(target) => void` | - |
|
|
437
|
-
| `formatLabel` | `(unit, value) => string` | - |
|
|
375
|
+
| Prop | Type | Required | Default | Description |
|
|
376
|
+
|------|------|----------|---------|-------------|
|
|
377
|
+
| `target` | `CountdownTarget` | **Yes** | - | Target date and label |
|
|
378
|
+
| `alternateTargets` | `CountdownTarget[]` | No | `[]` | Alternate targets to switch between |
|
|
379
|
+
| `displayConfig` | `CountdownDisplayConfig` | No | `{}` | Display configuration |
|
|
380
|
+
| `interval` | `number` | No | `1000` | Update interval in milliseconds |
|
|
381
|
+
| `onExpire` | `() => void` | No | - | Callback when countdown expires |
|
|
382
|
+
| `onTargetChange` | `(target) => void` | No | - | Callback when target changes |
|
|
383
|
+
| `formatLabel` | `(unit, value) => string` | No | - | Custom label formatting |
|
|
438
384
|
|
|
439
385
|
### CountdownTarget
|
|
440
386
|
|
|
441
|
-
| Prop |
|
|
442
|
-
|
|
443
|
-
| `date` | `Date` |
|
|
444
|
-
| `label` | `string` |
|
|
445
|
-
| `icon` | `string` |
|
|
387
|
+
| Prop | Type | Required | Default | Description |
|
|
388
|
+
|------|------|----------|---------|-------------|
|
|
389
|
+
| `date` | `Date` | **Yes** | - | Target date (must be in future) |
|
|
390
|
+
| `label` | `string` | **Yes** | - | Target label |
|
|
391
|
+
| `icon` | `string` | No | - | Icon name (Ionicons) |
|
|
446
392
|
|
|
447
393
|
### CountdownDisplayConfig
|
|
448
394
|
|
|
449
|
-
| Prop |
|
|
450
|
-
|
|
451
|
-
| `showLabel` | `boolean` | `true` |
|
|
452
|
-
| `showToggle` | `boolean` |
|
|
453
|
-
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
454
|
-
| `showDays` | `boolean` |
|
|
455
|
-
| `showHours` | `boolean` | `true` |
|
|
456
|
-
| `showMinutes` | `boolean` | `true` |
|
|
457
|
-
| `showSeconds` | `boolean` | `true` |
|
|
458
|
-
|
|
459
|
-
### useCountdown Options
|
|
460
|
-
|
|
461
|
-
| Prop | Tip | Varsayılan | Açıklama |
|
|
462
|
-
|------|-----|------------|----------|
|
|
463
|
-
| `interval` | `number` | `1000` | Güncelleme aralığı (ms) |
|
|
464
|
-
| `autoStart` | `boolean` | `true` | Otomatik başlat |
|
|
465
|
-
| `onExpire` | `() => void` | - | Süre dolunca |
|
|
466
|
-
| `onTick` | `(time) => void` | - | Her tick'te |
|
|
467
|
-
|
|
468
|
-
### useCountdown Return
|
|
469
|
-
|
|
470
|
-
| Prop | Tip | Açıklama |
|
|
471
|
-
|------|-----|----------|
|
|
472
|
-
| `timeRemaining` | `TimeRemaining` | Kalan süre |
|
|
473
|
-
| `isActive` | `boolean` | Aktif mi |
|
|
474
|
-
| `isExpired` | `boolean` | Doldu mu |
|
|
475
|
-
| `start` | `() => void` | Başlat |
|
|
476
|
-
| `stop` | `() => void` | Durdur |
|
|
477
|
-
| `reset` | `() => void` | Sıfırla |
|
|
478
|
-
| `setTarget` | `(target) => void` | Hedef belirle |
|
|
395
|
+
| Prop | Type | Default | Description |
|
|
396
|
+
|------|------|---------|-------------|
|
|
397
|
+
| `showLabel` | `boolean` | `true` | Show target label |
|
|
398
|
+
| `showToggle` | `boolean` | Auto | Show target toggle button |
|
|
399
|
+
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Display size |
|
|
400
|
+
| `showDays` | `boolean` | Auto | Show days (auto based on duration) |
|
|
401
|
+
| `showHours` | `boolean` | `true` | Show hours |
|
|
402
|
+
| `showMinutes` | `boolean` | `true` | Show minutes |
|
|
403
|
+
| `showSeconds` | `boolean` | `true` | Show seconds |
|
|
479
404
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
### 1. Hedef Seçimi
|
|
483
|
-
|
|
484
|
-
```tsx
|
|
485
|
-
// Gelecek tarih
|
|
486
|
-
target={{ date: new Date('2025-12-31') }} // ✅
|
|
487
|
-
|
|
488
|
-
// Geçmiş tarih
|
|
489
|
-
target={{ date: new Date('2020-01-01') }} // ❌
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### 2. Performans
|
|
493
|
-
|
|
494
|
-
```tsx
|
|
495
|
-
// Uygun interval
|
|
496
|
-
interval={1000} // ✅ 1 saniye (önerilen)
|
|
497
|
-
interval={100} // ❌ 100ms (çok sık)
|
|
498
|
-
```
|
|
405
|
+
### useCountdown Hook
|
|
499
406
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
407
|
+
```typescript
|
|
408
|
+
useCountdown(target: CountdownTarget, options?: {
|
|
409
|
+
interval?: number; // Default: 1000
|
|
410
|
+
autoStart?: boolean; // Default: true
|
|
411
|
+
onExpire?: () => void;
|
|
412
|
+
onTick?: (time: TimeRemaining) => void;
|
|
413
|
+
}): {
|
|
414
|
+
timeRemaining: TimeRemaining;
|
|
415
|
+
isActive: boolean;
|
|
416
|
+
isExpired: boolean;
|
|
417
|
+
start: () => void;
|
|
418
|
+
stop: () => void;
|
|
419
|
+
reset: () => void;
|
|
420
|
+
setTarget: (target: CountdownTarget) => void;
|
|
421
|
+
}
|
|
515
422
|
```
|
|
516
423
|
|
|
517
|
-
##
|
|
518
|
-
|
|
519
|
-
Countdown, tam erişilebilirlik desteği sunar:
|
|
520
|
-
|
|
521
|
-
- ✅ Screen reader desteği
|
|
522
|
-
- ✅ Semantic anlamlar
|
|
523
|
-
- ✅ Timer role
|
|
524
|
-
- ✅ Live region
|
|
424
|
+
## Accessibility
|
|
525
425
|
|
|
526
|
-
|
|
426
|
+
- ✅ Screen reader announces time remaining
|
|
427
|
+
- ✅ Timer role for semantic meaning
|
|
428
|
+
- ✅ Live region for updates
|
|
429
|
+
- ✅ Label announces target event
|
|
430
|
+
- ✅ Accessible toggle between targets
|
|
527
431
|
|
|
528
|
-
|
|
529
|
-
2. **Memoization**: Component'leri memo edin
|
|
530
|
-
3. **Cleanup**: useEffect'te cleanup yapın
|
|
531
|
-
4. **Throttle**: onTick callback'ini throttle edin
|
|
432
|
+
## Performance Tips
|
|
532
433
|
|
|
533
|
-
|
|
434
|
+
1. **Interval**: Use 1000ms (1 second) for most cases
|
|
435
|
+
2. **Cleanup**: Always cleanup in useEffect
|
|
436
|
+
3. **Throttle**: Throttle onTick callbacks
|
|
437
|
+
4. **Memoization**: Memo target objects and callbacks
|
|
438
|
+
5. **Visibility**: Stop countdown when not visible
|
|
534
439
|
|
|
535
|
-
|
|
536
|
-
- [`CountdownHeader`](#countdownheader) - Countdown başlığı
|
|
537
|
-
- [`AtomicText`](../../atoms/AtomicText/README.md) - Metin bileşeni
|
|
538
|
-
- [`AtomicIcon`](../../atoms/AtomicIcon/README.md) - İkon bileşeni
|
|
440
|
+
## Related Components
|
|
539
441
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
calculateTimeRemaining(targetDate: Date): TimeRemaining
|
|
545
|
-
|
|
546
|
-
// Sayıyı padding yap
|
|
547
|
-
padNumber(num: number): string
|
|
548
|
-
|
|
549
|
-
// Sonraki gün başlangıcı
|
|
550
|
-
getNextDayStart(): Date
|
|
551
|
-
|
|
552
|
-
// Sonraki yıl başlangıcı
|
|
553
|
-
getNextYearStart(): Date
|
|
554
|
-
```
|
|
442
|
+
- [`StepProgress`](../StepProgress/README.md) - Step progress indicator
|
|
443
|
+
- [`AtomicText`](../../atoms/AtomicText/README.md) - Text component
|
|
444
|
+
- [`AtomicIcon`](../../atoms/AtomicIcon/README.md) - Icon component
|
|
445
|
+
- [`BaseModal`](../BaseModal/README.md) - Modal component
|
|
555
446
|
|
|
556
|
-
##
|
|
447
|
+
## License
|
|
557
448
|
|
|
558
449
|
MIT
|