react-native-timer-picker 1.6.0 → 1.8.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 +74 -25
- package/dist/commonjs/assets/select_click.mp3 +0 -0
- package/dist/commonjs/components/{TimerPicker/DurationScroll.js → DurationScroll/index.js} +113 -63
- package/dist/commonjs/components/DurationScroll/index.js.map +1 -0
- package/dist/commonjs/components/DurationScroll/types.js +6 -0
- package/dist/commonjs/components/DurationScroll/types.js.map +1 -0
- package/dist/commonjs/components/Modal/index.js +21 -20
- package/dist/commonjs/components/Modal/index.js.map +1 -1
- package/dist/commonjs/components/Modal/{Modal.styles.js → styles.js} +1 -1
- package/dist/commonjs/components/Modal/styles.js.map +1 -0
- package/dist/commonjs/components/Modal/types.js +6 -0
- package/dist/commonjs/components/Modal/types.js.map +1 -0
- package/dist/commonjs/components/TimerPicker/index.js +68 -79
- package/dist/commonjs/components/TimerPicker/index.js.map +1 -1
- package/dist/commonjs/components/TimerPicker/{TimerPicker.styles.js → styles.js} +1 -3
- package/dist/commonjs/components/TimerPicker/styles.js.map +1 -0
- package/dist/commonjs/components/TimerPicker/types.js +6 -0
- package/dist/commonjs/components/TimerPicker/types.js.map +1 -0
- package/dist/commonjs/components/{index.js → TimerPickerModal/index.js} +42 -99
- package/dist/commonjs/components/TimerPickerModal/index.js.map +1 -0
- package/dist/commonjs/components/{TimerPickerModal.styles.js → TimerPickerModal/styles.js} +1 -3
- package/dist/commonjs/components/TimerPickerModal/styles.js.map +1 -0
- package/dist/commonjs/components/TimerPickerModal/types.js +6 -0
- package/dist/commonjs/components/TimerPickerModal/types.js.map +1 -0
- package/dist/commonjs/index.js +14 -13
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/tests/DurationScroll.test.js +1 -1
- package/dist/commonjs/tests/DurationScroll.test.js.map +1 -1
- package/dist/commonjs/tests/Modal.test.js +7 -7
- package/dist/commonjs/tests/Modal.test.js.map +1 -1
- package/dist/commonjs/tests/TimerPicker.test.js.map +1 -1
- package/dist/commonjs/tests/TimerPickerModal.test.js +2 -2
- package/dist/commonjs/tests/TimerPickerModal.test.js.map +1 -1
- package/dist/commonjs/utils/generateNumbers.js.map +1 -1
- package/dist/commonjs/utils/getAdjustedLimit.js.map +1 -1
- package/dist/commonjs/utils/getScrollIndex.js +2 -2
- package/dist/commonjs/utils/getScrollIndex.js.map +1 -1
- package/dist/module/assets/select_click.mp3 +0 -0
- package/dist/module/components/{TimerPicker/DurationScroll.js → DurationScroll/index.js} +114 -64
- package/dist/module/components/DurationScroll/index.js.map +1 -0
- package/dist/module/components/DurationScroll/types.js +2 -0
- package/dist/module/components/DurationScroll/types.js.map +1 -0
- package/dist/module/components/Modal/index.js +19 -19
- package/dist/module/components/Modal/index.js.map +1 -1
- package/dist/module/components/Modal/{Modal.styles.js → styles.js} +1 -1
- package/dist/module/components/Modal/styles.js.map +1 -0
- package/dist/module/components/Modal/types.js +2 -0
- package/dist/module/components/Modal/types.js.map +1 -0
- package/dist/module/components/TimerPicker/index.js +67 -78
- package/dist/module/components/TimerPicker/index.js.map +1 -1
- package/dist/module/components/TimerPicker/{TimerPicker.styles.js → styles.js} +1 -2
- package/dist/module/components/TimerPicker/styles.js.map +1 -0
- package/dist/module/components/TimerPicker/types.js +2 -0
- package/dist/module/components/TimerPicker/types.js.map +1 -0
- package/dist/module/components/{index.js → TimerPickerModal/index.js} +41 -98
- package/dist/module/components/TimerPickerModal/index.js.map +1 -0
- package/dist/module/components/{TimerPickerModal.styles.js → TimerPickerModal/styles.js} +1 -2
- package/dist/module/components/TimerPickerModal/styles.js.map +1 -0
- package/dist/module/components/TimerPickerModal/types.js +2 -0
- package/dist/module/components/TimerPickerModal/types.js.map +1 -0
- package/dist/module/index.js +6 -4
- package/dist/module/index.js.map +1 -1
- package/dist/module/tests/DurationScroll.test.js +1 -1
- package/dist/module/tests/DurationScroll.test.js.map +1 -1
- package/dist/module/tests/Modal.test.js +1 -1
- package/dist/module/tests/Modal.test.js.map +1 -1
- package/dist/module/tests/TimerPicker.test.js.map +1 -1
- package/dist/module/tests/TimerPickerModal.test.js +2 -2
- package/dist/module/tests/TimerPickerModal.test.js.map +1 -1
- package/dist/module/utils/generateNumbers.js.map +1 -1
- package/dist/module/utils/getAdjustedLimit.js.map +1 -1
- package/dist/module/utils/getScrollIndex.js +2 -2
- package/dist/module/utils/getScrollIndex.js.map +1 -1
- package/dist/typescript/components/DurationScroll/index.d.ts +4 -0
- package/dist/typescript/components/{TimerPicker/DurationScroll.d.ts → DurationScroll/types.d.ts} +36 -29
- package/dist/typescript/components/Modal/index.d.ts +3 -14
- package/dist/typescript/components/Modal/types.d.ts +15 -0
- package/dist/typescript/components/TimerPicker/index.d.ts +2 -57
- package/dist/typescript/components/TimerPicker/styles.d.ts +1022 -0
- package/dist/typescript/components/TimerPicker/types.d.ts +61 -0
- package/dist/typescript/components/TimerPickerModal/index.d.ts +4 -0
- package/dist/typescript/components/TimerPickerModal/styles.d.ts +738 -0
- package/dist/typescript/components/{index.d.ts → TimerPickerModal/types.d.ts} +24 -26
- package/dist/typescript/index.d.ts +6 -4
- package/dist/typescript/utils/generateNumbers.d.ts +4 -4
- package/dist/typescript/utils/getAdjustedLimit.d.ts +1 -1
- package/dist/typescript/utils/getScrollIndex.d.ts +2 -2
- package/package.json +14 -11
- package/src/assets/select_click.mp3 +0 -0
- package/src/components/{TimerPicker/DurationScroll.tsx → DurationScroll/index.tsx} +126 -110
- package/src/components/DurationScroll/types.ts +63 -0
- package/src/components/Modal/index.tsx +20 -30
- package/src/components/Modal/types.ts +17 -0
- package/src/components/TimerPicker/index.tsx +70 -138
- package/src/components/TimerPicker/{TimerPicker.styles.ts → styles.ts} +13 -13
- package/src/components/TimerPicker/types.ts +72 -0
- package/src/components/{index.tsx → TimerPickerModal/index.tsx} +44 -147
- package/src/components/{TimerPickerModal.styles.ts → TimerPickerModal/styles.ts} +9 -9
- package/src/components/TimerPickerModal/types.ts +52 -0
- package/src/index.ts +6 -7
- package/src/tests/DurationScroll.test.tsx +3 -1
- package/src/tests/Modal.test.tsx +3 -1
- package/src/tests/TimerPicker.test.tsx +2 -0
- package/src/tests/TimerPickerModal.test.tsx +3 -1
- package/src/utils/generateNumbers.ts +4 -4
- package/src/utils/getAdjustedLimit.ts +1 -1
- package/src/utils/getScrollIndex.ts +3 -3
- package/dist/commonjs/components/Modal/Modal.styles.js.map +0 -1
- package/dist/commonjs/components/TimerPicker/DurationScroll.js.map +0 -1
- package/dist/commonjs/components/TimerPicker/TimerPicker.styles.js.map +0 -1
- package/dist/commonjs/components/TimerPickerModal.styles.js.map +0 -1
- package/dist/commonjs/components/index.js.map +0 -1
- package/dist/module/components/Modal/Modal.styles.js.map +0 -1
- package/dist/module/components/TimerPicker/DurationScroll.js.map +0 -1
- package/dist/module/components/TimerPicker/TimerPicker.styles.js.map +0 -1
- package/dist/module/components/TimerPickerModal.styles.js.map +0 -1
- package/dist/module/components/index.js.map +0 -1
- package/dist/typescript/components/TimerPicker/TimerPicker.styles.d.ts +0 -29
- package/dist/typescript/components/TimerPickerModal.styles.d.ts +0 -19
- /package/dist/typescript/components/Modal/{Modal.styles.d.ts → styles.d.ts} +0 -0
- /package/src/components/Modal/{Modal.styles.ts → styles.ts} +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { View,
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { CustomTimerPickerModalStyles } from "./
|
|
1
|
+
import type { MutableRefObject } from "react";
|
|
2
|
+
import type { View, TouchableOpacity, Text } from "react-native";
|
|
3
|
+
import type Modal from "../Modal";
|
|
4
|
+
import type { TimerPickerProps } from "../TimerPicker/types";
|
|
5
|
+
import type { CustomTimerPickerModalStyles } from "./styles";
|
|
6
6
|
export interface TimerPickerModalRef {
|
|
7
|
+
latestDuration: {
|
|
8
|
+
hours: MutableRefObject<number> | undefined;
|
|
9
|
+
minutes: MutableRefObject<number> | undefined;
|
|
10
|
+
seconds: MutableRefObject<number> | undefined;
|
|
11
|
+
};
|
|
7
12
|
reset: (options?: {
|
|
8
13
|
animated?: boolean;
|
|
9
14
|
}) => void;
|
|
@@ -14,33 +19,26 @@ export interface TimerPickerModalRef {
|
|
|
14
19
|
}, options?: {
|
|
15
20
|
animated?: boolean;
|
|
16
21
|
}) => void;
|
|
17
|
-
latestDuration: {
|
|
18
|
-
hours: MutableRefObject<number> | undefined;
|
|
19
|
-
minutes: MutableRefObject<number> | undefined;
|
|
20
|
-
seconds: MutableRefObject<number> | undefined;
|
|
21
|
-
};
|
|
22
22
|
}
|
|
23
23
|
export interface TimerPickerModalProps extends TimerPickerProps {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
hours: number;
|
|
28
|
-
minutes: number;
|
|
29
|
-
seconds: number;
|
|
30
|
-
}) => void;
|
|
31
|
-
onCancel?: () => void;
|
|
24
|
+
buttonContainerProps?: React.ComponentProps<typeof View>;
|
|
25
|
+
buttonTouchableOpacityProps?: React.ComponentProps<typeof TouchableOpacity>;
|
|
26
|
+
cancelButtonText?: string;
|
|
32
27
|
closeOnOverlayPress?: boolean;
|
|
33
|
-
hideCancelButton?: boolean;
|
|
34
28
|
confirmButtonText?: string;
|
|
35
|
-
cancelButtonText?: string;
|
|
36
|
-
modalTitle?: string;
|
|
37
|
-
modalProps?: React.ComponentProps<typeof Modal>;
|
|
38
29
|
containerProps?: React.ComponentProps<typeof View>;
|
|
39
30
|
contentContainerProps?: React.ComponentProps<typeof View>;
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
hideCancelButton?: boolean;
|
|
32
|
+
modalProps?: React.ComponentProps<typeof Modal>;
|
|
33
|
+
modalTitle?: string;
|
|
42
34
|
modalTitleProps?: React.ComponentProps<typeof Text>;
|
|
35
|
+
onCancel?: () => void;
|
|
36
|
+
onConfirm: ({ hours, minutes, seconds, }: {
|
|
37
|
+
hours: number;
|
|
38
|
+
minutes: number;
|
|
39
|
+
seconds: number;
|
|
40
|
+
}) => void;
|
|
41
|
+
setIsVisible: (isVisible: boolean) => void;
|
|
43
42
|
styles?: CustomTimerPickerModalStyles;
|
|
43
|
+
visible: boolean;
|
|
44
44
|
}
|
|
45
|
-
declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<TimerPickerModalProps & React.RefAttributes<TimerPickerModalRef>>>;
|
|
46
|
-
export default _default;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { default as TimerPickerModal
|
|
2
|
-
export {
|
|
3
|
-
export { CustomTimerPickerModalStyles } from "./components/TimerPickerModal
|
|
4
|
-
export {
|
|
1
|
+
export { default as TimerPickerModal } from "./components/TimerPickerModal";
|
|
2
|
+
export { TimerPickerModalProps, TimerPickerModalRef, } from "./components/TimerPickerModal/types";
|
|
3
|
+
export { CustomTimerPickerModalStyles } from "./components/TimerPickerModal/styles";
|
|
4
|
+
export { default as TimerPicker } from "./components/TimerPicker";
|
|
5
|
+
export { TimerPickerProps, TimerPickerRef, } from "./components/TimerPicker/types";
|
|
6
|
+
export { CustomTimerPickerStyles } from "./components/TimerPicker/styles";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export declare const generateNumbers: (numberOfItems: number, options: {
|
|
2
|
-
repeatNTimes?: number;
|
|
3
|
-
padNumbersWithZero?: boolean;
|
|
4
2
|
disableInfiniteScroll?: boolean;
|
|
3
|
+
padNumbersWithZero?: boolean;
|
|
5
4
|
padWithNItems: number;
|
|
5
|
+
repeatNTimes?: number;
|
|
6
6
|
}) => string[];
|
|
7
7
|
export declare const generate12HourNumbers: (options: {
|
|
8
|
-
repeatNTimes?: number;
|
|
9
|
-
padNumbersWithZero?: boolean;
|
|
10
8
|
disableInfiniteScroll?: boolean;
|
|
9
|
+
padNumbersWithZero?: boolean;
|
|
11
10
|
padWithNItems: number;
|
|
11
|
+
repeatNTimes?: number;
|
|
12
12
|
}) => string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LimitType } from "../components/
|
|
1
|
+
import type { LimitType } from "../components/DurationScroll/types";
|
|
2
2
|
export declare const getAdjustedLimit: (limit: LimitType | undefined, numberOfItems: number) => {
|
|
3
3
|
max: number;
|
|
4
4
|
min: number;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-timer-picker",
|
|
3
|
-
"description": "A simple, flexible, performant duration picker for React Native apps 🔥\n\nGreat for timers, alarms and duration inputs
|
|
3
|
+
"description": "A simple, flexible, performant duration picker for React Native apps 🔥\n\nGreat for timers, alarms and duration inputs ⏰🕰️⏳\n\nIncludes iOS-style haptic and audio feedback 🍏",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Tim Roberts",
|
|
6
6
|
"url": "https://github.com/troberts-28"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.8.0",
|
|
10
10
|
"main": "dist/commonjs/index.js",
|
|
11
11
|
"types": "dist/typescript/src/index.d.ts",
|
|
12
12
|
"scripts": {
|
|
@@ -100,25 +100,28 @@
|
|
|
100
100
|
"react-native": ">=0.59.0"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@babel/core": "
|
|
103
|
+
"@babel/core": "^7.20.0",
|
|
104
104
|
"@babel/plugin-transform-class-properties": "^7.22.5",
|
|
105
105
|
"@babel/plugin-transform-flow-strip-types": "^7.22.5",
|
|
106
106
|
"@babel/plugin-transform-private-methods": "^7.22.5",
|
|
107
107
|
"@testing-library/react-native": "^12.0.0",
|
|
108
108
|
"@types/jest": "^29.0.0",
|
|
109
|
-
"@types/react": "
|
|
110
|
-
"@types/react-native": "
|
|
111
|
-
"@typescript-eslint/eslint-plugin": "
|
|
112
|
-
"@typescript-eslint/parser": "
|
|
109
|
+
"@types/react": "^18.0.27",
|
|
110
|
+
"@types/react-native": "^0.70.6",
|
|
111
|
+
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
|
112
|
+
"@typescript-eslint/parser": "^5.49.0",
|
|
113
113
|
"babel-jest": "^29.6.2",
|
|
114
|
-
"eslint": "
|
|
115
|
-
"eslint-plugin-
|
|
116
|
-
"eslint-plugin-react
|
|
114
|
+
"eslint": "^8.44.0",
|
|
115
|
+
"eslint-plugin-import": "^2.29.0",
|
|
116
|
+
"eslint-plugin-react": "^7.33.1",
|
|
117
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
118
|
+
"eslint-plugin-sort-destructure-keys": "^1.5.0",
|
|
119
|
+
"eslint-plugin-typescript-sort-keys": "^3.1.0",
|
|
117
120
|
"jest": "^29.0.0",
|
|
118
121
|
"metro-react-native-babel-preset": "^0.71.1",
|
|
119
122
|
"react-native-builder-bob": "^0.18.3",
|
|
120
123
|
"react-test-renderer": "^18.0.0",
|
|
121
|
-
"typescript": "
|
|
124
|
+
"typescript": "^4.7.4"
|
|
122
125
|
},
|
|
123
126
|
"react-native": "src/index.ts",
|
|
124
127
|
"source": "src/index.ts",
|
|
Binary file
|
|
@@ -3,103 +3,56 @@ import React, {
|
|
|
3
3
|
useCallback,
|
|
4
4
|
forwardRef,
|
|
5
5
|
useImperativeHandle,
|
|
6
|
-
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
7
8
|
} from "react";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
FlatList,
|
|
9
|
+
|
|
10
|
+
import { View, Text, FlatList } from "react-native";
|
|
11
|
+
import type {
|
|
12
12
|
ViewabilityConfigCallbackPairs,
|
|
13
13
|
ViewToken,
|
|
14
14
|
NativeSyntheticEvent,
|
|
15
15
|
NativeScrollEvent,
|
|
16
16
|
} from "react-native";
|
|
17
17
|
|
|
18
|
+
import { colorToRgba } from "../../utils/colorToRgba";
|
|
18
19
|
import {
|
|
19
20
|
generate12HourNumbers,
|
|
20
21
|
generateNumbers,
|
|
21
22
|
} from "../../utils/generateNumbers";
|
|
22
|
-
import { colorToRgba } from "../../utils/colorToRgba";
|
|
23
|
-
import { generateStyles } from "./TimerPicker.styles";
|
|
24
23
|
import { getAdjustedLimit } from "../../utils/getAdjustedLimit";
|
|
25
24
|
import { getScrollIndex } from "../../utils/getScrollIndex";
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
reset: (options?: { animated?: boolean }) => void;
|
|
29
|
-
setValue: (value: number, options?: { animated?: boolean }) => void;
|
|
30
|
-
latestDuration: MutableRefObject<number>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type LinearGradientPoint = {
|
|
34
|
-
x: number;
|
|
35
|
-
y: number;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type LinearGradientProps = React.ComponentProps<typeof View> & {
|
|
39
|
-
colors: string[];
|
|
40
|
-
locations?: number[] | null;
|
|
41
|
-
start?: LinearGradientPoint | null;
|
|
42
|
-
end?: LinearGradientPoint | null;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type LimitType = {
|
|
46
|
-
max?: number;
|
|
47
|
-
min?: number;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
interface DurationScrollProps {
|
|
51
|
-
allowFontScaling?: boolean;
|
|
52
|
-
numberOfItems: number;
|
|
53
|
-
label?: string | React.ReactElement;
|
|
54
|
-
initialValue?: number;
|
|
55
|
-
onDurationChange: (duration: number) => void;
|
|
56
|
-
padNumbersWithZero?: boolean;
|
|
57
|
-
disableInfiniteScroll?: boolean;
|
|
58
|
-
isDisabled?: boolean;
|
|
59
|
-
limit?: LimitType;
|
|
60
|
-
aggressivelyGetLatestDuration: boolean;
|
|
61
|
-
is12HourPicker?: boolean;
|
|
62
|
-
amLabel?: string;
|
|
63
|
-
pmLabel?: string;
|
|
64
|
-
padWithNItems: number;
|
|
65
|
-
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
66
|
-
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
67
|
-
bottomPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
-
LinearGradient?: any;
|
|
70
|
-
testID?: string;
|
|
71
|
-
styles: ReturnType<typeof generateStyles>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
-
const KEY_EXTRACTOR = (_: any, index: number) => index.toString();
|
|
26
|
+
import type { DurationScrollProps, DurationScrollRef } from "./types";
|
|
76
27
|
|
|
77
28
|
const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
78
|
-
(
|
|
79
|
-
{
|
|
80
|
-
numberOfItems,
|
|
81
|
-
label,
|
|
82
|
-
initialValue = 0,
|
|
83
|
-
onDurationChange,
|
|
84
|
-
padNumbersWithZero = false,
|
|
85
|
-
disableInfiniteScroll = false,
|
|
86
|
-
limit,
|
|
87
|
-
isDisabled,
|
|
29
|
+
(props, ref) => {
|
|
30
|
+
const {
|
|
88
31
|
aggressivelyGetLatestDuration,
|
|
89
32
|
allowFontScaling = false,
|
|
90
|
-
is12HourPicker,
|
|
91
33
|
amLabel,
|
|
92
|
-
|
|
93
|
-
padWithNItems,
|
|
94
|
-
pickerGradientOverlayProps,
|
|
95
|
-
topPickerGradientOverlayProps,
|
|
34
|
+
Audio,
|
|
96
35
|
bottomPickerGradientOverlayProps,
|
|
36
|
+
clickSoundAsset,
|
|
37
|
+
disableInfiniteScroll = false,
|
|
38
|
+
Haptics,
|
|
39
|
+
initialValue = 0,
|
|
40
|
+
is12HourPicker,
|
|
41
|
+
isDisabled,
|
|
42
|
+
label,
|
|
43
|
+
limit,
|
|
97
44
|
LinearGradient,
|
|
98
|
-
|
|
45
|
+
numberOfItems,
|
|
46
|
+
onDurationChange,
|
|
47
|
+
padNumbersWithZero = false,
|
|
48
|
+
padWithNItems,
|
|
49
|
+
pickerGradientOverlayProps,
|
|
50
|
+
pmLabel,
|
|
99
51
|
styles,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
52
|
+
testID,
|
|
53
|
+
topPickerGradientOverlayProps,
|
|
54
|
+
} = props;
|
|
55
|
+
|
|
103
56
|
const data = !is12HourPicker
|
|
104
57
|
? generateNumbers(numberOfItems, {
|
|
105
58
|
padNumbersWithZero,
|
|
@@ -125,10 +78,42 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
125
78
|
disableInfiniteScroll,
|
|
126
79
|
});
|
|
127
80
|
|
|
81
|
+
// keep track of the latest duration as it scrolls
|
|
128
82
|
const latestDuration = useRef(0);
|
|
83
|
+
// keep track of the last index scrolled past for haptic/audio feedback
|
|
84
|
+
const lastFeedbackIndex = useRef(0);
|
|
129
85
|
|
|
130
86
|
const flatListRef = useRef<FlatList | null>(null);
|
|
131
87
|
|
|
88
|
+
const [clickSound, setClickSound] = useState<
|
|
89
|
+
| {
|
|
90
|
+
replayAsync: () => Promise<void>;
|
|
91
|
+
unloadAsync: () => Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
| undefined
|
|
94
|
+
>();
|
|
95
|
+
|
|
96
|
+
// Preload the sound when the component mounts
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const loadSound = async () => {
|
|
99
|
+
if (Audio) {
|
|
100
|
+
const { sound } = await Audio.Sound.createAsync(
|
|
101
|
+
clickSoundAsset ??
|
|
102
|
+
require("../../assets/select_click.mp3"),
|
|
103
|
+
{ shouldPlay: false }
|
|
104
|
+
);
|
|
105
|
+
setClickSound(sound);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
loadSound();
|
|
109
|
+
|
|
110
|
+
// Unload sound when component unmounts
|
|
111
|
+
return () => {
|
|
112
|
+
clickSound?.unloadAsync();
|
|
113
|
+
};
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [Audio]);
|
|
116
|
+
|
|
132
117
|
useImperativeHandle(ref, () => ({
|
|
133
118
|
reset: (options) => {
|
|
134
119
|
flatListRef.current?.scrollToIndex({
|
|
@@ -182,11 +167,11 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
182
167
|
</Text>
|
|
183
168
|
{is12HourPicker ? (
|
|
184
169
|
<View
|
|
185
|
-
|
|
186
|
-
|
|
170
|
+
pointerEvents="none"
|
|
171
|
+
style={styles.pickerAmPmContainer}>
|
|
187
172
|
<Text
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
allowFontScaling={allowFontScaling}
|
|
174
|
+
style={[styles.pickerAmPmLabel]}>
|
|
190
175
|
{isAm ? amLabel : pmLabel}
|
|
191
176
|
</Text>
|
|
192
177
|
</View>
|
|
@@ -211,30 +196,63 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
211
196
|
|
|
212
197
|
const onScroll = useCallback(
|
|
213
198
|
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
214
|
-
// this function is only used when the picker is in a modal
|
|
199
|
+
// this function is only used when the picker is in a modal and/or has Haptic/Audio feedback
|
|
215
200
|
// it is used to ensure that the modal gets the latest duration on clicking
|
|
216
201
|
// the confirm button, even if the scrollview is still scrolling
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
let newDuration =
|
|
222
|
-
(disableInfiniteScroll
|
|
223
|
-
? newIndex
|
|
224
|
-
: newIndex + padWithNItems) %
|
|
225
|
-
(numberOfItems + 1);
|
|
202
|
+
if (!aggressivelyGetLatestDuration && !Haptics && !Audio) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
226
205
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
206
|
+
if (aggressivelyGetLatestDuration) {
|
|
207
|
+
const newIndex = Math.round(
|
|
208
|
+
e.nativeEvent.contentOffset.y /
|
|
209
|
+
styles.pickerItemContainer.height
|
|
210
|
+
);
|
|
211
|
+
let newDuration =
|
|
212
|
+
(disableInfiniteScroll
|
|
213
|
+
? newIndex
|
|
214
|
+
: newIndex + padWithNItems) %
|
|
215
|
+
(numberOfItems + 1);
|
|
216
|
+
|
|
217
|
+
if (newDuration !== latestDuration.current) {
|
|
218
|
+
// check limits
|
|
219
|
+
if (newDuration > adjustedLimited.max) {
|
|
220
|
+
newDuration = adjustedLimited.max;
|
|
221
|
+
} else if (newDuration < adjustedLimited.min) {
|
|
222
|
+
newDuration = adjustedLimited.min;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
latestDuration.current = newDuration;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (Haptics || Audio) {
|
|
230
|
+
const feedbackIndex = Math.round(
|
|
231
|
+
(e.nativeEvent.contentOffset.y +
|
|
232
|
+
styles.pickerItemContainer.height / 2) /
|
|
233
|
+
styles.pickerItemContainer.height
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
if (feedbackIndex !== lastFeedbackIndex.current) {
|
|
237
|
+
// this check stops the feedback firing when the component mounts
|
|
238
|
+
if (lastFeedbackIndex.current) {
|
|
239
|
+
// fire haptic feedback if available
|
|
240
|
+
Haptics?.selectionAsync();
|
|
241
|
+
|
|
242
|
+
// play click sound if available
|
|
243
|
+
clickSound?.replayAsync();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
lastFeedbackIndex.current = feedbackIndex;
|
|
247
|
+
}
|
|
232
248
|
}
|
|
233
|
-
latestDuration.current = newDuration;
|
|
234
249
|
},
|
|
250
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
235
251
|
[
|
|
236
252
|
adjustedLimited.max,
|
|
237
253
|
adjustedLimited.min,
|
|
254
|
+
aggressivelyGetLatestDuration,
|
|
255
|
+
clickSound,
|
|
238
256
|
disableInfiniteScroll,
|
|
239
257
|
numberOfItems,
|
|
240
258
|
padWithNItems,
|
|
@@ -337,7 +355,6 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
337
355
|
|
|
338
356
|
return (
|
|
339
357
|
<View
|
|
340
|
-
testID={testID}
|
|
341
358
|
pointerEvents={isDisabled ? "none" : undefined}
|
|
342
359
|
style={[
|
|
343
360
|
{
|
|
@@ -347,36 +364,35 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
347
364
|
overflow: "visible",
|
|
348
365
|
},
|
|
349
366
|
isDisabled && styles.disabledPickerContainer,
|
|
350
|
-
]}
|
|
367
|
+
]}
|
|
368
|
+
testID={testID}>
|
|
351
369
|
<FlatList
|
|
352
370
|
ref={flatListRef}
|
|
353
371
|
data={data}
|
|
372
|
+
decelerationRate={0.88}
|
|
354
373
|
getItemLayout={getItemLayout}
|
|
355
374
|
initialScrollIndex={initialScrollIndex}
|
|
356
|
-
|
|
375
|
+
keyExtractor={(_, index) => index.toString()}
|
|
376
|
+
onMomentumScrollEnd={onMomentumScrollEnd}
|
|
377
|
+
onScroll={onScroll}
|
|
357
378
|
renderItem={renderItem}
|
|
358
|
-
|
|
359
|
-
showsVerticalScrollIndicator={false}
|
|
360
|
-
decelerationRate={0.88}
|
|
379
|
+
scrollEnabled={!isDisabled}
|
|
361
380
|
scrollEventThrottle={16}
|
|
381
|
+
showsVerticalScrollIndicator={false}
|
|
362
382
|
snapToAlignment="start"
|
|
363
|
-
scrollEnabled={!isDisabled}
|
|
364
383
|
// used in place of snapToOffset due to bug on Android
|
|
365
384
|
snapToOffsets={[...Array(data.length)].map(
|
|
366
385
|
(_, i) => i * styles.pickerItemContainer.height
|
|
367
386
|
)}
|
|
387
|
+
testID="duration-scroll-flatlist"
|
|
368
388
|
viewabilityConfigCallbackPairs={
|
|
369
389
|
!disableInfiniteScroll
|
|
370
390
|
? viewabilityConfigCallbackPairs?.current
|
|
371
391
|
: undefined
|
|
372
392
|
}
|
|
373
|
-
|
|
374
|
-
onScroll={
|
|
375
|
-
aggressivelyGetLatestDuration ? onScroll : undefined
|
|
376
|
-
}
|
|
377
|
-
testID="duration-scroll-flatlist"
|
|
393
|
+
windowSize={numberOfItemsToShow}
|
|
378
394
|
/>
|
|
379
|
-
<View style={styles.pickerLabelContainer}
|
|
395
|
+
<View pointerEvents="none" style={styles.pickerLabelContainer}>
|
|
380
396
|
{typeof label === "string" ? (
|
|
381
397
|
<Text
|
|
382
398
|
allowFontScaling={allowFontScaling}
|
|
@@ -400,9 +416,9 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
400
416
|
opacity: 0,
|
|
401
417
|
}),
|
|
402
418
|
]}
|
|
403
|
-
start={{ x: 1, y: 0.3 }}
|
|
404
419
|
end={{ x: 1, y: 1 }}
|
|
405
420
|
pointerEvents="none"
|
|
421
|
+
start={{ x: 1, y: 0.3 }}
|
|
406
422
|
{...pickerGradientOverlayProps}
|
|
407
423
|
{...topPickerGradientOverlayProps}
|
|
408
424
|
style={[styles.pickerGradientOverlay, { top: 0 }]}
|
|
@@ -418,9 +434,9 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>(
|
|
|
418
434
|
styles.pickerContainer.backgroundColor ??
|
|
419
435
|
"white",
|
|
420
436
|
]}
|
|
421
|
-
start={{ x: 1, y: 0 }}
|
|
422
437
|
end={{ x: 1, y: 0.7 }}
|
|
423
438
|
pointerEvents="none"
|
|
439
|
+
start={{ x: 1, y: 0 }}
|
|
424
440
|
{...pickerGradientOverlayProps}
|
|
425
441
|
{...bottomPickerGradientOverlayProps}
|
|
426
442
|
style={[
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type { MutableRefObject } from "react";
|
|
3
|
+
|
|
4
|
+
import type { View } from "react-native";
|
|
5
|
+
|
|
6
|
+
import type { generateStyles } from "../TimerPicker/styles";
|
|
7
|
+
|
|
8
|
+
export interface DurationScrollProps {
|
|
9
|
+
Audio?: any;
|
|
10
|
+
Haptics?: any;
|
|
11
|
+
LinearGradient?: any;
|
|
12
|
+
aggressivelyGetLatestDuration: boolean;
|
|
13
|
+
allowFontScaling?: boolean;
|
|
14
|
+
amLabel?: string;
|
|
15
|
+
bottomPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
16
|
+
clickSoundAsset?: SoundAssetType;
|
|
17
|
+
disableInfiniteScroll?: boolean;
|
|
18
|
+
initialValue?: number;
|
|
19
|
+
is12HourPicker?: boolean;
|
|
20
|
+
isDisabled?: boolean;
|
|
21
|
+
label?: string | React.ReactElement;
|
|
22
|
+
limit?: LimitType;
|
|
23
|
+
numberOfItems: number;
|
|
24
|
+
onDurationChange: (duration: number) => void;
|
|
25
|
+
padNumbersWithZero?: boolean;
|
|
26
|
+
padWithNItems: number;
|
|
27
|
+
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
28
|
+
pmLabel?: string;
|
|
29
|
+
styles: ReturnType<typeof generateStyles>;
|
|
30
|
+
testID?: string;
|
|
31
|
+
topPickerGradientOverlayProps?: Partial<LinearGradientProps>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DurationScrollRef {
|
|
35
|
+
latestDuration: MutableRefObject<number>;
|
|
36
|
+
reset: (options?: { animated?: boolean }) => void;
|
|
37
|
+
setValue: (value: number, options?: { animated?: boolean }) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type LinearGradientPoint = {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type LinearGradientProps = React.ComponentProps<typeof View> & {
|
|
46
|
+
colors: string[];
|
|
47
|
+
end?: LinearGradientPoint | null;
|
|
48
|
+
locations?: number[] | null;
|
|
49
|
+
start?: LinearGradientPoint | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type LimitType = {
|
|
53
|
+
max?: number;
|
|
54
|
+
min?: number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type SoundAssetType =
|
|
58
|
+
| number
|
|
59
|
+
| {
|
|
60
|
+
headers?: Record<string, string>;
|
|
61
|
+
overrideFileExtensionAndroid?: string;
|
|
62
|
+
uri: string;
|
|
63
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
3
|
import {
|
|
4
4
|
Animated,
|
|
5
5
|
Easing,
|
|
@@ -8,34 +8,24 @@ import {
|
|
|
8
8
|
useWindowDimensions,
|
|
9
9
|
} from "react-native";
|
|
10
10
|
|
|
11
|
-
import { styles } from "./
|
|
11
|
+
import { styles } from "./styles";
|
|
12
|
+
import type { ModalProps } from "./types";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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;
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
children,
|
|
28
|
-
onOverlayPress,
|
|
29
|
-
onHide,
|
|
30
|
-
isVisible = false,
|
|
31
|
-
animationDuration = 300,
|
|
32
|
-
overlayOpacity = 0.4,
|
|
33
|
-
modalProps,
|
|
34
|
-
contentStyle,
|
|
35
|
-
overlayStyle,
|
|
36
|
-
testID = "modal",
|
|
37
|
-
}: ModalProps): React.ReactElement => {
|
|
38
|
-
const { width: screenWidth, height: screenHeight } = useWindowDimensions();
|
|
28
|
+
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
|
|
39
29
|
|
|
40
30
|
const isMounted = useRef(false);
|
|
41
31
|
const animatedOpacity = useRef(new Animated.Value(0));
|
|
@@ -105,8 +95,8 @@ export const Modal = ({
|
|
|
105
95
|
|
|
106
96
|
return (
|
|
107
97
|
<ReactNativeModal
|
|
108
|
-
transparent
|
|
109
98
|
animationType="fade"
|
|
99
|
+
transparent
|
|
110
100
|
visible={isVisible}
|
|
111
101
|
{...modalProps}
|
|
112
102
|
testID={testID}>
|
|
@@ -123,8 +113,8 @@ export const Modal = ({
|
|
|
123
113
|
/>
|
|
124
114
|
</TouchableWithoutFeedback>
|
|
125
115
|
<Animated.View
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
pointerEvents="box-none"
|
|
117
|
+
style={[styles.content, contentAnimatedStyle, contentStyle]}>
|
|
128
118
|
{children}
|
|
129
119
|
</Animated.View>
|
|
130
120
|
</ReactNativeModal>
|