Skip to content

Commit 495b11b

Browse files
authored
Merge pull request #8 from troberts-28/release-v1.2.0
Release v1.2.0
2 parents bfdc79f + bce1cc8 commit 495b11b

File tree

9 files changed

+236
-52
lines changed

9 files changed

+236
-52
lines changed

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,12 @@ return (
308308
| hideHours | Hide the hours picker | Boolean | false | false |
309309
| hideMinutes | Hide the minutes picker | Boolean | false | false |
310310
| hideSeconds | Hide the seconds picker | Boolean | false | false |
311-
| hourLabel | Label for the hours picker | String | h | false |
312-
| minuteLabel | Label for the minutes picker | String | m | false |
313-
| secondLabel | Label for the seconds picker | String | s | false |
311+
| hourLimit | Limit on the hours it is possible to select | { max?: Number, min?: Number } | - | false |
312+
| minuteLimit | Limit on the minutes it is possible to select | { max?: Number, min?: Number } | - | false |
313+
| secondLimit | Limit on the seconds it is possible to select | { max?: Number, min?: Number } | - | false |
314+
| hourLabel | Label for the hours picker | String \| React.ReactElement | h | false |
315+
| minuteLabel | Label for the minutes picker | String \| React.ReactElement | m | false |
316+
| secondLabel | Label for the seconds picker | String \| React.ReactElement | s | false |
314317
| padWithNItems | Number of items to pad the picker with on either side | Number | 1 | false |
315318
| disableInfiniteScroll | Disable the infinite scroll feature | Boolean | false | false |
316319
| 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 |
@@ -332,6 +335,7 @@ The following custom styles can be supplied to re-style the component in any way
332335
| pickerLabel | Style for the picker's labels | TextStyle |
333336
| pickerItemContainer | Container for each number in the picker | ViewStyle |
334337
| pickerItem | Style for each individual picker number | TextStyle |
338+
| disabledPickerItem | Style for any numbers outside any set limits | TextStyle |
335339
| pickerGradientOverlay | Style for the gradient overlay (fade out) | ViewStyle |
336340

337341
### TimerPickerModal ⏰
@@ -375,12 +379,26 @@ The following custom styles can be supplied to re-style the component in any way
375379

376380
### TimerPickerModal
377381

382+
The library exposes a TimerPickerModalRef type, which can be used to type your ref to the modal:
383+
384+
```javascript
385+
const timerPickerModalRef = useRef<TimerPickerModalRef>(null);
386+
```
387+
388+
It has the following available methods:
389+
378390
`reset` - imperative method to reset the selected duration to their initial values.
379391

380392
```javascript
381393
timerPickerModalRef.current.reset();
382394
```
383395

396+
`setValue` - imperative method to set the selected duration to a particular value
397+
398+
```javascript
399+
timerPickerModalRef.current.setValue({hours: number, minutes: number, seconds: number});
400+
```
401+
384402
## License 📝
385403

386404
This project is licensed under the [MIT License](LICENSE).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"url": "https://github.com/troberts-28"
77
},
88
"license": "MIT",
9-
"version": "1.1.4",
9+
"version": "1.2.0",
1010
"main": "dist/index.js",
1111
"types": "dist/index.d.ts",
1212
"scripts": {

src/components/Modal/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const Modal = ({
3434
modalProps,
3535
contentStyle,
3636
overlayStyle,
37-
testID
37+
testID,
3838
}: ModalProps): React.ReactElement => {
3939
const [screenHeight, setScreenHeight] = useState(
4040
Dimensions.get("window").height
@@ -97,7 +97,7 @@ export const Modal = ({
9797
Animated.timing(animatedOpacity.current, {
9898
easing: Easing.inOut(Easing.quad),
9999
// Using native driver in the modal makes the content flash
100-
useNativeDriver: false,
100+
useNativeDriver: true,
101101
duration: animationDuration,
102102
toValue: 1,
103103
}).start();
@@ -107,7 +107,7 @@ export const Modal = ({
107107
Animated.timing(animatedOpacity.current, {
108108
easing: Easing.inOut(Easing.quad),
109109
// Using native driver in the modal makes the content flash
110-
useNativeDriver: false,
110+
useNativeDriver: true,
111111
duration: animationDuration,
112112
toValue: 0,
113113
}).start(() => {
@@ -133,7 +133,9 @@ export const Modal = ({
133133
visible={isVisible}
134134
{...modalProps}
135135
testID={testID ?? "modal"}>
136-
<TouchableWithoutFeedback onPress={onOverlayPress} testID="modal-backdrop">
136+
<TouchableWithoutFeedback
137+
onPress={onOverlayPress}
138+
testID="modal-backdrop">
137139
<Animated.View
138140
style={[
139141
styles.backdrop,
@@ -152,4 +154,4 @@ export const Modal = ({
152154
);
153155
};
154156

155-
export default Modal;
157+
export default React.memo(Modal);

src/components/TimerPicker/DurationScroll.tsx

Lines changed: 114 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import {
66
FlatList,
77
ViewabilityConfigCallbackPairs,
88
ViewToken,
9+
NativeSyntheticEvent,
10+
NativeScrollEvent,
911
} from "react-native";
1012

1113
import { generateNumbers } from "../../utils/generateNumbers";
1214
import { colorToRgba } from "../../utils/colorToRgba";
1315
import { generateStyles } from "./TimerPicker.styles";
16+
import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
1417

1518
type LinearGradientPoint = {
1619
x: number;
@@ -24,13 +27,19 @@ export type LinearGradientProps = React.ComponentProps<typeof View> & {
2427
end?: LinearGradientPoint | null;
2528
};
2629

30+
export type LimitType = {
31+
max?: number;
32+
min?: number;
33+
};
34+
2735
interface DurationScrollProps {
2836
numberOfItems: number;
29-
label?: string;
37+
label?: string | React.ReactElement;
3038
initialIndex?: number;
3139
onDurationChange: (duration: number) => void;
3240
padNumbersWithZero?: boolean;
3341
disableInfiniteScroll?: boolean;
42+
limit?: LimitType;
3443
padWithNItems: number;
3544
pickerGradientOverlayProps?: LinearGradientProps;
3645
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -39,13 +48,17 @@ interface DurationScrollProps {
3948
styles: ReturnType<typeof generateStyles>;
4049
}
4150

51+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
52+
const KEY_EXTRACTOR = (_: any, index: number) => index.toString();
53+
4254
const DurationScroll = ({
4355
numberOfItems,
4456
label,
4557
initialIndex = 0,
4658
onDurationChange,
4759
padNumbersWithZero = false,
4860
disableInfiniteScroll = false,
61+
limit,
4962
padWithNItems,
5063
pickerGradientOverlayProps,
5164
LinearGradient,
@@ -63,13 +76,88 @@ const DurationScroll = ({
6376

6477
const numberOfItemsToShow = 1 + padWithNItems * 2;
6578

66-
const renderItem = ({ item }: { item: string }) => (
67-
<View
68-
key={item}
69-
style={styles.pickerItemContainer}
70-
testID="picker-item">
71-
<Text style={styles.pickerItem}>{item}</Text>
72-
</View>
79+
const adjustedLimited = getAdjustedLimit(limit, numberOfItems);
80+
81+
const renderItem = useCallback(
82+
({ item }: { item: string }) => {
83+
const intItem = parseInt(item);
84+
85+
return (
86+
<View
87+
key={item}
88+
style={styles.pickerItemContainer}
89+
testID="picker-item">
90+
<Text
91+
style={[
92+
styles.pickerItem,
93+
intItem > adjustedLimited.max ||
94+
intItem < adjustedLimited.min
95+
? styles.disabledPickerItem
96+
: {},
97+
]}>
98+
{item}
99+
</Text>
100+
</View>
101+
);
102+
},
103+
[
104+
adjustedLimited.max,
105+
adjustedLimited.min,
106+
styles.disabledPickerItem,
107+
styles.pickerItem,
108+
styles.pickerItemContainer,
109+
]
110+
);
111+
112+
const onMomentumScrollEnd = useCallback(
113+
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
114+
const newIndex = Math.round(
115+
e.nativeEvent.contentOffset.y /
116+
styles.pickerItemContainer.height
117+
);
118+
let newDuration =
119+
(disableInfiniteScroll ? newIndex : newIndex + padWithNItems) %
120+
(numberOfItems + 1);
121+
122+
// check limits
123+
if (newDuration > adjustedLimited.max) {
124+
const targetScrollIndex =
125+
newIndex - (newDuration - adjustedLimited.max);
126+
flatListRef.current?.scrollToIndex({
127+
animated: true,
128+
index:
129+
// guard against scrolling beyond end of list
130+
targetScrollIndex >= 0
131+
? targetScrollIndex
132+
: adjustedLimited.max - 1,
133+
}); // scroll down to max
134+
newDuration = adjustedLimited.max;
135+
} else if (newDuration < adjustedLimited.min) {
136+
const targetScrollIndex =
137+
newIndex + (adjustedLimited.min - newDuration);
138+
flatListRef.current?.scrollToIndex({
139+
animated: true,
140+
index:
141+
// guard against scrolling beyond end of list
142+
targetScrollIndex <= data.length - 1
143+
? targetScrollIndex
144+
: adjustedLimited.min,
145+
}); // scroll up to min
146+
newDuration = adjustedLimited.min;
147+
}
148+
149+
onDurationChange(newDuration);
150+
},
151+
[
152+
adjustedLimited.max,
153+
adjustedLimited.min,
154+
data.length,
155+
disableInfiniteScroll,
156+
numberOfItems,
157+
onDurationChange,
158+
padWithNItems,
159+
styles.pickerItemContainer.height,
160+
]
73161
);
74162

75163
const onViewableItemsChanged = useCallback(
@@ -95,6 +183,15 @@ const DurationScroll = ({
95183
[numberOfItems]
96184
);
97185

186+
const getItemLayout = useCallback(
187+
(_: ArrayLike<string> | null | undefined, index: number) => ({
188+
length: styles.pickerItemContainer.height,
189+
offset: styles.pickerItemContainer.height * index,
190+
index,
191+
}),
192+
[styles.pickerItemContainer.height]
193+
);
194+
98195
const viewabilityConfigCallbackPairs =
99196
useRef<ViewabilityConfigCallbackPairs>([
100197
{
@@ -113,11 +210,7 @@ const DurationScroll = ({
113210
<FlatList
114211
ref={flatListRef}
115212
data={data}
116-
getItemLayout={(_, index) => ({
117-
length: styles.pickerItemContainer.height,
118-
offset: styles.pickerItemContainer.height * index,
119-
index,
120-
})}
213+
getItemLayout={getItemLayout}
121214
initialScrollIndex={
122215
(initialIndex % numberOfItems) +
123216
numberOfItems +
@@ -126,7 +219,7 @@ const DurationScroll = ({
126219
}
127220
windowSize={numberOfItemsToShow}
128221
renderItem={renderItem}
129-
keyExtractor={(_, index) => index.toString()}
222+
keyExtractor={KEY_EXTRACTOR}
130223
showsVerticalScrollIndicator={false}
131224
decelerationRate="fast"
132225
scrollEventThrottle={16}
@@ -140,20 +233,15 @@ const DurationScroll = ({
140233
? viewabilityConfigCallbackPairs?.current
141234
: undefined
142235
}
143-
onMomentumScrollEnd={(e) => {
144-
const newIndex = Math.round(
145-
e.nativeEvent.contentOffset.y /
146-
styles.pickerItemContainer.height
147-
);
148-
onDurationChange(
149-
(disableInfiniteScroll ? newIndex : newIndex + padWithNItems) %
150-
(numberOfItems + 1)
151-
);
152-
}}
236+
onMomentumScrollEnd={onMomentumScrollEnd}
153237
testID="duration-scroll-flatlist"
154238
/>
155239
<View style={styles.pickerLabelContainer}>
156-
<Text style={styles.pickerLabel}>{label}</Text>
240+
{typeof label === "string" ? (
241+
<Text style={styles.pickerLabel}>{label}</Text>
242+
) : (
243+
label ?? null
244+
)}
157245
</View>
158246
{LinearGradient ? (
159247
<>
@@ -193,4 +281,4 @@ const DurationScroll = ({
193281
);
194282
};
195283

196-
export default DurationScroll;
284+
export default React.memo(DurationScroll);

src/components/TimerPicker/TimerPicker.styles.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface CustomTimerPickerStyles {
1010
pickerLabel?: any;
1111
pickerItemContainer?: any;
1212
pickerItem?: any;
13+
disabledPickerItem?: any;
1314
pickerGradientOverlay?: any;
1415
}
1516

@@ -69,6 +70,10 @@ export const generateStyles = (
6970
...customStyles?.text,
7071
...customStyles?.pickerItem,
7172
},
73+
disabledPickerItem: {
74+
opacity: 0.2,
75+
...customStyles?.disabledPickerItem,
76+
},
7277
pickerGradientOverlay: {
7378
position: "absolute",
7479
left: 0,

0 commit comments

Comments
 (0)