react-native-timer-picker 1.2.7 → 1.2.9

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.
Files changed (110) hide show
  1. package/dist/commonjs/components/Modal/Modal.styles.js +32 -0
  2. package/dist/commonjs/components/Modal/Modal.styles.js.map +1 -0
  3. package/dist/commonjs/components/Modal/index.js +108 -0
  4. package/dist/commonjs/components/Modal/index.js.map +1 -0
  5. package/dist/commonjs/components/TimerPicker/DurationScroll.js +210 -0
  6. package/dist/commonjs/components/TimerPicker/DurationScroll.js.map +1 -0
  7. package/dist/commonjs/components/TimerPicker/TimerPicker.styles.js +67 -0
  8. package/dist/commonjs/components/TimerPicker/TimerPicker.styles.js.map +1 -0
  9. package/dist/commonjs/components/TimerPicker/index.js +130 -0
  10. package/dist/commonjs/components/TimerPicker/index.js.map +1 -0
  11. package/dist/commonjs/components/TimerPickerModal.styles.js +69 -0
  12. package/dist/commonjs/components/TimerPickerModal.styles.js.map +1 -0
  13. package/dist/commonjs/components/index.js +156 -0
  14. package/dist/commonjs/components/index.js.map +1 -0
  15. package/dist/commonjs/index.js +21 -0
  16. package/dist/commonjs/index.js.map +1 -0
  17. package/dist/commonjs/tests/DurationScroll.test.js +56 -0
  18. package/dist/commonjs/tests/DurationScroll.test.js.map +1 -0
  19. package/dist/commonjs/tests/Modal.test.js +40 -0
  20. package/dist/commonjs/tests/Modal.test.js.map +1 -0
  21. package/dist/commonjs/tests/TimerPicker.test.js +37 -0
  22. package/dist/commonjs/tests/TimerPicker.test.js.map +1 -0
  23. package/dist/commonjs/tests/TimerPickerModal.test.js +73 -0
  24. package/dist/commonjs/tests/TimerPickerModal.test.js.map +1 -0
  25. package/dist/commonjs/utils/colorToRgba.js +51 -0
  26. package/dist/commonjs/utils/colorToRgba.js.map +1 -0
  27. package/dist/commonjs/utils/generateNumbers.js +32 -0
  28. package/dist/commonjs/utils/generateNumbers.js.map +1 -0
  29. package/dist/commonjs/utils/getAdjustedLimit.js +32 -0
  30. package/dist/commonjs/utils/getAdjustedLimit.js.map +1 -0
  31. package/dist/commonjs/utils/getScrollIndex.js +17 -0
  32. package/dist/commonjs/utils/getScrollIndex.js.map +1 -0
  33. package/dist/commonjs/utils/padWithZero.js +15 -0
  34. package/dist/commonjs/utils/padWithZero.js.map +1 -0
  35. package/dist/module/components/Modal/Modal.styles.js +26 -0
  36. package/dist/module/components/Modal/Modal.styles.js.map +1 -0
  37. package/dist/module/components/Modal/index.js +100 -0
  38. package/dist/module/components/Modal/index.js.map +1 -0
  39. package/dist/module/components/TimerPicker/DurationScroll.js +202 -0
  40. package/dist/module/components/TimerPicker/DurationScroll.js.map +1 -0
  41. package/dist/module/components/TimerPicker/TimerPicker.styles.js +59 -0
  42. package/dist/module/components/TimerPicker/TimerPicker.styles.js.map +1 -0
  43. package/dist/module/components/TimerPicker/index.js +121 -0
  44. package/dist/module/components/TimerPicker/index.js.map +1 -0
  45. package/dist/module/components/TimerPickerModal.styles.js +61 -0
  46. package/dist/module/components/TimerPickerModal.styles.js.map +1 -0
  47. package/dist/module/components/index.js +147 -0
  48. package/dist/module/components/index.js.map +1 -0
  49. package/dist/module/index.js +3 -0
  50. package/dist/module/index.js.map +1 -0
  51. package/dist/module/tests/DurationScroll.test.js +53 -0
  52. package/dist/module/tests/DurationScroll.test.js.map +1 -0
  53. package/dist/module/tests/Modal.test.js +37 -0
  54. package/dist/module/tests/Modal.test.js.map +1 -0
  55. package/dist/module/tests/TimerPicker.test.js +34 -0
  56. package/dist/module/tests/TimerPicker.test.js.map +1 -0
  57. package/dist/module/tests/TimerPickerModal.test.js +70 -0
  58. package/dist/module/tests/TimerPickerModal.test.js.map +1 -0
  59. package/dist/module/utils/colorToRgba.js +44 -0
  60. package/dist/module/utils/colorToRgba.js.map +1 -0
  61. package/dist/module/utils/generateNumbers.js +25 -0
  62. package/dist/module/utils/generateNumbers.js.map +1 -0
  63. package/dist/module/utils/getAdjustedLimit.js +25 -0
  64. package/dist/module/utils/getAdjustedLimit.js.map +1 -0
  65. package/dist/module/utils/getScrollIndex.js +10 -0
  66. package/dist/module/utils/getScrollIndex.js.map +1 -0
  67. package/dist/module/utils/padWithZero.js +8 -0
  68. package/dist/module/utils/padWithZero.js.map +1 -0
  69. package/dist/typescript/index.d.ts +4 -0
  70. package/dist/{utils → typescript/utils}/colorToRgba.d.ts +1 -1
  71. package/dist/{utils → typescript/utils}/getScrollIndex.d.ts +1 -1
  72. package/package.json +29 -12
  73. package/{dist/components/Modal/Modal.styles.js → src/components/Modal/Modal.styles.ts} +4 -6
  74. package/src/components/Modal/index.tsx +134 -0
  75. package/src/components/TimerPicker/DurationScroll.tsx +337 -0
  76. package/src/components/TimerPicker/TimerPicker.styles.ts +87 -0
  77. package/src/components/TimerPicker/index.tsx +216 -0
  78. package/src/components/TimerPickerModal.styles.ts +87 -0
  79. package/src/components/index.tsx +243 -0
  80. package/src/index.ts +14 -0
  81. package/src/tests/DurationScroll.test.tsx +57 -0
  82. package/src/tests/Modal.test.tsx +34 -0
  83. package/src/tests/TimerPicker.test.tsx +27 -0
  84. package/src/tests/TimerPickerModal.test.tsx +70 -0
  85. package/{dist/utils/colorToRgba.js → src/utils/colorToRgba.ts} +18 -17
  86. package/src/utils/generateNumbers.ts +34 -0
  87. package/{dist/utils/getAdjustedLimit.js → src/utils/getAdjustedLimit.ts} +14 -7
  88. package/src/utils/getScrollIndex.ts +15 -0
  89. package/src/utils/padWithZero.ts +7 -0
  90. package/dist/components/Modal/index.js +0 -109
  91. package/dist/components/TimerPicker/DurationScroll.js +0 -211
  92. package/dist/components/TimerPicker/TimerPicker.styles.js +0 -41
  93. package/dist/components/TimerPicker/index.js +0 -81
  94. package/dist/components/TimerPickerModal.styles.js +0 -37
  95. package/dist/components/index.js +0 -118
  96. package/dist/index.d.ts +0 -4
  97. package/dist/index.js +0 -10
  98. package/dist/utils/generateNumbers.js +0 -30
  99. package/dist/utils/getScrollIndex.js +0 -10
  100. package/dist/utils/padWithZero.js +0 -12
  101. /package/dist/{components → typescript/components}/Modal/Modal.styles.d.ts +0 -0
  102. /package/dist/{components → typescript/components}/Modal/index.d.ts +0 -0
  103. /package/dist/{components → typescript/components}/TimerPicker/DurationScroll.d.ts +0 -0
  104. /package/dist/{components → typescript/components}/TimerPicker/TimerPicker.styles.d.ts +0 -0
  105. /package/dist/{components → typescript/components}/TimerPicker/index.d.ts +0 -0
  106. /package/dist/{components → typescript/components}/TimerPickerModal.styles.d.ts +0 -0
  107. /package/dist/{components → typescript/components}/index.d.ts +0 -0
  108. /package/dist/{utils → typescript/utils}/generateNumbers.d.ts +0 -0
  109. /package/dist/{utils → typescript/utils}/getAdjustedLimit.d.ts +0 -0
  110. /package/dist/{utils → typescript/utils}/padWithZero.d.ts +0 -0
