1 | import React, { useEffect, useState, useCallback } from 'react';
|
2 | import { View } from 'react-native';
|
3 | import dayjs from 'dayjs';
|
4 |
|
5 | import localeData from 'dayjs/plugin/localeData';
|
6 | import localizedFormat from 'dayjs/plugin/localizedFormat';
|
7 | import utc from 'dayjs/plugin/utc';
|
8 |
|
9 | import { getSurroundingTimeUnits } from '../Utils';
|
10 | import { useSurroundingTimeUnits } from '../Hooks';
|
11 | import { ThemeContext, LocaleContext } from '../Contexts';
|
12 | import { VIEW } from '../Constants';
|
13 |
|
14 | import {
|
15 | Months,
|
16 | Days,
|
17 | Arrow as DefaultArrow,
|
18 | Title as DefaultTitle,
|
19 | Weekdays as DefaultWeekdays,
|
20 | } from '../Components';
|
21 |
|
22 | import type {
|
23 | Theme,
|
24 | Locale,
|
25 | ArrowComponentType,
|
26 | TitleComponentType,
|
27 | DayComponentType,
|
28 | MonthComponentType,
|
29 | WeekdaysComponentType,
|
30 | } from '../Entities';
|
31 |
|
32 | dayjs.extend(localeData);
|
33 | dayjs.extend(localizedFormat);
|
34 | dayjs.extend(utc);
|
35 |
|
36 | export interface Props {
|
37 | ArrowComponent?: ArrowComponentType;
|
38 | TitleComponent?: TitleComponentType;
|
39 | DayComponent?: DayComponentType;
|
40 | MonthComponent?: MonthComponentType;
|
41 | WeekdaysComponent?: WeekdaysComponentType;
|
42 | testID?: string;
|
43 | showExtraDates?: boolean;
|
44 | allowYearView?: boolean;
|
45 | onPressDay: (date: string) => void;
|
46 | minDate?: string;
|
47 | maxDate?: string;
|
48 | initVisibleDate?: string;
|
49 | dateProperties: {
|
50 | [date: string ]: {
|
51 | isSelected?: boolean;
|
52 | };
|
53 | };
|
54 | }
|
55 |
|
56 | const BaseCalendar: React.FC<Props> = ({
|
57 | ArrowComponent: CustomArrow,
|
58 | TitleComponent: CustomTitle,
|
59 | WeekdaysComponent: CustomWeekdays,
|
60 | DayComponent,
|
61 | MonthComponent,
|
62 | allowYearView,
|
63 | showExtraDates,
|
64 | onPressDay,
|
65 | maxDate,
|
66 | minDate,
|
67 | initVisibleDate,
|
68 | dateProperties,
|
69 | testID,
|
70 | }) => {
|
71 | const theme = React.useContext<Theme>(ThemeContext);
|
72 | const locale = React.useContext<Locale>(LocaleContext);
|
73 |
|
74 | const [rightArrowDisabled, disableRightArrow] = useState<boolean>(false);
|
75 | const [leftArrowDisabled, disableLeftArrow] = useState<boolean>(false);
|
76 | const [activeView, setActiveView] = useState<VIEW>(VIEW.MONTH);
|
77 | const [visibleDate, setVisibleDate] = useState<string>(
|
78 | initVisibleDate ?? dayjs().local().format()
|
79 | );
|
80 |
|
81 | const localeAwareVisibleDate = React.useMemo(
|
82 | () => dayjs(visibleDate).locale(locale.name, locale),
|
83 | [locale, visibleDate]
|
84 | );
|
85 |
|
86 | const weekdays = React.useMemo(() => locale.weekdaysShort ?? [''], [locale]);
|
87 |
|
88 | const { month, year } = useSurroundingTimeUnits(visibleDate);
|
89 |
|
90 | const verifyUnitIsPastMaxDate = useCallback(
|
91 | (unit) => {
|
92 | if (maxDate) {
|
93 | if (unit.isAfter(maxDate)) {
|
94 | disableRightArrow(true);
|
95 | } else {
|
96 | disableRightArrow(false);
|
97 | }
|
98 | }
|
99 | },
|
100 | [disableRightArrow, maxDate]
|
101 | );
|
102 |
|
103 | const verifyUnitIsBeforeMinDate = useCallback(
|
104 | (unit) => {
|
105 | if (minDate) {
|
106 | if (unit.isBefore(minDate)) {
|
107 | disableLeftArrow(true);
|
108 | } else {
|
109 | disableLeftArrow(false);
|
110 | }
|
111 | }
|
112 | },
|
113 | [disableLeftArrow, minDate]
|
114 | );
|
115 |
|
116 | const toggleCalendarView = useCallback(
|
117 | (newVisibleDate: string = visibleDate) => {
|
118 | const { month: _month, year: _year } = getSurroundingTimeUnits(newVisibleDate);
|
119 |
|
120 | if (activeView === VIEW.MONTH) {
|
121 | setActiveView(VIEW.YEAR);
|
122 | verifyUnitIsPastMaxDate(_year.next);
|
123 | verifyUnitIsBeforeMinDate(_year.last);
|
124 | }
|
125 |
|
126 | if (activeView === VIEW.YEAR) {
|
127 | setActiveView(VIEW.MONTH);
|
128 | verifyUnitIsPastMaxDate(_month.next);
|
129 | verifyUnitIsBeforeMinDate(_month.last);
|
130 | }
|
131 | },
|
132 | [activeView, verifyUnitIsBeforeMinDate, verifyUnitIsPastMaxDate, visibleDate]
|
133 | );
|
134 |
|
135 | useEffect(() => {
|
136 | verifyUnitIsPastMaxDate(month.next.start);
|
137 | verifyUnitIsBeforeMinDate(month.last.end);
|
138 | }, [
|
139 | month.last.end,
|
140 | month.next.start,
|
141 | verifyUnitIsPastMaxDate,
|
142 | verifyUnitIsBeforeMinDate,
|
143 | ]);
|
144 |
|
145 | const addMonth = useCallback(() => {
|
146 | setVisibleDate(month.next.start.local().format());
|
147 | verifyUnitIsPastMaxDate(month.afterNext);
|
148 | verifyUnitIsBeforeMinDate(month.current.end);
|
149 | }, [month, verifyUnitIsBeforeMinDate, verifyUnitIsPastMaxDate]);
|
150 |
|
151 | const subtractMonth = useCallback(() => {
|
152 | setVisibleDate(month.last.start.local().format());
|
153 | verifyUnitIsPastMaxDate(month.current.start);
|
154 | verifyUnitIsBeforeMinDate(month.beforeLast);
|
155 | }, [month, verifyUnitIsBeforeMinDate, verifyUnitIsPastMaxDate]);
|
156 |
|
157 | const addYear = useCallback(() => {
|
158 | setVisibleDate(year.next.persistMonth.local().format());
|
159 | verifyUnitIsPastMaxDate(year.afterNext);
|
160 | verifyUnitIsBeforeMinDate(year.current.end);
|
161 | }, [year, verifyUnitIsBeforeMinDate, verifyUnitIsPastMaxDate]);
|
162 |
|
163 | const subtractYear = useCallback(() => {
|
164 | setVisibleDate(year.last.persistMonth.local().format());
|
165 | verifyUnitIsPastMaxDate(year.current.start);
|
166 | verifyUnitIsBeforeMinDate(year.beforeLast);
|
167 | }, [year, verifyUnitIsBeforeMinDate, verifyUnitIsPastMaxDate]);
|
168 |
|
169 | const onPressMonth = (newVisibleDate: string) => {
|
170 | setVisibleDate(newVisibleDate);
|
171 | toggleCalendarView(newVisibleDate);
|
172 | };
|
173 |
|
174 | const Weekdays = CustomWeekdays || DefaultWeekdays;
|
175 | const Arrow = CustomArrow || DefaultArrow;
|
176 | const Title = CustomTitle || DefaultTitle;
|
177 |
|
178 | return (
|
179 | <View style={theme.calendarContainer} testID={testID}>
|
180 | <View testID={'header'} style={theme.headerContainer}>
|
181 | <Arrow
|
182 | direction={'left'}
|
183 | isDisabled={leftArrowDisabled}
|
184 | onPress={activeView === VIEW.MONTH ? subtractMonth : subtractYear}
|
185 | />
|
186 | <Title
|
187 | activeView={activeView}
|
188 | locale={locale}
|
189 | date={localeAwareVisibleDate.format('YYYY-MM-DD')}
|
190 | isDisabled={!allowYearView}
|
191 | onPress={toggleCalendarView}
|
192 | />
|
193 | <Arrow
|
194 | direction={'right'}
|
195 | isDisabled={rightArrowDisabled}
|
196 | onPress={activeView === VIEW.MONTH ? addMonth : addYear}
|
197 | />
|
198 | </View>
|
199 | {activeView === VIEW.MONTH ? (
|
200 | <>
|
201 | <Weekdays days={weekdays} />
|
202 | <Days
|
203 | DayComponent={DayComponent}
|
204 | visibleDate={localeAwareVisibleDate}
|
205 | showExtraDates={!!showExtraDates}
|
206 | dateProperties={dateProperties}
|
207 | onPressDay={onPressDay}
|
208 | minDate={minDate}
|
209 | maxDate={maxDate}
|
210 | />
|
211 | </>
|
212 | ) : (
|
213 | <Months
|
214 | MonthComponent={MonthComponent}
|
215 | visibleDate={localeAwareVisibleDate}
|
216 | minDate={minDate ? dayjs(minDate).local().format() : undefined}
|
217 | maxDate={maxDate ? dayjs(maxDate).local().format() : undefined}
|
218 | dateProperties={dateProperties}
|
219 | onPressMonth={onPressMonth}
|
220 | />
|
221 | )}
|
222 | </View>
|
223 | );
|
224 | };
|
225 |
|
226 | export default BaseCalendar;
|