@hero-design/rn 8.104.1-alpha.2 → 8.105.0
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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +6 -10
- package/assets/fonts/hero-icons-mobile.ttf +0 -0
- package/es/index.js +683 -402
- package/lib/assets/fonts/hero-icons-mobile.ttf +0 -0
- package/lib/index.js +683 -401
- package/package.json +1 -1
- package/src/components/Calendar/CalendarRange.tsx +35 -117
- package/src/components/Calendar/__tests__/helper.spec.ts +197 -0
- package/src/components/Calendar/constants.ts +9 -0
- package/src/components/Calendar/helpers.ts +112 -0
- package/src/components/Calendar/index.tsx +34 -159
- package/src/components/Calendar/shared/hooks/useCalendarLayout.ts +37 -0
- package/src/components/Calendar/types.ts +62 -0
- package/src/components/DatePicker/DatePickerCalendar.tsx +2 -1
- package/src/components/Icon/HeroIcon/glyphMap.json +1 -1
- package/src/components/Icon/IconList.ts +2 -0
- package/src/components/SegmentedControl/SegmentedItem.tsx +192 -0
- package/src/components/SegmentedControl/StyledSegmentedControl.tsx +62 -0
- package/src/components/SegmentedControl/__tests__/SegmentedItem.spec.tsx +162 -0
- package/src/components/SegmentedControl/__tests__/__snapshots__/SegmentedItem.spec.tsx.snap +131 -0
- package/src/components/SegmentedControl/__tests__/__snapshots__/index.spec.tsx.snap +359 -0
- package/src/components/SegmentedControl/__tests__/index.spec.tsx +247 -0
- package/src/components/SegmentedControl/index.tsx +61 -0
- package/src/components/SegmentedControl/types.ts +46 -0
- package/src/components/Typography/Body/StyledBody.tsx +2 -2
- package/src/components/Typography/Body/__tests__/__snapshots__/index.spec.tsx.snap +51 -0
- package/src/components/Typography/Body/__tests__/index.spec.tsx +1 -0
- package/src/components/Typography/Body/index.tsx +2 -13
- package/src/components/Typography/Caption/StyledCaption.tsx +2 -2
- package/src/components/Typography/Caption/__tests__/__snapshots__/index.spec.tsx.snap +50 -0
- package/src/components/Typography/Caption/__tests__/index.spec.tsx +1 -0
- package/src/components/Typography/Caption/index.tsx +2 -13
- package/src/components/Typography/Label/StyledLabel.tsx +2 -2
- package/src/components/Typography/Label/__tests__/__snapshots__/index.spec.tsx.snap +48 -0
- package/src/components/Typography/Label/__tests__/index.spec.tsx +1 -0
- package/src/components/Typography/Label/index.tsx +2 -13
- package/src/components/Typography/Title/StyledTitle.tsx +2 -2
- package/src/components/Typography/Title/__tests__/__snapshots__/index.spec.tsx.snap +51 -0
- package/src/components/Typography/Title/__tests__/index.spec.tsx +1 -0
- package/src/components/Typography/Title/index.tsx +2 -13
- package/src/components/Typography/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +54 -0
- package/src/theme/components/segmentedControl.ts +60 -0
- package/src/theme/components/typography.ts +1 -0
- package/src/theme/getTheme.ts +3 -0
- package/src/types.ts +2 -0
- package/stats/8.104.0/rn-stats.html +3 -1
- package/stats/8.105.0/rn-stats.html +4844 -0
- package/types/components/Calendar/CalendarRange.d.ts +4 -16
- package/types/components/Calendar/constants.d.ts +4 -0
- package/types/components/Calendar/helpers.d.ts +14 -0
- package/types/components/Calendar/index.d.ts +5 -56
- package/types/components/Calendar/shared/hooks/useCalendarLayout.d.ts +8 -0
- package/types/components/Calendar/types.d.ts +58 -0
- package/types/components/Icon/IconList.d.ts +1 -1
- package/types/components/Icon/index.d.ts +1 -1
- package/types/components/SegmentedControl/SegmentedItem.d.ts +18 -0
- package/types/components/SegmentedControl/StyledSegmentedControl.d.ts +26 -0
- package/types/components/SegmentedControl/index.d.ts +31 -0
- package/types/components/SegmentedControl/types.d.ts +43 -0
- package/types/components/TextInput/index.d.ts +1 -1
- package/types/components/Typography/Body/StyledBody.d.ts +2 -2
- package/types/components/Typography/Body/index.d.ts +2 -1
- package/types/components/Typography/Caption/StyledCaption.d.ts +2 -2
- package/types/components/Typography/Caption/index.d.ts +2 -1
- package/types/components/Typography/Label/StyledLabel.d.ts +2 -2
- package/types/components/Typography/Label/index.d.ts +2 -1
- package/types/components/Typography/Title/StyledTitle.d.ts +2 -2
- package/types/components/Typography/Title/index.d.ts +2 -1
- package/types/components/Typography/types.d.ts +1 -1
- package/types/index.d.ts +2 -1
- package/types/theme/components/segmentedControl.d.ts +46 -0
- package/types/theme/components/typography.d.ts +1 -0
- package/types/theme/getTheme.d.ts +2 -0
- package/types/types.d.ts +2 -1
package/package.json
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
MonthYearPickerViewIOS,
|
|
4
4
|
} from '@hero-design/react-native-month-year-picker';
|
|
5
5
|
import format from 'date-fns/fp/format';
|
|
6
|
-
import React, {
|
|
7
|
-
import {
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
import { Platform, TouchableOpacity } from 'react-native';
|
|
8
8
|
import { useTheme } from '../../theme';
|
|
9
9
|
import { noop } from '../../utils/functions';
|
|
10
10
|
import Box from '../Box';
|
|
@@ -21,49 +21,23 @@ import {
|
|
|
21
21
|
StyledDisabledCalendarRowItem,
|
|
22
22
|
} from './StyledCalendar';
|
|
23
23
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
getCalendarButtonState,
|
|
25
|
+
getCalendarDate,
|
|
26
26
|
isDateInRange,
|
|
27
27
|
isEqDate,
|
|
28
28
|
setStartOrEndDate,
|
|
29
|
+
shouldUseMonthPicker,
|
|
29
30
|
} from './helpers';
|
|
30
31
|
import SelectedDate from './CalendarRangeConnector';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Sunday last column => 1
|
|
35
|
-
const WEEK_INDEX_OFFSET = 1;
|
|
36
|
-
const SUNDAY_INDEX = 6;
|
|
37
|
-
|
|
38
|
-
// Always render 7 rows x 6 items for consistent layout
|
|
39
|
-
const TOTAL_DATES_ITEMS = 7 * 6;
|
|
40
|
-
|
|
41
|
-
type ParsedMaskedDate = {
|
|
42
|
-
[key: string]: boolean;
|
|
43
|
-
};
|
|
32
|
+
import { CalendarProps } from './types';
|
|
33
|
+
import { DAYS_OF_WEEK } from './constants';
|
|
34
|
+
import { useCalendarLayout } from './shared/hooks/useCalendarLayout';
|
|
44
35
|
|
|
45
36
|
export type CalendarDateRange = {
|
|
46
37
|
startDate?: Date;
|
|
47
38
|
endDate?: Date;
|
|
48
39
|
};
|
|
49
40
|
|
|
50
|
-
export interface CalendarProps {
|
|
51
|
-
value?: CalendarDateRange;
|
|
52
|
-
visibleDate: Date;
|
|
53
|
-
minDate?: Date;
|
|
54
|
-
maxDate?: Date;
|
|
55
|
-
onChange?: (dateRange: CalendarDateRange) => void;
|
|
56
|
-
onPreviousPress?: () => void;
|
|
57
|
-
onNextPress?: () => void;
|
|
58
|
-
onTitlePress?: () => void;
|
|
59
|
-
markedDates?: Date[];
|
|
60
|
-
testID?: string;
|
|
61
|
-
onMonthChange?: (date: Date) => void;
|
|
62
|
-
onToggleMonthPicker?: (visible: boolean) => void;
|
|
63
|
-
monthPickerConfirmLabel?: string;
|
|
64
|
-
monthPickerCancelLabel?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
41
|
const CalendarRange = ({
|
|
68
42
|
value,
|
|
69
43
|
visibleDate,
|
|
@@ -79,87 +53,37 @@ const CalendarRange = ({
|
|
|
79
53
|
onToggleMonthPicker = noop,
|
|
80
54
|
monthPickerConfirmLabel,
|
|
81
55
|
monthPickerCancelLabel,
|
|
82
|
-
}: CalendarProps
|
|
56
|
+
}: Omit<CalendarProps, 'value' | 'onChange'> & {
|
|
57
|
+
value?: CalendarDateRange;
|
|
58
|
+
onChange?: (date: CalendarDateRange) => void;
|
|
59
|
+
}) => {
|
|
83
60
|
const theme = useTheme();
|
|
84
|
-
const currentMonth = visibleDate.getMonth();
|
|
85
|
-
const currentYear = visibleDate.getFullYear();
|
|
86
|
-
const now = new Date();
|
|
87
|
-
const parsedMaskedDate: ParsedMaskedDate = markedDates.reduce(
|
|
88
|
-
(current, markedDate) => ({
|
|
89
|
-
...current,
|
|
90
|
-
[markedDate.toDateString()]: true,
|
|
91
|
-
}),
|
|
92
|
-
{}
|
|
93
|
-
);
|
|
94
|
-
const [monthPickerVisible, setMonthPickerVisible] = useState(false);
|
|
95
|
-
const [contentHeight, setContentHeight] = useState(0);
|
|
96
|
-
const [contentWidth, setContentWidth] = useState(0);
|
|
97
|
-
const calendarItemWidth = useMemo(
|
|
98
|
-
() =>
|
|
99
|
-
contentWidth > 0
|
|
100
|
-
? Math.floor(
|
|
101
|
-
(contentWidth - theme.__hd__.calendar.space.cellPadding) / 7
|
|
102
|
-
)
|
|
103
|
-
: undefined,
|
|
104
|
-
[contentWidth, theme]
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const useMonthPicker = onMonthChange !== noop;
|
|
108
|
-
|
|
109
|
-
const firstDateOfMonth = new Date(currentYear, currentMonth, 1);
|
|
110
|
-
const lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0);
|
|
111
61
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
// Index of day in week is shifted by 1 due to Sunday is the last column
|
|
115
|
-
const firstDayWeekIndexOfMonth =
|
|
116
|
-
firstDateOfMonth.getDay() === 0
|
|
117
|
-
? SUNDAY_INDEX
|
|
118
|
-
: firstDateOfMonth.getDay() - WEEK_INDEX_OFFSET;
|
|
62
|
+
const [monthPickerVisible, setMonthPickerVisible] = useState(false);
|
|
119
63
|
|
|
120
|
-
const
|
|
121
|
-
|
|
64
|
+
const { onLayout, contentHeight, contentWidth, calendarItemWidth } =
|
|
65
|
+
useCalendarLayout();
|
|
122
66
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
67
|
+
const {
|
|
68
|
+
parsedMaskedDate,
|
|
69
|
+
daysOfPreviousMonth,
|
|
70
|
+
daysOfCurrentMonth,
|
|
71
|
+
daysOfNextMonth,
|
|
72
|
+
} = getCalendarDate({
|
|
73
|
+
visibleDate,
|
|
74
|
+
markedDates,
|
|
75
|
+
minDate,
|
|
76
|
+
maxDate,
|
|
77
|
+
onMonthChange,
|
|
131
78
|
});
|
|
79
|
+
const { disablePrevButton, disableNextButton } = getCalendarButtonState({
|
|
80
|
+
visibleDate,
|
|
81
|
+
minDate,
|
|
82
|
+
maxDate,
|
|
83
|
+
});
|
|
84
|
+
const shouldShowMonthPicker = shouldUseMonthPicker(onMonthChange);
|
|
132
85
|
|
|
133
|
-
const
|
|
134
|
-
getValidDate(
|
|
135
|
-
new Date(currentYear, currentMonth, index + 1),
|
|
136
|
-
minDate,
|
|
137
|
-
maxDate
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const daysOfNextMonth = initArray(
|
|
142
|
-
TOTAL_DATES_ITEMS -
|
|
143
|
-
(daysOfPreviousMonth.length + daysOfCurrentMonth.length),
|
|
144
|
-
(index) =>
|
|
145
|
-
getValidDate(
|
|
146
|
-
new Date(currentYear, currentMonth + 1, index + 1),
|
|
147
|
-
minDate,
|
|
148
|
-
maxDate
|
|
149
|
-
)
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const disablePrevButton =
|
|
153
|
-
minDate === undefined
|
|
154
|
-
? false
|
|
155
|
-
: !daysOfPreviousMonth.some((date) => date !== undefined) &&
|
|
156
|
-
minDate >= firstDateOfMonth;
|
|
157
|
-
|
|
158
|
-
const disableNextButton =
|
|
159
|
-
maxDate === undefined
|
|
160
|
-
? false
|
|
161
|
-
: !daysOfNextMonth.some((date) => date !== undefined) ||
|
|
162
|
-
maxDate <= lastDateOfMonth;
|
|
86
|
+
const now = new Date();
|
|
163
87
|
|
|
164
88
|
const onDateChange = (date: Date) => {
|
|
165
89
|
const newDateRange = setStartOrEndDate({
|
|
@@ -222,18 +146,12 @@ const CalendarRange = ({
|
|
|
222
146
|
);
|
|
223
147
|
};
|
|
224
148
|
|
|
225
|
-
const onLayout = (e: LayoutChangeEvent) => {
|
|
226
|
-
const { width, height } = e.nativeEvent.layout;
|
|
227
|
-
setContentHeight(height);
|
|
228
|
-
setContentWidth(width);
|
|
229
|
-
};
|
|
230
|
-
|
|
231
149
|
return (
|
|
232
150
|
<StyledContainer testID={testID}>
|
|
233
151
|
<StyledCalendarHeader>
|
|
234
152
|
<ContentNavigator
|
|
235
153
|
value={
|
|
236
|
-
!
|
|
154
|
+
!shouldShowMonthPicker ? (
|
|
237
155
|
format('MMMM yyyy', visibleDate)
|
|
238
156
|
) : (
|
|
239
157
|
<TouchableOpacity
|
|
@@ -269,7 +187,7 @@ const CalendarRange = ({
|
|
|
269
187
|
}
|
|
270
188
|
onPreviousPress={onPreviousPress}
|
|
271
189
|
onNextPress={onNextPress}
|
|
272
|
-
onPress={
|
|
190
|
+
onPress={shouldShowMonthPicker ? undefined : onTitlePress}
|
|
273
191
|
previousDisabled={disablePrevButton}
|
|
274
192
|
nextDisabled={disableNextButton}
|
|
275
193
|
fontSize="large"
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { noop } from '../../../utils/functions';
|
|
1
2
|
import {
|
|
3
|
+
getCalendarDate,
|
|
2
4
|
getValidDate,
|
|
3
5
|
initArray,
|
|
4
6
|
isDateInRange,
|
|
5
7
|
isEqDate,
|
|
6
8
|
setStartOrEndDate,
|
|
9
|
+
shouldUseMonthPicker,
|
|
10
|
+
getCalendarButtonState,
|
|
7
11
|
} from '../helpers';
|
|
8
12
|
|
|
9
13
|
describe('initArray', () => {
|
|
@@ -176,3 +180,196 @@ describe('setStartOrEndDate', () => {
|
|
|
176
180
|
});
|
|
177
181
|
});
|
|
178
182
|
});
|
|
183
|
+
|
|
184
|
+
describe('shouldUseMonthPicker', () => {
|
|
185
|
+
it('returns correct value', () => {
|
|
186
|
+
const result = shouldUseMonthPicker(noop);
|
|
187
|
+
expect(result).toEqual(false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('getCalendarDate', () => {
|
|
192
|
+
const mockDate = new Date('2025-08-15');
|
|
193
|
+
const mockMarkedDates = [new Date('2025-08-10'), new Date('2025-08-20')];
|
|
194
|
+
|
|
195
|
+
it('should calculate correct dates for current month', () => {
|
|
196
|
+
const result = getCalendarDate({
|
|
197
|
+
visibleDate: mockDate,
|
|
198
|
+
markedDates: [],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result.firstDateOfMonth).toEqual(new Date('2025-08-01'));
|
|
202
|
+
expect(result.lastDateOfMonth).toEqual(new Date('2025-08-31'));
|
|
203
|
+
expect(result.daysOfCurrentMonth).toHaveLength(31);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle marked dates correctly', () => {
|
|
207
|
+
const result = getCalendarDate({
|
|
208
|
+
visibleDate: mockDate,
|
|
209
|
+
markedDates: mockMarkedDates,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(result.parsedMaskedDate[mockMarkedDates[0].toDateString()]).toBe(
|
|
213
|
+
true
|
|
214
|
+
);
|
|
215
|
+
expect(result.parsedMaskedDate[mockMarkedDates[1].toDateString()]).toBe(
|
|
216
|
+
true
|
|
217
|
+
);
|
|
218
|
+
expect(result.parsedMaskedDate[new Date('2025-08-15').toDateString()]).toBe(
|
|
219
|
+
undefined
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should calculate correct dates for previous month overflow', () => {
|
|
224
|
+
const result = getCalendarDate({
|
|
225
|
+
visibleDate: mockDate,
|
|
226
|
+
markedDates: [],
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// August 1, 2025 is a Friday, so we should have 4 days from July
|
|
230
|
+
expect(result.daysOfPreviousMonth).toHaveLength(4);
|
|
231
|
+
|
|
232
|
+
// First visible date should be July 28, 2025
|
|
233
|
+
const firstVisibleDate = result.daysOfPreviousMonth[0];
|
|
234
|
+
expect(firstVisibleDate?.getFullYear()).toBe(2025);
|
|
235
|
+
expect(firstVisibleDate?.getMonth()).toBe(6); // 6 = July
|
|
236
|
+
expect(firstVisibleDate?.getDate()).toBe(28);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should calculate correct dates for next month overflow', () => {
|
|
240
|
+
const result = getCalendarDate({
|
|
241
|
+
visibleDate: mockDate,
|
|
242
|
+
markedDates: [],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// August 31, 2025 is a Sunday, so we should have 7 days from September
|
|
246
|
+
expect(result.daysOfNextMonth).toHaveLength(7);
|
|
247
|
+
|
|
248
|
+
// First date of next month should be September 1, 2025
|
|
249
|
+
const firstNextMonthDate = result.daysOfNextMonth[0];
|
|
250
|
+
expect(firstNextMonthDate?.getFullYear()).toBe(2025);
|
|
251
|
+
expect(firstNextMonthDate?.getMonth()).toBe(8); // 8 = September
|
|
252
|
+
expect(firstNextMonthDate?.getDate()).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should respect minDate constraint', () => {
|
|
256
|
+
const result = getCalendarDate({
|
|
257
|
+
visibleDate: mockDate,
|
|
258
|
+
markedDates: [],
|
|
259
|
+
minDate: new Date('2025-08-01'), // Set minDate to start of August
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// All dates from previous month should be undefined
|
|
263
|
+
const invalidDate = result.daysOfPreviousMonth[0];
|
|
264
|
+
expect(invalidDate).toBeUndefined();
|
|
265
|
+
|
|
266
|
+
// First date of current month should be valid
|
|
267
|
+
const validDate = result.daysOfCurrentMonth[0];
|
|
268
|
+
expect(validDate).toBeDefined();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should respect maxDate constraint', () => {
|
|
272
|
+
const result = getCalendarDate({
|
|
273
|
+
visibleDate: mockDate,
|
|
274
|
+
markedDates: [],
|
|
275
|
+
maxDate: new Date('2025-08-31'), // Set maxDate to end of August
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Current month dates should be valid
|
|
279
|
+
const validDate = result.daysOfCurrentMonth[0];
|
|
280
|
+
expect(validDate).toBeDefined();
|
|
281
|
+
|
|
282
|
+
// All dates from next month should be undefined
|
|
283
|
+
const invalidDate = result.daysOfNextMonth[0];
|
|
284
|
+
expect(invalidDate).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should handle edge case when month starts on Sunday', () => {
|
|
288
|
+
// Using March 2026 which starts on Sunday
|
|
289
|
+
const sundayDate = new Date('2026-03-01');
|
|
290
|
+
const result = getCalendarDate({
|
|
291
|
+
visibleDate: sundayDate,
|
|
292
|
+
markedDates: [],
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// When month starts on Sunday, there should be 6 days from previous month
|
|
296
|
+
expect(result.daysOfPreviousMonth).toHaveLength(6);
|
|
297
|
+
expect(result.firstDateOfMonth).toEqual(new Date('2026-03-01'));
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('calculateCalendarNavigationButtons', () => {
|
|
302
|
+
const mockVisibleDate = new Date('2025-08-15');
|
|
303
|
+
|
|
304
|
+
it('should enable both buttons when no min/max dates are set', () => {
|
|
305
|
+
const result = getCalendarButtonState({
|
|
306
|
+
visibleDate: mockVisibleDate,
|
|
307
|
+
markedDates: [],
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(result.disablePrevButton).toBe(false);
|
|
311
|
+
expect(result.disableNextButton).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should disable prev button when minDate is set and no valid previous days', () => {
|
|
315
|
+
const minDate = new Date('2025-08-01');
|
|
316
|
+
const result = getCalendarButtonState({
|
|
317
|
+
visibleDate: mockVisibleDate,
|
|
318
|
+
markedDates: [],
|
|
319
|
+
minDate,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(result.disablePrevButton).toBe(true);
|
|
323
|
+
expect(result.disableNextButton).toBe(false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should disable next button when maxDate is set and no valid next days', () => {
|
|
327
|
+
const maxDate = new Date('2025-08-31');
|
|
328
|
+
const result = getCalendarButtonState({
|
|
329
|
+
visibleDate: mockVisibleDate,
|
|
330
|
+
markedDates: [],
|
|
331
|
+
maxDate,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(result.disablePrevButton).toBe(false);
|
|
335
|
+
expect(result.disableNextButton).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should enable prev button when minDate is set but valid previous days exist', () => {
|
|
339
|
+
const minDate = new Date('2025-07-15');
|
|
340
|
+
const result = getCalendarButtonState({
|
|
341
|
+
visibleDate: mockVisibleDate,
|
|
342
|
+
markedDates: [],
|
|
343
|
+
minDate,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(result.disablePrevButton).toBe(false);
|
|
347
|
+
expect(result.disableNextButton).toBe(false);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should enable next button when maxDate is set but valid next days exist', () => {
|
|
351
|
+
const maxDate = new Date('2025-09-15');
|
|
352
|
+
const result = getCalendarButtonState({
|
|
353
|
+
visibleDate: mockVisibleDate,
|
|
354
|
+
markedDates: [],
|
|
355
|
+
maxDate,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(result.disablePrevButton).toBe(false);
|
|
359
|
+
expect(result.disableNextButton).toBe(false);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should handle both min and max dates together', () => {
|
|
363
|
+
const minDate = new Date('2025-08-01');
|
|
364
|
+
const maxDate = new Date('2025-08-31');
|
|
365
|
+
const result = getCalendarButtonState({
|
|
366
|
+
visibleDate: mockVisibleDate,
|
|
367
|
+
markedDates: [],
|
|
368
|
+
minDate,
|
|
369
|
+
maxDate,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(result.disablePrevButton).toBe(true);
|
|
373
|
+
expect(result.disableNextButton).toBe(true);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const DAYS_OF_WEEK = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
|
|
2
|
+
|
|
3
|
+
// Sunday first column => 0
|
|
4
|
+
// Sunday last column => 1
|
|
5
|
+
export const WEEK_INDEX_OFFSET = 1;
|
|
6
|
+
export const SUNDAY_INDEX = 6;
|
|
7
|
+
|
|
8
|
+
// Always render 7 rows x 6 items for consistent layout
|
|
9
|
+
export const TOTAL_DATES_ITEMS = 7 * 6;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { noop } from '../../utils/functions';
|
|
2
|
+
import {
|
|
3
|
+
SUNDAY_INDEX,
|
|
4
|
+
TOTAL_DATES_ITEMS,
|
|
5
|
+
WEEK_INDEX_OFFSET,
|
|
6
|
+
} from './constants';
|
|
7
|
+
import { CalendarProps, ParsedMaskedDate } from './types';
|
|
8
|
+
|
|
1
9
|
export const initArray = <T>(length: number, func: (value: number) => T): T[] =>
|
|
2
10
|
Array.from(Array(length)).map((_, index) => func(index));
|
|
3
11
|
|
|
@@ -101,3 +109,107 @@ export const setStartOrEndDate = ({
|
|
|
101
109
|
endDate: undefined,
|
|
102
110
|
};
|
|
103
111
|
};
|
|
112
|
+
|
|
113
|
+
export const shouldUseMonthPicker = (
|
|
114
|
+
onMonthChange: CalendarProps['onMonthChange']
|
|
115
|
+
) => onMonthChange !== noop;
|
|
116
|
+
|
|
117
|
+
export const getCalendarDate = ({
|
|
118
|
+
visibleDate,
|
|
119
|
+
markedDates = [],
|
|
120
|
+
minDate,
|
|
121
|
+
maxDate,
|
|
122
|
+
}: CalendarProps) => {
|
|
123
|
+
const currentMonth = visibleDate.getMonth();
|
|
124
|
+
const currentYear = visibleDate.getFullYear();
|
|
125
|
+
const parsedMaskedDate: ParsedMaskedDate = markedDates.reduce(
|
|
126
|
+
(current, markedDate) => ({
|
|
127
|
+
...current,
|
|
128
|
+
[markedDate.toDateString()]: true,
|
|
129
|
+
}),
|
|
130
|
+
{}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const firstDateOfMonth = new Date(currentYear, currentMonth, 1);
|
|
134
|
+
const lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0);
|
|
135
|
+
|
|
136
|
+
const lastDateOfPreviousMonth = new Date(currentYear, currentMonth, 0);
|
|
137
|
+
|
|
138
|
+
// Index of day in week is shifted by 1 due to Sunday is the last column
|
|
139
|
+
const firstDayWeekIndexOfMonth =
|
|
140
|
+
firstDateOfMonth.getDay() === 0
|
|
141
|
+
? SUNDAY_INDEX
|
|
142
|
+
: firstDateOfMonth.getDay() - WEEK_INDEX_OFFSET;
|
|
143
|
+
|
|
144
|
+
const lastDayIndexOfCurrentMonth = lastDateOfMonth.getDate();
|
|
145
|
+
const lastDayIndexOfPreviousMonth = lastDateOfPreviousMonth.getDate();
|
|
146
|
+
|
|
147
|
+
const daysOfPreviousMonth = initArray(firstDayWeekIndexOfMonth, (index) => {
|
|
148
|
+
const reversedIndex = firstDayWeekIndexOfMonth - index - 1;
|
|
149
|
+
const count = lastDayIndexOfPreviousMonth - reversedIndex;
|
|
150
|
+
return getValidDate(
|
|
151
|
+
new Date(currentYear, currentMonth - 1, count),
|
|
152
|
+
minDate,
|
|
153
|
+
maxDate
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const daysOfCurrentMonth = initArray(lastDayIndexOfCurrentMonth, (index) =>
|
|
158
|
+
getValidDate(
|
|
159
|
+
new Date(currentYear, currentMonth, index + 1),
|
|
160
|
+
minDate,
|
|
161
|
+
maxDate
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const daysOfNextMonth = initArray(
|
|
166
|
+
TOTAL_DATES_ITEMS -
|
|
167
|
+
(daysOfPreviousMonth.length + daysOfCurrentMonth.length),
|
|
168
|
+
(index) =>
|
|
169
|
+
getValidDate(
|
|
170
|
+
new Date(currentYear, currentMonth + 1, index + 1),
|
|
171
|
+
minDate,
|
|
172
|
+
maxDate
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
firstDateOfMonth,
|
|
178
|
+
lastDateOfMonth,
|
|
179
|
+
parsedMaskedDate,
|
|
180
|
+
daysOfPreviousMonth,
|
|
181
|
+
daysOfCurrentMonth,
|
|
182
|
+
daysOfNextMonth,
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const getCalendarButtonState = ({
|
|
187
|
+
visibleDate,
|
|
188
|
+
markedDates,
|
|
189
|
+
minDate,
|
|
190
|
+
maxDate,
|
|
191
|
+
}: CalendarProps) => {
|
|
192
|
+
const {
|
|
193
|
+
daysOfPreviousMonth,
|
|
194
|
+
daysOfNextMonth,
|
|
195
|
+
firstDateOfMonth,
|
|
196
|
+
lastDateOfMonth,
|
|
197
|
+
} = getCalendarDate({ visibleDate, markedDates, minDate, maxDate });
|
|
198
|
+
|
|
199
|
+
const disablePrevButton =
|
|
200
|
+
minDate === undefined
|
|
201
|
+
? false
|
|
202
|
+
: !daysOfPreviousMonth.some((date) => date !== undefined) &&
|
|
203
|
+
minDate >= firstDateOfMonth;
|
|
204
|
+
|
|
205
|
+
const disableNextButton =
|
|
206
|
+
maxDate === undefined
|
|
207
|
+
? false
|
|
208
|
+
: !daysOfNextMonth.some((date) => date !== undefined) ||
|
|
209
|
+
maxDate <= lastDateOfMonth;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
disablePrevButton,
|
|
213
|
+
disableNextButton,
|
|
214
|
+
};
|
|
215
|
+
};
|