@@ -0,0 +1,337 @@
1
+ import React, {
2
+ useRef,
3
+ useCallback,
4
+ forwardRef,
5
+ useImperativeHandle,
6
+ } from "react";
7
+ import {
8
+ View,
9
+ Text,
10
+ FlatList,
11
+ ViewabilityConfigCallbackPairs,
12
+ ViewToken,
13
+ NativeSyntheticEvent,
14
+ NativeScrollEvent,
15
+ } from "react-native";
16
+
17
+ import { generateNumbers } from "../../utils/generateNumbers";
18
+ import { colorToRgba } from "../../utils/colorToRgba";
19
+ import { generateStyles } from "./TimerPicker.styles";
20
+ import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
21
+ import { getScrollIndex } from "../../utils/getScrollIndex";
22
+
23
+ export interface DurationScrollRef {
24
+ reset: (options?: { animated?: boolean }) => void;
25
+ setValue: (value: number, options?: { animated?: boolean }) => void;
26
+ }
27
+
28
+ type LinearGradientPoint = {
29
+ x: number;
30
+ y: number;
31
+ };
32
+
33
+ export type LinearGradientProps = React.ComponentProps<typeof View> & {
34
+ colors: string[];
35
+ locations?: number[] | null;
36
+ start?: LinearGradientPoint | null;
37
+ end?: LinearGradientPoint | null;
38
+ };
39
+
40
+ export type LimitType = {
41
+ max?: number;
42
+ min?: number;
43
+ };
44
+
45
+ interface DurationScrollProps {
46
+ numberOfItems: number;
47
+ label?: string | React.ReactElement;
48
+ initialValue?: number;
49
+ onDurationChange: (duration: number) => void;
50
+ padNumbersWithZero?: boolean;
51
+ disableInfiniteScroll?: boolean;
52
+ limit?: LimitType;
53
+ padWithNItems: number;
54
+ pickerGradientOverlayProps?: Partial<LinearGradientProps>;
55
+ topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
56
+ bottomPickerGradientOverlayProps?: Partial<LinearGradientProps>;
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ LinearGradient?: any;
59
+ testID?: string;
60
+ styles: ReturnType<typeof generateStyles>;
61
+ }
62
+
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const KEY_EXTRACTOR = (_: any, index: number) => index.toString();
65
+
66
+ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
67
+ (
68
+ {
69
+ numberOfItems,
70
+ label,
71
+ initialValue = 0,
72
+ onDurationChange,
73
+ padNumbersWithZero = false,
74
+ disableInfiniteScroll = false,
75
+ limit,
76
+ padWithNItems,
77
+ pickerGradientOverlayProps,
78
+ topPickerGradientOverlayProps,
79
+ bottomPickerGradientOverlayProps,
80
+ LinearGradient,
81
+ testID,
82
+ styles,
83
+ },
84
+ ref
85
+ ): React.ReactElement => {
86
+ const flatListRef = useRef<FlatList | null>(null);
87
+
88
+ const data = generateNumbers(numberOfItems, {
89
+ padWithZero: padNumbersWithZero,
90
+ repeatNTimes: 3,
91
+ disableInfiniteScroll,
92
+ padWithNItems: padWithNItems,
93
+ });
94
+
95
+ const numberOfItemsToShow = 1 + padWithNItems * 2;
96
+
97
+ const adjustedLimited = getAdjustedLimit(limit, numberOfItems);
98
+
99
+ const initialScrollIndex = getScrollIndex({
100
+ value: initialValue,
101
+ numberOfItems,
102
+ padWithNItems,
103
+ disableInfiniteScroll,
104
+ });
105
+
106
+ useImperativeHandle(ref, () => ({
107
+ reset: (options) => {
108
+ flatListRef.current?.scrollToIndex({
109
+ animated: options?.animated ?? false,
110
+ index: initialScrollIndex,
111
+ });
112
+ },
113
+ setValue: (value, options) => {
114
+ flatListRef.current?.scrollToIndex({
115
+ animated: options?.animated ?? false,
116
+ index: getScrollIndex({
117
+ value: value,
118
+ numberOfItems,
119
+ padWithNItems,
120
+ disableInfiniteScroll,
121
+ }),
122
+ });
123
+ },
124
+ }));
125
+
126
+ const renderItem = useCallback(
127
+ ({ item }: { item: string }) => {
128
+ const intItem = parseInt(item);
129
+
130
+ return (
131
+ <View
132
+ key={item}
133
+ style={styles.pickerItemContainer}
134
+ testID="picker-item">
135
+ <Text
136
+ style={[
137
+ styles.pickerItem,
138
+ intItem > adjustedLimited.max ||
139
+ intItem < adjustedLimited.min
140
+ ? styles.disabledPickerItem
141
+ : {},
142
+ ]}>
143
+ {item}
144
+ </Text>
145
+ </View>
146
+ );
147
+ },
148
+ [
149
+ adjustedLimited.max,
150
+ adjustedLimited.min,
151
+ styles.disabledPickerItem,
152
+ styles.pickerItem,
153
+ styles.pickerItemContainer,
154
+ ]
155
+ );
156
+
157
+ const onMomentumScrollEnd = useCallback(
158
+ (e: NativeSyntheticEvent<NativeScrollEvent>) => {
159
+ const newIndex = Math.round(
160
+ e.nativeEvent.contentOffset.y /
161
+ styles.pickerItemContainer.height
162
+ );
163
+ let newDuration =
164
+ (disableInfiniteScroll
165
+ ? newIndex
166
+ : newIndex + padWithNItems) %
167
+ (numberOfItems + 1);
168
+
169
+ // check limits
170
+ if (newDuration > adjustedLimited.max) {
171
+ const targetScrollIndex =
172
+ newIndex - (newDuration - adjustedLimited.max);
173
+ flatListRef.current?.scrollToIndex({
174
+ animated: true,
175
+ index:
176
+ // guard against scrolling beyond end of list
177
+ targetScrollIndex >= 0
178
+ ? targetScrollIndex
179
+ : adjustedLimited.max - 1,
180
+ }); // scroll down to max
181
+ newDuration = adjustedLimited.max;
182
+ } else if (newDuration < adjustedLimited.min) {
183
+ const targetScrollIndex =
184
+ newIndex + (adjustedLimited.min - newDuration);
185
+ flatListRef.current?.scrollToIndex({
186
+ animated: true,
187
+ index:
188
+ // guard against scrolling beyond end of list
189
+ targetScrollIndex <= data.length - 1
190
+ ? targetScrollIndex
191
+ : adjustedLimited.min,
192
+ }); // scroll up to min
193
+ newDuration = adjustedLimited.min;
194
+ }
195
+
196
+ onDurationChange(newDuration);
197
+ },
198
+ [
199
+ adjustedLimited.max,
200
+ adjustedLimited.min,
201
+ data.length,
202
+ disableInfiniteScroll,
203
+ numberOfItems,
204
+ onDurationChange,
205
+ padWithNItems,
206
+ styles.pickerItemContainer.height,
207
+ ]
208
+ );
209
+
210
+ const onViewableItemsChanged = useCallback(
211
+ ({ viewableItems }: { viewableItems: ViewToken[] }) => {
212
+ if (
213
+ viewableItems[0]?.index &&
214
+ viewableItems[0].index < numberOfItems * 0.5
215
+ ) {
216
+ flatListRef.current?.scrollToIndex({
217
+ animated: false,
218
+ index: viewableItems[0].index + numberOfItems,
219
+ });
220
+ } else if (
221
+ viewableItems[0]?.index &&
222
+ viewableItems[0].index >= numberOfItems * 2.5
223
+ ) {
224
+ flatListRef.current?.scrollToIndex({
225
+ animated: false,
226
+ index: viewableItems[0].index - numberOfItems,
227
+ });
228
+ }
229
+ },
230
+ [numberOfItems]
231
+ );
232
+
233
+ const getItemLayout = useCallback(
234
+ (_: ArrayLike<string> | null | undefined, index: number) => ({
235
+ length: styles.pickerItemContainer.height,
236
+ offset: styles.pickerItemContainer.height * index,
237
+ index,
238
+ }),
239
+ [styles.pickerItemContainer.height]
240
+ );
241
+
242
+ const viewabilityConfigCallbackPairs =
243
+ useRef<ViewabilityConfigCallbackPairs>([
244
+ {
245
+ viewabilityConfig: { viewAreaCoveragePercentThreshold: 25 },
246
+ onViewableItemsChanged: onViewableItemsChanged,
247
+ },
248
+ ]);
249
+
250
+ return (
251
+ <View
252
+ testID={testID}
253
+ style={{
254
+ height:
255
+ styles.pickerItemContainer.height * numberOfItemsToShow,
256
+ overflow: "hidden",
257
+ }}>
258
+ <FlatList
259
+ ref={flatListRef}
260
+ data={data}
261
+ getItemLayout={getItemLayout}
262
+ initialScrollIndex={initialScrollIndex}
263
+ windowSize={numberOfItemsToShow}
264
+ renderItem={renderItem}
265
+ keyExtractor={KEY_EXTRACTOR}
266
+ showsVerticalScrollIndicator={false}
267
+ decelerationRate={0.9}
268
+ scrollEventThrottle={16}
269
+ snapToAlignment="start"
270
+ // used in place of snapToOffset due to bug on Android
271
+ snapToOffsets={[...Array(data.length)].map(
272
+ (_, i) => i * styles.pickerItemContainer.height
273
+ )}
274
+ viewabilityConfigCallbackPairs={
275
+ !disableInfiniteScroll
276
+ ? viewabilityConfigCallbackPairs?.current
277
+ : undefined
278
+ }
279
+ onMomentumScrollEnd={onMomentumScrollEnd}
280
+ testID="duration-scroll-flatlist"
281
+ />
282
+ <View style={styles.pickerLabelContainer} pointerEvents="none">
283
+ {typeof label === "string" ? (
284
+ <Text style={styles.pickerLabel}>{label}</Text>
285
+ ) : (
286
+ label ?? null
287
+ )}
288
+ </View>
289
+ {LinearGradient ? (
290
+ <>
291
+ <LinearGradient
292
+ colors={[
293
+ styles.pickerContainer.backgroundColor ??
294
+ "white",
295
+ colorToRgba({
296
+ color:
297
+ styles.pickerContainer
298
+ .backgroundColor ?? "white",
299
+ opacity: 0,
300
+ }),
301
+ ]}
302
+ start={{ x: 1, y: 0.3 }}
303
+ end={{ x: 1, y: 1 }}
304
+ pointerEvents="none"
305
+ {...pickerGradientOverlayProps}
306
+ {...topPickerGradientOverlayProps}
307
+ style={[styles.pickerGradientOverlay, { top: 0 }]}
308
+ />
309
+ <LinearGradient
310
+ colors={[
311
+ colorToRgba({
312
+ color:
313
+ styles.pickerContainer
314
+ .backgroundColor ?? "white",
315
+ opacity: 0,
316
+ }),
317
+ styles.pickerContainer.backgroundColor ??
318
+ "white",
319
+ ]}
320
+ start={{ x: 1, y: 0 }}
321
+ end={{ x: 1, y: 0.7 }}
322
+ pointerEvents="none"
323
+ {...pickerGradientOverlayProps}
324
+ {...bottomPickerGradientOverlayProps}
325
+ style={[
326
+ styles.pickerGradientOverlay,
327
+ { bottom: -1 },
328
+ ]}
329
+ />
330
+ </>
331
+ ) : null}
332
+ </View>
333
+ );
334
+ }
335
+ );
336
+
337
+ export default React.memo(DurationScroll);
@@ -0,0 +1,87 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { StyleSheet } from "react-native";
3
+
4
+ export interface CustomTimerPickerStyles {
5
+ theme?: "light" | "dark";
6
+ backgroundColor?: string;
7
+ text?: any;
8
+ pickerContainer?: any;
9
+ pickerLabelContainer?: any;
10
+ pickerLabel?: any;
11
+ pickerItemContainer?: any;
12
+ pickerItem?: any;
13
+ disabledPickerItem?: any;
14
+ pickerGradientOverlay?: any;
15
+ }
16
+
17
+ const DARK_MODE_BACKGROUND_COLOR = "#232323";
18
+ const DARK_MODE_TEXT_COLOR = "#E9E9E9";
19
+ const LIGHT_MODE_BACKGROUND_COLOR = "#F1F1F1";
20
+ const LIGHT_MODE_TEXT_COLOR = "#1B1B1B";
21
+
22
+ export const generateStyles = (
23
+ customStyles: CustomTimerPickerStyles | undefined,
24
+ options: { padWithNItems: number }
25
+ ) =>
26
+ StyleSheet.create({
27
+ pickerContainer: {
28
+ flexDirection: "row",
29
+ marginRight: "8%",
30
+ backgroundColor:
31
+ customStyles?.backgroundColor ??
32
+ (customStyles?.theme === "dark"
33
+ ? DARK_MODE_BACKGROUND_COLOR
34
+ : LIGHT_MODE_BACKGROUND_COLOR),
35
+ ...customStyles?.pickerContainer,
36
+ },
37
+ pickerLabelContainer: {
38
+ position: "absolute",
39
+ right: 4,
40
+ top: 0,
41
+ bottom: 0,
42
+ justifyContent: "center",
43
+ ...customStyles?.pickerLabelContainer,
44
+ },
45
+ pickerLabel: {
46
+ fontSize: 18,
47
+ fontWeight: "bold",
48
+ marginTop: (customStyles?.pickerItem?.fontSize ?? 25) / 6,
49
+ color:
50
+ customStyles?.theme === "dark"
51
+ ? DARK_MODE_TEXT_COLOR
52
+ : LIGHT_MODE_TEXT_COLOR,
53
+ ...customStyles?.text,
54
+ ...customStyles?.pickerLabel,
55
+ },
56
+ pickerItemContainer: {
57
+ height: 50,
58
+ justifyContent: "center",
59
+ alignItems: "center",
60
+ width: (customStyles?.pickerItem?.fontSize ?? 25) * 3.6,
61
+ ...customStyles?.pickerItemContainer,
62
+ },
63
+ pickerItem: {
64
+ textAlignVertical: "center",
65
+ fontSize: 25,
66
+ color:
67
+ customStyles?.theme === "dark"
68
+ ? DARK_MODE_TEXT_COLOR
69
+ : LIGHT_MODE_TEXT_COLOR,
70
+ ...customStyles?.text,
71
+ ...customStyles?.pickerItem,
72
+ },
73
+ disabledPickerItem: {
74
+ opacity: 0.2,
75
+ ...customStyles?.disabledPickerItem,
76
+ },
77
+ pickerGradientOverlay: {
78
+ position: "absolute",
79
+ left: 0,
80
+ right: 0,
81
+ height:
82
+ options.padWithNItems === 0
83
+ ? "30%"
84
+ : (customStyles?.pickerItemContainer?.height ?? 50) * 0.8,
85
+ ...customStyles?.pickerGradientOverlay,
86
+ },
87
+ });
@@ -0,0 +1,216 @@
1
+ import React, {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from "react";
9
+ import { View } from "react-native";
10
+
11
+ import DurationScroll, { DurationScrollRef, LimitType } from "./DurationScroll";
12
+
13
+ import { generateStyles, CustomTimerPickerStyles } from "./TimerPicker.styles";
14
+ import { LinearGradientProps } from "./DurationScroll";
15
+
16
+ export interface TimerPickerRef {
17
+ reset: (options?: { animated?: boolean }) => void;
18
+ setValue: (
19
+ value: {
20
+ hours: number;
21
+ minutes: number;
22
+ seconds: number;
23
+ },
24
+ options?: { animated?: boolean }
25
+ ) => void;
26
+ }
27
+
28
+ export interface TimerPickerProps {
29
+ onDurationChange?: (duration: {
30
+ hours: number;
31
+ minutes: number;
32
+ seconds: number;
33
+ }) => void;
34
+ initialHours?: number;
35
+ initialMinutes?: number;
36
+ initialSeconds?: number;
37
+ hideHours?: boolean;
38
+ hideMinutes?: boolean;
39
+ hideSeconds?: boolean;
40
+ hourLimit?: LimitType;
41
+ minuteLimit?: LimitType;
42
+ secondLimit?: LimitType;
43
+ hourLabel?: string | React.ReactElement;
44
+ minuteLabel?: string | React.ReactElement;
45
+ secondLabel?: string | React.ReactElement;
46
+ padWithNItems?: number;
47
+ disableInfiniteScroll?: boolean;
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ LinearGradient?: any;
50
+ pickerContainerProps?: React.ComponentProps<typeof View>;
51
+ pickerGradientOverlayProps?: Partial<LinearGradientProps>;
52
+ topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
53
+ bottomPickerGradientOverlayProps?: Partial<LinearGradientProps>;
54
+ styles?: CustomTimerPickerStyles;
55
+ }
56
+
57
+ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
58
+ (
59
+ {
60
+ onDurationChange,
61
+ initialHours = 0,
62
+ initialMinutes = 0,
63
+ initialSeconds = 0,
64
+ hideHours = false,
65
+ hideMinutes = false,
66
+ hideSeconds = false,
67
+ hourLimit,
68
+ minuteLimit,
69
+ secondLimit,
70
+ hourLabel = "h",
71
+ minuteLabel = "m",
72
+ secondLabel = "s",
73
+ padWithNItems = 1,
74
+ disableInfiniteScroll = false,
75
+ LinearGradient,
76
+ pickerContainerProps,
77
+ pickerGradientOverlayProps,
78
+ topPickerGradientOverlayProps,
79
+ bottomPickerGradientOverlayProps,
80
+ styles: customStyles,
81
+ },
82
+ ref
83
+ ): React.ReactElement => {
84
+ const checkedPadWithNItems =
85
+ padWithNItems >= 0 ? Math.round(padWithNItems) : 0;
86
+
87
+ const styles = useMemo(
88
+ () =>
89
+ generateStyles(customStyles, {
90
+ padWithNItems: checkedPadWithNItems,
91
+ }),
92
+
93
+ [checkedPadWithNItems, customStyles]
94
+ );
95
+
96
+ const [selectedHours, setSelectedHours] = useState(initialHours);
97
+ const [selectedMinutes, setSelectedMinutes] = useState(initialMinutes);
98
+ const [selectedSeconds, setSelectedSeconds] = useState(initialSeconds);
99
+
100
+ useEffect(() => {
101
+ onDurationChange?.({
102
+ hours: selectedHours,
103
+ minutes: selectedMinutes,
104
+ seconds: selectedSeconds,
105
+ });
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, [selectedHours, selectedMinutes, selectedSeconds]);
108
+
109
+ const hoursDurationScrollRef = useRef<DurationScrollRef>(null);
110
+ const minutesDurationScrollRef = useRef<DurationScrollRef>(null);
111
+ const secondsDurationScrollRef = useRef<DurationScrollRef>(null);
112
+
113
+ useImperativeHandle(ref, () => ({
114
+ reset: (options) => {
115
+ setSelectedHours(initialHours);
116
+ setSelectedMinutes(initialMinutes);
117
+ setSelectedSeconds(initialSeconds);
118
+ hoursDurationScrollRef.current?.reset(options);
119
+ minutesDurationScrollRef.current?.reset(options);
120
+ secondsDurationScrollRef.current?.reset(options);
121
+ },
122
+ setValue: (value, options) => {
123
+ setSelectedHours(value.hours);
124
+ setSelectedMinutes(value.minutes);
125
+ setSelectedSeconds(value.seconds);
126
+ hoursDurationScrollRef.current?.setValue(value.hours, options);
127
+ minutesDurationScrollRef.current?.setValue(
128
+ value.minutes,
129
+ options
130
+ );
131
+ secondsDurationScrollRef.current?.setValue(
132
+ value.seconds,
133
+ options
134
+ );
135
+ },
136
+ }));
137
+
138
+ return (
139
+ <View
140
+ {...pickerContainerProps}
141
+ style={styles.pickerContainer}
142
+ testID="timer-picker">
143
+ {!hideHours ? (
144
+ <DurationScroll
145
+ ref={hoursDurationScrollRef}
146
+ numberOfItems={23}
147
+ label={hourLabel}
148
+ initialValue={initialHours}
149
+ onDurationChange={setSelectedHours}
150
+ pickerGradientOverlayProps={pickerGradientOverlayProps}
151
+ topPickerGradientOverlayProps={
152
+ topPickerGradientOverlayProps
153
+ }
154
+ bottomPickerGradientOverlayProps={
155
+ bottomPickerGradientOverlayProps
156
+ }
157
+ disableInfiniteScroll={disableInfiniteScroll}
158
+ padWithNItems={checkedPadWithNItems}
159
+ limit={hourLimit}
160
+ LinearGradient={LinearGradient}
161
+ styles={styles}
162
+ testID="duration-scroll-hour"
163
+ />
164
+ ) : null}
165
+ {!hideMinutes ? (
166
+ <DurationScroll
167
+ ref={minutesDurationScrollRef}
168
+ numberOfItems={59}
169
+ label={minuteLabel}
170
+ initialValue={initialMinutes}
171
+ onDurationChange={setSelectedMinutes}
172
+ padNumbersWithZero
173
+ pickerGradientOverlayProps={pickerGradientOverlayProps}
174
+ topPickerGradientOverlayProps={
175
+ topPickerGradientOverlayProps
176
+ }
177
+ bottomPickerGradientOverlayProps={
178
+ bottomPickerGradientOverlayProps
179
+ }
180
+ disableInfiniteScroll={disableInfiniteScroll}
181
+ padWithNItems={checkedPadWithNItems}
182
+ limit={minuteLimit}
183
+ LinearGradient={LinearGradient}
184
+ styles={styles}
185
+ testID="duration-scroll-minute"
186
+ />
187
+ ) : null}
188
+ {!hideSeconds ? (
189
+ <DurationScroll
190
+ ref={secondsDurationScrollRef}
191
+ numberOfItems={59}
192
+ label={secondLabel}
193
+ initialValue={initialSeconds}
194
+ onDurationChange={setSelectedSeconds}
195
+ padNumbersWithZero
196
+ pickerGradientOverlayProps={pickerGradientOverlayProps}
197
+ topPickerGradientOverlayProps={
198
+ topPickerGradientOverlayProps
199
+ }
200
+ bottomPickerGradientOverlayProps={
201
+ bottomPickerGradientOverlayProps
202
+ }
203
+ disableInfiniteScroll={disableInfiniteScroll}
204
+ padWithNItems={checkedPadWithNItems}
205
+ limit={secondLimit}
206
+ LinearGradient={LinearGradient}
207
+ styles={styles}
208
+ testID="duration-scroll-second"
209
+ />
210
+ ) : null}
211
+ </View>
212
+ );
213
+ }
214
+ );
215
+
216
+ export default React.memo(TimerPicker);