@momo-kits/calendar 0.79.6 → 0.80.2

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,386 @@
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 {
251
+ isDoubleDateMode,
252
+ priceList,
253
+ disabledDays,
254
+ isHideHoliday,
255
+ isOffLunar,
256
+ } = this.props;
257
+ let priceListFormat = priceList?.outbound;
258
+ if (isDoubleDateMode) {
259
+ priceListFormat =
260
+ tabSelected === 0 ? priceList?.outbound : priceList?.inbound;
261
+ }
262
+
263
+ const {theme, translate} = this.context;
264
+
265
+ return (
266
+ <ContainerContext.Consumer>
267
+ {size => (
268
+ <View style={styles.container}>
269
+ <View>
270
+ <HeaderControl
271
+ ref={this.headerControlRef}
272
+ selectedDate={this.selectedDate}
273
+ onPressBackArrow={this.onPressBackArrow}
274
+ onPressNextArrow={this.onPressNextArrow}
275
+ />
276
+ <View style={styles.blueSeperator} />
277
+ <View>
278
+ <View style={styles.viewDay}>
279
+ {[1, 2, 3, 4, 5, 6, 7].map(item => (
280
+ <Text
281
+ color={
282
+ item === 6 || item === 7
283
+ ? Colors.red_03
284
+ : Colors.black_17
285
+ }
286
+ typography={'label_s'}
287
+ style={[
288
+ styles.textDay,
289
+ {
290
+ width: (size.width - Spacing.M * 2) / 7,
291
+ },
292
+ ]}
293
+ key={item}>
294
+ {Util.mapWeeKDate(item)}
295
+ </Text>
296
+ ))}
297
+ </View>
298
+ <MonthList
299
+ ref={ref => (this.monthListRef = ref)}
300
+ today={this.today}
301
+ minDate={this.minDate}
302
+ maxDate={this.maxDate}
303
+ startDate={startDate}
304
+ endDate={endDate}
305
+ onChoose={this.onChoose}
306
+ onScrollCalendar={this.onScrollCalendar}
307
+ isShowLunar={!isOffLunar && showLunar}
308
+ isDoubleDateMode={isDoubleDateMode}
309
+ tabSelected={tabSelected}
310
+ lunarConverter={this.converter}
311
+ holidays={holidays}
312
+ selectedDate={this.selectedDate}
313
+ priceList={priceListFormat}
314
+ disabledDays={disabledDays}
315
+ />
316
+ </View>
317
+ </View>
318
+ {!isOffLunar && (
319
+ <View
320
+ style={[
321
+ styles.viewLunar,
322
+ {borderColor: theme.colors.border.default},
323
+ ]}>
324
+ <CheckBox
325
+ onChange={this.toggleLunarDate}
326
+ value={showLunar}
327
+ label={translate('showLunar')}
328
+ />
329
+ </View>
330
+ )}
331
+
332
+ {!isHideHoliday && (
333
+ <ScrollView
334
+ contentContainerStyle={styles.contentScroll}
335
+ showsVerticalScrollIndicator={false}
336
+ nestedScrollEnabled>
337
+ {temp &&
338
+ temp.length > 0 &&
339
+ temp.map(
340
+ (
341
+ item: {
342
+ mixedLabel: any;
343
+ label: any;
344
+ day: number;
345
+ month: number;
346
+ highlight: string;
347
+ },
348
+ idx: {toString: () => React.Key | null | undefined},
349
+ ) => {
350
+ const labelHoliday = showLunar
351
+ ? item.mixedLabel || item.label || ''
352
+ : item.label || '';
353
+ const labelDate = `${
354
+ item.day > 9 ? item.day : `0${item.day}`
355
+ }/${item.month > 9 ? item.month : `0${item.month}`}`;
356
+ const labelHighlight = showLunar
357
+ ? item.highlight || ''
358
+ : '';
359
+ return (
360
+ <View style={styles.row} key={idx.toString()}>
361
+ <Text
362
+ color={theme.colors.error.primary}
363
+ typography={'description_s'}
364
+ style={styles.txtMonthLunar}>
365
+ {labelDate}
366
+ </Text>
367
+ <Text
368
+ typography={'description_s'}
369
+ style={styles.subTextLunar}>
370
+ {`${translate(labelHoliday)}`}
371
+ </Text>
372
+ <Text typography={'description_s'}>
373
+ {labelHighlight}
374
+ </Text>
375
+ </View>
376
+ );
377
+ },
378
+ )}
379
+ </ScrollView>
380
+ )}
381
+ </View>
382
+ )}
383
+ </ContainerContext.Consumer>
384
+ );
385
+ }
386
+ }
package/Day.tsx ADDED
@@ -0,0 +1,242 @@
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
+ isDisabled = false,
82
+ } = props || this.props;
83
+ this.isValid =
84
+ !isDisabled &&
85
+ date &&
86
+ (date >= minDate || date.isSame(minDate, 'day')) &&
87
+ (date <= maxDate || date.isSame(maxDate, 'day'));
88
+ this.isMid =
89
+ (isDoubleDateMode && date > startDate && date < endDate) ||
90
+ (!date && empty >= startDate && empty <= endDate);
91
+ this.isStart = date && date.isSame(startDate, 'd');
92
+ this.isStartPart = !!(this.isStart && endDate);
93
+ this.isEnd = isDoubleDateMode && date && date.isSame(endDate, 'day');
94
+ this.isFocus = this.isMid || this.isStart || this.isEnd;
95
+ this.isWeekEnd = index === 6 || index === 5;
96
+ this.showLunar = isShowLunar;
97
+ this.lunarDate = lunarDate;
98
+ this.isDoubleDateMode = isDoubleDateMode;
99
+ this.isLunarHoliday = isLunarHoliday;
100
+ this.isLunarDayStart = this.lunarDate && this.lunarDate.lunarDay === 1;
101
+ this.isSolarHoliday = isSolarHoliday;
102
+ this.isInScope = isDoubleDateMode
103
+ ? tabSelected === 0 ||
104
+ (tabSelected === 1 &&
105
+ startDate &&
106
+ date &&
107
+ date.isSameOrAfter(startDate, 'day'))
108
+ : true;
109
+
110
+ return this.isFocus || this.diffPrice;
111
+ };
112
+
113
+ shouldComponentUpdate(nextProps: DayProps) {
114
+ const {
115
+ isDoubleDateMode,
116
+ isShowLunar,
117
+ tabSelected,
118
+ isSolarHoliday,
119
+ isLunarHoliday,
120
+ price,
121
+ isBestPrice,
122
+ startDate,
123
+ endDate,
124
+ } = this.props;
125
+ const prevStatus = this.isFocus;
126
+ const selectionModeChange = isDoubleDateMode !== nextProps.isDoubleDateMode;
127
+ const lunarChange = isShowLunar !== nextProps.isShowLunar;
128
+ const nextStatus = this.statusCheck(nextProps);
129
+ const tabChange = tabSelected !== nextProps.tabSelected;
130
+ const solarHoliday = isSolarHoliday !== nextProps.isSolarHoliday;
131
+ const lunarHoliday = isLunarHoliday !== nextProps.isLunarHoliday;
132
+ const diffPrice =
133
+ price !== nextProps.price && isBestPrice !== nextProps.isBestPrice;
134
+ const startDateChange =
135
+ startDate && !startDate?.isSame?.(nextProps.startDate, 'day');
136
+ const endDateChange =
137
+ endDate && !endDate?.isSame?.(nextProps.endDate, 'day');
138
+ return (
139
+ prevStatus !== nextStatus ||
140
+ selectionModeChange ||
141
+ lunarChange ||
142
+ tabChange ||
143
+ solarHoliday ||
144
+ lunarHoliday ||
145
+ diffPrice ||
146
+ startDateChange ||
147
+ endDateChange
148
+ );
149
+ }
150
+
151
+ render() {
152
+ const {theme} = this.context;
153
+ const {date, empty, price, isBestPrice} = this.props;
154
+ const text = date ? date.date() : '';
155
+ let textColor = theme.colors.text.default;
156
+ let lunarTextColor = theme.colors.text.hint;
157
+ let priceColor = theme.colors.text.default;
158
+
159
+ if (this.isWeekEnd || this.isSolarHoliday || this.isLunarHoliday) {
160
+ textColor = theme.colors.error.primary;
161
+ priceColor = theme.colors.error.primary;
162
+ }
163
+
164
+ if (isBestPrice) {
165
+ priceColor = theme.colors.error.primary;
166
+ }
167
+
168
+ if (this.isStart || this.isEnd) {
169
+ textColor = Colors.black_01;
170
+ priceColor = Colors.black_01;
171
+ lunarTextColor = Colors.black_01;
172
+ }
173
+
174
+ if (!this.isValid || !this.isInScope) {
175
+ textColor = theme.colors.text.disable;
176
+ priceColor = theme.colors.text.disable;
177
+ lunarTextColor = theme.colors.text.disable;
178
+ }
179
+
180
+ return (
181
+ <ContainerContext.Consumer>
182
+ {size => {
183
+ const itemWidth = (size.width - Spacing.M) / 7;
184
+ return (
185
+ <View style={[styles.dayContainer, {width: itemWidth}]}>
186
+ <View
187
+ style={[
188
+ this.isMid &&
189
+ !empty && {
190
+ backgroundColor: theme.colors.background.tonal,
191
+ },
192
+ this.isStartPart && styles.dayStartContainer,
193
+ this.isEnd && styles.dayEndContainer,
194
+ ]}>
195
+ <TouchableOpacity
196
+ style={[
197
+ styles.day,
198
+ {
199
+ width: itemWidth,
200
+ paddingVertical: !!price ? Spacing.XS : Spacing.S,
201
+ },
202
+ (this.isStart || this.isEnd) && {
203
+ backgroundColor: theme.colors.primary,
204
+ },
205
+ !!price && {height: scaleSize(48)},
206
+ {height: size.height},
207
+ ]}
208
+ disabled={!(this.isValid && this.isInScope)}
209
+ onPress={this.chooseDay}>
210
+ {this.lunarDate && this.showLunar && !!text && (
211
+ <Text
212
+ style={{
213
+ position: 'absolute',
214
+ top: Spacing.XS,
215
+ right: Spacing.XS,
216
+ }}
217
+ typography={'label_xxs'}
218
+ color={lunarTextColor}>
219
+ {this.lunarDate.lunarDay === 1
220
+ ? `${this.lunarDate.lunarDay}/${this.lunarDate.lunarMonth}`
221
+ : this.lunarDate.lunarDay}
222
+ </Text>
223
+ )}
224
+ <Text typography={'header_s'} color={textColor}>
225
+ {text}
226
+ </Text>
227
+ {!!price && (
228
+ <Text typography={'description_xs'} color={priceColor}>
229
+ {price}
230
+ </Text>
231
+ )}
232
+ </TouchableOpacity>
233
+ </View>
234
+ </View>
235
+ );
236
+ }}
237
+ </ContainerContext.Consumer>
238
+ );
239
+ }
240
+ }
241
+
242
+ 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);