@momo-kits/calendar 0.121.0-rc.3 → 0.121.0-rc.5

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.
package/CalendarPro.tsx CHANGED
@@ -1,7 +1,14 @@
1
- import React, {Component, createRef} from 'react';
2
-
1
+ import React, {
2
+ useContext,
3
+ useRef,
4
+ useState,
5
+ useEffect,
6
+ useMemo,
7
+ forwardRef,
8
+ useImperativeHandle,
9
+ } from 'react';
3
10
  import {ScrollView, View} from 'react-native';
4
- import Moment from 'moment';
11
+ import moment, {Moment} from 'moment';
5
12
  import {
6
13
  ApplicationContext,
7
14
  CheckBox,
@@ -15,377 +22,281 @@ import LunarDateConverter from './LunarDateConverter';
15
22
  import Util from './Util';
16
23
  import {
17
24
  CalendarProProps,
18
- CalendarProState,
25
+ CalendarProRef,
19
26
  HeaderControlRef,
27
+ MonthListRef,
20
28
  Holidays,
21
29
  } from './types';
22
30
  import {ContainerContext} from './index';
23
31
  import styles from './styles';
24
32
 
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;
33
+ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
34
+ (
35
+ {
36
+ startDate: propStartDate,
37
+ endDate: propEndDate,
38
+ minDate: propMinDate,
39
+ maxDate: propMaxDate,
40
+ isShowLunar: propIsShowLunar,
41
+ selectedDate: propSelectedDate,
42
+ isDoubleDateMode,
43
+ onDateChange,
44
+ priceList,
45
+ isHideHoliday,
46
+ isOffLunar,
47
+ onCallbackCalendar,
48
+ disabledDays,
49
+ },
50
+ ref
51
+ ) => {
52
+ const {theme, translate} = useContext(ApplicationContext);
53
+ const size = useContext(ContainerContext);
39
54
 
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
- }
55
+ const today = useMemo(() => moment(), []);
56
+ const headerRef = useRef<Moment>(today.clone());
57
+ const headerControlRef = useRef<HeaderControlRef>(null);
58
+ const monthListRef = useRef<MonthListRef>(null);
59
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
- }
60
+ // Compute min/max date range
61
+ const [minDate, maxDate] = useMemo(() => {
62
+ let max = moment(propMaxDate);
63
+ let min = moment(propMinDate);
64
+ const maxValid = max.isValid();
65
+ const minValid = min.isValid();
66
+ if (!maxValid && !minValid) {
67
+ max = moment().add(12, 'months');
68
+ min = moment();
69
+ } else if (!maxValid && minValid) {
70
+ max = min.clone().add(12, 'months');
71
+ } else if (maxValid && !minValid) {
72
+ min = max.clone().subtract(12, 'months');
73
+ }
74
+ return [min, max];
75
+ }, [propMinDate, propMaxDate, isDoubleDateMode]);
74
76
 
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;
77
+ // State
78
+ const [startDate, setStartDate] = useState<Moment>(propStartDate);
79
+ const [endDate, setEndDate] = useState<Moment | null>(propEndDate || null);
80
+ const [showLunar, setShowLunar] = useState<boolean>(!!propIsShowLunar);
81
+ const [tabSelected, setTabSelected] = useState<number>(0);
82
+ const [holidays, setHolidays] = useState<Holidays[]>([]);
83
+ const [temp, setTemp] = useState<any[]>([]);
84
+ const [headerKey, setHeaderKey] = useState<string>('');
85
+ const [ownUpdate, setOwnUpdate] = useState<boolean>(false);
86
+
87
+ const converter: any = useMemo(() => LunarDateConverter(), []);
89
88
 
