react-native-timer-picker 1.9.0 → 1.10.0
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/README.md +3 -0
- package/dist/commonjs/components/TimerPicker/index.js +6 -3
- package/dist/commonjs/components/TimerPicker/index.js.map +1 -1
- package/dist/commonjs/components/TimerPicker/types.js.map +1 -1
- package/dist/commonjs/tests/TimerPicker.test.js +14 -0
- package/dist/commonjs/tests/TimerPicker.test.js.map +1 -1
- package/dist/module/components/TimerPicker/index.js +6 -3
- package/dist/module/components/TimerPicker/index.js.map +1 -1
- package/dist/module/components/TimerPicker/types.js.map +1 -1
- package/dist/module/tests/TimerPicker.test.js +14 -0
- package/dist/module/tests/TimerPicker.test.js.map +1 -1
- package/dist/typescript/components/TimerPicker/types.d.ts +3 -0
- package/package.json +1 -2
- package/src/components/DurationScroll/index.tsx +0 -459
- package/src/components/DurationScroll/types.ts +0 -75
- package/src/components/Modal/index.tsx +0 -124
- package/src/components/Modal/styles.ts +0 -26
- package/src/components/Modal/types.ts +0 -17
- package/src/components/TimerPicker/index.tsx +0 -196
- package/src/components/TimerPicker/styles.ts +0 -123
- package/src/components/TimerPicker/types.ts +0 -74
- package/src/components/TimerPickerModal/index.tsx +0 -190
- package/src/components/TimerPickerModal/styles.ts +0 -88
- package/src/components/TimerPickerModal/types.ts +0 -52
- package/src/index.ts +0 -13
- package/src/tests/DurationScroll.test.tsx +0 -66
- package/src/tests/Modal.test.tsx +0 -36
- package/src/tests/TimerPicker.test.tsx +0 -29
- package/src/tests/TimerPickerModal.test.tsx +0 -72
- package/src/utils/colorToRgba.ts +0 -49
- package/src/utils/generateNumbers.ts +0 -64
- package/src/utils/getAdjustedLimit.ts +0 -35
- package/src/utils/getScrollIndex.ts +0 -15
- package/src/utils/padNumber.ts +0 -10
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useRef,
|
|
3
|
-
useCallback,
|
|
4
|
-
forwardRef,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
useState,
|
|
7
|
-
useEffect,
|
|
8
|
-
} from "react";
|
|
9
|
-
|
|
10
|
-
import { View, Text, FlatList as RNFlatList } from "react-native";
|
|
11
|
-
import type {
|
|
12
|
-
ViewabilityConfigCallbackPairs,
|
|
13
|
-
ViewToken,
|
|
14
|
-
NativeSyntheticEvent,
|
|
15
|
-
NativeScrollEvent,
|
|
16
|
-
} from "react-native";
|
|
17
|
-
|
|
18
|
-
import { colorToRgba } from "../../utils/colorToRgba";
|
|
19
|
-
import {
|
|
20
|
-
generate12HourNumbers,
|
|
21
|
-
generateNumbers,
|
|
22
|
-
} from "../../utils/generateNumbers";
|
|
23
|
-
import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
|
|
24
|
-
import { getScrollIndex } from "../../utils/getScrollIndex";
|
|
25
|
-
|
|
26
|
-
import type { DurationScrollProps, DurationScrollRef } from "./types";
|
|
27
|
-
|
|
28
|
-
const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
29
|
-
(props, ref) => {
|
|
30
|
-
const {
|
|
31
|
-
aggressivelyGetLatestDuration,
|
|
32
|
-
allowFontScaling = false,
|
|
33
|
-
amLabel,
|
|
34
|
-
Audio,
|
|
35
|
-
bottomPickerGradientOverlayProps,
|
|
36
|
-
clickSoundAsset,
|
|
37
|
-
disableInfiniteScroll = false,
|
|
38
|
-
FlatList = RNFlatList,
|
|
39
|
-
Haptics,
|
|
40
|
-
initialValue = 0,
|
|
41
|
-
is12HourPicker,
|
|
42
|
-
isDisabled,
|
|
43
|
-
label,
|
|
44
|
-
limit,
|
|
45
|
-
LinearGradient,
|
|
46
|
-
numberOfItems,
|
|
47
|
-
onDurationChange,
|
|
48
|
-
padNumbersWithZero = false,
|
|
49
|
-
padWithNItems,
|
|
50
|
-
pickerGradientOverlayProps,
|
|
51
|
-
pmLabel,
|
|
52
|
-
styles,
|
|
53
|
-
testID,
|
|
54
|
-
topPickerGradientOverlayProps,
|
|
55
|
-
} = props;
|
|
56
|
-
|
|
57
|
-
const data = !is12HourPicker
|
|
58
|
-
? generateNumbers(numberOfItems, {
|
|
59
|
-
padNumbersWithZero,
|
|
60
|
-
repeatNTimes: 3,
|
|
61
|
-
disableInfiniteScroll,
|
|
62
|
-
padWithNItems,
|
|
63
|
-
})
|
|
64
|
-
: generate12HourNumbers({
|
|
65
|
-
padNumbersWithZero,
|
|
66
|
-
repeatNTimes: 3,
|
|
67
|
-
disableInfiniteScroll,
|
|
68
|
-
padWithNItems,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const numberOfItemsToShow = 1 + padWithNItems * 2;
|
|
72
|
-
|
|
73
|
-
const adjustedLimited = getAdjustedLimit(limit, numberOfItems);
|
|
74
|
-
|
|
75
|
-
const initialScrollIndex = getScrollIndex({
|
|
76
|
-
value: initialValue,
|
|
77
|
-
numberOfItems,
|
|
78
|
-
padWithNItems,
|
|
79
|
-
disableInfiniteScroll,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// keep track of the latest duration as it scrolls
|
|
83
|
-
const latestDuration = useRef(0);
|
|
84
|
-
// keep track of the last index scrolled past for haptic/audio feedback
|
|
85
|
-
const lastFeedbackIndex = useRef(0);
|
|
86
|
-
|
|
87
|
-
const flatListRef = useRef<RNFlatList | null>(null);
|
|
88
|
-
|
|
89
|
-
const [clickSound, setClickSound] = useState<
|
|
90
|
-
| {
|
|
91
|
-
replayAsync: () => Promise<void>;
|
|
92
|
-
unloadAsync: () => Promise<void>;
|
|
93
|
-
}
|
|
94
|
-
| undefined
|
|
95
|
-
>();
|
|
96
|
-
|
|
97
|
-
// Preload the sound when the component mounts
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
const loadSound = async () => {
|
|
100
|
-
if (Audio) {
|
|
101
|
-
const { sound } = await Audio.Sound.createAsync(
|
|
102
|
-
clickSoundAsset ?? {
|
|
103
|
-
// use a hosted sound as a fallback (do not use local asset due to loader issues
|
|
104
|
-
// in some environments when including mp3 in library)
|
|
105
|
-
uri: "https://drive.google.com/uc?export=download&id=10e1YkbNsRh-vGx1jmS1Nntz8xzkBp4_I",
|
|
106
|
-
},
|
|
107
|
-
{ shouldPlay: false }
|
|
108
|
-
);
|
|
109
|
-
setClickSound(sound);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
loadSound();
|
|
113
|
-
|
|
114
|
-
// Unload sound when component unmounts
|
|
115
|
-
return () => {
|
|
116
|
-
clickSound?.unloadAsync();
|
|
117
|
-
};
|
|
118
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
}, [Audio]);
|
|
120
|
-
|
|
121
|
-
useImperativeHandle(ref, () => ({
|
|
122
|
-
reset: (options) => {
|
|
123
|
-
flatListRef.current?.scrollToIndex({
|
|
124
|
-
animated: options?.animated ?? false,
|
|
125
|
-
index: initialScrollIndex,
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
setValue: (value, options) => {
|
|
129
|
-
flatListRef.current?.scrollToIndex({
|
|
130
|
-
animated: options?.animated ?? false,
|
|
131
|
-
index: getScrollIndex({
|
|
132
|
-
value: value,
|
|
133
|
-
numberOfItems,
|
|
134
|
-
padWithNItems,
|
|
135
|
-
disableInfiniteScroll,
|
|
136
|
-
}),
|
|
137
|
-
});
|
|
138
|
-
},
|
|
139
|
-
latestDuration: latestDuration,
|
|
140
|
-
}));
|
|
141
|
-
|
|
142
|
-
const renderItem = useCallback(
|
|
143
|
-
({ item }: { item: string }) => {
|
|
144
|
-
let stringItem = item;
|
|
145
|
-
let intItem: number;
|
|
146
|
-
let isAm: boolean | undefined;
|
|
147
|
-
|
|
148
|
-
if (!is12HourPicker) {
|
|
149
|
-
intItem = parseInt(item);
|
|
150
|
-
} else {
|
|
151
|
-
isAm = item.includes("AM");
|
|
152
|
-
stringItem = item.replace(/\s[AP]M/g, "");
|
|
153
|
-
intItem = parseInt(stringItem);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<View
|
|
158
|
-
key={item}
|
|
159
|
-
style={styles.pickerItemContainer}
|
|
160
|
-
testID="picker-item">
|
|
161
|
-
<Text
|
|
162
|
-
allowFontScaling={allowFontScaling}
|
|
163
|
-
style={[
|
|
164
|
-
styles.pickerItem,
|
|
165
|
-
intItem > adjustedLimited.max ||
|
|
166
|
-
intItem < adjustedLimited.min
|
|
167
|
-
? styles.disabledPickerItem
|
|
168
|
-
: {},
|
|
169
|
-
]}>
|
|
170
|
-
{stringItem}
|
|
171
|
-
</Text>
|
|
172
|
-
{is12HourPicker ? (
|
|
173
|
-
<View
|
|
174
|
-
pointerEvents="none"
|
|
175
|
-
style={styles.pickerAmPmContainer}>
|
|
176
|
-
<Text
|
|
177
|
-
allowFontScaling={allowFontScaling}
|
|
178
|
-
style={[styles.pickerAmPmLabel]}>
|
|
179
|
-
{isAm ? amLabel : pmLabel}
|
|
180
|
-
</Text>
|
|
181
|
-
</View>
|
|
182
|
-
) : null}
|
|
183
|
-
</View>
|
|
184
|
-
);
|
|
185
|
-
},
|
|
186
|
-
[
|
|
187
|
-
adjustedLimited.max,
|
|
188
|
-
adjustedLimited.min,
|
|
189
|
-
allowFontScaling,
|
|
190
|
-
amLabel,
|
|
191
|
-
is12HourPicker,
|
|
192
|
-
pmLabel,
|
|
193
|
-
styles.disabledPickerItem,
|
|
194
|
-
styles.pickerAmPmContainer,
|
|
195
|
-
styles.pickerAmPmLabel,
|
|
196
|
-
styles.pickerItem,
|
|
197
|
-
styles.pickerItemContainer,
|
|
198
|
-
]
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const onScroll = useCallback(
|
|
202
|
-
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
203
|
-
// this function is only used when the picker is in a modal and/or has Haptic/Audio feedback
|
|
204
|
-
// it is used to ensure that the modal gets the latest duration on clicking
|
|
205
|
-
// the confirm button, even if the scrollview is still scrolling
|
|
206
|
-
if (!aggressivelyGetLatestDuration && !Haptics && !Audio) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (aggressivelyGetLatestDuration) {
|
|
211
|
-
const newIndex = Math.round(
|
|
212
|
-
e.nativeEvent.contentOffset.y /
|
|
213
|
-
styles.pickerItemContainer.height
|
|
214
|
-
);
|
|
215
|
-
let newDuration =
|
|
216
|
-
(disableInfiniteScroll
|
|
217
|
-
? newIndex
|
|
218
|
-
: newIndex + padWithNItems) %
|
|
219
|
-
(numberOfItems + 1);
|
|
220
|
-
|
|
221
|
-
if (newDuration !== latestDuration.current) {
|
|
222
|
-
// check limits
|
|
223
|
-
if (newDuration > adjustedLimited.max) {
|
|
224
|
-
newDuration = adjustedLimited.max;
|
|
225
|
-
} else if (newDuration < adjustedLimited.min) {
|
|
226
|
-
newDuration = adjustedLimited.min;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
latestDuration.current = newDuration;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (Haptics || Audio) {
|
|
234
|
-
const feedbackIndex = Math.round(
|
|
235
|
-
(e.nativeEvent.contentOffset.y +
|
|
236
|
-
styles.pickerItemContainer.height / 2) /
|
|
237
|
-
styles.pickerItemContainer.height
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
if (feedbackIndex !== lastFeedbackIndex.current) {
|
|
241
|
-
// this check stops the feedback firing when the component mounts
|
|
242
|
-
if (lastFeedbackIndex.current) {
|
|
243
|
-
// fire haptic feedback if available
|
|
244
|
-
Haptics?.selectionAsync();
|
|
245
|
-
|
|
246
|
-
// play click sound if available
|
|
247
|
-
clickSound?.replayAsync();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
lastFeedbackIndex.current = feedbackIndex;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
|
-
[
|
|
256
|
-
adjustedLimited.max,
|
|
257
|
-
adjustedLimited.min,
|
|
258
|
-
aggressivelyGetLatestDuration,
|
|
259
|
-
clickSound,
|
|
260
|
-
disableInfiniteScroll,
|
|
261
|
-
numberOfItems,
|
|
262
|
-
padWithNItems,
|
|
263
|
-
styles.pickerItemContainer.height,
|
|
264
|
-
]
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
const onMomentumScrollEnd = useCallback(
|
|
268
|
-
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
269
|
-
const newIndex = Math.round(
|
|
270
|
-
e.nativeEvent.contentOffset.y /
|
|
271
|
-
styles.pickerItemContainer.height
|
|
272
|
-
);
|
|
273
|
-
let newDuration =
|
|
274
|
-
(disableInfiniteScroll
|
|
275
|
-
? newIndex
|
|
276
|
-
: newIndex + padWithNItems) %
|
|
277
|
-
(numberOfItems + 1);
|
|
278
|
-
|
|
279
|
-
// check limits
|
|
280
|
-
if (newDuration > adjustedLimited.max) {
|
|
281
|
-
const targetScrollIndex =
|
|
282
|
-
newIndex - (newDuration - adjustedLimited.max);
|
|
283
|
-
flatListRef.current?.scrollToIndex({
|
|
284
|
-
animated: true,
|
|
285
|
-
index:
|
|
286
|
-
// guard against scrolling beyond end of list
|
|
287
|
-
targetScrollIndex >= 0
|
|
288
|
-
? targetScrollIndex
|
|
289
|
-
: adjustedLimited.max - 1,
|
|
290
|
-
}); // scroll down to max
|
|
291
|
-
newDuration = adjustedLimited.max;
|
|
292
|
-
} else if (newDuration < adjustedLimited.min) {
|
|
293
|
-
const targetScrollIndex =
|
|
294
|
-
newIndex + (adjustedLimited.min - newDuration);
|
|
295
|
-
flatListRef.current?.scrollToIndex({
|
|
296
|
-
animated: true,
|
|
297
|
-
index:
|
|
298
|
-
// guard against scrolling beyond end of list
|
|
299
|
-
targetScrollIndex <= data.length - 1
|
|
300
|
-
? targetScrollIndex
|
|
301
|
-
: adjustedLimited.min,
|
|
302
|
-
}); // scroll up to min
|
|
303
|
-
newDuration = adjustedLimited.min;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
onDurationChange(newDuration);
|
|
307
|
-
},
|
|
308
|
-
[
|
|
309
|
-
adjustedLimited.max,
|
|
310
|
-
adjustedLimited.min,
|
|
311
|
-
data.length,
|
|
312
|
-
disableInfiniteScroll,
|
|
313
|
-
numberOfItems,
|
|
314
|
-
onDurationChange,
|
|
315
|
-
padWithNItems,
|
|
316
|
-
styles.pickerItemContainer.height,
|
|
317
|
-
]
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
const onViewableItemsChanged = useCallback(
|
|
321
|
-
({ viewableItems }: { viewableItems: ViewToken[] }) => {
|
|
322
|
-
if (
|
|
323
|
-
viewableItems[0]?.index &&
|
|
324
|
-
viewableItems[0].index < numberOfItems * 0.5
|
|
325
|
-
) {
|
|
326
|
-
flatListRef.current?.scrollToIndex({
|
|
327
|
-
animated: false,
|
|
328
|
-
index: viewableItems[0].index + numberOfItems,
|
|
329
|
-
});
|
|
330
|
-
} else if (
|
|
331
|
-
viewableItems[0]?.index &&
|
|
332
|
-
viewableItems[0].index >= numberOfItems * 2.5
|
|
333
|
-
) {
|
|
334
|
-
flatListRef.current?.scrollToIndex({
|
|
335
|
-
animated: false,
|
|
336
|
-
index: viewableItems[0].index - numberOfItems,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
},
|
|
340
|
-
[numberOfItems]
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
const getItemLayout = useCallback(
|
|
344
|
-
(_: ArrayLike<string> | null | undefined, index: number) => ({
|
|
345
|
-
length: styles.pickerItemContainer.height,
|
|
346
|
-
offset: styles.pickerItemContainer.height * index,
|
|
347
|
-
index,
|
|
348
|
-
}),
|
|
349
|
-
[styles.pickerItemContainer.height]
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
const viewabilityConfigCallbackPairs =
|
|
353
|
-
useRef<ViewabilityConfigCallbackPairs>([
|
|
354
|
-
{
|
|
355
|
-
viewabilityConfig: { viewAreaCoveragePercentThreshold: 25 },
|
|
356
|
-
onViewableItemsChanged: onViewableItemsChanged,
|
|
357
|
-
},
|
|
358
|
-
]);
|
|
359
|
-
|
|
360
|
-
return (
|
|
361
|
-
<View
|
|
362
|
-
pointerEvents={isDisabled ? "none" : undefined}
|
|
363
|
-
style={[
|
|
364
|
-
{
|
|
365
|
-
height:
|
|
366
|
-
styles.pickerItemContainer.height *
|
|
367
|
-
numberOfItemsToShow,
|
|
368
|
-
overflow: "visible",
|
|
369
|
-
},
|
|
370
|
-
isDisabled && styles.disabledPickerContainer,
|
|
371
|
-
]}
|
|
372
|
-
testID={testID}>
|
|
373
|
-
<FlatList
|
|
374
|
-
ref={flatListRef}
|
|
375
|
-
data={data}
|
|
376
|
-
decelerationRate={0.88}
|
|
377
|
-
getItemLayout={getItemLayout}
|
|
378
|
-
initialScrollIndex={initialScrollIndex}
|
|
379
|
-
keyExtractor={(_, index) => index.toString()}
|
|
380
|
-
nestedScrollEnabled
|
|
381
|
-
onMomentumScrollEnd={onMomentumScrollEnd}
|
|
382
|
-
onScroll={onScroll}
|
|
383
|
-
renderItem={renderItem}
|
|
384
|
-
scrollEnabled={!isDisabled}
|
|
385
|
-
scrollEventThrottle={16}
|
|
386
|
-
showsVerticalScrollIndicator={false}
|
|
387
|
-
snapToAlignment="start"
|
|
388
|
-
// used in place of snapToOffset due to bug on Android
|
|
389
|
-
snapToOffsets={[...Array(data.length)].map(
|
|
390
|
-
(_, i) => i * styles.pickerItemContainer.height
|
|
391
|
-
)}
|
|
392
|
-
testID="duration-scroll-flatlist"
|
|
393
|
-
viewabilityConfigCallbackPairs={
|
|
394
|
-
!disableInfiniteScroll
|
|
395
|
-
? viewabilityConfigCallbackPairs?.current
|
|
396
|
-
: undefined
|
|
397
|
-
}
|
|
398
|
-
windowSize={numberOfItemsToShow}
|
|
399
|
-
/>
|
|
400
|
-
<View pointerEvents="none" style={styles.pickerLabelContainer}>
|
|
401
|
-
{typeof label === "string" ? (
|
|
402
|
-
<Text
|
|
403
|
-
allowFontScaling={allowFontScaling}
|
|
404
|
-
style={styles.pickerLabel}>
|
|
405
|
-
{label}
|
|
406
|
-
</Text>
|
|
407
|
-
) : (
|
|
408
|
-
label ?? null
|
|
409
|
-
)}
|
|
410
|
-
</View>
|
|
411
|
-
{LinearGradient ? (
|
|
412
|
-
<>
|
|
413
|
-
<LinearGradient
|
|
414
|
-
colors={[
|
|
415
|
-
styles.pickerContainer.backgroundColor ??
|
|
416
|
-
"white",
|
|
417
|
-
colorToRgba({
|
|
418
|
-
color:
|
|
419
|
-
styles.pickerContainer
|
|
420
|
-
.backgroundColor ?? "white",
|
|
421
|
-
opacity: 0,
|
|
422
|
-
}),
|
|
423
|
-
]}
|
|
424
|
-
end={{ x: 1, y: 1 }}
|
|
425
|
-
pointerEvents="none"
|
|
426
|
-
start={{ x: 1, y: 0.3 }}
|
|
427
|
-
{...pickerGradientOverlayProps}
|
|
428
|
-
{...topPickerGradientOverlayProps}
|
|
429
|
-
style={[styles.pickerGradientOverlay, { top: 0 }]}
|
|
430
|
-
/>
|
|
431
|
-
<LinearGradient
|
|
432
|
-
colors={[
|
|
433
|
-
colorToRgba({
|
|
434
|
-
color:
|
|
435
|
-
styles.pickerContainer
|
|
436
|
-
.backgroundColor ?? "white",
|
|
437
|
-
opacity: 0,
|
|
438
|
-
}),
|
|
439
|
-
styles.pickerContainer.backgroundColor ??
|
|
440
|
-
"white",
|
|
441
|
-
]}
|
|
442
|
-
end={{ x: 1, y: 0.7 }}
|
|
443
|
-
pointerEvents="none"
|
|
444
|
-
start={{ x: 1, y: 0 }}
|
|
445
|
-
{...pickerGradientOverlayProps}
|
|
446
|
-
{...bottomPickerGradientOverlayProps}
|
|
447
|
-
style={[
|
|
448
|
-
styles.pickerGradientOverlay,
|
|
449
|
-
{ bottom: -1 },
|
|
450
|
-
]}
|
|
451
|
-
/>
|
|
452
|
-
</>
|
|
453
|
-
) : null}
|
|
454
|
-
</View>
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
export default React.memo(DurationScroll);
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import type { MutableRefObject } from "react";
|
|
3
|
-
|
|
4
|
-
import type {
|
|
5
|
-
View,
|
|
6
|
-
FlatList as RNFlatList,
|
|
7
|
-
FlatListProps as RNFlatListProps,
|
|
8
|
-
} from "react-native";
|
|
9
|
-
|
|
10
|
-
import type { generateStyles } from "../TimerPicker/styles";
|
|
11
|
-
|
|
12
|
-
export type CustomFlatList = <ItemT = any>(
|
|
13
|
-
props: React.PropsWithChildren<
|
|
14
|
-
RNFlatListProps<ItemT> & React.RefAttributes<RNFlatList<ItemT>>
|
|
15
|
-
>,
|
|
16
|
-
ref: React.ForwardedRef<RNFlatList<ItemT>>
|
|
17
|
-
) => React.ReactElement | null;
|
|
18
|
-
|
|
19
|
-
export interface DurationScrollProps {
|
|
20
|
-
Audio?: any;
|
|
21
|
-
FlatList?: CustomFlatList;
|
|
22
|
-
Haptics?: any;
|
|
23
|
-
LinearGradient?: any;
|
|
24
|
-
aggressivelyGetLatestDuration: boolean;
|
|
25
|
-
allowFontScaling?: boolean;
|
|
26
|
-
amLabel?: string;
|
|
27
|
-
bottomPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
28
|
-
clickSoundAsset?: SoundAssetType;
|
|
29
|
-
disableInfiniteScroll?: boolean;
|
|
30
|
-
initialValue?: number;
|
|
31
|
-
is12HourPicker?: boolean;
|
|
32
|
-
isDisabled?: boolean;
|
|
33
|
-
label?: string | React.ReactElement;
|
|
34
|
-
limit?: LimitType;
|
|
35
|
-
numberOfItems: number;
|
|
36
|
-
onDurationChange: (duration: number) => void;
|
|
37
|
-
padNumbersWithZero?: boolean;
|
|
38
|
-
padWithNItems: number;
|
|
39
|
-
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
40
|
-
pmLabel?: string;
|
|
41
|
-
styles: ReturnType<typeof generateStyles>;
|
|
42
|
-
testID?: string;
|
|
43
|
-
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface DurationScrollRef {
|
|
47
|
-
latestDuration: MutableRefObject<number>;
|
|
48
|
-
reset: (options?: { animated?: boolean }) => void;
|
|
49
|
-
setValue: (value: number, options?: { animated?: boolean }) => void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type LinearGradientPoint = {
|
|
53
|
-
x: number;
|
|
54
|
-
y: number;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type LinearGradientProps = React.ComponentProps<typeof View> & {
|
|
58
|
-
colors: string[];
|
|
59
|
-
end?: LinearGradientPoint | null;
|
|
60
|
-
locations?: number[] | null;
|
|
61
|
-
start?: LinearGradientPoint | null;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type LimitType = {
|
|
65
|
-
max?: number;
|
|
66
|
-
min?: number;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export type SoundAssetType =
|
|
70
|
-
| number
|
|
71
|
-
| {
|
|
72
|
-
headers?: Record<string, string>;
|
|
73
|
-
overrideFileExtensionAndroid?: string;
|
|
74
|
-
uri: string;
|
|
75
|
-
};
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Animated,
|
|
5
|
-
Easing,
|
|
6
|
-
Modal as ReactNativeModal,
|
|
7
|
-
TouchableWithoutFeedback,
|
|
8
|
-
useWindowDimensions,
|
|
9
|
-
} from "react-native";
|
|
10
|
-
|
|
11
|
-
import { styles } from "./styles";
|
|
12
|
-
import type { ModalProps } from "./types";
|
|
13
|
-
|
|
14
|
-
export const Modal = (props: ModalProps) => {
|
|
15
|
-
const {
|
|
16
|
-
animationDuration = 300,
|
|
17
|
-
children,
|
|
18
|
-
contentStyle,
|
|
19
|
-
isVisible = false,
|
|
20
|
-
modalProps,
|
|
21
|
-
onHide,
|
|
22
|
-
onOverlayPress,
|
|
23
|
-
overlayOpacity = 0.4,
|
|
24
|
-
overlayStyle,
|
|
25
|
-
testID = "modal",
|
|
26
|
-
} = props;
|
|
27
|
-
|
|
28
|
-
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
|
|
29
|
-
|
|
30
|
-
const isMounted = useRef(false);
|
|
31
|
-
const animatedOpacity = useRef(new Animated.Value(0));
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
isMounted.current = true;
|
|
35
|
-
if (isVisible) {
|
|
36
|
-
show();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return () => {
|
|
40
|
-
isMounted.current = false;
|
|
41
|
-
};
|
|
42
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
const backdropAnimatedStyle = {
|
|
46
|
-
opacity: animatedOpacity.current.interpolate({
|
|
47
|
-
inputRange: [0, 1],
|
|
48
|
-
outputRange: [0, overlayOpacity],
|
|
49
|
-
}),
|
|
50
|
-
};
|
|
51
|
-
const contentAnimatedStyle = {
|
|
52
|
-
transform: [
|
|
53
|
-
{
|
|
54
|
-
translateY: animatedOpacity.current.interpolate({
|
|
55
|
-
inputRange: [0, 1],
|
|
56
|
-
outputRange: [screenHeight, 0],
|
|
57
|
-
extrapolate: "clamp",
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const show = useCallback(() => {
|
|
64
|
-
Animated.timing(animatedOpacity.current, {
|
|
65
|
-
easing: Easing.inOut(Easing.quad),
|
|
66
|
-
// Using native driver in the modal makes the content flash
|
|
67
|
-
useNativeDriver: true,
|
|
68
|
-
duration: animationDuration,
|
|
69
|
-
toValue: 1,
|
|
70
|
-
}).start();
|
|
71
|
-
}, [animationDuration]);
|
|
72
|
-
|
|
73
|
-
const hide = useCallback(() => {
|
|
74
|
-
Animated.timing(animatedOpacity.current, {
|
|
75
|
-
easing: Easing.inOut(Easing.quad),
|
|
76
|
-
// Using native driver in the modal makes the content flash
|
|
77
|
-
useNativeDriver: true,
|
|
78
|
-
duration: animationDuration,
|
|
79
|
-
toValue: 0,
|
|
80
|
-
}).start(() => {
|
|
81
|
-
if (isMounted.current) {
|
|
82
|
-
onHide?.();
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}, [animationDuration, onHide]);
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (isVisible) {
|
|
89
|
-
show();
|
|
90
|
-
} else {
|
|
91
|
-
hide();
|
|
92
|
-
}
|
|
93
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
|
-
}, [isVisible]);
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<ReactNativeModal
|
|
98
|
-
animationType="fade"
|
|
99
|
-
transparent
|
|
100
|
-
visible={isVisible}
|
|
101
|
-
{...modalProps}
|
|
102
|
-
testID={testID}>
|
|
103
|
-
<TouchableWithoutFeedback
|
|
104
|
-
onPress={onOverlayPress}
|
|
105
|
-
testID="modal-backdrop">
|
|
106
|
-
<Animated.View
|
|
107
|
-
style={[
|
|
108
|
-
styles.backdrop,
|
|
109
|
-
backdropAnimatedStyle,
|
|
110
|
-
{ width: screenWidth, height: screenHeight },
|
|
111
|
-
overlayStyle,
|
|
112
|
-
]}
|
|
113
|
-
/>
|
|
114
|
-
</TouchableWithoutFeedback>
|
|
115
|
-
<Animated.View
|
|
116
|
-
pointerEvents="box-none"
|
|
117
|
-
style={[styles.content, contentAnimatedStyle, contentStyle]}>
|
|
118
|
-
{children}
|
|
119
|
-
</Animated.View>
|
|
120
|
-
</ReactNativeModal>
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export default React.memo(Modal);
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from "react-native";
|
|
2
|
-
|
|
3
|
-
export const styles = StyleSheet.create({
|
|
4
|
-
container: {
|
|
5
|
-
position: "absolute",
|
|
6
|
-
top: 0,
|
|
7
|
-
left: 0,
|
|
8
|
-
right: 0,
|
|
9
|
-
bottom: 0,
|
|
10
|
-
},
|
|
11
|
-
backdrop: {
|
|
12
|
-
position: "absolute",
|
|
13
|
-
top: 0,
|
|
14
|
-
bottom: 0,
|
|
15
|
-
left: 0,
|
|
16
|
-
right: 0,
|
|
17
|
-
backgroundColor: "black",
|
|
18
|
-
opacity: 0,
|
|
19
|
-
},
|
|
20
|
-
content: {
|
|
21
|
-
flex: 1,
|
|
22
|
-
justifyContent: "center",
|
|
23
|
-
alignItems: "center",
|
|
24
|
-
zIndex: 1,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ComponentProps } from "react";
|
|
2
|
-
|
|
3
|
-
import type { ViewStyle } from "react-native";
|
|
4
|
-
import type { Modal as ReactNativeModal } from "react-native";
|
|
5
|
-
|
|
6
|
-
export interface ModalProps {
|
|
7
|
-
animationDuration?: number;
|
|
8
|
-
children?: React.ReactElement;
|
|
9
|
-
contentStyle?: ViewStyle;
|
|
10
|
-
isVisible?: boolean;
|
|
11
|
-
modalProps?: ComponentProps<typeof ReactNativeModal>;
|
|
12
|
-
onHide?: () => void;
|
|
13
|
-
onOverlayPress?: () => void;
|
|
14
|
-
overlayOpacity?: number;
|
|
15
|
-
overlayStyle?: ViewStyle;
|
|
16
|
-
testID?: string;
|
|
17
|
-
}
|