@momo-kits/date-picker 0.73.3-beta.5 → 0.74.2-react-native.1

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.
@@ -0,0 +1,207 @@
1
+ import React, {FC, useCallback, useContext, useEffect, useRef} from 'react';
2
+ import {Animated, FlatList, View} from 'react-native';
3
+ import styles from './styles';
4
+ import {ApplicationContext, scaleSize} from '@momo-kits/foundation';
5
+ import {WheelPickerProps} from './types';
6
+ import {debounce} from './utils';
7
+
8
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
9
+ const WheelPicker: FC<WheelPickerProps> = ({
10
+ name,
11
+ data,
12
+ style,
13
+ onChange,
14
+ selectedData,
15
+ }) => {
16
+ const {theme} = useContext(ApplicationContext);
17
+ const flatListRef = useRef<FlatList>(null);
18
+ const scrollAnimatedValue = useRef(new Animated.Value(0)).current;
19
+ const active = useRef(0);
20
+ const scrollListener = useRef('0');
21
+ const itemSize = 42;
22
+
23
+ useEffect(() => {
24
+ scrollListener.current && clearInterval(Number(scrollListener.current));
25
+ scrollListener.current = scrollAnimatedValue.addListener(
26
+ ({value}) => (active.current = value),
27
+ );
28
+
29
+ return () => {
30
+ clearInterval(Number(scrollListener.current));
31
+ };
32
+ }, [scrollAnimatedValue]);
33
+
34
+ useEffect(() => {
35
+ debouncedScrollEnd();
36
+ return () => {
37
+ debouncedScrollEnd.cancel();
38
+ };
39
+ }, [data.length]);
40
+
41
+ const findClosestIndex = (array: string[], value: string) => {
42
+ let closest = array.reduce((a, b) => {
43
+ let aDiff = Math.abs(Number(a) - Number(value));
44
+ let bDiff = Math.abs(Number(b) - Number(value));
45
+
46
+ if (aDiff == bDiff) {
47
+ return a < b ? a : b;
48
+ } else {
49
+ return bDiff < aDiff ? b : a;
50
+ }
51
+ });
52
+
53
+ return array.indexOf(closest);
54
+ };
55
+
56
+ const debouncedScrollEnd = debounce(() => {
57
+ let selectedIndex = data.indexOf(selectedData);
58
+
59
+ if (selectedIndex === -1) {
60
+ selectedIndex = findClosestIndex(data, selectedData);
61
+ }
62
+
63
+ if (selectedIndex < 2) {
64
+ selectedIndex = 2;
65
+ }
66
+ if (selectedIndex > data.length - 3) {
67
+ selectedIndex = data.length - 3;
68
+ }
69
+ flatListRef.current?.scrollToIndex({
70
+ index: selectedIndex - 2,
71
+ animated: true,
72
+ });
73
+ }, 100);
74
+
75
+ const onMomentumScrollEnd = () => {
76
+ let index = Math.round(active.current / itemSize);
77
+ onChange?.(name, data[index + 2]);
78
+ };
79
+
80
+ const ItemComponent: FC<any> = React.memo(props => {
81
+ const {item, opacity, scale} = props;
82
+
83
+ return (
84
+ <Animated.View
85
+ style={[
86
+ styles.wheelItem,
87
+ {
88
+ opacity,
89
+ },
90
+ ]}>
91
+ <Animated.Text
92
+ style={{
93
+ fontFamily: `${theme.font}-Semibold`,
94
+ transform: [{scale}],
95
+ fontSize: scaleSize(15),
96
+ lineHeight: scaleSize(22),
97
+ }}>
98
+ {item}
99
+ </Animated.Text>
100
+ </Animated.View>
101
+ );
102
+ });
103
+
104
+ const renderItem = useCallback(({item, index}) => {
105
+ const opacityAnimated = (a: number, b: number, c: number) => {
106
+ return {
107
+ inputRange: [...data.map((_: any, i: number) => i * itemSize)],
108
+ outputRange: [
109
+ ...data.map((_: any, i: number) => {
110
+ const center = i + 2;
111
+ if (center === index) {
112
+ return a;
113
+ } else if (center + 1 === index || center - 1 === index) {
114
+ return b;
115
+ } else {
116
+ return c;
117
+ }
118
+ }),
119
+ ],
120
+ };
121
+ };
122
+
123
+ const scaleAnimated = (a: number, b: number) => {
124
+ return {
125
+ inputRange: [...data.map((_: any, i: number) => i * itemSize)],
126
+ outputRange: [
127
+ ...data.map((_: any, i: number) => {
128
+ const center = i + 2;
129
+ if (center === index) {
130
+ return a;
131
+ } else {
132
+ return b;
133
+ }
134
+ }),
135
+ ],
136
+ };
137
+ };
138
+
139
+ return (
140
+ <ItemComponent
141
+ item={item}
142
+ opacity={scrollAnimatedValue.interpolate(opacityAnimated(1, 0.8, 0.4))}
143
+ scale={scrollAnimatedValue.interpolate(scaleAnimated(1, 0.87))}
144
+ />
145
+ );
146
+ }, []);
147
+
148
+ const renderOverlay = () => {
149
+ return (
150
+ <View style={styles.overlay}>
151
+ <View style={styles.selectedItem} />
152
+ </View>
153
+ );
154
+ };
155
+
156
+ const getItemLayout = (
157
+ data: unknown[] | undefined | null,
158
+ index: number,
159
+ ) => ({
160
+ length: itemSize,
161
+ offset: itemSize * index,
162
+ index,
163
+ });
164
+
165
+ const onScrollToIndexFailed = (error: any) => {
166
+ console.warn('FlatList scrollToIndex failed', error);
167
+ };
168
+
169
+ return (
170
+ <View
171
+ key={`Wheel picker ${name}`}
172
+ style={[
173
+ style,
174
+ styles.wheelPicker,
175
+ {
176
+ borderColor: theme.colors.border.default,
177
+ backgroundColor: theme.colors.background.surface,
178
+ },
179
+ ]}>
180
+ <AnimatedFlatList
181
+ windowSize={15}
182
+ initialNumToRender={15}
183
+ maxToRenderPerBatch={15}
184
+ pagingEnabled
185
+ snapToInterval={itemSize}
186
+ decelerationRate={'fast'}
187
+ getItemLayout={getItemLayout}
188
+ onScrollToIndexFailed={onScrollToIndexFailed}
189
+ onScroll={Animated.event(
190
+ [{nativeEvent: {contentOffset: {y: scrollAnimatedValue}}}],
191
+ {
192
+ useNativeDriver: true,
193
+ },
194
+ )}
195
+ onMomentumScrollEnd={onMomentumScrollEnd}
196
+ ref={flatListRef}
197
+ keyExtractor={(item, index) => `Wheel picker item ${item}-${index}`}
198
+ showsVerticalScrollIndicator={false}
199
+ data={data}
200
+ renderItem={renderItem}
201
+ />
202
+ {renderOverlay()}
203
+ </View>
204
+ );
205
+ };
206
+
207
+ export default WheelPicker;
package/index.tsx ADDED
@@ -0,0 +1,148 @@
1
+ import React, {FC, useEffect, useState} from 'react';
2
+ import {Dimensions, LayoutChangeEvent, View} from 'react-native';
3
+ import {Spacing} from '@momo-kits/foundation';
4
+ import WheelPicker from './WheelPicker';
5
+ import {
6
+ getDaysInMonth,
7
+ getHours,
8
+ getMinutes,
9
+ getMonths,
10
+ getYears,
11
+ timeMode,
12
+ } from './utils';
13
+ import {DateObject, DateTimePickerProps, PickerDataObject} from './types';
14
+ import styles from './styles';
15
+
16
+ const date = new Date();
17
+ const minDateDefault = new Date(date);
18
+ const maxDateDefault = new Date(date);
19
+ minDateDefault.setFullYear(minDateDefault.getFullYear() - 10);
20
+ maxDateDefault.setFullYear(maxDateDefault.getFullYear() + 10);
21
+ date.setHours(0, 0);
22
+ const screenWidth = Dimensions.get('window').width;
23
+
24
+ const DateTimePicker: FC<DateTimePickerProps> = ({
25
+ format = 'DD-MM-YYYY',
26
+ minuteInterval = 1,
27
+ onChange,
28
+ selectedValue = date,
29
+ minDate = minDateDefault,
30
+ maxDate = maxDateDefault,
31
+ }) => {
32
+ const [data, setData] = useState<DateObject[]>([]);
33
+ let [currentDate, setCurrentDate] = useState<PickerDataObject>({
34
+ day: selectedValue.getDate(),
35
+ month: selectedValue.getMonth() + 1,
36
+ year: selectedValue.getFullYear(),
37
+ hour: selectedValue.getHours(),
38
+ min: selectedValue.getMinutes(),
39
+ });
40
+ const [containerWidth, setContainerWidth] = useState(screenWidth);
41
+
42
+ useEffect(() => {
43
+ setupData();
44
+ return () => {};
45
+ }, [selectedValue, currentDate]);
46
+
47
+ const setupData = () => {
48
+ const formatParts = format.split(/[^A-Za-z]+/);
49
+ const isOnlyHour = formatParts.length === 1 && formatParts[0] === 'HH';
50
+
51
+ const componentData: {[key: string]: DateObject} = {
52
+ DD: {
53
+ name: 'day',
54
+ data: getDaysInMonth(
55
+ +currentDate.year,
56
+ +currentDate.month,
57
+ minDate,
58
+ maxDate,
59
+ ),
60
+ },
61
+ MM: {
62
+ name: 'month',
63
+ data: getMonths(minDate, maxDate, +currentDate.year),
64
+ },
65
+ YYYY: {
66
+ name: 'year',
67
+ data: getYears(minDate, maxDate),
68
+ },
69
+ HH: {name: 'hour', data: getHours(isOnlyHour ? 12 : 24)},
70
+ mm: {name: 'min', data: getMinutes(minuteInterval)},
71
+ };
72
+
73
+ const initialData = formatParts.map(part => {
74
+ const component = componentData[part];
75
+ if (!component) {
76
+ throw new Error(`Invalid format component: ${part}`);
77
+ }
78
+ return {
79
+ ...component,
80
+ };
81
+ });
82
+
83
+ if (isOnlyHour) {
84
+ initialData.push({name: 'timeMode', data: timeMode});
85
+ }
86
+
87
+ setData(initialData);
88
+ };
89
+
90
+ const numOfColumns = data.length;
91
+
92
+ const onPickerChange = (name: string, dataItem: string) => {
93
+ const newDate = {
94
+ ...currentDate,
95
+ [name]: dataItem,
96
+ };
97
+
98
+ if (name === 'month') {
99
+ const finalDay = new Date(newDate.year, newDate.month, 0).getDate();
100
+ if (newDate.day > finalDay) {
101
+ newDate.day = finalDay;
102
+ }
103
+ }
104
+
105
+ onChange?.(
106
+ new Date(
107
+ newDate.year,
108
+ newDate.month - 1,
109
+ newDate.day,
110
+ newDate.hour,
111
+ newDate.min,
112
+ ),
113
+ );
114
+
115
+ setCurrentDate(newDate);
116
+ };
117
+
118
+ const paddingArray = ['', ''];
119
+
120
+ const onLayout = (e: LayoutChangeEvent) => {
121
+ setContainerWidth(e.nativeEvent.layout.width);
122
+ };
123
+
124
+ return (
125
+ <View onLayout={onLayout} style={styles.datePicker}>
126
+ {data.map((dataItem, index) => {
127
+ return (
128
+ <WheelPicker
129
+ key={`${dataItem.name}_${index}`}
130
+ selectedData={String(currentDate[dataItem.name])}
131
+ onChange={onPickerChange}
132
+ data={[...paddingArray, ...dataItem.data, ...paddingArray]}
133
+ name={dataItem.name}
134
+ style={{
135
+ width:
136
+ (containerWidth -
137
+ Spacing.M * 2 -
138
+ (numOfColumns - 1) * Spacing.M) /
139
+ numOfColumns,
140
+ }}
141
+ />
142
+ );
143
+ })}
144
+ </View>
145
+ );
146
+ };
147
+
148
+ export {DateTimePicker};
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@momo-kits/date-picker",
3
- "version": "0.73.3-beta.5",
3
+ "version": "0.74.2-react-native.1",
4
4
  "private": false,