90
- this.monthListRef?.scrollToMonth(dateScroll);
89
+ useImperativeHandle(ref, () => ({
90
+ setDoubleDateAndTabIndex: (
91
+ firstDate?: Moment | null,
92
+ secondDate?: Moment | null,
93
+ tabIdx?: number
94
+ ) => {
95
+ if (typeof tabIdx === 'number') setTabSelected(tabIdx);
96
+ if (firstDate) setStartDate(firstDate);
97
+ if (secondDate !== undefined) setEndDate(secondDate);
98
+ },
99
+ setDateRange: (
100
+ dateRange: {startDate: any; endDate: any},
101
+ ) => {
102
+ if (monthListRef.current && dateRange.startDate) {
103
+ monthListRef.current.scrollToMonth(moment(dateRange.startDate));
91
104
  }
92
- );
93
- }
94
- };
105
+ },
106
+ }));
95
107
 
96
- ownSetState(state: any) {
97
- this.setState({
98
- ...state,
99
- ownUpdate: true,
100
- });
101
- }
108
+ // Sync showLunar prop
109
+ useEffect(() => {
110
+ if (ownUpdate) {
111
+ setOwnUpdate(false);
112
+ } else if (propIsShowLunar !== showLunar) {
113
+ setShowLunar(!!propIsShowLunar);
114
+ }
115
+ }, [propIsShowLunar]);
102
116
 
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.isSame(max) && this.props.isDoubleDateMode) return {};
120
- if (min.isAfter(max)) return {};
121
- this.minDate = min;
122
- this.maxDate = max;
123
- };
117
+ // Scroll to initial selected date on mount or when it changes
118
+ useEffect(() => {
119
+ const timer = setTimeout(() => {
120
+ monthListRef.current?.scrollToMonth(propSelectedDate);
121
+ }, 500);
122
+ return () => clearTimeout(timer);
123
+ }, [propSelectedDate]);
124
124
 
