@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.
Files changed (77) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/CHANGELOG.md +6 -10
  3. package/assets/fonts/hero-icons-mobile.ttf +0 -0
  4. package/es/index.js +683 -402
  5. package/lib/assets/fonts/hero-icons-mobile.ttf +0 -0
  6. package/lib/index.js +683 -401
  7. package/package.json +1 -1
  8. package/src/components/Calendar/CalendarRange.tsx +35 -117
  9. package/src/components/Calendar/__tests__/helper.spec.ts +197 -0
  10. package/src/components/Calendar/constants.ts +9 -0
  11. package/src/components/Calendar/helpers.ts +112 -0
  12. package/src/components/Calendar/index.tsx +34 -159
  13. package/src/components/Calendar/shared/hooks/useCalendarLayout.ts +37 -0
  14. package/src/components/Calendar/types.ts +62 -0
  15. package/src/components/DatePicker/DatePickerCalendar.tsx +2 -1
  16. package/src/components/Icon/HeroIcon/glyphMap.json +1 -1
  17. package/src/components/Icon/IconList.ts +2 -0
  18. package/src/components/SegmentedControl/SegmentedItem.tsx +192 -0
  19. package/src/components/SegmentedControl/StyledSegmentedControl.tsx +62 -0
  20. package/src/components/SegmentedControl/__tests__/SegmentedItem.spec.tsx +162 -0
  21. package/src/components/SegmentedControl/__tests__/__snapshots__/SegmentedItem.spec.tsx.snap +131 -0
  22. package/src/components/SegmentedControl/__tests__/__snapshots__/index.spec.tsx.snap +359 -0
  23. package/src/components/SegmentedControl/__tests__/index.spec.tsx +247 -0
  24. package/src/components/SegmentedControl/index.tsx +61 -0
  25. package/src/components/SegmentedControl/types.ts +46 -0
  26. package/src/components/Typography/Body/StyledBody.tsx +2 -2
  27. package/src/components/Typography/Body/__tests__/__snapshots__/index.spec.tsx.snap +51 -0
  28. package/src/components/Typography/Body/__tests__/index.spec.tsx +1 -0
  29. package/src/components/Typography/Body/index.tsx +2 -13
  30. package/src/components/Typography/Caption/StyledCaption.tsx +2 -2
  31. package/src/components/Typography/Caption/__tests__/__snapshots__/index.spec.tsx.snap +50 -0
  32. package/src/components/Typography/Caption/__tests__/index.spec.tsx +1 -0
  33. package/src/components/Typography/Caption/index.tsx +2 -13
  34. package/src/components/Typography/Label/StyledLabel.tsx +2 -2
  35. package/src/components/Typography/Label/__tests__/__snapshots__/index.spec.tsx.snap +48 -0
  36. package/src/components/Typography/Label/__tests__/index.spec.tsx +1 -0
  37. package/src/components/Typography/Label/index.tsx +2 -13
  38. package/src/components/Typography/Title/StyledTitle.tsx +2 -2
  39. package/src/components/Typography/Title/__tests__/__snapshots__/index.spec.tsx.snap +51 -0
  40. package/src/components/Typography/Title/__tests__/index.spec.tsx +1 -0
  41. package/src/components/Typography/Title/index.tsx +2 -13
  42. package/src/components/Typography/types.ts +3 -2
  43. package/src/index.ts +2 -0
  44. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +54 -0
  45. package/src/theme/components/segmentedControl.ts +60 -0
  46. package/src/theme/components/typography.ts +1 -0
  47. package/src/theme/getTheme.ts +3 -0
  48. package/src/types.ts +2 -0
  49. package/stats/8.104.0/rn-stats.html +3 -1
  50. package/stats/8.105.0/rn-stats.html +4844 -0
  51. package/types/components/Calendar/CalendarRange.d.ts +4 -16
  52. package/types/components/Calendar/constants.d.ts +4 -0
  53. package/types/components/Calendar/helpers.d.ts +14 -0
  54. package/types/components/Calendar/index.d.ts +5 -56
  55. package/types/components/Calendar/shared/hooks/useCalendarLayout.d.ts +8 -0
  56. package/types/components/Calendar/types.d.ts +58 -0
  57. package/types/components/Icon/IconList.d.ts +1 -1
  58. package/types/components/Icon/index.d.ts +1 -1
  59. package/types/components/SegmentedControl/SegmentedItem.d.ts +18 -0
  60. package/types/components/SegmentedControl/StyledSegmentedControl.d.ts +26 -0
  61. package/types/components/SegmentedControl/index.d.ts +31 -0
  62. package/types/components/SegmentedControl/types.d.ts +43 -0
  63. package/types/components/TextInput/index.d.ts +1 -1
  64. package/types/components/Typography/Body/StyledBody.d.ts +2 -2
  65. package/types/components/Typography/Body/index.d.ts +2 -1
  66. package/types/components/Typography/Caption/StyledCaption.d.ts +2 -2
  67. package/types/components/Typography/Caption/index.d.ts +2 -1
  68. package/types/components/Typography/Label/StyledLabel.d.ts +2 -2
  69. package/types/components/Typography/Label/index.d.ts +2 -1
  70. package/types/components/Typography/Title/StyledTitle.d.ts +2 -2
  71. package/types/components/Typography/Title/index.d.ts +2 -1
  72. package/types/components/Typography/types.d.ts +1 -1
  73. package/types/index.d.ts +2 -1
  74. package/types/theme/components/segmentedControl.d.ts +46 -0
  75. package/types/theme/components/typography.d.ts +1 -0
  76. package/types/theme/getTheme.d.ts +2 -0
  77. package/types/types.d.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.104.1-alpha.2",
3
+ "version": "8.105.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -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, { useMemo, useState } from 'react';
7
- import { LayoutChangeEvent, Platform, TouchableOpacity } from 'react-native';
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
- getValidDate,
25
- initArray,
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
- const DAYS_OF_WEEK = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
33
- // Sunday first column => 0
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 lastDateOfPreviousMonth = new Date(currentYear, currentMonth, 0);
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 lastDayIndexOfCurrentMonth = lastDateOfMonth.getDate();
121
- const lastDayIndexOfPreviousMonth = lastDateOfPreviousMonth.getDate();
64
+ const { onLayout, contentHeight, contentWidth, calendarItemWidth } =
65
+ useCalendarLayout();
122
66
 
123
- const daysOfPreviousMonth = initArray(firstDayWeekIndexOfMonth, (index) => {
124
- const reversedIndex = firstDayWeekIndexOfMonth - index - 1;
125
- const count = lastDayIndexOfPreviousMonth - reversedIndex;
126
- return getValidDate(
127
- new Date(currentYear, currentMonth - 1, count),
128
- minDate,
129
- maxDate
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 daysOfCurrentMonth = initArray(lastDayIndexOfCurrentMonth, (index) =>
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
- !useMonthPicker ? (
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={useMonthPicker ? undefined : onTitlePress}
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
+ };