Skip to content

Commit 2687cd3

Browse files
committed
Merge branch 'feature/custom-time-scopes' into develop
2 parents fd8362f + 49e2c00 commit 2687cd3

File tree

5 files changed

+104
-19
lines changed

5 files changed

+104
-19
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ return (
442442
| hourLimit | Limit on the hours it is possible to select | `{ max?: Number, min?: Number }` | - | false |
443443
| minuteLimit | Limit on the minutes it is possible to select | `{ max?: Number, min?: Number }` | - | false |
444444
| secondLimit | Limit on the seconds it is possible to select | `{ max?: Number, min?: Number }` | - | false |
445+
| maximumHours | The highest value on the hours picker | Number | 23 | false |
446+
| maximumMinutes | The highest value on the minutes picker | Number | 59 | false |
447+
| maximumSeconds | The highest value on the seconds picker | Number | 59 | false |
445448
| hourLabel | Label for the hours picker | String \| React.ReactElement | h | false |
446449
| minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
447450
| secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |

src/components/DurationScroll/index.tsx

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,55 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
4545
label,
4646
limit,
4747
LinearGradient,
48-
numberOfItems,
48+
maximumValue,
4949
onDurationChange,
5050
padNumbersWithZero = false,
5151
padWithNItems,
5252
pickerFeedback,
5353
pickerGradientOverlayProps,
5454
pmLabel,
5555
repeatNumbersNTimes = 3,
56+
repeatNumbersNTimesNotExplicitlySet,
5657
styles,
5758
testID,
5859
topPickerGradientOverlayProps,
5960
} = props;
6061

62+
const numberOfItems = useMemo(() => {
63+
// guard against negative maximum values
64+
if (maximumValue < 0) {
65+
return 1;
66+
}
67+
68+
return maximumValue + 1;
69+
}, [maximumValue]);
70+
6171
const safeRepeatNumbersNTimes = useMemo(() => {
72+
// do not repeat numbers if there is only one option
73+
if (numberOfItems === 1) {
74+
return 1;
75+
}
76+
6277
if (!disableInfiniteScroll && repeatNumbersNTimes < 2) {
6378
return 2;
6479
} else if (repeatNumbersNTimes < 1) {
6580
return 1;
6681
}
6782

83+
// if this variable is not explicitly set, we calculate a reasonable value based on
84+
// the number of items in the picker, avoiding regular jumps up/down the list
85+
// whilst avoiding rendering too many items in the picker
86+
if (repeatNumbersNTimesNotExplicitlySet) {
87+
return Math.max(Math.round(180 / numberOfItems), 1);
88+
}
89+
6890
return Math.round(repeatNumbersNTimes);
69-
}, [disableInfiniteScroll, repeatNumbersNTimes]);
91+
}, [
92+
disableInfiniteScroll,
93+
numberOfItems,
94+
repeatNumbersNTimes,
95+
repeatNumbersNTimesNotExplicitlySet,
96+
]);
7097