125
- onChoose = (day: Moment.Moment) => {
126
- const {startDate, tabSelected} = this.state;
127
- const {isDoubleDateMode, onDateChange} = this.props;
128
- if (isDoubleDateMode) {
129
- if (tabSelected === 1) {
130
- if (startDate && day >= Moment(startDate)) {
131
- this.ownSetState({
132
- endDate: day,
133
- });
134
- } else if (startDate && day < Moment(startDate)) {
135
- this.ownSetState({
136
- startDate: day,
137
- endDate: null,
138
- });
125
+ // Handlers
126
+ const onChooseDay = (day: Moment) => {
127
+ if (isDoubleDateMode) {
128
+ if (tabSelected === 1) {
129
+ if (startDate && day >= startDate) {
130
+ setEndDate(day);
131
+ } else if (startDate && day < startDate) {
132
+ setStartDate(day);
133
+ setEndDate(null);
134
+ }
135
+ } else {
136
+ setStartDate(day);
139
137
  }
140
138
  } else {
141
- this.ownSetState({
142
- startDate: day,
143
- });
139
+ setStartDate(day);
140
+ setEndDate(null);
144
141
  }
145
- } else {
146
- this.ownSetState({
147
- startDate: day,
148
- endDate: null,
149
- });
150
- }
151
- if (onDateChange) {
152
- onDateChange(day);
153
- }
154
- };
142
+ onDateChange?.(day);
143
+ };
155
144
 
156
- executeProcessAfterScrollCalendar = (date: Moment.Moment, key: any) => {
157
- const holidays: any[] = Object.values(
158
- Util.getHolidaysInMonth(Moment(date))
159
- );
160
- const {showLunar} = this.state;
161
- if (this.headerControlRef) {
162
- this.headerControlRef.current?.onUpdateInfo({date});
163
- this.header = date.clone().startOf('month');
164
- }
165
- let data = [];
166
- if (!showLunar) {
167
- data = holidays.filter(item => !item.lunar || item.mixedLabel);
168
- } else {
169
- data = holidays;
170
- }
171
- this.ownSetState({
172
- holidays,
173
- temp: data,
174
- headerKey: key,
175
- });
176
- };
145
+ const executeProcessAfterScrollCalendar = (date: Moment, key: string) => {
146
+ // get sorted array of holidays directly
147
+ const allHolidays = Util.getHolidaysInMonth(moment(date));
148
+ const filtered = showLunar
149
+ ? allHolidays
150
+ : allHolidays.filter(item => !item.lunar || item.mixedLabel);
151
+ headerControlRef.current?.onUpdateInfo({date});
152
+ headerRef.current = date.clone().startOf('month');
153
+ setHolidays(allHolidays);
154
+ setTemp(filtered);
155
+ setHeaderKey(key);
156
+ };
177
157
 
178
- onScrollCalendar = (data: {key: string; date: Moment.Moment}) => {
179
- const {headerKey} = this.state;
180
- if (data) {
181
- if (data.key !== headerKey) {
182
- this.executeProcessAfterScrollCalendar(data.date, data.key);
158
+ const onScrollCalendar = (info: {
159
+ date: Moment;
160
+ key: string;
161
+ currentIndex: number;
162
+ }) => {
163
+ if (info.key !== headerKey) {
164
+ executeProcessAfterScrollCalendar(info.date, info.key);
183
165
  }
184
- }
185
- };
186
-
187
- setDoubleDateAndTabIndex = (
188
- firstDate: Moment.Moment | Date | undefined,
189
- secondDate?: Moment.Moment | Date | undefined | null,
190
- tabSelected?: any
191
- ) => {
192
- this.ownSetState({
193
- startDate: firstDate ? Moment(firstDate) : null,
194
- endDate: secondDate ? Moment(secondDate) : null,
195
- tabSelected,
196
- });
197
- };
198
-
199
- toggleLunarDate = () => {
200
- const {showLunar, holidays} = this.state;
201
- const {onCallbackCalendar} = this.props;
202
- let data: Holidays[] = [];
203
- const nextStateShowLunar = !showLunar;
204
- if (!nextStateShowLunar) {
205
- data = holidays?.filter(item => !item.lunar || item.mixedLabel);
206
- } else {
207
- data = holidays;
208
- }
209
- if (onCallbackCalendar) {
210
- onCallbackCalendar('lunar', nextStateShowLunar);
211
- }
166
+ };
212
167
 
213
- this.ownSetState({
214
- showLunar: !showLunar,
215
- temp: data,
216
- ownUpdate: true,
217
- });
218
- };
168
+ const onToggleLunar = () => {
169
+ setOwnUpdate(true);
170
+ const next = !showLunar;
171
+ const filtered = next
172
+ ? holidays
173
+ : holidays.filter(item => !item.lunar || item.mixedLabel);
174
+ onCallbackCalendar?.('lunar', next);
175
+ setShowLunar(next);
176
+ setTemp(filtered);
177
+ };
219
178
 
220
- onPressBackArrow = () => {
221
- const previousDate = Moment(this.header)
222
- .startOf('month')
223
- .subtract(1, 'months');
224
- if (
225
- this.headerControlRef &&
226
- previousDate.isSameOrAfter(this.minDate, 'month')
227
- ) {
228
- this.header = previousDate;
229
- this.headerControlRef.current?.onUpdateInfo({date: previousDate});
230
- this.monthListRef?.scrollToMonth(previousDate);
231
- }
232
- };
179
+ const onPressBackArrow = () => {
180
+ const prev = headerRef.current
181
+ .clone()
182
+ .startOf('month')
183
+ .subtract(1, 'months');
184
+ if (prev.isSameOrAfter(minDate, 'month')) {
185
+ headerRef.current = prev;
186
+ headerControlRef.current?.onUpdateInfo({date: prev});
187
+ monthListRef.current?.scrollToMonth(prev);
188
+ }
189
+ };
233
190
 
234
- onPressNextArrow = () => {
235
- const nextDate = Moment(this.header).startOf('month').add(1, 'months');
236
- if (
237
- this.headerControlRef &&
238
- nextDate.isSameOrBefore(this.maxDate, 'month')
239
- ) {
240
- this.header = nextDate;
241
- this.headerControlRef.current?.onUpdateInfo({date: nextDate});
242
- this.monthListRef?.scrollToMonth(nextDate);
243
- }
244
- };
191
+ const onPressNextArrow = () => {
192
+ const next = headerRef.current.clone().startOf('month').add(1, 'months');
193
+ if (next.isSameOrBefore(maxDate, 'month')) {
194
+ headerRef.current = next;
195
+ headerControlRef.current?.onUpdateInfo({date: next});
196
+ monthListRef.current?.scrollToMonth(next);
197
+ }
198
+ };
245
199
 
246
- render() {
247
- const {
248
- startDate,
249
- endDate,
250
- showLunar = false,
251
- tabSelected,
252
- holidays,
253
- temp,
254
- } = this.state;
255
- const {
256
- isDoubleDateMode,
257
- priceList,
258
- disabledDays,
259
- isHideHoliday,
260
- isOffLunar,
261
- } = this.props;
200
+ // Determine price list format
262
201
  let priceListFormat = priceList?.outbound;
263
202
  if (isDoubleDateMode) {
264
203
  priceListFormat =
265
204
  tabSelected === 0 ? priceList?.outbound : priceList?.inbound;
266
205
  }
267
206
 
268
- const {theme, translate} = this.context;
269
-
270
207
  return (
271
- <ContainerContext.Consumer>
272
- {size => (
273
- <View style={styles.container}>
274
- <View>
275
- <HeaderControl
276
- ref={this.headerControlRef}
277
- selectedDate={this.selectedDate}
278
- onPressBackArrow={this.onPressBackArrow}
279
- onPressNextArrow={this.onPressNextArrow}
280
- />
281
- <View style={styles.blueSeperator} />
282
- <View>
283
- <View style={styles.viewDay}>
284
- {[1, 2, 3, 4, 5, 6, 7].map(item => (
285
- <Text
286
- color={
287
- item === 6 || item === 7
288
- ? Colors.red_03
289
- : Colors.black_17
290
- }
291
- typography={'label_s_medium'}
292
- style={[
293
- styles.textDay,
294
- {
295
- width: (size.width - Spacing.M * 2) / 7,
296
- },
297
- ]}
298
- key={item}>
299
- {translate(Util.mapWeeKDate(item))}
300
- </Text>
301
- ))}
302
- </View>
303
- <MonthList
304
- ref={ref => (this.monthListRef = ref)}
305
- today={this.today}
306
- minDate={this.minDate}
307
- maxDate={this.maxDate}
308
- startDate={startDate}
309
- endDate={endDate}
310
- onChoose={this.onChoose}
311
- onScrollCalendar={this.onScrollCalendar}
312
- isShowLunar={!isOffLunar && showLunar}
313
- isDoubleDateMode={isDoubleDateMode}
314
- tabSelected={tabSelected}
315
- lunarConverter={this.converter}
316
- holidays={holidays}
317
- selectedDate={this.selectedDate}
318
- priceList={priceListFormat}
319
- disabledDays={disabledDays}
320
- />
321
- </View>
322
- </View>
323
- {!isOffLunar && (
324
- <View
208
+ <View style={styles.container}>
209
+ <HeaderControl
210
+ ref={headerControlRef}
211
+ selectedDate={propSelectedDate}
212
+ onPressBackArrow={onPressBackArrow}
213
+ onPressNextArrow={onPressNextArrow}
214
+ />
215
+ <View style={styles.blueSeperator} />
216
+ <View>
217
+ <View style={styles.viewDay}>
218
+ {[1, 2, 3, 4, 5, 6, 7].map(dayIdx => (
219
+ <Text
220
+ key={dayIdx}
221
+ color={dayIdx >= 6 ? Colors.red_03 : Colors.black_17}
222
+ typography="label_s_medium"
325
223
  style={[
326
- styles.viewLunar,
327
- {borderColor: theme.colors.border.default},
224
+ styles.textDay,
225
+ {width: (size.width - Spacing.M * 2) / 7},
328
226
  ]}>
329
- <CheckBox
330
- onChange={this.toggleLunarDate}
331
- value={showLunar}
332
- label={translate('showLunar')}
333
- />
334
- </View>
335
- )}
336
-
337
- {!isHideHoliday && (
338
- <ScrollView
339
- contentContainerStyle={styles.contentScroll}
340
- showsVerticalScrollIndicator={false}
341
- nestedScrollEnabled>
342
- {temp &&
343
- temp.length > 0 &&
344
- temp.map(
345
- (
346
- item: {
347
- mixedLabel: any;
348
- label: any;
349
- day: number;
350
- month: number;
351
- highlight: string;
352
- },
353
- idx: {toString: () => React.Key | null | undefined}
354
- ) => {
355
- const labelHoliday = showLunar
356
- ? item.mixedLabel || item.label || ''
357
- : item.label || '';
358
- const labelDate = `${
359
- item.day > 9 ? item.day : `0${item.day}`
360
- }/${item.month > 9 ? item.month : `0${item.month}`}`;
361
- const labelHighlight = showLunar
362
- ? item.highlight || ''
363
- : '';
364
- return (
365
- <View style={styles.row} key={idx.toString()}>
366
- <Text
367
- color={theme.colors.error.primary}
368
- typography={'description_default_regular'}
369
- style={styles.txtMonthLunar}>
370
- {labelDate}
371
- </Text>
372
- <Text
373
- typography={'description_default_regular'}
374
- style={styles.subTextLunar}>
375
- {`${translate(labelHoliday)}`}
376
- </Text>
377
- <Text typography={'description_default_regular'}>
378
- {labelHighlight}
379
- </Text>
380
- </View>
381
- );
382
- }
383
- )}
384
- </ScrollView>
385
- )}
227
+ {translate?.(Util.mapWeeKDate(dayIdx))}
228
+ </Text>
229
+ ))}
230
+ </View>
231
+ <MonthList
232
+ ref={monthListRef}
233
+ today={today}
234
+ minDate={minDate}
235
+ maxDate={maxDate}
236
+ startDate={startDate!}
237
+ endDate={endDate!}
238
+ onChoose={onChooseDay}
239
+ onScrollCalendar={onScrollCalendar}
240
+ isShowLunar={!isOffLunar && showLunar}
241
+ isDoubleDateMode={isDoubleDateMode}
242
+ tabSelected={tabSelected}
243
+ lunarConverter={converter}
244
+ holidays={holidays}
245
+ selectedDate={propSelectedDate}
246
+ priceList={priceListFormat}
247
+ disabledDays={disabledDays}
248
+ />
249
+ </View>
250
+ {!isOffLunar && (
251
+ <View
252
+ style={[
253
+ styles.viewLunar,
254
+ {borderColor: theme.colors.border.default},
255
+ ]}>
256
+ <CheckBox
257
+ onChange={onToggleLunar}
258
+ value={showLunar}
259
+ label={translate?.('showLunar')}
260
+ />
386
261
  </View>
387
262
  )}
388
- </ContainerContext.Consumer>
263
+ {!isHideHoliday && (
264
+ <ScrollView
265
+ contentContainerStyle={styles.contentScroll}
266
+ showsVerticalScrollIndicator={false}
267
+ nestedScrollEnabled>
268
+ {temp.map((item, idx) => {
269
+ const labelDate = `${item.day > 9 ? item.day : `0${item.day}`}/${
270
+ item.month > 9 ? item.month : `0${item.month}`
271
+ }`;
272
+ const labelHoliday = showLunar
273
+ ? item.mixedLabel || item.label
274
+ : item.label;
275
+ const labelHighlight = showLunar ? item.highlight || '' : '';
276
+ return (
277
+ <View style={styles.row} key={idx.toString()}>
278
+ <Text
279
+ color={theme.colors.error.primary}
280
+ typography="description_default_regular"
281
+ style={styles.txtMonthLunar}>
282
+ {labelDate}
283
+ </Text>
284
+ <Text
285
+ typography="description_default_regular"
286
+ style={styles.subTextLunar}>
287
+ {translate?.(labelHoliday)}
288
+ </Text>
289
+ <Text typography="description_default_regular">
290
+ {labelHighlight}
291
+ </Text>
292
+ </View>
293
+ );
294
+ })}
295
+ </ScrollView>
296
+ )}
297
+ </View>
389
298
  );
390
299
  }
391
- }
300
+ );
301
+
302
+ export default CalendarPro;