UNPKG

6.99 kBTypeScriptView Raw
1import React, { useEffect, useState, useCallback } from 'react';
2import { View } from 'react-native';
3import dayjs from 'dayjs';
4
5import localeData from 'dayjs/plugin/localeData';
6import localizedFormat from 'dayjs/plugin/localizedFormat';
7import utc from 'dayjs/plugin/utc';
8
9import { getSurroundingTimeUnits } from '../Utils';
10import { useSurroundingTimeUnits } from '../Hooks';
11import { ThemeContext, LocaleContext } from '../Contexts';
12import { VIEW } from '../Constants';
13
14import {
15 Months,
16 Days,
17 Arrow as DefaultArrow,
18 Title as DefaultTitle,
19 Weekdays as DefaultWeekdays,
20} from '../Components';
21
22import type {
23 Theme,
24 Locale,
25 ArrowComponentType,
26 TitleComponentType,
27 DayComponentType,
28 MonthComponentType,
29 WeekdaysComponentType,
30} from '../Entities';
31
32dayjs.extend(localeData);
33dayjs.extend(localizedFormat);
34dayjs.extend(utc);
35
36export 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; // Boolean that disables the calendar year view
45 onPressDay: (date: string) => void; // Recieves a date format string
46 minDate?: string; // YYYY-MM-DD format string respresenting the minimum date that can be selected
47 maxDate?: string; // YYYY-MM-DD format string respresenting the maximum date that can be selected
48 initVisibleDate?: string; // YYYY-MM-DD format string respresenting the date that should be visible when the calendar first renders
49 dateProperties: {
50 [date: string /* YYYY-MM-DD */]: {
51 isSelected?: boolean;
52 };
53 };
54}
55
56const 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
226export default BaseCalendar;