@momo-kits/date-picker 0.77.8 → 0.78.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,163 @@
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, Text} from '@momo-kits/foundation';
5
+ import {WheelPickerProps} from './types';
6
+ import {debounce} from './utils';
7
+
8
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
9
+
10
+ const WheelPicker: FC<WheelPickerProps> = ({
11
+ name,
12
+ data,
13
+ style,
14
+ onChange,
15
+ selectedData,
16
+ }) => {
17
+ const {theme} = useContext(ApplicationContext);
18
+ const flatListRef = useRef<FlatList>(null);
19
+ const scrollAnimatedValue = useRef(new Animated.Value(0)).current;
20
+ const active = useRef(0);
21
+ const scrollListener = useRef('0');
22
+ const itemSize = 42;
23
+
24
+ useEffect(() => {
25
+ scrollListener.current && clearInterval(Number(scrollListener.current));
26
+ scrollListener.current = scrollAnimatedValue.addListener(
27
+ ({value}) => (active.current = value),
28
+ );
29
+
30
+ return () => {
31
+ clearInterval(Number(scrollListener.current));
32
+ };
33
+ }, [scrollAnimatedValue]);
34
+
35
+ useEffect(() => {
36
+ debouncedScrollEnd();
37
+ return () => {
38
+ debouncedScrollEnd.cancel();
39
+ };
40
+ }, [data]);
41
+
42
+ const debouncedScrollEnd = debounce(() => {
43
+ let selectedIndex = data.findIndex(item => item === selectedData);
44
+
45
+ if (selectedIndex < 2) {
46
+ selectedIndex = 2;
47
+ }
48
+ if (selectedIndex > data.length - 3) {
49
+ selectedIndex = data.length - 3;
50
+ }
51
+ if (selectedIndex >= 2) {
52
+ flatListRef.current?.scrollToIndex({
53
+ index: selectedIndex - 2,
54
+ animated: true,
55
+ });
56
+ }
57
+ }, 100);
58
+
59
+ const ItemComponent: FC<any> = React.memo(props => {
60
+ const {item, opacity} = props;
61
+ return (
62
+ <Animated.View
63
+ style={[
64
+ styles.wheelItem,
65
+ {
66
+ opacity,
67
+ },
68
+ ]}>
69
+ <Text typography={'action_s'}>{item}</Text>
70
+ </Animated.View>
71
+ );
72
+ });
73
+
74
+ const renderItem = useCallback(({item, index}) => {
75
+ const opacityAnimated = (a: number, b: number, c: number) => {
76
+ return {
77
+ inputRange: [...data.map((_: any, i: number) => i * itemSize)],
78
+ outputRange: [
79
+ ...data.map((_: any, i: number) => {
80
+ const center = i + 2;
81
+ if (center === index) {
82
+ return a;
83
+ } else if (center + 1 === index || center - 1 === index) {
84
+ return b;
85
+ } else {
86
+ return c;
87
+ }
88
+ }),
89
+ ],
90
+ };
91
+ };
92
+
93
+ return (
94
+ <ItemComponent
95
+ item={item}
96
+ opacity={scrollAnimatedValue.interpolate(opacityAnimated(1, 0.8, 0.4))}
97
+ />
98
+ );
99
+ }, []);
100
+
101
+ const renderOverlay = () => {
102
+ return (
103
+ <View style={styles.overlay}>
104
+ <View style={styles.selectedItem} />
105
+ </View>
106
+ );
107
+ };
108
+
109
+ const getItemLayout = (
110
+ data: unknown[] | undefined | null,
111
+ index: number,
112
+ ) => ({
113
+ length: itemSize,
114
+ offset: itemSize * index,
115
+ index,
116
+ });
117
+
118
+ const onScrollToIndexFailed = (error: any) => {
119
+ console.warn('FlatList scrollToIndex failed', error);
120
+ };
121
+
122
+ return (
123
+ <View
124
+ key={`Wheel picker ${name}`}
125
+ style={[
126
+ style,
127
+ styles.wheelPicker,
128
+ {
129
+ borderColor: theme.colors.border.default,
130
+ backgroundColor: theme.colors.background.surface,
131
+ },
132
+ ]}>
133
+ <AnimatedFlatList
134
+ windowSize={15}
135
+ initialNumToRender={5}
136
+ maxToRenderPerBatch={5}
137
+ pagingEnabled
138
+ snapToInterval={itemSize}
139
+ decelerationRate={'fast'}
140
+ getItemLayout={getItemLayout}
141
+ onScrollToIndexFailed={onScrollToIndexFailed}
142
+ onScroll={Animated.event(
143
+ [{nativeEvent: {contentOffset: {y: scrollAnimatedValue}}}],
144
+ {
145
+ useNativeDriver: true,
146
+ },
147
+ )}
148
+ onMomentumScrollEnd={() => {
149
+ const index = Math.round(active.current / itemSize);
150
+ onChange?.(name, data[index + 2]);
151
+ }}
152
+ ref={flatListRef}
153
+ keyExtractor={(item, index) => `Wheel picker item ${item}-${index}`}
154
+ showsVerticalScrollIndicator={false}
155
+ data={data}
156
+ renderItem={renderItem}
157
+ />
158
+ {renderOverlay()}
159
+ </View>
160
+ );
161
+ };
162
+
163
+ export default WheelPicker;
package/index.tsx ADDED
@@ -0,0 +1,137 @@
1
+ import React, {FC, useEffect, useState} from 'react';
2
+ import {Dimensions, 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 screenWidth = Dimensions.get('window').width;
17
+ const date = new Date();
18
+ date.setHours(0, 0);
19
+
20
+ const DateTimePicker: FC<DateTimePickerProps> = ({
21
+ format = 'DD-MM-YYYY',
22
+ minuteInterval = 1,
23
+ onChange,
24
+ selectedValue = date,
25
+ minDate = new Date(1900, 1, 1, 0, 0),
26
+ maxDate = new Date(date.getFullYear() + 10, 12, 31, 0, 0),
27
+ }) => {
28
+ const [data, setData] = useState<DateObject[]>([]);
29
+ let [currentDate, setCurrentDate] = useState<PickerDataObject>({
30
+ day: selectedValue.getDate(),
31
+ month: selectedValue.getMonth() + 1,
32
+ year: selectedValue.getFullYear(),
33
+ hour: selectedValue.getHours(),
34
+ min: selectedValue.getMinutes(),
35
+ });
36
+
37
+ useEffect(() => {
38
+ setupData();
39
+ return () => {};
40
+ }, [selectedValue, currentDate]);
41
+
42
+ const setupData = () => {
43
+ const formatParts = format.split(/[^A-Za-z]+/);
44
+ const isOnlyHour = formatParts.length === 1 && formatParts[0] === 'HH';
45
+
46
+ const componentData: {[key: string]: DateObject} = {
47
+ DD: {
48
+ name: 'day',
49
+ data: getDaysInMonth(
50
+ +currentDate.year,
51
+ +currentDate.month,
52
+ minDate,
53
+ maxDate,
54
+ ),
55
+ },
56
+ MM: {
57
+ name: 'month',
58
+ data: getMonths(minDate, maxDate, +currentDate.year),
59
+ },
60
+ YYYY: {
61
+ name: 'year',
62
+ data: getYears(minDate, maxDate),
63
+ },
64
+ HH: {name: 'hour', data: getHours(isOnlyHour ? 12 : 24)},
65
+ mm: {name: 'min', data: getMinutes(minuteInterval)},
66
+ };
67
+
68
+ const initialData = formatParts.map(part => {
69
+ const component = componentData[part];
70
+ if (!component) {
71
+ throw new Error(`Invalid format component: ${part}`);
72
+ }
73
+ return {
74
+ ...component,
75
+ };
76
+ });
77
+
78
+ if (isOnlyHour) {
79
+ initialData.push({name: 'timeMode', data: timeMode});
80
+ }
81
+
82
+ setData(initialData);
83
+ };
84
+
85
+ const numOfColumns = data.length;
86
+
87
+ const onPickerChange = (name: string, dataItem: string) => {
88
+ const newDate = {
89
+ ...currentDate,
90
+ [name]: dataItem,
91
+ };
92
+
93
+ if (name === 'month') {
94
+ const finalDay = new Date(newDate.year, newDate.month, 0).getDate();
95
+ if (newDate.day > finalDay) {
96
+ newDate.day = finalDay;
97
+ }
98
+ }
99
+
100
+ onChange?.(
101
+ new Date(
102
+ newDate.year,
103
+ newDate.month - 1,
104
+ newDate.day,
105
+ newDate.hour,
106
+ newDate.min,
107
+ ),
108
+ );
109
+
110
+ setCurrentDate(newDate);
111
+ };
112
+
113
+ const paddingArray = ['', ''];
114
+
115
+ return (
116
+ <View style={styles.datePicker}>
117
+ {data.map((dataItem, index) => {
118
+ return (
119
+ <WheelPicker
120
+ key={`${dataItem.name}_${index}`}
121
+ selectedData={String(currentDate[dataItem.name])}
122
+ onChange={onPickerChange}
123
+ data={[...paddingArray, ...dataItem.data, ...paddingArray]}
124
+ name={dataItem.name}
125
+ style={{
126
+ width:
127
+ (screenWidth - Spacing.M * 2 - (numOfColumns - 1) * Spacing.M) /
128
+ numOfColumns,
129
+ }}
130
+ />
131
+ );
132
+ })}
133
+ </View>
134
+ );
135
+ };
136
+
137
+ export default DateTimePicker;
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@momo-kits/date-picker",
3
- "version": "0.77.8",
3
+ "version": "0.78.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",
7
+ "@momo-kits/foundation": "latest",
11
8
  "react": "16.9.0",
12
- "react-native": ">=0.55"
9
+ "react-native": ">=0.55",
10
+ "prop-types": "^15.7.2"
13
11
  },
14
12
  "devDependencies": {},
15
- "license": "MoMo"
13
+ "license": "MoMo",
14
+ "dependencies": {}
16
15
  }
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,32 @@
1
+ import {ViewStyle} from 'react-native';
2
+
3
+ export type DateTimePickerProps = {
4
+ format?: string;
5
+ minuteInterval?: number;
6
+ onChange?: (data: Date) => void;
7
+ selectedValue?: Date;
8
+ minDate?: Date;
9
+ maxDate?: Date;
10
+ };
11
+
12
+ export type PickerDataObject = {
13
+ day: number;
14
+ month: number;
15
+ year: number;
16
+ hour: number;
17
+ min: number;
18
+ timeMode?: string;
19
+ };
20
+
21
+ export type DateObject = {
22
+ name: 'day' | 'month' | 'year' | 'hour' | 'min' | 'timeMode';
23
+ data: string[];
24
+ };
25
+
26
+ export type WheelPickerProps = {
27
+ name: string;
28
+ data: string[];
29
+ style: ViewStyle;
30
+ onChange: (name: string, data: string) => void;
31
+ selectedData: string;
32
+ };
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
+ };