@momo-kits/calendar 0.169.4-beta.3 → 0.169.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/CalendarHeader.tsx +2 -0
- package/CalendarPro.tsx +38 -33
- package/ContainerContext.ts +2 -12
- package/Day.tsx +10 -8
- package/HeaderControl.tsx +12 -10
- package/LunarDateConverter.ts +33 -35
- package/Month.tsx +16 -15
- package/MonthList.tsx +30 -32
- package/TabHeader.tsx +4 -2
- package/Util.ts +48 -32
- package/index.tsx +16 -38
- package/package.json +1 -1
- package/styles.ts +3 -9
package/CalendarHeader.tsx
CHANGED
|
@@ -13,6 +13,7 @@ type SwitchReturnSelectionProps = {
|
|
|
13
13
|
onChange: () => void;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/** Round-trip switch row above the date tabs; renders nothing when `hidden`. */
|
|
16
17
|
export const SwitchReturnSelection: React.FC<
|
|
17
18
|
SwitchReturnSelectionProps
|
|
18
19
|
> = props => {
|
|
@@ -48,6 +49,7 @@ type HeaderPanelProps = {
|
|
|
48
49
|
selectedDate: moment.Moment;
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
/** Departure and return tabs in double-date mode, else one departure tab. */
|
|
51
53
|
export const HeaderPanel: React.FC<HeaderPanelProps> = props => {
|
|
52
54
|
const { theme } = useContext(ApplicationContext);
|
|
53
55
|
const {
|
package/CalendarPro.tsx
CHANGED
|
@@ -28,6 +28,18 @@ import {
|
|
|
28
28
|
import { ContainerContext } from './ContainerContext';
|
|
29
29
|
import styles from './styles';
|
|
30
30
|
|
|
31
|
+
/** Holidays to list: lunar-only ones are hidden while lunar dates are off. */
|
|
32
|
+
const getVisibleHolidays = (holidays: Holidays[], showLunar: boolean) => {
|
|
33
|
+
if (showLunar) {
|
|
34
|
+
return holidays;
|
|
35
|
+
}
|
|
36
|
+
return holidays.filter(item => !item.lunar || item.mixedLabel);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Weekday row, month list, lunar toggle and holiday list of `Calendar`, which
|
|
41
|
+
* drives the selection through `setDoubleDateAndTabIndex`.
|
|
42
|
+
*/
|
|
31
43
|
const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
32
44
|
(
|
|
33
45
|
{
|
|
@@ -55,7 +67,7 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
55
67
|
const headerControlRef = useRef<HeaderControlRef>(null);
|
|
56
68
|
const monthListRef = useRef<MonthListRef>(null);
|
|
57
69
|
|
|
58
|
-
//
|
|
70
|
+
// An invalid bound falls back to 12 months from the other one (or today).
|
|
59
71
|
const [minDate, maxDate] = useMemo(() => {
|
|
60
72
|
let max = moment(propMaxDate);
|
|
61
73
|
let min = moment(propMinDate);
|
|
@@ -72,7 +84,6 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
72
84
|
return [min, max];
|
|
73
85
|
}, [propMinDate, propMaxDate]);
|
|
74
86
|
|
|
75
|
-
// State
|
|
76
87
|
const [startDate, setStartDate] = useState<Moment>(propStartDate);
|
|
77
88
|
const [endDate, setEndDate] = useState<Moment | null>(propEndDate || null);
|
|
78
89
|
const [showLunar, setShowLunar] = useState<boolean>(!!propIsShowLunar);
|
|
@@ -94,7 +105,7 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
94
105
|
},
|
|
95
106
|
}));
|
|
96
107
|
|
|
97
|
-
//
|
|
108
|
+
// Follow the isShowLunar prop, except right after the user toggled it.
|
|
98
109
|
useEffect(() => {
|
|
99
110
|
if (ownUpdate) {
|
|
100
111
|
setOwnUpdate(false);
|
|
@@ -103,7 +114,6 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
103
114
|
}
|
|
104
115
|
}, [ownUpdate, propIsShowLunar, showLunar]);
|
|
105
116
|
|
|
106
|
-
// Scroll to initial selected date on mount or when it changes
|
|
107
117
|
useEffect(() => {
|
|
108
118
|
const timer = setTimeout(() => {
|
|
109
119
|
monthListRef.current?.scrollToMonth(propSelectedDate);
|
|
@@ -111,7 +121,6 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
111
121
|
return () => clearTimeout(timer);
|
|
112
122
|
}, [propSelectedDate]);
|
|
113
123
|
|
|
114
|
-
// Handlers
|
|
115
124
|
const onChooseDay = (day: Moment) => {
|
|
116
125
|
if (isDoubleDateMode) {
|
|
117
126
|
if (tabSelected === 1) {
|
|
@@ -132,11 +141,8 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
132
141
|
};
|
|
133
142
|
|
|
134
143
|
const executeProcessAfterScrollCalendar = (date: Moment, key: string) => {
|
|
135
|
-
// get sorted array of holidays directly
|
|
136
144
|
const allHolidays = Util.getHolidaysInMonth(moment(date));
|
|
137
|
-
const filtered = showLunar
|
|
138
|
-
? allHolidays
|
|
139
|
-
: allHolidays.filter(item => !item.lunar || item.mixedLabel);
|
|
145
|
+
const filtered = getVisibleHolidays(allHolidays, showLunar);
|
|
140
146
|
headerControlRef.current?.onUpdateInfo({ date });
|
|
141
147
|
headerRef.current = date.clone().startOf('month');
|
|
142
148
|
setHolidays(allHolidays);
|
|
@@ -157,9 +163,7 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
157
163
|
const onToggleLunar = () => {
|
|
158
164
|
setOwnUpdate(true);
|
|
159
165
|
const next = !showLunar;
|
|
160
|
-
const filtered = next
|
|
161
|
-
? holidays
|
|
162
|
-
: holidays.filter(item => !item.lunar || item.mixedLabel);
|
|
166
|
+
const filtered = getVisibleHolidays(holidays, next);
|
|
163
167
|
onCallbackCalendar?.('lunar', next);
|
|
164
168
|
setShowLunar(next);
|
|
165
169
|
setTemp(filtered);
|
|
@@ -186,7 +190,6 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
186
190
|
}
|
|
187
191
|
};
|
|
188
192
|
|
|
189
|
-
// Determine price list format
|
|
190
193
|
let priceListFormat = priceList?.outbound;
|
|
191
194
|
if (isDoubleDateMode && tabSelected !== 0) {
|
|
192
195
|
priceListFormat = priceList?.inbound;
|
|
@@ -208,23 +211,25 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
208
211
|
<View style={styles.blueSeperator} />
|
|
209
212
|
<View>
|
|
210
213
|
<View style={styles.viewDay}>
|
|
211
|
-
{[1, 2, 3, 4, 5, 6, 7].map(dayIdx =>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
{[1, 2, 3, 4, 5, 6, 7].map(dayIdx => {
|
|
215
|
+
let dayColor = theme.colors.text.default;
|
|
216
|
+
if (dayIdx >= 6) {
|
|
217
|
+
dayColor = theme.colors.error.primary;
|
|
218
|
+
}
|
|
219
|
+
return (
|
|
220
|
+
<Text
|
|
221
|
+
key={dayIdx}
|
|
222
|
+
color={dayColor}
|
|
223
|
+
typography="label_s_medium"
|
|
224
|
+
style={[
|
|
225
|
+
styles.textDay,
|
|
226
|
+
{ width: (size.width - Spacing.S * 2) / 7 },
|
|
227
|
+
]}
|
|
228
|
+
>
|
|
229
|
+
{translate?.(Util.mapWeeKDate(dayIdx))}
|
|
230
|
+
</Text>
|
|
231
|
+
);
|
|
232
|
+
})}
|
|
228
233
|
</View>
|
|
229
234
|
<MonthList
|
|
230
235
|
ref={monthListRef}
|
|
@@ -265,9 +270,9 @@ const CalendarPro = forwardRef<CalendarProRef, CalendarProProps>(
|
|
|
265
270
|
nestedScrollEnabled
|
|
266
271
|
>
|
|
267
272
|
{temp.map((item, idx) => {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
}`;
|
|
273
|
+
const day = item.day > 9 ? item.day : `0${item.day}`;
|
|
274
|
+
const month = item.month > 9 ? item.month : `0${item.month}`;
|
|
275
|
+
const labelDate = `${day}/${month}`;
|
|
271
276
|
const labelHoliday = (showLunar && item.mixedLabel) || item.label;
|
|
272
277
|
const labelHighlight = showLunar ? item.highlight || '' : '';
|
|
273
278
|
return (
|
package/ContainerContext.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Calendar container size: `width`/`height` are style sizes for layout, `pageWidth` is
|
|
8
|
-
* the rendered width that horizontal paging and scroll offsets are measured in.
|
|
9
|
-
*/
|
|
10
|
-
export const ContainerContext = createContext({
|
|
11
|
-
width: 0,
|
|
12
|
-
height: 0,
|
|
13
|
-
pageWidth: 0,
|
|
14
|
-
});
|
|
3
|
+
/** Calendar width and day cell height, shared by every month and day cell. */
|
|
4
|
+
export const ContainerContext = createContext({ width: 0, height: 0 });
|
package/Day.tsx
CHANGED
|
@@ -12,6 +12,10 @@ import { DayProps } from './types';
|
|
|
12
12
|
import { ContainerContext } from './ContainerContext';
|
|
13
13
|
import styles from './styles';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* One day cell: solar date with optional lunar date and price, highlighted as
|
|
17
|
+
* the start, end or inside of the selected range.
|
|
18
|
+
*/
|
|
15
19
|
const Day: React.FC<DayProps> = props => {
|
|
16
20
|
const {
|
|
17
21
|
startDate,
|
|
@@ -46,15 +50,13 @@ const Day: React.FC<DayProps> = props => {
|
|
|
46
50
|
return <View style={[styles.dayContainer, { width: itemWidth }]} />;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
(date.isBefore(maxDate, 'day') || date.isSame(maxDate, 'day'));
|
|
53
|
+
const isAfterMin = date.isSameOrAfter(minDate, 'day');
|
|
54
|
+
const isBeforeMax = date.isSameOrBefore(maxDate, 'day');
|
|
55
|
+
const isValid = !isDisabled && isAfterMin && isBeforeMax;
|
|
53
56
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
date.isBefore(endDate, 'day');
|
|
57
|
+
const isAfterStart = date.isAfter(startDate, 'day');
|
|
58
|
+
const isBeforeEnd = date.isBefore(endDate, 'day');
|
|
59
|
+
const isMid = isDoubleDateMode && isAfterStart && isBeforeEnd;
|
|
58
60
|
|
|
59
61
|
const isStart = date.isSame(startDate, 'day');
|
|
60
62
|
const isStartPart = !!(isStart && endDate);
|
package/HeaderControl.tsx
CHANGED
|
@@ -5,20 +5,21 @@ import React, {
|
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
useState,
|
|
7
7
|
} from 'react';
|
|
8
|
-
import {TouchableOpacity, View} from 'react-native';
|
|
8
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
9
9
|
import moment from 'moment';
|
|
10
|
-
import {ApplicationContext, Icon, Text} from '@momo-kits/foundation';
|
|
10
|
+
import { ApplicationContext, Icon, Text } from '@momo-kits/foundation';
|
|
11
11
|
|
|
12
12
|
import Util from './Util';
|
|
13
|
-
import {HeaderControlProps, HeaderControlRef} from './types';
|
|
13
|
+
import { HeaderControlProps, HeaderControlRef } from './types';
|
|
14
14
|
import styles from './styles';
|
|
15
15
|
|
|
16
|
+
/** Month title between previous/next arrows; `onUpdateInfo` sets the month. */
|
|
16
17
|
const HeaderControl: ForwardRefRenderFunction<
|
|
17
18
|
HeaderControlRef,
|
|
18
19
|
HeaderControlProps
|
|
19
|
-
> = ({onPressBackArrow, onPressNextArrow, selectedDate}, ref) => {
|
|
20
|
-
const {translate} = useContext(ApplicationContext);
|
|
21
|
-
const [info, setInfo] = useState<{date: moment.Moment}>({
|
|
20
|
+
> = ({ onPressBackArrow, onPressNextArrow, selectedDate }, ref) => {
|
|
21
|
+
const { translate } = useContext(ApplicationContext);
|
|
22
|
+
const [info, setInfo] = useState<{ date: moment.Moment }>({
|
|
22
23
|
date: moment(selectedDate || new Date()),
|
|
23
24
|
});
|
|
24
25
|
|
|
@@ -26,14 +27,15 @@ const HeaderControl: ForwardRefRenderFunction<
|
|
|
26
27
|
onUpdateInfo,
|
|
27
28
|
}));
|
|
28
29
|
|
|
29
|
-
const onUpdateInfo = (
|
|
30
|
+
const onUpdateInfo = (
|
|
31
|
+
date: React.SetStateAction<{ date: moment.Moment }>,
|
|
32
|
+
) => {
|
|
30
33
|
setInfo(date);
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
if (info && info.date) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
)}/${info.date.year()}`;
|
|
37
|
+
const monthKey = Util.mapMonth(info.date.month() + 1) || '';
|
|
38
|
+
const headerFormat = `${translate?.(monthKey)}/${info.date.year()}`;
|
|
37
39
|
return (
|
|
38
40
|
<View style={styles.headerControlContainer}>
|
|
39
41
|
<TouchableOpacity onPress={onPressBackArrow}>
|
package/LunarDateConverter.ts
CHANGED
|
@@ -11,6 +11,20 @@ function Solar(this: any) {
|
|
|
11
11
|
this.solarYear = 0;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** Day number of March 1 of `year` in the March-based count of `SolarToInt`. */
|
|
15
|
+
const daysBeforeYear = (year: number) => {
|
|
16
|
+
return (
|
|
17
|
+
365 * year +
|
|
18
|
+
parseInt(String(year / 4), 10) -
|
|
19
|
+
parseInt(String(year / 100), 10) +
|
|
20
|
+
parseInt(String(year / 400), 10)
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Table-based solar ↔ lunar date converter, used with `new`.
|
|
26
|
+
* Supports years from 1900 to the end of its tables and throws outside them.
|
|
27
|
+
*/
|
|
14
28
|
function LunarSolarConverter(this: any) {
|
|
15
29
|
this.lunar_month_days = [
|
|
16
30
|
1887, 0x1694, 0x16aa, 0x4ad5, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a,
|
|
@@ -73,18 +87,12 @@ function LunarSolarConverter(this: any) {
|
|
|
73
87
|
0x107c53, 0x107e48,
|
|
74
88
|
];
|
|
75
89
|
|
|
76
|
-
/**
|
|
77
|
-
* @return {number}
|
|
78
|
-
*/
|
|
79
90
|
this.GetBitInt = function (data: number, length: number, shift: number) {
|
|
80
91
|
// eslint-disable-next-line no-bitwise -- unpacks bit-packed lunar table data
|
|
81
92
|
return (data & (((1 << length) - 1) << shift)) >> shift;
|
|
82
93
|
};
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* @return {number}
|
|
87
|
-
*/
|
|
95
|
+
/** Day number of a Gregorian date; inaccurate before Oct 1582. */
|
|
88
96
|
this.SolarToInt = function (y: number, m: number, d: number) {
|
|
89
97
|
m = (m + 9) % 12;
|
|
90
98
|
y = parseInt(String(y), 10) - parseInt(String(m / 10), 10);
|
|
@@ -98,22 +106,13 @@ function LunarSolarConverter(this: any) {
|
|
|
98
106
|
);
|
|
99
107
|
};
|
|
100
108
|
|
|
109
|
+
/** Gregorian date of a day number from `SolarToInt`. */
|
|
101
110
|
this.SolarFromInt = function (g: number) {
|
|
102
111
|
let y = parseInt(String((10000 * g + 14780) / 3652425), 10);
|
|
103
|
-
let ddd =
|
|
104
|
-
g -
|
|
105
|
-
(365 * y +
|
|
106
|
-
parseInt(String(y / 4), 10) -
|
|
107
|
-
parseInt(String(y / 100), 10) +
|
|
108
|
-
parseInt(String(y / 400), 10));
|
|
112
|
+
let ddd = g - daysBeforeYear(y);
|
|
109
113
|
if (ddd < 0) {
|
|
110
114
|
y--;
|
|
111
|
-
ddd =
|
|
112
|
-
g -
|
|
113
|
-
(365 * y +
|
|
114
|
-
parseInt(String(y / 4), 10) -
|
|
115
|
-
parseInt(String(y / 100), 10) +
|
|
116
|
-
parseInt(String(y / 400), 10));
|
|
115
|
+
ddd = g - daysBeforeYear(y);
|
|
117
116
|
}
|
|
118
117
|
const mi = parseInt(String((100 * ddd + 52) / 3060), 10);
|
|
119
118
|
const mm = ((mi + 2) % 12) + 1;
|
|
@@ -133,15 +132,15 @@ function LunarSolarConverter(this: any) {
|
|
|
133
132
|
lunarMonth: number;
|
|
134
133
|
lunarDay: number;
|
|
135
134
|
}) {
|
|
136
|
-
|
|
135
|
+
const firstTableYear = this.lunar_month_days[0];
|
|
137
136
|
const minLunarYear = 1900;
|
|
138
|
-
|
|
139
|
-
const maxLunarYear = this.lunar_month_days[0] + this.lunar_month_days.length - 1;
|
|
137
|
+
const maxLunarYear = firstTableYear + this.lunar_month_days.length - 1;
|
|
140
138
|
if (lunar.lunarYear < minLunarYear || lunar.lunarYear > maxLunarYear) {
|
|
141
|
-
throw new Error(
|
|
139
|
+
throw new Error(
|
|
140
|
+
`LunarToSolar: year ${lunar.lunarYear} out of supported range ${minLunarYear}-${maxLunarYear}`,
|
|
141
|
+
);
|
|
142
142
|
}
|
|
143
|
-
const days =
|
|
144
|
-
this.lunar_month_days[lunar.lunarYear - this.lunar_month_days[0]];
|
|
143
|
+
const days = this.lunar_month_days[lunar.lunarYear - firstTableYear];
|
|
145
144
|
const leap = this.GetBitInt(days, 4, 13);
|
|
146
145
|
let offset = 0;
|
|
147
146
|
let loopend = leap;
|
|
@@ -171,19 +170,19 @@ function LunarSolarConverter(this: any) {
|
|
|
171
170
|
solarMonth: number;
|
|
172
171
|
solarDay: number;
|
|
173
172
|
}) {
|
|
174
|
-
// year range check: only support solar dates from 1900
|
|
175
173
|
const minSolarYear = 1900;
|
|
176
|
-
// using converter data, maximum supported solar year
|
|
177
174
|
const maxSolarYear = this.solar_1_1[0] + this.solar_1_1.length - 1;
|
|
178
175
|
if (solar.solarYear < minSolarYear || solar.solarYear > maxSolarYear) {
|
|
179
|
-
throw new Error(
|
|
176
|
+
throw new Error(
|
|
177
|
+
`SolarToLunar: year ${solar.solarYear} out of supported range ${minSolarYear}-${maxSolarYear}`,
|
|
178
|
+
);
|
|
180
179
|
}
|
|
181
180
|
// @ts-ignore
|
|
182
181
|
const lunar = new Lunar();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
const { solarYear, solarMonth, solarDay } = solar;
|
|
183
|
+
let index = solarYear - this.solar_1_1[0];
|
|
184
|
+
// eslint-disable-next-line no-bitwise -- packs date to compare with solar_1_1 table
|
|
185
|
+
const data = (solarYear << 9) | (solarMonth << 5) | solarDay;
|
|
187
186
|
if (this.solar_1_1[index] > data) {
|
|
188
187
|
index--;
|
|
189
188
|
}
|
|
@@ -191,9 +190,8 @@ function LunarSolarConverter(this: any) {
|
|
|
191
190
|
const y = this.GetBitInt(solar11, 12, 9);
|
|
192
191
|
const m = this.GetBitInt(solar11, 4, 5);
|
|
193
192
|
const d = this.GetBitInt(solar11, 5, 0);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.SolarToInt(y, m, d);
|
|
193
|
+
const dayNumber = this.SolarToInt(solarYear, solarMonth, solarDay);
|
|
194
|
+
let offset = dayNumber - this.SolarToInt(y, m, d);
|
|
197
195
|
|
|
198
196
|
const days = this.lunar_month_days[index];
|
|
199
197
|
const leap = this.GetBitInt(days, 4, 13);
|
package/Month.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import React, {useContext, useMemo} from 'react';
|
|
2
|
-
import {View} from 'react-native';
|
|
3
|
-
import moment, {Moment} from 'moment';
|
|
1
|
+
import React, { useContext, useMemo } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import moment, { Moment } from 'moment';
|
|
4
4
|
import Day from './Day';
|
|
5
|
-
import {useScaleSize} from '@momo-kits/foundation';
|
|
6
|
-
import {MonthProps} from './types';
|
|
7
|
-
import {ContainerContext} from './ContainerContext';
|
|
5
|
+
import { useScaleSize } from '@momo-kits/foundation';
|
|
6
|
+
import { MonthProps } from './types';
|
|
7
|
+
import { ContainerContext } from './ContainerContext';
|
|
8
8
|
import styles from './styles';
|
|
9
9
|
|
|
10
|
+
/** One month as rows of 7 `Day` cells; `dateList` is padded to whole weeks. */
|
|
10
11
|
const Month: React.FC<MonthProps> = ({
|
|
11
12
|
dateList,
|
|
12
13
|
holidays,
|
|
@@ -49,26 +50,26 @@ const Month: React.FC<MonthProps> = ({
|
|
|
49
50
|
const size = useContext(ContainerContext);
|
|
50
51
|
const scaledHeight = useScaleSize(48);
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
const disabledSet = useMemo(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
53
|
+
// YYYY-MM-DD set so each day cell is an O(1) lookup.
|
|
54
|
+
const disabledSet = useMemo(() => {
|
|
55
|
+
const keys = (disabledDays || []).map(d => moment(d).format('YYYY-MM-DD'));
|
|
56
|
+
return new Set(keys);
|
|
57
|
+
}, [disabledDays]);
|
|
57
58
|
|
|
58
59
|
if (!month) {
|
|
59
60
|
return <View />;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
return (
|
|
63
|
-
<View style={{width: size.width}}>
|
|
64
|
+
<View style={{ width: size.width }}>
|
|
64
65
|
<View style={styles.monthRows}>
|
|
65
66
|
{rows.map((dayList, rowIndex) => (
|
|
66
67
|
<View
|
|
67
68
|
key={`row${rowIndex}`}
|
|
68
|
-
style={[styles.monthRow, {height: scaledHeight}]}
|
|
69
|
+
style={[styles.monthRow, { height: scaledHeight }]}
|
|
70
|
+
>
|
|
69
71
|
{dayList.map((item, i) => {
|
|
70
|
-
const {isEmpty, date: dateMoment, lunarDate} = item;
|
|
71
|
-
// compute key for price/disabled only if not empty
|
|
72
|
+
const { isEmpty, date: dateMoment, lunarDate } = item;
|
|
72
73
|
const hasDate = !isEmpty && dateMoment;
|
|
73
74
|
const keyDay = hasDate ? dateMoment.format('YYYY-MM-DD') : '';
|
|
74
75
|
const priceInfo = keyDay ? priceListDate?.[keyDay] : undefined;
|
package/MonthList.tsx
CHANGED
|
@@ -26,6 +26,14 @@ const MAX_RENDER_PER_BATCH = Platform.OS === 'android' ? 1 : 12;
|
|
|
26
26
|
// @ts-ignore
|
|
27
27
|
const converter = new LunarDateConverter();
|
|
28
28
|
|
|
29
|
+
const isSameMonth = (a: Moment, b: Moment) => {
|
|
30
|
+
return a.year() === b.year() && a.month() === b.month();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Horizontally paged months from `minDate` to `maxDate`; reports the visible
|
|
35
|
+
* month through `onScrollCalendar` and `scrollToMonth` pages to a month.
|
|
36
|
+
*/
|
|
29
37
|
const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
30
38
|
const {
|
|
31
39
|
selectedDate,
|
|
@@ -59,10 +67,7 @@ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
|
59
67
|
const months: { date: Moment; dateList: any[] }[] = [];
|
|
60
68
|
const mMin = moment(minDate).date(1);
|
|
61
69
|
const mMax = moment(maxDate);
|
|
62
|
-
while (
|
|
63
|
-
mMax > mMin ||
|
|
64
|
-
(mMax.year() === mMin.year() && mMax.month() === mMin.month())
|
|
65
|
-
) {
|
|
70
|
+
while (mMax > mMin || isSameMonth(mMax, mMin)) {
|
|
66
71
|
const d = moment(mMin);
|
|
67
72
|
const dayList: any[] = [];
|
|
68
73
|
const monthIdx = d.month();
|
|
@@ -109,41 +114,35 @@ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
|
109
114
|
[selectedDate, getIndexOfMonth],
|
|
110
115
|
);
|
|
111
116
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
Math.max(currentScrollIndex, 0) * size.pageWidth,
|
|
116
|
-
);
|
|
117
|
-
const pageWidth = useRef(size.pageWidth);
|
|
117
|
+
// Paging keeps the pixel offset on resize (fold, rotation); re-snap it.
|
|
118
|
+
const scrollOffset = useRef(Math.max(currentScrollIndex, 0) * size.width);
|
|
119
|
+
const pageWidth = useRef(size.width);
|
|
118
120
|
|
|
119
121
|
useLayoutEffect(() => {
|
|
120
122
|
const previousWidth = pageWidth.current;
|
|
121
|
-
pageWidth.current = size.
|
|
122
|
-
if (previousWidth === size.
|
|
123
|
+
pageWidth.current = size.width;
|
|
124
|
+
if (previousWidth === size.width || previousWidth <= 0) {
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
const page = Math.round(scrollOffset.current / previousWidth);
|
|
126
|
-
scrollOffset.current = page * size.
|
|
128
|
+
scrollOffset.current = page * size.width;
|
|
127
129
|
listRef.current?.scrollToOffset({
|
|
128
130
|
offset: scrollOffset.current,
|
|
129
131
|
animated: false,
|
|
130
132
|
});
|
|
131
|
-
}, [size.
|
|
133
|
+
}, [size.width]);
|
|
132
134
|
|
|
133
|
-
const onScroll = useCallback(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
},
|
|
137
|
-
[],
|
|
138
|
-
);
|
|
135
|
+
const onScroll = useCallback((e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
136
|
+
scrollOffset.current = e.nativeEvent.contentOffset.x;
|
|
137
|
+
}, []);
|
|
139
138
|
|
|
140
139
|
const scrollToMonth = useCallback(
|
|
141
140
|
(month: Moment) => {
|
|
142
141
|
const idx = getIndexOfMonth(month);
|
|
143
142
|
if (listRef.current && idx !== -1) {
|
|
144
143
|
const offset = idx * pageWidth.current;
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
const isAtTarget = Math.abs(offset - scrollOffset.current) < 1;
|
|
145
|
+
targetIndex.current = isAtTarget ? null : idx;
|
|
147
146
|
listRef.current.scrollToIndex({ index: idx, animated: true });
|
|
148
147
|
}
|
|
149
148
|
},
|
|
@@ -154,7 +153,9 @@ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
|
154
153
|
|
|
155
154
|
const onViewableItemsChanged = useRef(({ changed }: { changed: any[] }) => {
|
|
156
155
|
const visible = changed.find(token => token.isViewable);
|
|
157
|
-
|
|
156
|
+
// Ignore months passed while animating to a scrollToMonth target.
|
|
157
|
+
const isScrollingToTarget = targetIndex.current !== null;
|
|
158
|
+
if (isScrollingToTarget && visible?.index !== targetIndex.current) {
|
|
158
159
|
return;
|
|
159
160
|
}
|
|
160
161
|
targetIndex.current = null;
|
|
@@ -163,12 +164,9 @@ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
|
163
164
|
if (onScrollCalendarRef.current && item && currentKey.current !== key) {
|
|
164
165
|
currentKey.current = key;
|
|
165
166
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Spacing.L * 2 +
|
|
170
|
-
Spacing.XS,
|
|
171
|
-
});
|
|
167
|
+
const rowHeight = scaledHeight48Ref.current;
|
|
168
|
+
const rowsHeight = (rowHeight * item.dateList.length) / 7;
|
|
169
|
+
setHeightStyle({ height: rowsHeight + Spacing.L * 2 + Spacing.XS });
|
|
172
170
|
} catch {
|
|
173
171
|
setHeightStyle({});
|
|
174
172
|
}
|
|
@@ -190,11 +188,11 @@ const MonthList = forwardRef<MonthListRef, MonthListProps>((props, ref) => {
|
|
|
190
188
|
|
|
191
189
|
const getItemLayout = useCallback(
|
|
192
190
|
(_: any, index: number) => ({
|
|
193
|
-
length: size.
|
|
194
|
-
offset: size.
|
|
191
|
+
length: size.width,
|
|
192
|
+
offset: size.width * index,
|
|
195
193
|
index,
|
|
196
194
|
}),
|
|
197
|
-
[size.
|
|
195
|
+
[size.width],
|
|
198
196
|
);
|
|
199
197
|
|
|
200
198
|
const renderItem = useCallback(
|
package/TabHeader.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import styles from './styles';
|
|
|
11
11
|
import { ApplicationContext, Text } from '@momo-kits/foundation';
|
|
12
12
|
import { TabHeaderProps, TabHeaderRef } from './types';
|
|
13
13
|
|
|
14
|
+
/** Date tab with a label and the chosen date, updated through `updateView`. */
|
|
14
15
|
const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>((props, ref) => {
|
|
15
16
|
const { label, disabled, id, onChangeTab } = props;
|
|
16
17
|
const { theme, translate } = useContext(ApplicationContext);
|
|
@@ -27,8 +28,9 @@ const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>((props, ref) => {
|
|
|
27
28
|
setActiveTab: setActive,
|
|
28
29
|
}));
|
|
29
30
|
|
|
30
|
-
const weekday = (day: Moment.Moment) =>
|
|
31
|
-
translate?.(Util.WEEKDAYSFROMMOMENT[day.day()]);
|
|
31
|
+
const weekday = (day: Moment.Moment) => {
|
|
32
|
+
return translate?.(Util.WEEKDAYSFROMMOMENT[day.day()]);
|
|
33
|
+
};
|
|
32
34
|
const isHighlighted = active && !disabled;
|
|
33
35
|
|
|
34
36
|
const formattedDate = date ? date.format('DD/MM/YYYY') : '';
|
package/Util.ts
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import LunarDateConverter from './LunarDateConverter';
|
|
3
3
|
import holiday from './holidayData';
|
|
4
|
-
import {Holidays} from './types';
|
|
4
|
+
import { Holidays } from './types';
|
|
5
5
|
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
const converter = new LunarDateConverter();
|
|
8
8
|
|
|
9
|
+
/** Moment as the `{ solarDay, solarMonth, solarYear }` converter input. */
|
|
9
10
|
const Solar = (momentDate: moment.Moment) => ({
|
|
10
11
|
solarDay: momentDate.date(),
|
|
11
12
|
solarMonth: momentDate.month() + 1,
|
|
12
13
|
solarYear: momentDate.year(),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
|
|
16
|
+
const padTwoDigits = (value: number) => {
|
|
17
|
+
return value < 10 ? `0${value}` : `${value}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const formatYYYYMMDD = (dd: number, mm: number, yyyy: any) => {
|
|
21
|
+
return `${yyyy}-${padTwoDigits(mm)}-${padTwoDigits(dd)}`;
|
|
22
|
+
};
|
|
17
23
|
|
|
18
|
-
const formatDDMM = (dd: number, mm: number) =>
|
|
19
|
-
`${dd
|
|
24
|
+
const formatDDMM = (dd: number, mm: number) => {
|
|
25
|
+
return `${padTwoDigits(dd)}/${padTwoDigits(mm)}`;
|
|
26
|
+
};
|
|
20
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Groups holidays by DD/MM; a solar and a lunar holiday on the same day merge
|
|
30
|
+
* into one entry whose `mixedLabel` names both.
|
|
31
|
+
*/
|
|
21
32
|
const groupHolidaysByDate = (
|
|
22
|
-
holidays: Holidays[]
|
|
23
|
-
): {[key: string]: Holidays} => {
|
|
24
|
-
const groupedHolidays: {[key: string]: Holidays} = {};
|
|
33
|
+
holidays: Holidays[],
|
|
34
|
+
): { [key: string]: Holidays } => {
|
|
35
|
+
const groupedHolidays: { [key: string]: Holidays } = {};
|
|
25
36
|
if (holidays && holidays.length > 0) {
|
|
26
37
|
holidays.forEach(item => {
|
|
27
|
-
const {day, month, lunar, label} = item;
|
|
38
|
+
const { day, month, lunar, label } = item;
|
|
28
39
|
const key = formatDDMM(day, month);
|
|
29
40
|
let holidaysObj = { ...(groupedHolidays[key] || item) };
|
|
30
41
|
if (groupedHolidays[key]) {
|
|
@@ -49,28 +60,32 @@ const groupHolidaysByDate = (
|
|
|
49
60
|
return groupedHolidays;
|
|
50
61
|
};
|
|
51
62
|
|
|
52
|
-
/**
|
|
53
|
-
* Sort and group holidays by date, return sorted array of unique Holidays.
|
|
54
|
-
*/
|
|
63
|
+
/** Sorts holidays by date (in place) and merges those on the same day. */
|
|
55
64
|
const sortByDate = (arr: Holidays[]): Holidays[] => {
|
|
56
65
|
if (arr && arr.length > 1) {
|
|
57
|
-
arr.sort((a, b) =>
|
|
58
|
-
a.month !== b.month
|
|
59
|
-
|
|
66
|
+
arr.sort((a, b) => {
|
|
67
|
+
if (a.month !== b.month) {
|
|
68
|
+
return a.month - b.month;
|
|
69
|
+
}
|
|
70
|
+
return a.day - b.day;
|
|
71
|
+
});
|
|
60
72
|
}
|
|
61
|
-
//
|
|
73
|
+
// "DD/MM" keys aren't integer-like, so Object.values keeps the sorted order.
|
|
62
74
|
const grouped = groupHolidaysByDate(arr);
|
|
63
75
|
return Object.values(grouped);
|
|
64
76
|
};
|
|
65
77
|
|
|
78
|
+
/** Weekday/month i18n keys and holiday lookup for the calendar. */
|
|
66
79
|
const Utils = {
|
|
67
80
|
WEEKDAYSFROMMOMENT: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
|
|
68
81
|
|
|
82
|
+
/** i18n key of a weekday, 1 = Monday … 7 = Sunday. */
|
|
69
83
|
mapWeeKDate(i: number) {
|
|
70
84
|
const date = ['', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
71
85
|
return date[i];
|
|
72
86
|
},
|
|
73
87
|
|
|
88
|
+
/** i18n key of a month, 1 = January. */
|
|
74
89
|
mapMonth(i: number) {
|
|
75
90
|
const month = new Map([
|
|
76
91
|
[1, 'jan'],
|
|
@@ -89,9 +104,7 @@ const Utils = {
|
|
|
89
104
|
return month.get(i);
|
|
90
105
|
},
|
|
91
106
|
|
|
92
|
-
/**
|
|
93
|
-
* Get sorted list of holidays within the given month.
|
|
94
|
-
*/
|
|
107
|
+
/** Solar and lunar holidays in the month of `headerInfo`, sorted by date. */
|
|
95
108
|
getHolidaysInMonth(headerInfo: moment.Moment): Holidays[] {
|
|
96
109
|
if (headerInfo) {
|
|
97
110
|
const startDate = moment(headerInfo).startOf('month');
|
|
@@ -103,12 +116,11 @@ const Utils = {
|
|
|
103
116
|
const maxYear = maxLunarDate.lunarYear;
|
|
104
117
|
const currentYear = minYear !== maxYear ? [minYear, maxYear] : minYear;
|
|
105
118
|
|
|
106
|
-
// Handle Solar holidays
|
|
107
119
|
// @ts-ignore
|
|
108
120
|
if (holiday.solar[headerInfo.month() + 1]) {
|
|
109
121
|
// @ts-ignore
|
|
110
122
|
holiday.solar[headerInfo.month() + 1].forEach(
|
|
111
|
-
(date: {month: number; day: any}) => {
|
|
123
|
+
(date: { month: number; day: any }) => {
|
|
112
124
|
const dateAsMoment = moment({
|
|
113
125
|
year: headerInfo.year(),
|
|
114
126
|
month: date.month - 1,
|
|
@@ -117,11 +129,11 @@ const Utils = {
|
|
|
117
129
|
if (dateAsMoment.isBetween(startDate, endDate, 'day', '[]')) {
|
|
118
130
|
holidays.push(date);
|
|
119
131
|
}
|
|
120
|
-
}
|
|
132
|
+
},
|
|
121
133
|
);
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
holiday.lunar.forEach((item: {lunarDay: any; lunarMonth: any}) => {
|
|
136
|
+
holiday.lunar.forEach((item: { lunarDay: any; lunarMonth: any }) => {
|
|
125
137
|
if (currentYear instanceof Array) {
|
|
126
138
|
const solar1 = converter.LunarToSolar({
|
|
127
139
|
lunarDay: item.lunarDay,
|
|
@@ -134,14 +146,20 @@ const Utils = {
|
|
|
134
146
|
lunarYear: currentYear[1],
|
|
135
147
|
});
|
|
136
148
|
const solar1AsMoment = moment(
|
|
137
|
-
formatYYYYMMDD(
|
|
149
|
+
formatYYYYMMDD(
|
|
150
|
+
solar1.solarDay,
|
|
151
|
+
solar1.solarMonth,
|
|
152
|
+
solar1.solarYear,
|
|
153
|
+
),
|
|
138
154
|
);
|
|
139
155
|
const solar2AsMoment = moment(
|
|
140
|
-
formatYYYYMMDD(
|
|
156
|
+
formatYYYYMMDD(
|
|
157
|
+
solar2.solarDay,
|
|
158
|
+
solar2.solarMonth,
|
|
159
|
+
solar2.solarYear,
|
|
160
|
+
),
|
|
141
161
|
);
|
|
142
|
-
if (
|
|
143
|
-
solar1AsMoment.isBetween(startDate, endDate, 'day', '[]')
|
|
144
|
-
) {
|
|
162
|
+
if (solar1AsMoment.isBetween(startDate, endDate, 'day', '[]')) {
|
|
145
163
|
holidays.push({
|
|
146
164
|
...item,
|
|
147
165
|
day: solar1.solarDay,
|
|
@@ -163,11 +181,9 @@ const Utils = {
|
|
|
163
181
|
lunarYear: currentYear,
|
|
164
182
|
});
|
|
165
183
|
const solarAsMoment = moment(
|
|
166
|
-
formatYYYYMMDD(solar.solarDay, solar.solarMonth, solar.solarYear)
|
|
184
|
+
formatYYYYMMDD(solar.solarDay, solar.solarMonth, solar.solarYear),
|
|
167
185
|
);
|
|
168
|
-
if (
|
|
169
|
-
solarAsMoment.isBetween(startDate, endDate, 'day', '[]')
|
|
170
|
-
) {
|
|
186
|
+
if (solarAsMoment.isBetween(startDate, endDate, 'day', '[]')) {
|
|
171
187
|
holidays.push({
|
|
172
188
|
...item,
|
|
173
189
|
day: solar.solarDay,
|
package/index.tsx
CHANGED
|
@@ -11,24 +11,26 @@ import {
|
|
|
11
11
|
import CalendarPro from './CalendarPro';
|
|
12
12
|
import { HeaderPanel, SwitchReturnSelection } from './CalendarHeader';
|
|
13
13
|
import { CalendarProps, CalendarProRef, TabHeaderRef } from './types';
|
|
14
|
-
import { ContainerContext
|
|
14
|
+
import { ContainerContext } from './ContainerContext';
|
|
15
15
|
import styles from './styles';
|
|
16
16
|
|
|
17
17
|
const DOUBLE = 'doubleDate';
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Calendar with single or round-trip (`mode="doubleDate"`) selection, lunar
|
|
21
|
+
* dates, holidays and prices. Dates default to one year around today.
|
|
22
|
+
*/
|
|
19
23
|
const Calendar: React.FC<CalendarProps> = props => {
|
|
20
24
|
const { theme } = useContext(ApplicationContext);
|
|
21
25
|
const scaledHeight48 = useScaleSize(48);
|
|
22
26
|
const scaledHeight34 = useScaleSize(34);
|
|
23
27
|
|
|
24
|
-
// derive defaults for date bounds
|
|
25
28
|
const currentDate = new Date();
|
|
26
29
|
const minDateDefault = new Date(currentDate);
|
|
27
30
|
const maxDateDefault = new Date(currentDate);
|
|
28
31
|
minDateDefault.setFullYear(minDateDefault.getFullYear() - 1);
|
|
29
32
|
maxDateDefault.setFullYear(maxDateDefault.getFullYear() + 1);
|
|
30
33
|
|
|
31
|
-
// destructure all Calendar props
|
|
32
34
|
const {
|
|
33
35
|
mode,
|
|
34
36
|
id,
|
|
@@ -51,8 +53,6 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
51
53
|
const [containerWidth, setContainerWidth] = useState<number>(
|
|
52
54
|
Dimensions.get('window').width,
|
|
53
55
|
);
|
|
54
|
-
// rendered size per style unit, read from a probe of known style width
|
|
55
|
-
const [layoutScale, setLayoutScale] = useState(1);
|
|
56
56
|
const [isDoubleDateMode, setIsDoubleDateMode] = useState<boolean>(
|
|
57
57
|
mode === DOUBLE,
|
|
58
58
|
);
|
|
@@ -74,41 +74,30 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
74
74
|
const tabSelected = useRef<number>(id || 0);
|
|
75
75
|
|
|
76
76
|
const calendarPicker = useRef<CalendarProRef>(null);
|
|
77
|
-
// hold refs to TabHeader instances
|
|
78
77
|
const cellHeader1 = useRef<TabHeaderRef>(null);
|
|
79
78
|
const cellHeader2 = useRef<TabHeaderRef>(null);
|
|
80
79
|
const cellHeaderSingle = useRef<TabHeaderRef>(null);
|
|
81
80
|
|
|
82
|
-
// Initialize view on mount
|
|
83
81
|
useEffect(() => {
|
|
84
82
|
tabSelected.current = id || 0;
|
|
85
83
|
if (mode === DOUBLE) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
) {
|
|
84
|
+
const header1 = cellHeader1.current;
|
|
85
|
+
const header2 = cellHeader2.current;
|
|
86
|
+
const picker = calendarPicker.current;
|
|
87
|
+
if (header1 && header2 && picker) {
|
|
91
88
|
const start = doubleDate.current.first;
|
|
92
89
|
const end = doubleDate.current.second;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
doubleDate.current.first,
|
|
97
|
-
doubleDate.current.second,
|
|
98
|
-
tabSelected.current,
|
|
99
|
-
);
|
|
90
|
+
header1.updateView(start, tabSelected.current === 0);
|
|
91
|
+
header2.updateView(end, tabSelected.current === 1);
|
|
92
|
+
picker.setDoubleDateAndTabIndex(start, end, tabSelected.current);
|
|
100
93
|
}
|
|
101
94
|
} else {
|
|
102
95
|
calendarPicker.current?.setDoubleDateAndTabIndex(selectedDate);
|
|
103
96
|
}
|
|
104
97
|
}, []);
|
|
105
98
|
|
|
106
|
-
const onLayout = (e: LayoutChangeEvent) =>
|
|
99
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
107
100
|
setContainerWidth(e.nativeEvent.layout.width);
|
|
108
|
-
|
|
109
|
-
const handleProbeLayout = (e: LayoutChangeEvent) => {
|
|
110
|
-
const ratio = e.nativeEvent.layout.width / SCALE_PROBE_SIZE;
|
|
111
|
-
if (ratio > 0) setLayoutScale(ratio);
|
|
112
101
|
};
|
|
113
102
|
|
|
114
103
|
const onChangeTab = (tabId: number) => {
|
|
@@ -127,20 +116,16 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
127
116
|
const { onDateChange, onCTAStateChange } = props;
|
|
128
117
|
if (!cellHeader1.current || !cellHeader2.current || !calendarPicker.current)
|
|
129
118
|
return;
|
|
130
|
-
// set first date and reset second
|
|
131
119
|
doubleDate.current.first = selectedDate;
|
|
132
120
|
doubleDate.current.second = null;
|
|
133
121
|
tabSelected.current = 1;
|
|
134
|
-
// update headers
|
|
135
122
|
cellHeader1.current.updateView(selectedDate, false);
|
|
136
123
|
cellHeader2.current.updateView(null, true);
|
|
137
|
-
// update calendar
|
|
138
124
|
calendarPicker.current.setDoubleDateAndTabIndex(
|
|
139
125
|
doubleDate.current.first,
|
|
140
126
|
doubleDate.current.second,
|
|
141
127
|
tabSelected.current,
|
|
142
128
|
);
|
|
143
|
-
// notify parent
|
|
144
129
|
onDateChange?.({ first: doubleDate.current.first!, second: null });
|
|
145
130
|
onCTAStateChange?.(false);
|
|
146
131
|
};
|
|
@@ -149,7 +134,7 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
149
134
|
const { onDateChange, onCTAStateChange } = props;
|
|
150
135
|
if (!cellHeader1.current || !cellHeader2.current || !calendarPicker.current)
|
|
151
136
|
return;
|
|
152
|
-
//
|
|
137
|
+
// A date before the first one restarts the range.
|
|
153
138
|
if (selectedDate.isBefore(doubleDate.current.first!, 'day')) {
|
|
154
139
|
doubleDate.current.first = selectedDate;
|
|
155
140
|
doubleDate.current.second = null;
|
|
@@ -164,7 +149,6 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
164
149
|
onDateChange?.({ first: doubleDate.current.first!, second: null });
|
|
165
150
|
onCTAStateChange?.(false);
|
|
166
151
|
} else {
|
|
167
|
-
// valid second date
|
|
168
152
|
doubleDate.current.second = selectedDate;
|
|
169
153
|
tabSelected.current = 0;
|
|
170
154
|
cellHeader2.current.updateView(selectedDate, false);
|
|
@@ -196,7 +180,7 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
196
180
|
}
|
|
197
181
|
};
|
|
198
182
|
|
|
199
|
-
//
|
|
183
|
+
// Skip the mount run so the initial doubleDate props are kept.
|
|
200
184
|
const didMount = useRef(false);
|
|
201
185
|
|
|
202
186
|
useEffect(() => {
|
|
@@ -259,17 +243,11 @@ const Calendar: React.FC<CalendarProps> = props => {
|
|
|
259
243
|
return (
|
|
260
244
|
<ContainerContext.Provider
|
|
261
245
|
value={{
|
|
262
|
-
width: containerWidth
|
|
246
|
+
width: containerWidth,
|
|
263
247
|
height: priceList ? scaledHeight48 : scaledHeight34,
|
|
264
|
-
pageWidth: containerWidth,
|
|
265
248
|
}}
|
|
266
249
|
>
|
|
267
250
|
<View onLayout={onLayout} style={[style, styles.scrollView]}>
|
|
268
|
-
<View
|
|
269
|
-
pointerEvents="none"
|
|
270
|
-
style={styles.scaleProbe}
|
|
271
|
-
onLayout={handleProbeLayout}
|
|
272
|
-
/>
|
|
273
251
|
<View
|
|
274
252
|
style={[
|
|
275
253
|
{
|
package/package.json
CHANGED
package/styles.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {StyleSheet} from 'react-native';
|
|
2
|
-
import {Colors, Radius, Spacing} from '@momo-kits/foundation';
|
|
3
|
-
import {SCALE_PROBE_SIZE} from './ContainerContext';
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors, Radius, Spacing } from '@momo-kits/foundation';
|
|
4
3
|
|
|
5
4
|
export default StyleSheet.create({
|
|
6
|
-
//calendar
|
|
7
5
|
viewPanel: {
|
|
8
6
|
flexDirection: 'row',
|
|
9
7
|
marginHorizontal: Spacing.M,
|
|
@@ -31,8 +29,7 @@ export default StyleSheet.create({
|
|
|
31
29
|
width: '100%',
|
|
32
30
|
},
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
row: {flexDirection: 'row'},
|
|
32
|
+
row: { flexDirection: 'row' },
|
|
36
33
|
txtMonthLunar: {
|
|
37
34
|
marginRight: Spacing.S,
|
|
38
35
|
},
|
|
@@ -70,7 +67,6 @@ export default StyleSheet.create({
|
|
|
70
67
|
backgroundColor: Colors.blue_05,
|
|
71
68
|
},
|
|
72
69
|
|
|
73
|
-
//headerControl
|
|
74
70
|
txtHeader: {
|
|
75
71
|
textAlign: 'center',
|
|
76
72
|
},
|
|
@@ -83,7 +79,6 @@ export default StyleSheet.create({
|
|
|
83
79
|
padding: Spacing.S,
|
|
84
80
|
},
|
|
85
81
|
|
|
86
|
-
//day
|
|
87
82
|
dayContainer: {
|
|
88
83
|
borderRadius: Radius.XS,
|
|
89
84
|
alignItems: 'center',
|
|
@@ -101,7 +96,6 @@ export default StyleSheet.create({
|
|
|
101
96
|
borderTopRightRadius: Radius.XS,
|
|
102
97
|
borderBottomRightRadius: Radius.XS,
|
|
103
98
|
},
|
|
104
|
-
scaleProbe: {position: 'absolute', width: SCALE_PROBE_SIZE, height: 0},
|
|
105
99
|
invisible: {
|
|
106
100
|
height: 0,
|
|
107
101
|
width: 0,
|