Skip to content

Commit

Permalink
Merge branch 'release-v1.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
troberts-28 committed Dec 4, 2023
2 parents d1c954b + 4e4ae1d commit 4af4fc3
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 24 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# React Native Timer Picker ⏰🕰️⏳

[![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=for-the-badge)]()
![platforms](https://img.shields.io/badge/platforms-Android%20%7C%20iOS%20%7C%20Web-brightgreen.svg?style=for-the-badge&colorB=191A17)
![platforms](https://img.shields.io/badge/platforms-Android%20%7C%20iOS-brightgreen.svg?style=for-the-badge&colorB=191A17)
[![Version](https://img.shields.io/npm/v/react-native-timer-picker.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-timer-picker)
[![npm](https://img.shields.io/npm/dt/react-native-timer-picker.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-timer-picker)

Expand Down Expand Up @@ -33,7 +33,7 @@ Works with Expo and bare React Native apps.

## Demos 📱

**Try it out for yourself on [Expo Snack](https://snack.expo.dev/@nuumi/react-native-timer-picker-demo)!** Make sure to run it on iOS/Android to try it out properly.
**Try it out for yourself on [Expo Snack](https://snack.expo.dev/@nuumi/react-native-timer-picker-demo)!** Make sure to run it on iOS/Android to see it working properly.

<p>
<img src="demos/example1.gif" width="250" height="550" style="margin-right:50px"/>
Expand Down Expand Up @@ -331,6 +331,7 @@ return (
| minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
| secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |
| padWithNItems | Number of items to pad the picker with on either side | Number | 1 | false |
| aggressivelyGetLatestDuration | Set to True to ask DurationScroll to aggressively update the latestDuration ref | Boolean | false | false |
| disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
| LinearGradient | Linear Gradient Component | [expo-linear-gradient](https://www.npmjs.com/package/expo-linear-gradient).LinearGradient or [react-native-linear-gradient](https://www.npmjs.com/package/react-native-linear-gradient).default | - | false |
| pickerContainerProps | Props for the picker container | `React.ComponentProps<typeof View>` | - | false |
Expand Down Expand Up @@ -419,6 +420,19 @@ timerPickerRef.current.reset(options?: { animated: boolean });
timerPickerRef.current.setValue({ hours: number, minutes: number, seconds: number }, options?: { animated: boolean });
```

It also exposes the following ref object:

`latestDuration` - provides access to the latest duration (even during scrolls). **This only works if `aggressivelyGetLatestDuration` is set to True (as in TimerPickerModal).** It is used internally to ensure that the latest duration is returned in `TimerPickerModal` on pressing the confirm button, even if the inputs are still scrolling.

```javascript
const latestDuration = timerPickerRef.current?.latestDuration;
const newDuration = {
hours: latestDuration?.hours?.current,
minutes: latestDuration?.minutes?.current,
seconds: latestDuration?.seconds?.current,
};
```
### TimerPickerModal
An identical ref is also exposed for the TimerPickerModal component.
Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"expo": "^49.0.16",
"expo-linear-gradient": "~12.3.0",
"react": "18.2.0",
"react-native": "0.72.5"
"react-native": "0.72.6"
},
"devDependencies": {
"@babel/core": ">=7.20.0",
Expand Down
8 changes: 4 additions & 4 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6377,10 +6377,10 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==

react-native@0.72.5:
version "0.72.5"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.72.5.tgz#2c343fa6f3ead362cf07376634a33a4078864357"
integrity sha512-oIewslu5DBwOmo7x5rdzZlZXCqDIna0R4dUwVpfmVteORYLr4yaZo5wQnMeR+H7x54GaMhmgeqp0ZpULtulJFg==
react-native@0.72.6:
version "0.72.6"
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.72.6.tgz#9f8d090694907e2f83af22e115cc0e4a3d5fa626"
integrity sha512-RafPY2gM7mcrFySS8TL8x+TIO3q7oAlHpzEmC7Im6pmXni6n1AuufGaVh0Narbr1daxstw7yW7T9BKW5dpVc2A==
dependencies:
"@jest/create-cache-key-function" "^29.2.1"
"@react-native-community/cli" "11.3.7"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"url": "https://github.com/troberts-28"
},
"license": "MIT",
"version": "1.2.11",
"version": "1.3.0",
"main": "dist/commonjs/index.js",
"types": "dist/typescript/src/index.d.ts",
"scripts": {
"test": "jest --forceExit --silent",
"build": "bob build",
"clean": "rm yarn.lock && rm -rf ./node_modules && yarn install",
"start": "cp -Rf src example && cd example && yarn add expo && npx expo install && npx expo start",
"start": "cp -Rf src example && cd example && npx expo install && npx expo start",
"lint": "eslint --ext .ts,.tsx .",
"lint:fix": "eslint --ext .ts,.tsx . --fix",
"prepare": "yarn build"
Expand Down
47 changes: 44 additions & 3 deletions src/components/TimerPicker/DurationScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {
useCallback,
forwardRef,
useImperativeHandle,
MutableRefObject,
} from "react";
import {
View,
Expand All @@ -23,6 +24,7 @@ import { getScrollIndex } from "../../utils/getScrollIndex";
export interface DurationScrollRef {
reset: (options?: { animated?: boolean }) => void;
setValue: (value: number, options?: { animated?: boolean }) => void;
latestDuration: MutableRefObject<number>;
}

type LinearGradientPoint = {
Expand Down Expand Up @@ -50,6 +52,7 @@ interface DurationScrollProps {
padNumbersWithZero?: boolean;
disableInfiniteScroll?: boolean;
limit?: LimitType;
aggressivelyGetLatestDuration: boolean;
padWithNItems: number;
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
Expand All @@ -73,6 +76,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
padNumbersWithZero = false,
disableInfiniteScroll = false,
limit,
aggressivelyGetLatestDuration,
padWithNItems,
pickerGradientOverlayProps,
topPickerGradientOverlayProps,
Expand All @@ -83,8 +87,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
},
ref
): React.ReactElement => {
const flatListRef = useRef<FlatList | null>(null);

const data = generateNumbers(numberOfItems, {
padWithZero: padNumbersWithZero,
repeatNTimes: 3,
Expand All @@ -103,6 +105,10 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
disableInfiniteScroll,
});

const latestDuration = useRef(0);

const flatListRef = useRef<FlatList | null>(null);

useImperativeHandle(ref, () => ({
reset: (options) => {
flatListRef.current?.scrollToIndex({
Expand All @@ -121,6 +127,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
}),
});
},
latestDuration: latestDuration,
}));

const renderItem = useCallback(
Expand Down Expand Up @@ -154,6 +161,39 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
]
);

const onScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
// this function is only used when the picker is in a modal
// it is used to ensure that the modal gets the latest duration on clicking
// the confirm button, even if the scrollview is still scrolling
const newIndex = Math.round(
e.nativeEvent.contentOffset.y /
styles.pickerItemContainer.height
);
let newDuration =
(disableInfiniteScroll
? newIndex
: newIndex + padWithNItems) %
(numberOfItems + 1);

// check limits
if (newDuration > adjustedLimited.max) {
newDuration = adjustedLimited.max;
} else if (newDuration < adjustedLimited.min) {
newDuration = adjustedLimited.min;
}
latestDuration.current = newDuration;
},
[
adjustedLimited.max,
adjustedLimited.min,
disableInfiniteScroll,
numberOfItems,
padWithNItems,
styles.pickerItemContainer.height,
]
);

const onMomentumScrollEnd = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
const newIndex = Math.round(
Expand Down Expand Up @@ -264,7 +304,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
renderItem={renderItem}
keyExtractor={KEY_EXTRACTOR}
showsVerticalScrollIndicator={false}
decelerationRate={0.9}
decelerationRate={0.88}
scrollEventThrottle={16}
snapToAlignment="start"
// used in place of snapToOffset due to bug on Android
Expand All @@ -277,6 +317,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
: undefined
}
onMomentumScrollEnd={onMomentumScrollEnd}
onScroll={aggressivelyGetLatestDuration ? onScroll : undefined}
testID="duration-scroll-flatlist"
/>
<View style={styles.pickerLabelContainer} pointerEvents="none">
Expand Down
16 changes: 16 additions & 0 deletions src/components/TimerPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {
MutableRefObject,
forwardRef,
useEffect,
useImperativeHandle,
Expand All @@ -23,6 +24,11 @@ export interface TimerPickerRef {
},
options?: { animated?: boolean }
) => void;
latestDuration: {
hours: MutableRefObject<number> | undefined;
minutes: MutableRefObject<number> | undefined;
seconds: MutableRefObject<number> | undefined;
};
}

export interface TimerPickerProps {
Expand All @@ -34,6 +40,7 @@ export interface TimerPickerProps {
initialHours?: number;
initialMinutes?: number;
initialSeconds?: number;
aggressivelyGetLatestDuration?: boolean;
hideHours?: boolean;
hideMinutes?: boolean;
hideSeconds?: boolean;
Expand Down Expand Up @@ -72,6 +79,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
secondLabel = "s",
padWithNItems = 1,
disableInfiniteScroll = false,
aggressivelyGetLatestDuration = false,
LinearGradient,
pickerContainerProps,
pickerGradientOverlayProps,
Expand Down Expand Up @@ -133,6 +141,11 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
options
);
},
latestDuration: {
hours: hoursDurationScrollRef.current?.latestDuration,
minutes: minutesDurationScrollRef.current?.latestDuration,
seconds: secondsDurationScrollRef.current?.latestDuration,
},
}));

return (
Expand All @@ -146,6 +159,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
numberOfItems={23}
label={hourLabel}
initialValue={initialHours}
aggressivelyGetLatestDuration={aggressivelyGetLatestDuration}
onDurationChange={setSelectedHours}
pickerGradientOverlayProps={pickerGradientOverlayProps}
topPickerGradientOverlayProps={
Expand All @@ -168,6 +182,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
numberOfItems={59}
label={minuteLabel}
initialValue={initialMinutes}
aggressivelyGetLatestDuration={aggressivelyGetLatestDuration}
onDurationChange={setSelectedMinutes}
padNumbersWithZero
pickerGradientOverlayProps={pickerGradientOverlayProps}
Expand All @@ -191,6 +206,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
numberOfItems={59}
label={secondLabel}
initialValue={initialSeconds}
aggressivelyGetLatestDuration={aggressivelyGetLatestDuration}
onDurationChange={setSelectedSeconds}
padNumbersWithZero
pickerGradientOverlayProps={pickerGradientOverlayProps}
Expand Down
Loading

0 comments on commit 4af4fc3

Please sign in to comment.