7198
const numbersForFlatList = useMemo(() => {
7299
if (is12HourPicker) {
@@ -356,6 +383,10 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
356383

357384
const onViewableItemsChanged = useCallback(
358385
({ viewableItems }: { viewableItems: ViewToken[] }) => {
386+
if (numberOfItems === 1) {
387+
return;
388+
}
389+
359390
if (
360391
viewableItems[0]?.index &&
361392
viewableItems[0].index < numberOfItems * 0.5
@@ -378,6 +409,50 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
378409
[numberOfItems, safeRepeatNumbersNTimes]
379410
);
380411

412+
const [
413+
viewabilityConfigCallbackPairs,
414+
setViewabilityConfigCallbackPairs,
415+
] = useState<ViewabilityConfigCallbackPairs | undefined>(
416+
!disableInfiniteScroll
417+
? [
418+
{
419+
viewabilityConfig: {
420+
viewAreaCoveragePercentThreshold: 0,
421+
},
422+
onViewableItemsChanged: onViewableItemsChanged,
423+
},
424+
]
425+
: undefined
426+
);
427+
428+
const [flatListRenderKey, setFlatListRenderKey] = useState(0);
429+
430+
const initialRender = useRef(true);
431+
432+
useEffect(() => {
433+
// don't run on first render
434+
if (initialRender.current) {
435+
initialRender.current = false;
436+
return;
437+
}
438+
439+
// if the onViewableItemsChanged callback changes, we need to update viewabilityConfigCallbackPairs
440+
// which requires the FlatList to be remounted, hence the increase of the FlatList key
441+
setFlatListRenderKey((prev) => prev + 1);
442+
setViewabilityConfigCallbackPairs(
443+
!disableInfiniteScroll
444+
? [
445+
{
446+
viewabilityConfig: {
447+
viewAreaCoveragePercentThreshold: 0,
448+
},
449+
onViewableItemsChanged: onViewableItemsChanged,
450+
},
451+
]
452+
: undefined
453+
);
454+
}, [disableInfiniteScroll, onViewableItemsChanged]);
455+
381456
const getItemLayout = useCallback(
382457
(_: ArrayLike<string> | null | undefined, index: number) => ({
383458
length: styles.pickerItemContainer.height,
@@ -387,14 +462,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
387462
[styles.pickerItemContainer.height]
388463
);
389464

390-
const viewabilityConfigCallbackPairs =
391-
useRef<ViewabilityConfigCallbackPairs>([
392-
{
393-
viewabilityConfig: { viewAreaCoveragePercentThreshold: 0 },
394-
onViewableItemsChanged: onViewableItemsChanged,
395-
},
396-
]);
397-
398465
useImperativeHandle(ref, () => ({
399466
reset: (options) => {
400467
flatListRef.current?.scrollToIndex({
@@ -431,6 +498,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
431498
]}
432499
testID={testID}>
433500
<FlatList
501+
key={flatListRenderKey}
434502
ref={flatListRef}
435503
data={numbersForFlatList}
436504
decelerationRate={0.88}
@@ -445,15 +513,13 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
445513
scrollEventThrottle={16}
446514
showsVerticalScrollIndicator={false}
447515
snapToAlignment="start"
448-
// used in place of snapToOffset due to bug on Android
516+
// used in place of snapToInterval due to bug on Android
449517
snapToOffsets={[...Array(numbersForFlatList.length)].map(
450518
(_, i) => i * styles.pickerItemContainer.height
451519
)}
452520
testID="duration-scroll-flatlist"
453521
viewabilityConfigCallbackPairs={
454-
!disableInfiniteScroll
455-
? viewabilityConfigCallbackPairs?.current
456-
: undefined
522+
viewabilityConfigCallbackPairs
457523
}
458524
windowSize={numberOfItemsToShow}
459525
/>

src/components/DurationScroll/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ export interface DurationScrollProps {
3232
isDisabled?: boolean;
3333
label?: string | React.ReactElement;
3434
limit?: LimitType;
35-
numberOfItems: number;
35+
maximumValue: number;
3636
onDurationChange: (duration: number) => void;
3737
padNumbersWithZero?: boolean;
3838
padWithNItems: number;
3939
pickerFeedback?: () => void | Promise<void>;
4040
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
4141
pmLabel?: string;
4242
repeatNumbersNTimes?: number;
43+
repeatNumbersNTimesNotExplicitlySet: boolean;
4344
styles: ReturnType<typeof generateStyles>;
4445
testID?: string;
4546
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;

src/components/TimerPicker/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
2929
hourLimit,
3030
hoursPickerIsDisabled = false,
3131
initialValue,
32+
maximumHours = 23,
33+
maximumMinutes = 59,
34+
maximumSeconds = 59,
3235
minuteLabel,
3336
minuteLimit,
3437
minutesPickerIsDisabled = false,
@@ -39,7 +42,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
3942
padWithNItems = 1,
4043
pickerContainerProps,
4144
pmLabel = "pm",
42-
repeatHourNumbersNTimes = 7,
45+
repeatHourNumbersNTimes = 8,
4346
repeatMinuteNumbersNTimes = 3,
4447
repeatSecondNumbersNTimes = 3,
4548
secondLabel,
@@ -156,12 +159,15 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
156159
hourLabel ?? (!use12HourPicker ? "h" : undefined)
157160
}
158161
limit={hourLimit}
159-
numberOfItems={24}
162+
maximumValue={maximumHours}
160163
onDurationChange={setSelectedHours}
161164
padNumbersWithZero={padHoursWithZero}
162165
padWithNItems={safePadWithNItems}
163166
pmLabel={pmLabel}
164167
repeatNumbersNTimes={repeatHourNumbersNTimes}
168+
repeatNumbersNTimesNotExplicitlySet={
169+
props?.repeatHourNumbersNTimes === undefined
170+
}
165171
styles={styles}
166172
testID="duration-scroll-hour"
167173
{...otherProps}
@@ -179,11 +185,14 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
179185
isDisabled={minutesPickerIsDisabled}
180186
label={minuteLabel ?? "m"}
181187
limit={minuteLimit}
182-
numberOfItems={60}
188+
maximumValue={maximumMinutes}
183189
onDurationChange={setSelectedMinutes}
184190
padNumbersWithZero={padMinutesWithZero}
185191
padWithNItems={safePadWithNItems}
186192
repeatNumbersNTimes={repeatMinuteNumbersNTimes}
193+
repeatNumbersNTimesNotExplicitlySet={
194+
props?.repeatMinuteNumbersNTimes === undefined
195+
}
187196
styles={styles}
188197
testID="duration-scroll-minute"
189198
{...otherProps}
@@ -201,11 +210,14 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
201210
isDisabled={secondsPickerIsDisabled}
202211
label={secondLabel ?? "s"}
203212
limit={secondLimit}
204-
numberOfItems={60}
213+
maximumValue={maximumSeconds}
205214
onDurationChange={setSelectedSeconds}
206215
padNumbersWithZero={padSecondsWithZero}
207216
padWithNItems={safePadWithNItems}
208217
repeatNumbersNTimes={repeatSecondNumbersNTimes}
218+
repeatNumbersNTimesNotExplicitlySet={
219+
props?.repeatSecondNumbersNTimes === undefined
220+
}
209221
styles={styles}
210222
testID="duration-scroll-second"
211223
{...otherProps}

src/components/TimerPicker/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ export interface TimerPickerProps {
5353
minutes?: number;
5454
seconds?: number;
5555
};
56+
maximumHours?: number;
57+
maximumMinutes?: number;
58+
maximumSeconds?: number;
5659
minuteLabel?: string | React.ReactElement;
5760
minuteLimit?: LimitType;
5861
minutesPickerIsDisabled?: boolean;

0 commit comments

Comments
 (0)