5
- "main": "index.js",
6
- "dependencies": {},
5
+ "main": "index.tsx",
7
6
  "peerDependencies": {
8
- "@momo-kits/core-v2": ">=0.0.5-beta",
9
- "lodash": "^4.17.15",
10
- "prop-types": "^15.7.2",
11
- "react": "16.9.0",
12
- "react-native": ">=0.55"
7
+ "@momo-kits/foundation": "latest",
8
+ "react": "*",
9
+ "react-native": "*",
10
+ "prop-types": "15.7.2"
13
11
  },
14
- "devDependencies": {},
15
- "license": "MoMo"
12
+ "devDependencies": {
13
+ "@momo-platform/versions": "4.1.11"
14
+ },
15
+ "license": "MoMo",
16
+ "dependencies": {}
16
17
  }
package/publish.sh CHANGED
@@ -25,5 +25,4 @@ npm publish --tag beta --access=public
25
25
  cd ..
26
26
  rm -rf dist
27
27
 
28
-
29
- ##curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/date-picker new version release: '*"$VERSION"*' https://www.npmjs.com/package/@momo-kits/date-picker"}'
28
+ ##curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/date-picker new version release: '*"$VERSION"*' `https://www.npmjs.com/package/@momo-kits/date-picker`"}'
package/styles.ts ADDED
@@ -0,0 +1,34 @@
1
+ import {StyleSheet} from 'react-native';
2
+ import {Colors, Radius} from '@momo-kits/foundation';
3
+
4
+ export default StyleSheet.create({
5
+ wheelItem: {
6
+ height: 42,
7
+ width: '100%',
8
+ alignItems: 'center',
9
+ justifyContent: 'center',
10
+ },
11
+ wheelPicker: {
12
+ borderWidth: 1,
13
+ borderRadius: Radius.S,
14
+ },
15
+ overlay: {
16
+ position: 'absolute',
17
+ top: 0,
18
+ bottom: 0,
19
+ right: 0,
20
+ left: 0,
21
+ justifyContent: 'center',
22
+ zIndex: -1,
23
+ },
24
+ selectedItem: {
25
+ height: 42,
26
+ backgroundColor: Colors.blue_10,
27
+ },
28
+ datePicker: {
29
+ height: 210,
30
+ overflow: 'hidden',
31
+ flexDirection: 'row',
32
+ justifyContent: 'space-evenly',
33
+ },
34
+ });
package/types.ts ADDED
@@ -0,0 +1,88 @@
1
+ import {ViewStyle} from 'react-native';
2
+
3
+ export type DateTimePickerProps = {
4
+ /**
5
+ * Optional. Specifies the format for displaying the date and time.
6
+ */
7
+ format?: string;
8
+
9
+ /**
10
+ * Optional. The interval between minutes in the picker. For example, '15' would show minute options 0, 15, 30, 45, etc.
11
+ */
12
+ minuteInterval?: number;
13
+
14
+ /**
15
+ * Optional. A callback function that is triggered when the date or time value changes.
16
+ */
17
+ onChange?: (data: Date) => void;
18
+
19
+ /**
20
+ * Optional. The initially selected date and time value.
21
+ */
22
+ selectedValue?: Date;
23
+
24
+ /**
25
+ * Optional. The minimum date that can be selected.
26
+ */
27
+ minDate?: Date;
28
+
29
+ /**
30
+ * Optional. The maximum date that can be selected.
31
+ */
32
+ maxDate?: Date;
33
+ };
34
+
35
+ /**
36
+ * Represents the data structure for individual date and time components.
37
+ */
38
+ export type PickerDataObject = {
39
+ day: number;
40
+ month: number;
41
+ year: number;
42
+ hour: number;
43
+ min: number;
44
+
45
+ /**
46
+ * Optional. Specifies the mode of time presentation, e.g., 'AM' or 'PM'.
47
+ */
48
+ timeMode?: string;
49
+ };
50
+
51
+ /**
52
+ * Type representing a date object with a name identifying the component ('day', 'month', etc.) and the associated data.
53
+ */
54
+ export type DateObject = {
55
+ name: 'day' | 'month' | 'year' | 'hour' | 'min' | 'timeMode';
56
+ data: string[];
57
+ };
58
+
59
+ /**
60
+ * Properties for the WheelPicker component.
61
+ * This component provides a spinning-wheel style picker for selecting from provided data.
62
+ */
63
+ export type WheelPickerProps = {
64
+ /**
65
+ * The name of the picker, used to identify it.
66
+ */
67
+ name: string;
68
+
69
+ /**
70
+ * The data to be displayed and selected in the picker.
71
+ */
72
+ data: string[];
73
+
74
+ /**
75
+ * Custom styles to apply to the WheelPicker component.
76
+ */
77
+ style: ViewStyle;
78
+
79
+ /**
80
+ * A callback function that is triggered when the selected data changes.
81
+ */
82
+ onChange: (name: string, data: string) => void;
83
+
84
+ /**
85
+ * The currently selected data in the picker.
86
+ */
87
+ selectedData: string;
88
+ };
package/utils.ts ADDED
@@ -0,0 +1,112 @@
1
+ const paddingNum = (num: number) => {
2
+ return num > 9 ? String(num) : `0${num}`;
3
+ };
4
+ const getDaysInMonth = (
5
+ year: number,
6
+ month: number,
7
+ minDate: Date,
8
+ maxDate: Date,
9
+ ) => {
10
+ const daysInMonth = new Date(year, month, 0).getDate();
11
+
12
+ let startDay = 1;
13
+ let endDay = daysInMonth;
14
+
15
+ if (year === minDate.getFullYear() && month === minDate.getMonth()) {
16
+ startDay = minDate.getDate();
17
+ }
18
+ if (year === maxDate.getFullYear() && month === maxDate.getMonth()) {
19
+ endDay = maxDate.getDate();
20
+ }
21
+
22
+ const days = [];
23
+ for (let day = startDay; day <= endDay; day++) {
24
+ days.push(paddingNum(day));
25
+ }
26
+
27
+ return days;
28
+ };
29
+
30
+ const getMonths = (minDate: Date, maxDate: Date, currentYear: number) => {
31
+ let startMonth = 1;
32
+ let endMonth = 12;
33
+
34
+ if (currentYear === minDate.getFullYear()) {
35
+ startMonth = minDate.getMonth();
36
+ }
37
+ if (currentYear === maxDate.getFullYear()) {
38
+ endMonth = maxDate.getMonth();
39
+ }
40
+
41
+ const months = [];
42
+ for (let month = startMonth; month <= endMonth; month++) {
43
+ months.push(paddingNum(month));
44
+ }
45
+
46
+ return months;
47
+ };
48
+
49
+ const getYears = (minDate: Date, maxDate: Date) => {
50
+ const startYear = minDate.getFullYear();
51
+ const endYear = maxDate.getFullYear();
52
+ const years = [];
53
+
54
+ for (let year = startYear; year <= endYear; year++) {
55
+ years.push(year.toString());
56
+ }
57
+ return years;
58
+ };
59
+
60
+ const getHours = (hourMode: 12 | 24) => {
61
+ const hours = [];
62
+ for (let i = 0; i < hourMode; i++) {
63
+ let hour = i;
64
+ if (hourMode === 12) {
65
+ hour = i + 1;
66
+ }
67
+ hours.push(paddingNum(hour));
68
+ }
69
+ return hours;
70
+ };
71
+
72
+ const getMinutes = (interval: number) => {
73
+ const minutes = [];
74
+ for (let min = 0; min < 60; min += interval) {
75
+ minutes.push(paddingNum(min));
76
+ }
77
+
78
+ return minutes;
79
+ };
80
+
81
+ const timeMode = ['', '', 'AM', 'PM', '', ''];
82
+
83
+ function debounce<T extends (...args: any[]) => any>(func: T, delay: number) {
84
+ let timer: NodeJS.Timeout;
85
+
86
+ const debounced = function (
87
+ this: ThisParameterType<T>,
88
+ ...args: Parameters<T>
89
+ ) {
90
+ clearTimeout(timer);
91
+ const context = this;
92
+ timer = setTimeout(() => {
93
+ func.apply(context, args);
94
+ }, delay);
95
+ };
96
+
97
+ debounced.cancel = () => {
98
+ clearTimeout(timer);
99
+ };
100
+
101
+ return debounced;
102
+ }
103
+
104
+ export {
105
+ getDaysInMonth,
106
+ getMonths,
107
+ getYears,
108
+ getHours,
109
+ getMinutes,
110
+ timeMode,
111
+ debounce,
112
+ };