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