@momo-kits/calendar 0.79.6-beta.1 → 0.79.6-beta.3

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,379 @@
1
+ import React, {Component, createRef} from 'react';
2
+
3
+ import {Dimensions, ScrollView, View} from 'react-native';
4
+ import Moment from 'moment';
5
+ import {
6
+ ApplicationContext,
7
+ CheckBox,
8
+ Colors,
9
+ Spacing,
10
+ Text,
11
+ } from '@momo-kits/foundation';
12
+ import MonthList from './MonthList';
13
+ import HeaderControl from './HeaderControl';
14
+ import LunarDateConverter from './LunarDateConverter';
15
+ import Util from './Util';
16
+ import {CalendarProProps, CalendarProState, HeaderControlRef} from './types';
17
+ import {ContainerContext} from './index';
18
+ import styles from './styles';
19
+
20
+ const widthScreen = Dimensions.get('window').width;
21
+
22
+ export default class CalendarPro extends Component<
23
+ CalendarProProps,
24
+ CalendarProState
25
+ > {
26
+ static contextType = ApplicationContext;
27
+ today: Moment.Moment;
28
+ year: number;
29
+ header: Moment.Moment;
30
+ selectedDate: Moment.Moment;
31
+ converter: typeof LunarDateConverter;
32
+ minDate: Moment.Moment = Moment();
33
+ maxDate: Moment.Moment = Moment();
34
+ monthListRef?: MonthList | null;
35
+ headerControlRef;
36
+
37
+ constructor(props: CalendarProProps) {
38
+ super(props);
39
+ this.today = Moment();
40
+ this.year = this.today.year();
41
+ this.getDateRange();
42
+ this.header = this.today.clone();
43
+ this.selectedDate = props.selectedDate;
44
+ this.headerControlRef = createRef<HeaderControlRef>();
45
+ this.state = {
46
+ startDate: props.startDate,
47
+ endDate: props.endDate,
48
+ showLunar: props.isShowLunar,
49
+ tabSelected: 0,
50
+ holidays: [],
51
+ ownUpdate: false,
52
+ };
53
+ this.converter = new LunarDateConverter();
54
+ }
55
+
56
+ static getDerivedStateFromProps(
57
+ nextProps: {isShowLunar: any},
58
+ prevState: {ownUpdate: any; showLunar: any},
59
+ ) {
60
+ if (prevState.ownUpdate) {
61
+ return {
62
+ ownUpdate: false,
63
+ };
64
+ }
65
+ if (nextProps.isShowLunar !== prevState.showLunar) {
66
+ return {showLunar: nextProps.isShowLunar};
67
+ }
68
+ return null;
69
+ }
70
+
71
+ setDateRange = (
72
+ dateRange: {startDate: any; endDate: any},
73
+ isScrollToStartDate: any,
74
+ ) => {
75
+ if (dateRange && dateRange.startDate && dateRange.endDate) {
76
+ this.setState(
77
+ {
78
+ startDate: dateRange.startDate,
79
+ endDate: dateRange.endDate,
80
+ },
81
+ () => {
82
+ const dateScroll = isScrollToStartDate
83
+ ? dateRange.startDate
84
+ : dateRange.endDate;
85
+
86
+ this.monthListRef?.scrollToMonth(dateScroll);
87
+ },
88
+ );
89
+ }
90
+ };
91
+
92
+ ownSetState(state: any) {
93
+ this.setState({
94
+ ...state,
95
+ ownUpdate: true,
96
+ });
97
+ }
98
+
99
+ getDateRange = () => {
100
+ const {maxDate, minDate} = this.props;
101
+ let max = Moment(maxDate);
102
+ let min = Moment(minDate);
103
+ const maxValid = max.isValid();
104
+ const minValid = min.isValid();
105
+ if (!maxValid && !minValid) {
106
+ max = Moment().add(12, 'months');
107
+ min = Moment();
108
+ }
109
+ if (!maxValid && minValid) {
110
+ max = min.add(12, 'months');
111
+ }
112
+ if (maxValid && !minValid) {
113
+ min = max.subtract(12, 'months');
114
+ }
115
+ if (min.isSameOrAfter(max)) return {};
116
+ this.minDate = min;
117
+ this.maxDate = max;
118
+ };
119
+
120
+ onChoose = (day: Date) => {
121
+ const {startDate, tabSelected} = this.state;
122
+ const {isDoubleDateMode, onDateChange} = this.props;
123
+ if (isDoubleDateMode) {
124
+ if (tabSelected === 1) {
125
+ if (startDate && day >= startDate) {
126
+ this.ownSetState({
127
+ endDate: day,
128
+ });
129
+ } else if (startDate && day < startDate) {
130
+ this.ownSetState({
131
+ startDate: day,
132
+ endDate: null,
133
+ });
134
+ }
135
+ } else {
136
+ this.ownSetState({
137
+ startDate: day,
138
+ });
139
+ }
140
+ } else {
141
+ this.ownSetState({
142
+ startDate: day,
143
+ endDate: null,
144
+ });
145
+ }
146
+ if (onDateChange) {
147
+ onDateChange(day);
148
+ }
149
+ };
150
+
151
+ executeProcessAfterScrollCalendar = (date: Moment.Moment, key: any) => {
152
+ const holidays: {lunar: number; mixedLabel: string}[] = Object.values(
153
+ Util.getHolidaysInMonth(Moment(date)),
154
+ );
155
+ const {showLunar} = this.state;
156
+ if (this.headerControlRef) {
157
+ this.headerControlRef.current?.onUpdateInfo({date});
158
+ this.header = date.clone().startOf('month');
159
+ }
160
+ let data = [];
161
+ if (!showLunar) {
162
+ data = holidays.filter(item => !item.lunar || item.mixedLabel);
163
+ } else {
164
+ data = holidays;
165
+ }
166
+ this.ownSetState({
167
+ holidays,
168
+ temp: data,
169
+ headerKey: key,
170
+ });
171
+ };
172
+
173
+ onScrollCalendar = (data: {key: string; date: Moment.Moment}) => {
174
+ const {headerKey} = this.state;
175
+ if (data) {
176
+ if (data.key !== headerKey) {
177
+ this.executeProcessAfterScrollCalendar(data.date, data.key);
178
+ }
179
+ }
180
+ };
181
+
182
+ setDoubleDateAndTabIndex = (
183
+ firstDate: Moment.Moment | undefined,
184
+ secondDate: Moment.Moment | undefined,
185
+ tabSelected: any,
186
+ ) => {
187
+ this.ownSetState({
188
+ startDate: firstDate ? Moment(firstDate) : null,
189
+ endDate: secondDate ? Moment(secondDate) : null,
190
+ tabSelected,
191
+ });
192
+ };
193
+
194
+ toggleLunarDate = () => {
195
+ const {showLunar, holidays} = this.state;
196
+ const {onCallbackCalendar} = this.props;
197
+ let data: {lunar: number; mixedLabel: string}[] = [];
198
+ const nextStateShowLunar = !showLunar;
199
+ if (!nextStateShowLunar) {
200
+ data = holidays?.filter(item => !item.lunar || item.mixedLabel);
201
+ } else {
202
+ data = holidays;
203
+ }
204
+ if (onCallbackCalendar) {
205
+ onCallbackCalendar('lunar', nextStateShowLunar);
206
+ }
207
+
208
+ this.ownSetState({
209
+ showLunar: !showLunar,
210
+ temp: data,
211
+ ownUpdate: true,
212
+ });
213
+ };
214
+
215
+ onPressBackArrow = () => {
216
+ const previousDate = Moment(this.header)
217
+ .startOf('month')
218
+ .subtract(1, 'months');
219
+ if (
220
+ this.headerControlRef &&
221
+ previousDate.isSameOrAfter(this.minDate, 'month')
222
+ ) {
223
+ this.header = previousDate;
224
+ this.headerControlRef.current?.onUpdateInfo({date: previousDate});
225
+ this.monthListRef?.scrollToMonth(previousDate);
226
+ }
227
+ };
228
+
229
+ onPressNextArrow = () => {
230
+ const nextDate = Moment(this.header).startOf('month').add(1, 'months');
231
+ if (
232
+ this.headerControlRef &&
233
+ nextDate.isSameOrBefore(this.maxDate, 'month')
234
+ ) {
235
+ this.header = nextDate;
236
+ this.headerControlRef.current?.onUpdateInfo({date: nextDate});
237
+ this.monthListRef?.scrollToMonth(nextDate);
238
+ }
239
+ };
240
+
241
+ render() {
242
+ const {
243
+ startDate,
244
+ endDate,
245
+ showLunar = false,
246
+ tabSelected,
247
+ holidays,
248
+ temp,
249
+ } = this.state;
250
+ const {isDoubleDateMode, priceList, isHideHoliday, isOffLunar} = this.props;
251
+ let priceListFormat = priceList?.outbound;
252
+ if (isDoubleDateMode) {
253
+ priceListFormat =
254
+ tabSelected === 0 ? priceList?.outbound : priceList?.inbound;
255
+ }
256
+
257
+ const {theme, translate} = this.context;
258
+
259
+ return (
260
+ <ContainerContext.Consumer>
261
+ {size => (
262
+ <View style={styles.container}>
263
+ <View>
264
+ <HeaderControl
265
+ ref={this.headerControlRef}
266
+ selectedDate={this.selectedDate}
267
+ onPressBackArrow={this.onPressBackArrow}
268
+ onPressNextArrow={this.onPressNextArrow}
269
+ />
270
+ <View style={styles.blueSeperator} />
271
+ <View>
272
+ <View style={styles.viewDay}>
273
+ {[1, 2, 3, 4, 5, 6, 7].map(item => (
274
+ <Text
275
+ color={
276
+ item === 6 || item === 7
277
+ ? Colors.red_03
278
+ : Colors.black_17
279
+ }
280
+ typography={'label_s'}
281
+ style={[
282
+ styles.textDay,
283
+ {
284
+ width: (size.width - Spacing.M * 2) / 7,
285
+ },
286
+ ]}
287
+ key={item}>
288
+ {Util.mapWeeKDate(item)}
289
+ </Text>
290
+ ))}
291
+ </View>
292
+ <MonthList
293
+ ref={ref => (this.monthListRef = ref)}
294
+ today={this.today}
295
+ minDate={this.minDate}
296
+ maxDate={this.maxDate}
297
+ startDate={startDate}
298
+ endDate={endDate}
299
+ onChoose={this.onChoose}
300
+ onScrollCalendar={this.onScrollCalendar}
301
+ isShowLunar={!isOffLunar && showLunar}
302
+ isDoubleDateMode={isDoubleDateMode}
303
+ tabSelected={tabSelected}
304
+ lunarConverter={this.converter}
305
+ holidays={holidays}
306
+ selectedDate={this.selectedDate}
307
+ priceList={priceListFormat}
308
+ />
309
+ </View>
310
+ </View>
311
+ {!isOffLunar && (
312
+ <View
313
+ style={[
314
+ styles.viewLunar,
315
+ {borderColor: theme.colors.border.default},
316
+ ]}>
317
+ <CheckBox
318
+ onChange={this.toggleLunarDate}
319
+ value={showLunar}
320
+ label={translate('showLunar')}
321
+ />
322
+ </View>
323
+ )}
324
+
325
+ {!isHideHoliday && (
326
+ <ScrollView
327
+ contentContainerStyle={styles.contentScroll}
328
+ showsVerticalScrollIndicator={false}
329
+ nestedScrollEnabled>
330
+ {temp &&
331
+ temp.length > 0 &&
332
+ temp.map(
333
+ (
334
+ item: {
335
+ mixedLabel: any;
336
+ label: any;
337
+ day: number;
338
+ month: number;
339
+ highlight: string;
340
+ },
341
+ idx: {toString: () => React.Key | null | undefined},
342
+ ) => {
343
+ const labelHoliday = showLunar
344
+ ? item.mixedLabel || item.label || ''
345
+ : item.label || '';
346
+ const labelDate = `${
347
+ item.day > 9 ? item.day : `0${item.day}`
348
+ }/${item.month > 9 ? item.month : `0${item.month}`}`;
349
+ const labelHighlight = showLunar
350
+ ? item.highlight || ''
351
+ : '';
352
+ return (
353
+ <View style={styles.row} key={idx.toString()}>
354
+ <Text
355
+ color={theme.colors.error.primary}
356
+ typography={'description_s'}
357
+ style={styles.txtMonthLunar}>
358
+ {labelDate}
359
+ </Text>
360
+ <Text
361
+ typography={'description_s'}
362
+ style={styles.subTextLunar}>
363
+ {`${translate(labelHoliday)}`}
364
+ </Text>
365
+ <Text typography={'description_s'}>
366
+ {labelHighlight}
367
+ </Text>
368
+ </View>
369
+ );
370
+ },
371
+ )}
372
+ </ScrollView>
373
+ )}
374
+ </View>
375
+ )}
376
+ </ContainerContext.Consumer>
377
+ );
378
+ }
379
+ }
package/Day.tsx ADDED
@@ -0,0 +1,241 @@
1
+ import React, {Component} from 'react';
2
+
3
+ import {TouchableOpacity, View} from 'react-native';
4
+ import {
5
+ ApplicationContext,
6
+ Colors,
7
+ scaleSize,
8
+ Spacing,
9
+ Text,
10
+ } from '@momo-kits/foundation';
11
+ import {DayProps} from './types';
12
+ import {ContainerContext} from './index';
13
+ import styles from './styles';
14
+
15
+ class Day extends Component<DayProps> {
16
+ isFocus?: boolean;
17
+ isValid?: boolean;
18
+ isMid?: boolean;
19
+ isStart?: boolean;
20
+ isStartPart?: boolean;
21
+ isEnd?: boolean;
22
+ isWeekEnd?: boolean;
23
+ showLunar?: boolean;
24
+ lunarDate?: {lunarDay: number; lunarMonth: number};
25
+ isDoubleDateMode?: boolean;
26
+ isLunarHoliday?: boolean;
27
+ isLunarDayStart?: boolean;
28
+ isSolarHoliday?: boolean;
29
+ isInScope?: boolean;
30
+ diffPrice: any;
31
+
32
+ static contextType = ApplicationContext;
33
+ constructor(props: DayProps) {
34
+ super(props);
35
+ this.statusCheck();
36
+ }
37
+
38
+ chooseDay = () => {
39
+ const {onChoose, date} = this.props;
40
+ onChoose && onChoose(date);
41
+ };
42
+
43
+ findHoliday = (
44
+ date: {date: () => any; month: () => number},
45
+ holidays: any[],
46
+ ) => {
47
+ if (date && holidays && holidays.length > 0) {
48
+ const day = date.date();
49
+ const month = date.month() + 1;
50
+ return holidays.find(item => item.day === day && item.month === month);
51
+ }
52
+ return null;
53
+ };
54
+
55
+ checkHoliday = (
56
+ date: {date: () => any; month: () => number},
57
+ holidays: any[],
58
+ ) => {
59
+ const holiday = this.findHoliday(date, holidays);
60
+ return {
61
+ solarHoliday: !!(holiday && !holiday.lunar),
62
+ lunarHoliday: !!(holiday && holiday.lunar),
63
+ };
64
+ };
65
+
66
+ statusCheck = (props?: DayProps) => {
67
+ const {
68
+ startDate,
69
+ endDate,
70
+ date,
71
+ minDate,
72
+ maxDate,
73
+ empty,
74
+ index,
75
+ isShowLunar,
76
+ isDoubleDateMode,
77
+ lunarDate,
78
+ isSolarHoliday,
79
+ isLunarHoliday,
80
+ tabSelected,
81
+ } = props || this.props;
82
+ this.isValid =
83
+ date &&
84
+ (date >= minDate || date.isSame(minDate, 'day')) &&
85
+ (date <= maxDate || date.isSame(maxDate, 'day'));
86
+ this.isMid =
87
+ (isDoubleDateMode && date > startDate && date < endDate) ||
88
+ (!date && empty >= startDate && empty <= endDate);
89
+ this.isStart = date && date.isSame(startDate, 'd');
90
+ this.isStartPart = !!(this.isStart && endDate);
91
+ this.isEnd = isDoubleDateMode && date && date.isSame(endDate, 'day');
92
+ this.isFocus = this.isMid || this.isStart || this.isEnd;
93
+ this.isWeekEnd = index === 6 || index === 5;
94
+ this.showLunar = isShowLunar;
95
+ this.lunarDate = lunarDate;
96
+ this.isDoubleDateMode = isDoubleDateMode;
97
+ this.isLunarHoliday = isLunarHoliday;
98
+ this.isLunarDayStart = this.lunarDate && this.lunarDate.lunarDay === 1;
99
+ this.isSolarHoliday = isSolarHoliday;
100
+ this.isInScope = isDoubleDateMode
101
+ ? tabSelected === 0 ||
102
+ (tabSelected === 1 &&
103
+ startDate &&
104
+ date &&
105
+ date.isSameOrAfter(startDate, 'day'))
106
+ : true;
107
+
108
+ return this.isFocus || this.diffPrice;
109
+ };
110
+
111
+ shouldComponentUpdate(nextProps: DayProps) {
112
+ const {
113
+ isDoubleDateMode,
114
+ isShowLunar,
115
+ tabSelected,
116
+ isSolarHoliday,
117
+ isLunarHoliday,
118
+ price,
119
+ isBestPrice,
120
+ startDate,
121
+ endDate,
122
+ } = this.props;
123
+ const prevStatus = this.isFocus;
124
+ const selectionModeChange = isDoubleDateMode !== nextProps.isDoubleDateMode;
125
+ const lunarChange = isShowLunar !== nextProps.isShowLunar;
126
+ const nextStatus = this.statusCheck(nextProps);
127
+ const tabChange = tabSelected !== nextProps.tabSelected;
128
+ const solarHoliday = isSolarHoliday !== nextProps.isSolarHoliday;
129
+ const lunarHoliday = isLunarHoliday !== nextProps.isLunarHoliday;
130
+ const diffPrice =
131
+ price !== nextProps.price && isBestPrice !== nextProps.isBestPrice;
132
+ const startDateChange =
133
+ startDate && !startDate?.isSame?.(nextProps.startDate, 'day');
134
+ const endDateChange =
135
+ endDate && !endDate?.isSame?.(nextProps.endDate, 'day');
136
+ return (
137
+ prevStatus !== nextStatus ||
138
+ selectionModeChange ||
139
+ lunarChange ||
140
+ tabChange ||
141
+ solarHoliday ||
142
+ lunarHoliday ||
143
+ diffPrice ||
144
+ startDateChange ||
145
+ endDateChange
146
+ );
147
+ }
148
+
149
+ render() {
150
+ const {theme} = this.context;
151
+ const {date, empty, price, isBestPrice} = this.props;
152
+
153
+ const text = date ? date.date() : '';
154
+ let textColor = theme.colors.text.default;
155
+ let lunarTextColor = theme.colors.text.hint;
156
+ let priceColor = theme.colors.text.default;
157
+
158
+ if (this.isWeekEnd) {
159
+ textColor = theme.colors.error.primary;
160
+ priceColor = theme.colors.error.primary;
161
+ }
162
+
163
+ if (isBestPrice) {
164
+ priceColor = theme.colors.error.primary;
165
+ }
166
+
167
+ if (this.isStart || this.isEnd) {
168
+ textColor = Colors.black_01;
169
+ priceColor = Colors.black_01;
170
+ lunarTextColor = Colors.black_01;
171
+ }
172
+
173
+ if (!this.isValid || !this.isInScope) {
174
+ textColor = theme.colors.text.disable;
175
+ priceColor = theme.colors.text.disable;
176
+ lunarTextColor = theme.colors.text.disable;
177
+ }
178
+
179
+ return (
180
+ <ContainerContext.Consumer>
181
+ {size => {
182
+ const itemWidth = (size.width - Spacing.M) / 7;
183
+ return (
184
+ <View style={[styles.dayContainer, {width: itemWidth}]}>
185
+ <View
186
+ style={[
187
+ this.isMid &&
188
+ !empty && {
189
+ backgroundColor: theme.colors.background.tonal,
190
+ },
191
+ this.isStartPart && styles.dayStartContainer,
192
+ this.isEnd && styles.dayEndContainer,
193
+ ]}>
194
+ <TouchableOpacity
195
+ style={[
196
+ styles.day,
197
+ {
198
+ width: itemWidth,
199
+ paddingVertical: !!price ? Spacing.XS : Spacing.S,
200
+ },
201
+ (this.isStart || this.isEnd) && {
202
+ backgroundColor: theme.colors.primary,
203
+ },
204
+ !!price && {height: scaleSize(48)},
205
+ {height: size.height},
206
+ ]}
207
+ disabled={!(this.isValid && this.isInScope)}
208
+ onPress={this.chooseDay}>
209
+ {this.lunarDate && this.showLunar && !!text && (
210
+ <Text
211
+ style={{
212
+ position: 'absolute',
213
+ top: Spacing.XS,
214
+ right: Spacing.XS,
215
+ }}
216
+ typography={'label_xxs'}
217
+ color={lunarTextColor}>
218
+ {this.lunarDate.lunarDay === 1
219
+ ? `${this.lunarDate.lunarDay}/${this.lunarDate.lunarMonth}`
220
+ : this.lunarDate.lunarDay}
221
+ </Text>
222
+ )}
223
+ <Text typography={'header_s'} color={textColor}>
224
+ {text}
225
+ </Text>
226
+ {!!price && (
227
+ <Text typography={'description_xs'} color={priceColor}>
228
+ {price}
229
+ </Text>
230
+ )}
231
+ </TouchableOpacity>
232
+ </View>
233
+ </View>
234
+ );
235
+ }}
236
+ </ContainerContext.Consumer>
237
+ );
238
+ }
239
+ }
240
+
241
+ export default Day;
@@ -0,0 +1,53 @@
1
+ import React, {
2
+ forwardRef,
3
+ ForwardRefRenderFunction,
4
+ useImperativeHandle,
5
+ useState,
6
+ } from 'react';
7
+ import {TouchableOpacity, View} from 'react-native';
8
+ import moment from 'moment';
9
+ import Moment from 'moment';
10
+ import {Icon, Text} from '@momo-kits/foundation';
11
+
12
+ import Util from './Util';
13
+ import {HeaderControlProps, HeaderControlRef} from './types';
14
+ import styles from './styles';
15
+
16
+ const HeaderControl: ForwardRefRenderFunction<
17
+ HeaderControlRef,
18
+ HeaderControlProps
19
+ > = ({onPressBackArrow, onPressNextArrow, selectedDate}, ref) => {
20
+ const [info, setInfo] = useState<{date: Moment.Moment}>({
21
+ date: moment(selectedDate || new Date()),
22
+ });
23
+
24
+ useImperativeHandle(ref, () => ({
25
+ onUpdateInfo,
26
+ }));
27
+
28
+ const onUpdateInfo = (date: React.SetStateAction<{date: moment.Moment}>) => {
29
+ setInfo(date);
30
+ };
31
+
32
+ if (info && info.date) {
33
+ const headerFormat = `${Util.mapMonth(
34
+ info.date.month() + 1,
35
+ )}/${info.date.year()}`;
36
+ return (
37
+ <View style={styles.headerControlContainer}>
38
+ <TouchableOpacity onPress={onPressBackArrow}>
39
+ <Icon source="24_arrow_chevron_left_small" />
40
+ </TouchableOpacity>
41
+ <Text typography={'header_s'} style={styles.txtHeader}>
42
+ {headerFormat}
43
+ </Text>
44
+ <TouchableOpacity onPress={onPressNextArrow}>
45
+ <Icon source="24_arrow_chevron_right_small" />
46
+ </TouchableOpacity>
47
+ </View>
48
+ );
49
+ }
50
+ return <View />;
51
+ };
52
+
53
+ export default forwardRef(HeaderControl);