Skip to content

Commit 48893d5

Browse files
authored
Merge pull request #88119 from mukhrr/fix/87452
2 parents d985682 + e8332cd commit 48893d5

4 files changed

Lines changed: 279 additions & 117 deletions

File tree

src/components/DatePicker/CalendarPicker/MonthPickerModal.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,20 @@ type MonthPickerModalProps = {
1717
/** Currently selected month (0-indexed) */
1818
currentMonth?: number;
1919

20-
/** The year currently being viewed */
21-
currentYear?: number;
22-
23-
/** A minimum date (earliest) allowed to select */
24-
minDate?: Date;
25-
26-
/** A maximum date (latest) allowed to select */
27-
maxDate?: Date;
28-
2920
/** Function to call when the user selects a month */
3021
onMonthChange?: (month: number) => void;
3122

3223
/** Function to call when the user closes the month picker */
3324
onClose?: () => void;
3425
};
3526

36-
function MonthPickerModal({isVisible, currentMonth = new Date().getMonth(), currentYear = new Date().getFullYear(), minDate, maxDate, onMonthChange, onClose}: MonthPickerModalProps) {
27+
function MonthPickerModal({isVisible, currentMonth = new Date().getMonth(), onMonthChange, onClose}: MonthPickerModalProps) {
3728
const styles = useThemeStyles();
3829
const {translate} = useLocalize();
3930
const [searchText, setSearchText] = useState('');
4031
const monthNames = DateUtils.getMonthNames();
4132

42-
const allMonths = useMemo(() => DateUtils.getFilteredMonthItems(monthNames, currentYear, currentMonth, minDate, maxDate), [monthNames, currentMonth, currentYear, minDate, maxDate]);
33+
const allMonths = useMemo(() => DateUtils.getFilteredMonthItems(monthNames, currentMonth), [monthNames, currentMonth]);
4334

4435
const {data, headerMessage} = useMemo(() => {
4536
const filteredMonths = searchText === '' ? allMonths : allMonths.filter((month) => month.text.toLowerCase().includes(searchText.toLowerCase()));

src/components/DatePicker/CalendarPicker/index.tsx

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
1-
import {
2-
addMonths,
3-
addYears,
4-
endOfDay,
5-
endOfMonth,
6-
endOfYear,
7-
format,
8-
getYear,
9-
isSameDay,
10-
parseISO,
11-
setDate,
12-
setMonth,
13-
setYear,
14-
startOfDay,
15-
startOfMonth,
16-
startOfYear,
17-
subMonths,
18-
subYears,
19-
} from 'date-fns';
1+
import {addMonths, addYears, format, isSameDay, parseISO, setDate, setMonth, setYear, startOfDay, subMonths, subYears} from 'date-fns';
202
import {Str} from 'expensify-common';
213
import React, {useCallback, useEffect, useRef, useState} from 'react';
224
import type {StyleProp, ViewStyle} from 'react-native';
@@ -99,8 +81,8 @@ function CalendarPicker({
9981
const initialHeight = (calendarDaysMatrix?.length || CONST.MAX_CALENDAR_PICKER_ROWS) * CONST.CALENDAR_PICKER_DAY_HEIGHT;
10082
const heightValue = useSharedValue(initialHeight);
10183

102-
const minYear = getYear(new Date(minDate));
103-
const maxYear = getYear(new Date(maxDate));
84+
const minYear = CONST.CALENDAR_PICKER.MIN_YEAR;
85+
const maxYear = CONST.CALENDAR_PICKER.MAX_YEAR;
10486

10587
const [years, setYears] = useState<CalendarPickerListItem[]>(() =>
10688
Array.from({length: maxYear - minYear + 1}, (v, i) => i + minYear).map((year) => ({
@@ -140,12 +122,20 @@ function CalendarPicker({
140122
onSelected?.(format(newCurrentDateView, CONST.DATE.FNS_FORMAT_STRING));
141123
};
142124

125+
const isAtMinBoundary = currentYearView <= CONST.CALENDAR_PICKER.MIN_YEAR && currentMonthView === 0;
126+
const isAtMaxBoundary = currentYearView >= CONST.CALENDAR_PICKER.MAX_YEAR && currentMonthView === 11;
127+
const isAtMinYear = currentYearView <= CONST.CALENDAR_PICKER.MIN_YEAR;
128+
const isAtMaxYear = currentYearView >= CONST.CALENDAR_PICKER.MAX_YEAR;
129+
143130
/**
144131
* Handles the user pressing the previous month arrow of the calendar picker.
145132
*/
146133
const moveToPrevMonth = () => {
147134
setCurrentDateView((prev) => {
148135
const prevMonth = subMonths(new Date(prev), 1);
136+
if (prevMonth.getFullYear() < CONST.CALENDAR_PICKER.MIN_YEAR) {
137+
return prev;
138+
}
149139
// if year is subtracted, we need to update the years list
150140
if (prevMonth.getFullYear() < prev.getFullYear()) {
151141
setYears((prevYears) =>
@@ -165,6 +155,9 @@ function CalendarPicker({
165155
const moveToNextMonth = () => {
166156
setCurrentDateView((prev) => {
167157
const nextMonth = addMonths(new Date(prev), 1);
158+
if (nextMonth.getFullYear() > CONST.CALENDAR_PICKER.MAX_YEAR) {
159+
return prev;
160+
}
168161
// if year is added, we need to update the years list
169162
if (nextMonth.getFullYear() > prev.getFullYear()) {
170163
setYears((prevYears) =>
@@ -181,9 +174,9 @@ function CalendarPicker({
181174

182175
const moveToPrevYear = () => {
183176
setCurrentDateView((prev) => {
184-
let prevYear = subYears(new Date(prev), 1);
185-
if (prevYear < new Date(minDate)) {
186-
prevYear = new Date(minDate);
177+
const prevYear = subYears(new Date(prev), 1);
178+
if (prevYear.getFullYear() < CONST.CALENDAR_PICKER.MIN_YEAR) {
179+
return prev;
187180
}
188181
setYears((prevYears) => prevYears.map((item) => ({...item, isSelected: item.value === prevYear.getFullYear()})));
189182
return prevYear;
@@ -192,9 +185,9 @@ function CalendarPicker({
192185

193186
const moveToNextYear = () => {
194187
setCurrentDateView((prev) => {
195-
let nextYear = addYears(new Date(prev), 1);
196-
if (nextYear > new Date(maxDate)) {
197-
nextYear = new Date(maxDate);
188+
const nextYear = addYears(new Date(prev), 1);
189+
if (nextYear.getFullYear() > CONST.CALENDAR_PICKER.MAX_YEAR) {
190+
return prev;
198191
}
199192
setYears((prevYears) => prevYears.map((item) => ({...item, isSelected: item.value === nextYear.getFullYear()})));
200193
return nextYear;
@@ -203,11 +196,6 @@ function CalendarPicker({
203196

204197
const monthNames = DateUtils.getMonthNames().map((month) => Str.UCFirst(month));
205198
const daysOfWeek = DateUtils.getDaysOfWeek().map((day) => day.toUpperCase());
206-
const hasAvailableDatesNextMonth = startOfDay(new Date(maxDate)) > endOfMonth(new Date(currentDateView));
207-
const hasAvailableDatesPrevMonth = endOfDay(new Date(minDate)) < startOfMonth(new Date(currentDateView));
208-
const hasAvailableDatesNextYear = startOfDay(new Date(maxDate)) > endOfYear(new Date(currentDateView));
209-
const hasAvailableDatesPrevYear = endOfDay(new Date(minDate)) < startOfYear(new Date(currentDateView));
210-
211199
useEffect(() => {
212200
if (isSmallScreenWidth || isFirstRender.current) {
213201
isFirstRender.current = false;
@@ -247,15 +235,15 @@ function CalendarPicker({
247235
<PressableWithFeedback
248236
shouldUseAutoHitSlop={false}
249237
testID="prev-month-arrow"
250-
disabled={!hasAvailableDatesPrevMonth}
238+
disabled={isAtMinBoundary}
251239
onPress={moveToPrevMonth}
252240
hoverDimmingValue={1}
253241
accessibilityLabel={translate('common.previousMonth')}
254242
role={CONST.ROLE.BUTTON}
255243
sentryLabel={CONST.SENTRY_LABEL.CALENDAR_PICKER.PREV_MONTH}
256244
>
257245
<ArrowIcon
258-
disabled={!hasAvailableDatesPrevMonth}
246+
disabled={isAtMinBoundary}
259247
direction={CONST.DIRECTION.LEFT}
260248
/>
261249
</PressableWithFeedback>
@@ -286,29 +274,29 @@ function CalendarPicker({
286274
<PressableWithFeedback
287275
shouldUseAutoHitSlop={false}
288276
testID="next-month-arrow"
289-
disabled={!hasAvailableDatesNextMonth}
277+
disabled={isAtMaxBoundary}
290278
onPress={moveToNextMonth}
291279
hoverDimmingValue={1}
292280
accessibilityLabel={translate('common.nextMonth')}
293281
role={CONST.ROLE.BUTTON}
294282
sentryLabel={CONST.SENTRY_LABEL.CALENDAR_PICKER.NEXT_MONTH}
295283
>
296-
<ArrowIcon disabled={!hasAvailableDatesNextMonth} />
284+
<ArrowIcon disabled={isAtMaxBoundary} />
297285
</PressableWithFeedback>
298286
</View>
299287
<View style={[themeStyles.alignItemsCenter, themeStyles.flexRow, {flex: 2}]}>
300288
<PressableWithFeedback
301289
shouldUseAutoHitSlop={false}
302290
testID="prev-year-arrow"
303-
disabled={!hasAvailableDatesPrevYear}
291+
disabled={isAtMinYear}
304292
onPress={moveToPrevYear}
305293
hoverDimmingValue={1}
306294
accessibilityLabel={translate('common.previousYear')}
307295
role={CONST.ROLE.BUTTON}
308296
sentryLabel={CONST.SENTRY_LABEL.CALENDAR_PICKER.PREV_YEAR}
309297
>
310298
<ArrowIcon
311-
disabled={!hasAvailableDatesPrevYear}
299+
disabled={isAtMinYear}
312300
direction={CONST.DIRECTION.LEFT}
313301
/>
314302
</PressableWithFeedback>
@@ -322,7 +310,6 @@ function CalendarPicker({
322310
style={[themeStyles.alignItemsCenter]}
323311
wrapperStyle={[themeStyles.alignItemsCenter]}
324312
hoverDimmingValue={1}
325-
disabled={years.length <= 1}
326313
testID="currentYearButton"
327314
accessibilityLabel={`${currentYearView}, ${translate('common.currentYear')}`}
328315
role={CONST.ROLE.BUTTON}
@@ -339,14 +326,14 @@ function CalendarPicker({
339326
<PressableWithFeedback
340327
shouldUseAutoHitSlop={false}
341328
testID="next-year-arrow"
342-
disabled={!hasAvailableDatesNextYear}
329+
disabled={isAtMaxYear}
343330
onPress={moveToNextYear}
344331
hoverDimmingValue={1}
345332
accessibilityLabel={translate('common.nextYear')}
346333
role={CONST.ROLE.BUTTON}
347334
sentryLabel={CONST.SENTRY_LABEL.CALENDAR_PICKER.NEXT_YEAR}
348335
>
349-
<ArrowIcon disabled={!hasAvailableDatesNextYear} />
336+
<ArrowIcon disabled={isAtMaxYear} />
350337
</PressableWithFeedback>
351338
</View>
352339
</View>
@@ -432,9 +419,6 @@ function CalendarPicker({
432419
<MonthPickerModal
433420
isVisible={isMonthPickerVisible}
434421
currentMonth={currentMonthView}
435-
currentYear={currentYearView}
436-
minDate={minDate}
437-
maxDate={maxDate}
438422
onMonthChange={onMonthSelected}
439423
onClose={() => setIsMonthPickerVisible(false)}
440424
/>

src/libs/DateUtils.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
parse,
2626
set,
2727
startOfDay,
28-
startOfMonth,
2928
startOfWeek,
3029
subDays,
3130
subMilliseconds,
@@ -306,29 +305,15 @@ function getMonthNames(): string[] {
306305
}
307306

308307
/**
309-
* Filters month names by min/max date boundaries and returns list items for SelectionList.
308+
* Returns month list items for SelectionList.
310309
*/
311-
function getFilteredMonthItems(monthNames: string[], currentYear: number, currentMonth: number, minDate?: Date, maxDate?: Date) {
312-
const minMonthStart = minDate ? startOfMonth(new Date(minDate)) : undefined;
313-
const maxMonthEnd = maxDate ? endOfMonth(new Date(maxDate)) : undefined;
314-
315-
return monthNames
316-
.map((month, index) => {
317-
const monthStart = startOfMonth(new Date(currentYear, index));
318-
const monthEnd = endOfMonth(new Date(currentYear, index));
319-
const isBeforeMin = minMonthStart ? monthEnd < minMonthStart : false;
320-
const isAfterMax = maxMonthEnd ? monthStart > maxMonthEnd : false;
321-
if (isBeforeMin || isAfterMax) {
322-
return null;
323-
}
324-
return {
325-
text: month.charAt(0).toUpperCase() + month.slice(1),
326-
value: index,
327-
keyForList: index.toString(),
328-
isSelected: index === currentMonth,
329-
};
330-
})
331-
.filter((item): item is NonNullable<typeof item> => item !== null);
310+
function getFilteredMonthItems(monthNames: string[], currentMonth: number) {
311+
return monthNames.map((month, index) => ({
312+
text: month.charAt(0).toUpperCase() + month.slice(1),
313+
value: index,
314+
keyForList: index.toString(),
315+
isSelected: index === currentMonth,
316+
}));
332317
}
333318

334319
/**

0 commit comments

Comments
 (0)