@coreui/react-pro 5.10.0 → 5.11.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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/accordion/CAccordionButton.js +2 -2
  3. package/dist/cjs/components/accordion/CAccordionButton.js.map +1 -1
  4. package/dist/cjs/components/accordion/CAccordionItem.d.ts +1 -0
  5. package/dist/cjs/components/accordion/CAccordionItem.js +6 -3
  6. package/dist/cjs/components/accordion/CAccordionItem.js.map +1 -1
  7. package/dist/cjs/components/badge/CBadge.js +1 -1
  8. package/dist/cjs/components/badge/CBadge.js.map +1 -1
  9. package/dist/cjs/components/calendar/CCalendar.d.ts +57 -44
  10. package/dist/cjs/components/calendar/CCalendar.js +51 -187
  11. package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
  12. package/dist/cjs/components/calendar/CCalendarNavigation.d.ts +21 -0
  13. package/dist/cjs/components/calendar/CCalendarNavigation.js +23 -0
  14. package/dist/cjs/components/calendar/CCalendarNavigation.js.map +1 -0
  15. package/dist/cjs/components/calendar/CCalendarPanel.d.ts +28 -0
  16. package/dist/cjs/components/calendar/CCalendarPanel.js +149 -0
  17. package/dist/cjs/components/calendar/CCalendarPanel.js.map +1 -0
  18. package/dist/cjs/components/calendar/types.d.ts +3 -0
  19. package/dist/cjs/components/calendar/utils.d.ts +175 -18
  20. package/dist/cjs/components/calendar/utils.js +312 -43
  21. package/dist/cjs/components/calendar/utils.js.map +1 -1
  22. package/dist/cjs/components/date-picker/CDatePicker.d.ts +1 -1
  23. package/dist/cjs/components/date-range-picker/CDateRangePicker.d.ts +2 -2
  24. package/dist/cjs/components/date-range-picker/CDateRangePicker.js +0 -2
  25. package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
  26. package/dist/cjs/components/date-range-picker/utils.d.ts +16 -0
  27. package/dist/cjs/components/date-range-picker/utils.js +18 -2
  28. package/dist/cjs/components/date-range-picker/utils.js.map +1 -1
  29. package/dist/cjs/components/dropdown/CDropdownToggle.js +1 -1
  30. package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
  31. package/dist/cjs/components/modal/CModal.js +0 -1
  32. package/dist/cjs/components/modal/CModal.js.map +1 -1
  33. package/dist/cjs/components/nav/CNavGroup.js +1 -1
  34. package/dist/cjs/components/nav/CNavGroup.js.map +1 -1
  35. package/dist/cjs/components/progress/CProgress.d.ts +1 -1
  36. package/dist/cjs/components/range-slider/CRangeSlider.js +6 -2
  37. package/dist/cjs/components/range-slider/CRangeSlider.js.map +1 -1
  38. package/dist/cjs/components/sidebar/CSidebar.d.ts +7 -2
  39. package/dist/cjs/components/sidebar/CSidebar.js +7 -6
  40. package/dist/cjs/components/sidebar/CSidebar.js.map +1 -1
  41. package/dist/cjs/components/smart-pagination/CSmartPagination.d.ts +4 -12
  42. package/dist/cjs/components/smart-pagination/CSmartPagination.js.map +1 -1
  43. package/dist/cjs/components/smart-table/CSmartTableHead.js +10 -25
  44. package/dist/cjs/components/smart-table/CSmartTableHead.js.map +1 -1
  45. package/dist/cjs/components/time-picker/CTimePicker.d.ts +1 -1
  46. package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
  47. package/dist/cjs/components/time-picker/types.d.ts +5 -0
  48. package/dist/cjs/components/time-picker/utils.d.ts +8 -11
  49. package/dist/cjs/components/time-picker/utils.js +8 -4
  50. package/dist/cjs/components/time-picker/utils.js.map +1 -1
  51. package/dist/cjs/hooks/index.d.ts +2 -1
  52. package/dist/cjs/hooks/useClipboard.d.ts +12 -0
  53. package/dist/cjs/hooks/useClipboard.js +54 -0
  54. package/dist/cjs/hooks/useClipboard.js.map +1 -0
  55. package/dist/cjs/hooks/useColorModes.js +5 -5
  56. package/dist/cjs/hooks/useColorModes.js.map +1 -1
  57. package/dist/cjs/index.js +2 -0
  58. package/dist/cjs/index.js.map +1 -1
  59. package/dist/cjs/node_modules/react-device-detect/dist/lib.js +1 -1
  60. package/dist/cjs/node_modules/react-device-detect/dist/lib.js.map +1 -1
  61. package/dist/cjs/node_modules/tslib/tslib.es6.js +40 -0
  62. package/dist/cjs/node_modules/tslib/tslib.es6.js.map +1 -1
  63. package/dist/cjs/utils/index.d.ts +2 -1
  64. package/dist/cjs/utils/mergeClassNames.d.ts +2 -0
  65. package/dist/esm/components/accordion/CAccordionButton.js +2 -2
  66. package/dist/esm/components/accordion/CAccordionButton.js.map +1 -1
  67. package/dist/esm/components/accordion/CAccordionItem.d.ts +1 -0
  68. package/dist/esm/components/accordion/CAccordionItem.js +7 -4
  69. package/dist/esm/components/accordion/CAccordionItem.js.map +1 -1
  70. package/dist/esm/components/badge/CBadge.js +1 -1
  71. package/dist/esm/components/badge/CBadge.js.map +1 -1
  72. package/dist/esm/components/calendar/CCalendar.d.ts +57 -44
  73. package/dist/esm/components/calendar/CCalendar.js +52 -188
  74. package/dist/esm/components/calendar/CCalendar.js.map +1 -1
  75. package/dist/esm/components/calendar/CCalendarNavigation.d.ts +21 -0
  76. package/dist/esm/components/calendar/CCalendarNavigation.js +21 -0
  77. package/dist/esm/components/calendar/CCalendarNavigation.js.map +1 -0
  78. package/dist/esm/components/calendar/CCalendarPanel.d.ts +28 -0
  79. package/dist/esm/components/calendar/CCalendarPanel.js +147 -0
  80. package/dist/esm/components/calendar/CCalendarPanel.js.map +1 -0
  81. package/dist/esm/components/calendar/types.d.ts +3 -0
  82. package/dist/esm/components/calendar/utils.d.ts +175 -18
  83. package/dist/esm/components/calendar/utils.js +306 -44
  84. package/dist/esm/components/calendar/utils.js.map +1 -1
  85. package/dist/esm/components/date-picker/CDatePicker.d.ts +1 -1
  86. package/dist/esm/components/date-range-picker/CDateRangePicker.d.ts +2 -2
  87. package/dist/esm/components/date-range-picker/CDateRangePicker.js +0 -2
  88. package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
  89. package/dist/esm/components/date-range-picker/utils.d.ts +16 -0
  90. package/dist/esm/components/date-range-picker/utils.js +18 -2
  91. package/dist/esm/components/date-range-picker/utils.js.map +1 -1
  92. package/dist/esm/components/dropdown/CDropdownToggle.js +1 -1
  93. package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
  94. package/dist/esm/components/modal/CModal.js +0 -1
  95. package/dist/esm/components/modal/CModal.js.map +1 -1
  96. package/dist/esm/components/nav/CNavGroup.js +1 -1
  97. package/dist/esm/components/nav/CNavGroup.js.map +1 -1
  98. package/dist/esm/components/progress/CProgress.d.ts +1 -1
  99. package/dist/esm/components/range-slider/CRangeSlider.js +6 -2
  100. package/dist/esm/components/range-slider/CRangeSlider.js.map +1 -1
  101. package/dist/esm/components/sidebar/CSidebar.d.ts +7 -2
  102. package/dist/esm/components/sidebar/CSidebar.js +7 -6
  103. package/dist/esm/components/sidebar/CSidebar.js.map +1 -1
  104. package/dist/esm/components/smart-pagination/CSmartPagination.d.ts +4 -12
  105. package/dist/esm/components/smart-pagination/CSmartPagination.js.map +1 -1
  106. package/dist/esm/components/smart-table/CSmartTableHead.js +11 -26
  107. package/dist/esm/components/smart-table/CSmartTableHead.js.map +1 -1
  108. package/dist/esm/components/time-picker/CTimePicker.d.ts +1 -1
  109. package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
  110. package/dist/esm/components/time-picker/types.d.ts +5 -0
  111. package/dist/esm/components/time-picker/utils.d.ts +8 -11
  112. package/dist/esm/components/time-picker/utils.js +8 -4
  113. package/dist/esm/components/time-picker/utils.js.map +1 -1
  114. package/dist/esm/hooks/index.d.ts +2 -1
  115. package/dist/esm/hooks/useClipboard.d.ts +12 -0
  116. package/dist/esm/hooks/useClipboard.js +52 -0
  117. package/dist/esm/hooks/useClipboard.js.map +1 -0
  118. package/dist/esm/hooks/useColorModes.js +5 -5
  119. package/dist/esm/hooks/useColorModes.js.map +1 -1
  120. package/dist/esm/index.js +1 -0
  121. package/dist/esm/index.js.map +1 -1
  122. package/dist/esm/node_modules/react-device-detect/dist/lib.js +1 -1
  123. package/dist/esm/node_modules/react-device-detect/dist/lib.js.map +1 -1
  124. package/dist/esm/node_modules/tslib/tslib.es6.js +39 -1
  125. package/dist/esm/node_modules/tslib/tslib.es6.js.map +1 -1
  126. package/dist/esm/utils/index.d.ts +2 -1
  127. package/dist/esm/utils/mergeClassNames.d.ts +2 -0
  128. package/package.json +12 -10
  129. package/src/components/accordion/CAccordionButton.tsx +3 -2
  130. package/src/components/accordion/CAccordionItem.tsx +8 -3
  131. package/src/components/accordion/__tests__/__snapshots__/CAccordionButton.spec.tsx.snap +0 -2
  132. package/src/components/accordion/__tests__/__snapshots__/CAccordionHeader.spec.tsx.snap +0 -2
  133. package/src/components/badge/CBadge.tsx +1 -1
  134. package/src/components/calendar/CCalendar.tsx +154 -489
  135. package/src/components/calendar/CCalendarNavigation.tsx +120 -0
  136. package/src/components/calendar/CCalendarPanel.tsx +306 -0
  137. package/src/components/calendar/types.ts +5 -0
  138. package/src/components/calendar/utils.ts +414 -109
  139. package/src/components/carousel/__tests__/CCarousel.spec.tsx +1 -2
  140. package/src/components/date-picker/CDatePicker.tsx +1 -1
  141. package/src/components/date-range-picker/CDateRangePicker.tsx +2 -4
  142. package/src/components/date-range-picker/utils.ts +23 -3
  143. package/src/components/dropdown/CDropdownToggle.tsx +1 -1
  144. package/src/components/dropdown/__tests__/CDropdown.spec.tsx +43 -19
  145. package/src/components/modal/CModal.tsx +0 -2
  146. package/src/components/modal/__tests__/CModal.spec.tsx +33 -25
  147. package/src/components/nav/CNavGroup.tsx +5 -1
  148. package/src/components/nav/__tests__/__snapshots__/CNavGroup.spec.tsx.snap +4 -1
  149. package/src/components/popover/__tests__/CPopover.spec.tsx +1 -1
  150. package/src/components/popover/__tests__/__snapshots__/CPopover.spec.tsx.snap +2 -3
  151. package/src/components/progress/CProgress.tsx +1 -1
  152. package/src/components/range-slider/CRangeSlider.tsx +7 -2
  153. package/src/components/sidebar/CSidebar.tsx +15 -5
  154. package/src/components/smart-pagination/CSmartPagination.tsx +4 -12
  155. package/src/components/smart-table/CSmartTableHead.tsx +7 -34
  156. package/src/components/time-picker/CTimePicker.tsx +3 -3
  157. package/src/components/time-picker/types.ts +7 -0
  158. package/src/components/time-picker/utils.ts +15 -13
  159. package/src/components/tooltip/__tests__/__snapshots__/CTooltip.spec.tsx.snap +0 -1
  160. package/src/hooks/index.ts +2 -0
  161. package/src/hooks/useClipboard.ts +38 -0
  162. package/src/hooks/useColorModes.ts +5 -5
  163. package/src/utils/index.ts +2 -0
  164. package/src/utils/mergeClassNames.ts +25 -0
@@ -1,595 +1,250 @@
1
- import React, { forwardRef, KeyboardEvent, ReactNode, useEffect, useRef, useState } from 'react'
1
+ import React, { forwardRef, ReactNode, useEffect, useRef, useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import classNames from 'classnames'
4
4
 
5
- import { CButton } from '../button/CButton'
5
+ import { CCalendarNavigation } from './CCalendarNavigation'
6
6
 
7
7
  import {
8
8
  convertToDateObject,
9
- createGroupsInArray,
10
9
  getCalendarDate,
11
10
  getDateBySelectionType,
12
- getMonthDetails,
13
- getMonthsNames,
14
- getYears,
11
+ getSelectableDates,
15
12
  isDateDisabled,
16
- isDateInRange,
17
- isDateSelected,
18
13
  isDisableDateInRange,
19
14
  isSameDateAs,
20
- isToday,
21
15
  } from './utils'
16
+ import type { DisabledDate, SelectionTypes, ViewTypes } from './types'
22
17
 
23
18
  import { useForkedRef, useStateWithCallback } from '../../hooks'
24
-
25
- export type DisabledDate = ((date: Date) => boolean) | Date | Date[]
19
+ import { CCalendarPanel } from './CCalendarPanel'
26
20
 
27
21
  export interface CCalendarProps {
28
22
  /**
29
- * A string that provides an accessible label for the button that navigates to the next month in the calendar. This label is read by screen readers to describe the action associated with the button.
23
+ * A string that provides an accessible label for the button that navigates to the next month in the React Calendar component. This label is read by screen readers to describe the action associated with the button. Customize for a more accessible calendar experience.
30
24
  *
25
+ * @default 'Next month'
31
26
  * @since 5.5.0
32
27
  */
28
+
33
29
  ariaNavNextMonthLabel?: string
30
+
34
31
  /**
35
- * A string that provides an accessible label for the button that navigates to the next year in the calendar. This label is intended for screen readers to help users understand the button's functionality.
32
+ * A string that provides an accessible label for the button that navigates to the next year in the React Calendar component. Screen readers utilize this label to inform users of the button's functionality, aiding in a more inclusive application.
36
33
  *
34
+ * @default 'Next year'
37
35
  * @since 5.5.0
38
36
  */
39
37
  ariaNavNextYearLabel?: string
38
+
40
39
  /**
41
- * A string that provides an accessible label for the button that navigates to the previous month in the calendar. Screen readers will use this label to explain the purpose of the button.
40
+ * A string that provides an accessible label for the button that navigates to the previous month in the React Calendar component. This label is announced by screen readers, ensuring clarity around the button's purpose.
42
41
  *
42
+ * @default 'Previous month'
43
43
  * @since 5.5.0
44
44
  */
45
45
  ariaNavPrevMonthLabel?: string
46
+
46
47
  /**
47
- * A string that provides an accessible label for the button that navigates to the previous year in the calendar. This label helps screen reader users understand the button's function.
48
+ * A string that provides an accessible label for the button that navigates to the previous year in the React Calendar component. Use it to enhance the experience for screen readers and further accessibility.
48
49
  *
50
+ * @default 'Previous year'
49
51
  * @since 5.5.0
50
52
  */
51
53
  ariaNavPrevYearLabel?: string
54
+
52
55
  /**
53
- * Default date of the component
56
+ * Defines the initial date or reference date for the React Calendar component. This date can be either a Date object or a string that can be converted into a valid date.
54
57
  */
55
58
  calendarDate?: Date | string | null
59
+
56
60
  /**
57
- * The number of calendars that render on desktop devices.
61
+ * Specifies how many individual calendars should be displayed side by side on desktop devices. Increasing this number allows your users to view multiple months (or years, etc.) simultaneously.
58
62
  */
59
63
  calendars?: number
64
+
60
65
  /**
61
- * A string of all className you want applied to the component.
66
+ * A string of all classNames you want to apply to the outermost container of the React Calendar component. Use this to style or theme your calendar.
62
67
  */
63
68
  className?: string
69
+
64
70
  /**
65
- * Set the format of day name.
71
+ * Sets the format for day names. Accepts either a built-in format ('numeric' or '2-digit') or a custom function to return any desired string or number for the day.
66
72
  *
67
73
  * @default 'numeric'
68
74
  * @since 4.3.0
69
75
  */
70
76
  dayFormat?: 'numeric' | '2-digit' | ((date: Date) => string | number)
77
+
71
78
  /**
72
- * Specify the list of dates that cannot be selected.
79
+ * Specifies one or more dates that cannot be selected by the user. This is useful for disabling weekends, holidays, or any dates that you need to restrict within your React Calendar component.
73
80
  */
74
81
  disabledDates?: DisabledDate | DisabledDate[]
82
+
75
83
  /**
76
- * Initial selected to date (range).
84
+ * Defines the initially selected ending date (applicable when using range selection). Accepts both Date objects and valid date strings.
77
85
  */
78
86
  endDate?: Date | string | null
87
+
79
88
  /**
80
- * Sets the day of start week.
81
- * - 0 - Sunday,
82
- * - 1 - Monday,
83
- * - 2 - Tuesday,
84
- * - 3 - Wednesday,
85
- * - 4 - Thursday,
86
- * - 5 - Friday,
87
- * - 6 - Saturday,
89
+ * Sets the first day of the week. This can be adjusted for different locales or preferences:
90
+ * - `0` = Sunday
91
+ * - `1` = Monday
92
+ * - `2` = Tuesday
93
+ * - `3` = Wednesday
94
+ * - `4` = Thursday
95
+ * - `5` = Friday
96
+ * - `6` = Saturday
88
97
  *
89
98
  * @default 1
90
99
  */
91
100
  firstDayOfWeek?: number
101
+
92
102
  /**
93
- * Sets the default locale for components. If not set, it is inherited from the browser.
103
+ * Indicates the locale for the React Calendar component. If not explicitly provided, it inherits the locale settings from the browser.
94
104
  *
95
105
  * @default 'default'
96
106
  */
97
107
  locale?: string
108
+
98
109
  /**
99
- * Max selectable date.
110
+ * The maximum date that can be selected. Any date beyond this value will be disabled within the calendar. This is often used to restrict future selections.
100
111
  */
101
112
  maxDate?: Date | string | null
113
+
102
114
  /**
103
- * Min selectable date.
115
+ * The minimum date that can be selected. Any date before this value will be disabled in the calendar. Useful for preventing selection of past dates or creating limited booking windows.
104
116
  */
105
117
  minDate?: Date | string | null
118
+
106
119
  /**
107
- * Show arrows navigation.
120
+ * Toggles display of arrow-based navigation (previous/next controls) in the React Calendar component. If set to `false`, you can build custom navigation or handle the navigation programmatically.
108
121
  */
109
122
  navigation?: boolean
123
+
110
124
  /**
111
- * The custom next icon.
125
+ * Customizes the icon for navigating to the next view (month, year, etc.) depending on the current view. Accepts any valid `ReactNode`, enabling you to display text, images, or icons.
112
126
  */
113
127
  navNextIcon?: ReactNode
128
+
114
129
  /**
115
- * The custom next double icon.
130
+ * Customizes the icon for navigating multiple steps forward (double jump), e.g., skipping multiple months or years at once. Ideal for quick navigation through large date ranges.
116
131
  */
117
132
  navNextDoubleIcon?: ReactNode
133
+
118
134
  /**
119
- * The custom prev icon.
135
+ * Customizes the icon for navigating to the previous view in the calendar. Accepts any valid ReactNode to provide flexible styling or theming.
120
136
  */
121
137
  navPrevIcon?: ReactNode
138
+
122
139
  /**
123
- * The custom prev double icon.
140
+ * Customizes the icon for a double jump backward (e.g., jumping multiple months or years at once). This feature simplifies larger date range navigation within your React Calendar component.
124
141
  */
125
142
  navPrevDoubleIcon?: ReactNode
143
+
126
144
  /**
127
- * Reorder year-month navigation, and render year first.
145
+ * Reorders the year and month navigation, rendering the year selector first. This offers a different layout for users, particularly useful if you want to prioritize quick year changes.
128
146
  *
147
+ * @default false
129
148
  * @since 4.3.0
130
149
  */
131
150
  navYearFirst?: boolean
151
+
132
152
  /**
133
- * Allow range selection.
153
+ * Enables range selection mode in your React Calendar component, allowing users to select two dates for a range (start and end). Once enabled, the component will manage state for both dates.
134
154
  */
135
155
  range?: boolean
156
+
136
157
  /**
137
- * Set whether days in adjacent months shown before or after the current month are selectable. This only applies if the `showAdjacementDays` option is set to true.
158
+ * Sets whether days from adjacent months (shown at the start/end of the current view) are selectable. This only applies if `showAdjacementDays` is set to `true`.
138
159
  *
160
+ * @default false
139
161
  * @since 4.11.0
140
162
  */
141
163
  selectAdjacementDays?: boolean
164
+
142
165
  /**
143
- * Set whether to display dates in adjacent months (non-selectable) at the start and end of the current month.
166
+ * Controls whether the calendar should display dates from previous or next months (non-selectable if `selectAdjacementDays` is `false`). This can make the calendar visually continuous.
144
167
  *
168
+ * @default true
145
169
  * @since 4.11.0
146
170
  */
147
171
  showAdjacementDays?: boolean
172
+
148
173
  /**
149
- * Set whether to display week numbers in the calendar.
174
+ * Toggles the display of week numbers (ISO 8601) in the calendar's left column. Particularly beneficial for applications where users need to see the week index for scheduling or reporting.
150
175
  *
176
+ * @default false
151
177
  * @since 5.0.0
152
178
  */
153
179
  showWeekNumber?: boolean
180
+
154
181
  /**
155
- * Toggle select mode between start and end date.
182
+ * Indicates if the user's current selection is targeting the end date in range selection mode. Primarily used for controlled components where you need to manage which date (start or end) is currently active.
156
183
  */
157
184
  selectEndDate?: boolean
185
+
158
186
  /**
159
- * Specify the type of date selection as day, week, month, or year.
187
+ * Specifies the selection type for the React Calendar component. Supports:
188
+ * - `day`: Regular day selection (default)
189
+ * - `week`: Select an entire week
190
+ * - `month`: Select an entire month
191
+ * - `year`: Select an entire year
160
192
  *
193
+ * @default 'day'
161
194
  * @since 5.0.0
162
195
  */
163
- selectionType?: 'day' | 'week' | 'month' | 'year'
196
+ selectionType?: SelectionTypes
197
+
164
198
  /**
165
- * Initial selected date.
199
+ * Defines the initially selected starting date. This date can be a Date object or a string convertible into a valid date. Useful when you want to show a pre-selected date.
166
200
  */
167
201
  startDate?: Date | string | null
202
+
168
203
  /**
169
- * Set length or format of day name.
204
+ * Sets the format or length of the weekday names displayed in the calendar header. You can use numeric values like 2 (two-letter abbreviations) or built-in string formats like `'long'`, `'narrow'`, and `'short'`, or provide a custom function to fully control how weekdays are displayed.
170
205
  *
171
206
  * @default 2
172
207
  */
173
208
  weekdayFormat?: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
209
+
174
210
  /**
175
- * Label displayed over week numbers in the calendar.
211
+ * Defines the label that appears above the week numbers column. For example, you might label it `'WK'` or `'Week #'`.
176
212
  *
177
213
  * @since 5.0.0
178
214
  */
179
215
  weekNumbersLabel?: string
216
+
180
217
  /**
181
- * Callback fired when the calendar date changed.
218
+ * Callback fired when the calendar's reference date (i.e., the date indicating which month/year is currently displayed) changes. Receives the new `Date | string` as an argument for further handling.
182
219
  */
183
220
  onCalendarDateChange?: (date: Date | string) => void
221
+
184
222
  /**
185
- * Callback fired when the user hovers over the calendar cell.
223
+ * Callback fired when the user hovers over a calendar cell. The argument is the date of the cell that the user is currently hovering.
186
224
  */
187
225
  onDateHover?: (date: Date | string | null) => void
226
+
188
227
  /**
189
- * Callback fired when the start date changed.
228
+ * Callback triggered when the start date of the selection changes. Useful for updating other parts of your application or performing side effects based on the newly chosen start date.
190
229
  */
191
- onStartDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void // TODO: remove formatedDate?
230
+ onStartDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void
231
+
192
232
  /**
193
- * Callback fired when the end date changed.
233
+ * Callback triggered when the end date of the selection changes. Handy for synchronizing the selected date range with external states or components.
194
234
  */
195
- onEndDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void // TODO: remove formatedDate?
235
+ onEndDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void
236
+
196
237
  /**
197
- * Callback fired when the selection type changed.
238
+ * Callback fired when toggling between selecting the start date and the end date in range selection mode. Receives a boolean indicating if the user is currently selecting the end date.
198
239
  */
199
240
  onSelectEndChange?: (value: boolean) => void
241
+
200
242
  /**
201
- * Callback fired when the view type of calendar changed.
243
+ * Callback fired when the calendar's view changes between days, months, or years. For instance, moving from day view to month view or from month view to year view.
202
244
  */
203
245
  onViewChanged?: (view: string) => void
204
246
  }
205
247
 
206
- const Calendar = (props: {
207
- calendarDate: Date
208
- dayFormat?: 'numeric' | '2-digit' | ((date: Date) => string | number)
209
- disabledDates?: DisabledDate | DisabledDate[]
210
- endDate: Date | null
211
- firstDayOfWeek: number
212
- hoverDate: Date | null
213
- locale: string
214
- maxDate: Date | null
215
- minDate: Date | null
216
- onCalendarClick: (date: Date) => void
217
- onCalendarKeyDown: (event: KeyboardEvent<HTMLDivElement>, date: Date) => void
218
- onCalendarMouseEnter: (date: Date) => void
219
- onCalendarMouseLeave: () => void
220
- order: number
221
- selectAdjacementDays: boolean
222
- selectEndDate: boolean | undefined
223
- selectionType: 'day' | 'week' | 'month' | 'year'
224
- showAdjacementDays: boolean
225
- showWeekNumber: boolean
226
- startDate: Date | null
227
- view: 'days' | 'months' | 'years'
228
- weekdayFormat: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
229
- weekNumbersLabel: string | undefined
230
- }) => {
231
- const {
232
- calendarDate,
233
- dayFormat,
234
- disabledDates,
235
- endDate,
236
- firstDayOfWeek,
237
- hoverDate,
238
- locale,
239
- maxDate,
240
- minDate,
241
- onCalendarClick,
242
- onCalendarKeyDown,
243
- onCalendarMouseEnter,
244
- onCalendarMouseLeave,
245
- selectAdjacementDays,
246
- selectEndDate,
247
- selectionType,
248
- showWeekNumber,
249
- showAdjacementDays,
250
- startDate,
251
- view,
252
- weekdayFormat,
253
- weekNumbersLabel,
254
- } = props
255
- const [listOfMonths, setListOfMonths] = useState<string[][]>([])
256
-
257
- useEffect(() => {
258
- setListOfMonths(createGroupsInArray(getMonthsNames(locale), 4))
259
- }, [])
260
-
261
- const monthDetails = getMonthDetails(
262
- calendarDate.getFullYear(),
263
- calendarDate.getMonth(),
264
- firstDayOfWeek,
265
- )
266
- const listOfYears = createGroupsInArray(getYears(calendarDate.getFullYear()), 4)
267
- const weekDays = monthDetails[0].days
268
-
269
- return (
270
- <table>
271
- {view === 'days' && (
272
- <thead>
273
- <tr>
274
- {showWeekNumber && (
275
- <th className="calendar-cell">
276
- <div className="calendar-header-cell-inner">{weekNumbersLabel}</div>
277
- </th>
278
- )}
279
- {weekDays.map(({ date }: { date: Date }, idx: number) => (
280
- <th
281
- className="calendar-cell"
282
- abbr={date.toLocaleDateString(locale, { weekday: 'long' })}
283
- key={idx}
284
- >
285
- <div className="calendar-header-cell-inner">
286
- {typeof weekdayFormat === 'function'
287
- ? weekdayFormat(date)
288
- : typeof weekdayFormat === 'string'
289
- ? date.toLocaleDateString(locale, { weekday: weekdayFormat })
290
- : date
291
- .toLocaleDateString(locale, { weekday: 'long' })
292
- .slice(0, weekdayFormat)}
293
- </div>
294
- </th>
295
- ))}
296
- </tr>
297
- </thead>
298
- )}
299
- <tbody>
300
- {view === 'days' &&
301
- monthDetails.map((week, index) => {
302
- const date = convertToDateObject(
303
- week.weekNumber === 0
304
- ? `${calendarDate.getFullYear()}W53`
305
- : `${calendarDate.getFullYear()}W${week.weekNumber}`,
306
- selectionType,
307
- )
308
- const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
309
- const isSelected = isDateSelected(date, startDate, endDate)
310
- const current = week.days.some((day) => day.month === 'current')
311
- return (
312
- <tr
313
- className={classNames('calendar-row', {
314
- disabled: isDisabled,
315
- range: selectionType === 'week' && isDateInRange(date, startDate, endDate),
316
- 'range-hover':
317
- selectionType === 'week' && hoverDate && selectEndDate
318
- ? isDateInRange(date, startDate, hoverDate)
319
- : isDateInRange(date, hoverDate, endDate),
320
- selected: isSelected,
321
- })}
322
- key={index}
323
- tabIndex={selectionType === 'week' && current && !isDisabled ? 0 : -1}
324
- {...(isSelected && { 'aria-selected': true })}
325
- {...(selectionType === 'week' &&
326
- !isDisabled && {
327
- onBlur: () => onCalendarMouseLeave(),
328
- onClick: () => onCalendarClick(date),
329
- onFocus: () => onCalendarMouseEnter(date),
330
- onKeyDown: (event) => onCalendarKeyDown(event, date),
331
- onMouseEnter: () => onCalendarMouseEnter(date),
332
- onMouseLeave: () => onCalendarMouseLeave(),
333
- })}
334
- >
335
- {showWeekNumber && (
336
- <th className="calendar-cell-week-number">
337
- {week.weekNumber === 0 ? 53 : week.weekNumber}
338
- </th>
339
- )}
340
- {week.days.map(({ date, month }: { date: Date; month: string }, idx: number) => {
341
- const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
342
- const isSelected = isDateSelected(date, startDate, endDate)
343
- return month === 'current' || showAdjacementDays ? (
344
- <td
345
- className={classNames('calendar-cell', {
346
- ...(selectionType === 'day' && {
347
- clickable: month !== 'current' && selectAdjacementDays,
348
- disabled: isDisabled,
349
- 'range-hover':
350
- month === 'current' &&
351
- (hoverDate && selectEndDate
352
- ? isDateInRange(date, startDate, hoverDate)
353
- : isDateInRange(date, hoverDate, endDate)),
354
- range: month === 'current' && isDateInRange(date, startDate, endDate),
355
- selected: isSelected,
356
- }),
357
- [month]: true,
358
- today: month === 'current' && isToday(date),
359
- })}
360
- key={idx}
361
- tabIndex={
362
- selectionType === 'day' &&
363
- (month === 'current' || selectAdjacementDays) &&
364
- !isDisabled
365
- ? 0
366
- : -1
367
- }
368
- title={date.toLocaleDateString(locale)}
369
- {...(isSelected && { 'aria-selected': true })}
370
- {...(selectionType === 'day' &&
371
- (month === 'current' || selectAdjacementDays) && {
372
- onBlur: () => onCalendarMouseLeave(),
373
- onClick: () => onCalendarClick(date),
374
- onFocus: () => onCalendarMouseEnter(date),
375
- onKeyDown: (event) => onCalendarKeyDown(event, date),
376
- onMouseEnter: () => onCalendarMouseEnter(date),
377
- onMouseLeave: () => onCalendarMouseLeave(),
378
- })}
379
- {...(month !== 'current' &&
380
- !selectAdjacementDays && {
381
- onMouseEnter: () => onCalendarMouseLeave(),
382
- })}
383
- >
384
- <div className="calendar-cell-inner">
385
- {typeof dayFormat === 'function'
386
- ? dayFormat(date)
387
- : date.toLocaleDateString(locale, { day: dayFormat })}
388
- </div>
389
- </td>
390
- ) : (
391
- <td key={idx}></td>
392
- )
393
- })}
394
- </tr>
395
- )
396
- })}
397
- {view === 'months' &&
398
- listOfMonths.map((row, index) => {
399
- return (
400
- <tr key={index}>
401
- {row.map((month, idx) => {
402
- const monthNumber = index * 3 + idx
403
- const date = new Date(calendarDate.getFullYear(), monthNumber, 1)
404
- const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
405
- const isSelected = isDateSelected(date, startDate, endDate)
406
- return (
407
- <td
408
- className={classNames('calendar-cell', {
409
- disabled: isDisabled,
410
- selected: isSelected,
411
- 'range-hover':
412
- selectionType === 'month' &&
413
- (hoverDate && selectEndDate
414
- ? isDateInRange(date, startDate, hoverDate)
415
- : isDateInRange(date, hoverDate, endDate)),
416
- range: isDateInRange(date, startDate, endDate),
417
- })}
418
- key={idx}
419
- tabIndex={isDisabled ? -1 : 0}
420
- {...(isSelected && { 'aria-selected': true })}
421
- {...(!isDisabled && {
422
- onBlur: () => onCalendarMouseLeave(),
423
- onClick: () => onCalendarClick(date),
424
- onFocus: () => onCalendarMouseEnter(date),
425
- onKeyDown: (event) => onCalendarKeyDown(event, date),
426
- onMouseEnter: () => onCalendarMouseEnter(date),
427
- onMouseLeave: () => onCalendarMouseLeave(),
428
- })}
429
- >
430
- <div className="calendar-cell-inner">{month}</div>
431
- </td>
432
- )
433
- })}
434
- </tr>
435
- )
436
- })}
437
- {view === 'years' &&
438
- listOfYears.map((row, index) => {
439
- return (
440
- <tr key={index}>
441
- {row.map((year, idx) => {
442
- const date = new Date(year, 0, 1)
443
- const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
444
- const isSelected = isDateSelected(date, startDate, endDate)
445
- return (
446
- <td
447
- className={classNames('calendar-cell year', {
448
- disabled: isDisabled,
449
- selected: isSelected,
450
- 'range-hover':
451
- selectionType === 'year' &&
452
- (hoverDate && selectEndDate
453
- ? isDateInRange(date, startDate, hoverDate)
454
- : isDateInRange(date, hoverDate, endDate)),
455
- range: isDateInRange(date, startDate, endDate),
456
- })}
457
- key={idx}
458
- tabIndex={isDisabled ? -1 : 0}
459
- {...(isSelected && { 'aria-selected': true })}
460
- {...(!isDisabled && {
461
- onBlur: () => onCalendarMouseLeave(),
462
- onClick: () => onCalendarClick(date),
463
- onFocus: () => onCalendarMouseEnter(date),
464
- onKeyDown: (event) => onCalendarKeyDown(event, date),
465
- onMouseEnter: () => onCalendarMouseEnter(date),
466
- onMouseLeave: () => onCalendarMouseLeave(),
467
- })}
468
- >
469
- <div className="calendar-cell-inner">
470
- {new Date(year, 0, 1).toLocaleDateString(locale, { year: 'numeric' })}
471
- </div>
472
- </td>
473
- )
474
- })}
475
- </tr>
476
- )
477
- })}
478
- </tbody>
479
- </table>
480
- )
481
- }
482
-
483
- const Navigation = (props: {
484
- ariaNavNextMonthLabel: string
485
- ariaNavNextYearLabel: string
486
- ariaNavPrevMonthLabel: string
487
- ariaNavPrevYearLabel: string
488
- calendarDate: Date
489
- locale: string
490
- navigation: boolean
491
- navNextDoubleIcon: ReactNode
492
- navNextIcon: ReactNode
493
- navPrevDoubleIcon: ReactNode
494
- navPrevIcon: ReactNode
495
- navYearFirst: boolean | undefined
496
- onMonthClick: () => void
497
- onNavigationClick: (direction: string, double?: boolean) => void
498
- onYearClick: () => void
499
- view: 'days' | 'months' | 'years'
500
- }) => {
501
- const {
502
- ariaNavNextMonthLabel,
503
- ariaNavNextYearLabel,
504
- ariaNavPrevMonthLabel,
505
- ariaNavPrevYearLabel,
506
- calendarDate,
507
- locale,
508
- navigation,
509
- navNextDoubleIcon,
510
- navNextIcon,
511
- navPrevDoubleIcon,
512
- navPrevIcon,
513
- navYearFirst,
514
- onMonthClick,
515
- onNavigationClick,
516
- onYearClick,
517
- view,
518
- } = props
519
-
520
- return (
521
- <div className="calendar-nav">
522
- {navigation && (
523
- <div className="calendar-nav-prev">
524
- <CButton
525
- color="transparent"
526
- size="sm"
527
- aria-label={ariaNavPrevYearLabel}
528
- onClick={() => onNavigationClick('prev', true)}
529
- >
530
- {navPrevDoubleIcon ?? (
531
- <span className="calendar-nav-icon calendar-nav-icon-double-prev" />
532
- )}
533
- </CButton>
534
- {view === 'days' && (
535
- <CButton
536
- color="transparent"
537
- size="sm"
538
- aria-label={ariaNavPrevMonthLabel}
539
- onClick={() => onNavigationClick('prev')}
540
- >
541
- {navPrevIcon ?? <span className="calendar-nav-icon calendar-nav-icon-prev" />}
542
- </CButton>
543
- )}
544
- </div>
545
- )}
546
- <div
547
- className="calendar-nav-date"
548
- aria-live="polite"
549
- {...(navYearFirst && { style: { display: 'flex', justifyContent: 'center' } })}
550
- >
551
- {view === 'days' && (
552
- <CButton color="transparent" size="sm" onClick={() => navigation && onMonthClick()}>
553
- {calendarDate && calendarDate.toLocaleDateString(locale, { month: 'long' })}
554
- </CButton>
555
- )}
556
- <CButton
557
- color="transparent"
558
- size="sm"
559
- onClick={() => navigation && onYearClick()}
560
- {...(navYearFirst && { style: { order: '-1' } })}
561
- >
562
- {calendarDate && calendarDate.toLocaleDateString(locale, { year: 'numeric' })}
563
- </CButton>
564
- </div>
565
- {navigation && (
566
- <div className="calendar-nav-next">
567
- {view === 'days' && (
568
- <CButton
569
- color="transparent"
570
- size="sm"
571
- aria-label={ariaNavNextMonthLabel}
572
- onClick={() => onNavigationClick('next')}
573
- >
574
- {navNextIcon ?? <span className="calendar-nav-icon calendar-nav-icon-next" />}
575
- </CButton>
576
- )}
577
- <CButton
578
- color="transparent"
579
- size="sm"
580
- aria-label={ariaNavNextYearLabel}
581
- onClick={() => onNavigationClick('next', true)}
582
- >
583
- {navNextDoubleIcon ?? (
584
- <span className="calendar-nav-icon calendar-nav-icon-double-next" />
585
- )}
586
- </CButton>
587
- </div>
588
- )}
589
- </div>
590
- )
591
- }
592
-
593
248
  export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
594
249
  (
595
250
  {
@@ -637,20 +292,14 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
637
292
  const [_calendarDate, setCalendarDate] = useState<Date | null>(null)
638
293
 
639
294
  useEffect(() => {
640
- if (selectionType === 'day' || selectionType === 'week') {
641
- setView('days')
642
- return
295
+ const viewMap = {
296
+ day: 'days',
297
+ week: 'days',
298
+ month: 'months',
299
+ year: 'years',
643
300
  }
644
301
 
645
- if (selectionType === 'month') {
646
- setView('months')
647
- return
648
- }
649
-
650
- if (selectionType === 'year') {
651
- setView('years')
652
- return
653
- }
302
+ setView((viewMap[selectionType] as ViewTypes) || 'days')
654
303
  }, [selectionType])
655
304
 
656
305
  useEffect(() => {
@@ -715,13 +364,21 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
715
364
  }, [selectEndDate])
716
365
 
717
366
  useEffect(() => {
718
- !isInitialMount.current &&
719
- typeof _selectEndDate === 'boolean' &&
720
- onSelectEndChange &&
721
- onSelectEndChange(_selectEndDate)
367
+ if (!isInitialMount.current && typeof _selectEndDate === 'boolean') {
368
+ onSelectEndChange?.(_selectEndDate)
369
+ }
722
370
  }, [_selectEndDate])
723
371
 
724
- const [view, setView] = useStateWithCallback<'days' | 'months' | 'years'>('days', onViewChanged)
372
+ const [view, setView] = useStateWithCallback<ViewTypes>('days', onViewChanged)
373
+ const [focusOn, setFocusOn] = useState<number | undefined>()
374
+
375
+ useEffect(() => {
376
+ if (typeof focusOn === 'number') {
377
+ const list = getSelectableDates(calendarRef.current as HTMLDivElement)
378
+ list[focusOn]?.focus()
379
+ setFocusOn(undefined)
380
+ }
381
+ }, [view])
725
382
 
726
383
  useEffect(() => {
727
384
  isInitialMount.current = false
@@ -736,19 +393,23 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
736
393
  const month = _calendarDate.getMonth()
737
394
  const d = new Date(year, month, 1)
738
395
 
739
- years && d.setFullYear(d.getFullYear() + years)
740
- months && d.setMonth(d.getMonth() + months)
741
- typeof setMonth === 'number' && d.setMonth(setMonth)
396
+ if (years) {
397
+ d.setFullYear(d.getFullYear() + years)
398
+ }
399
+
400
+ if (months) {
401
+ d.setMonth(d.getMonth() + months)
402
+ }
403
+
404
+ if (typeof setMonth === 'number') {
405
+ d.setMonth(setMonth)
406
+ }
742
407
 
743
408
  setCalendarDate(d)
744
- onCalendarDateChange && onCalendarDateChange(d)
409
+ onCalendarDateChange?.(d)
745
410
  }
746
411
 
747
412
  const handleCalendarClick = (date: Date, index?: number) => {
748
- if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
749
- return
750
- }
751
-
752
413
  const _date = new Date(date)
753
414
 
754
415
  if (view === 'days') {
@@ -767,6 +428,11 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
767
428
  return
768
429
  }
769
430
 
431
+ // Allow to change the calendarDate but not startDate or endDate
432
+ if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
433
+ return
434
+ }
435
+
770
436
  if (range) {
771
437
  if (_selectEndDate) {
772
438
  setSelectEndDate(false)
@@ -814,6 +480,13 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
814
480
  ) => {
815
481
  if (event.code === 'Space' || event.key === 'Enter') {
816
482
  event.preventDefault()
483
+ if (
484
+ (view === 'months' && selectionType !== 'month') ||
485
+ (view === 'years' && selectionType !== 'year')
486
+ ) {
487
+ setFocusOn(0)
488
+ }
489
+
817
490
  handleCalendarClick(date, index)
818
491
  }
819
492
 
@@ -826,16 +499,16 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
826
499
  event.preventDefault()
827
500
 
828
501
  if (
829
- maxDate &&
830
- date >= convertToDateObject(maxDate, selectionType) &&
502
+ _maxDate &&
503
+ date >= _maxDate &&
831
504
  (event.key === 'ArrowRight' || event.key === 'ArrowDown')
832
505
  ) {
833
506
  return
834
507
  }
835
508
 
836
509
  if (
837
- minDate &&
838
- date <= convertToDateObject(minDate, selectionType) &&
510
+ _minDate &&
511
+ date <= _minDate &&
839
512
  (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
840
513
  ) {
841
514
  return
@@ -847,12 +520,7 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
847
520
  element = element.closest('tr[tabindex="0"]') as HTMLElement
848
521
  }
849
522
 
850
- const list: HTMLElement[] = [].concat(
851
- ...Element.prototype.querySelectorAll.call(
852
- calendarRef.current,
853
- selectionType === 'week' ? 'tr[tabindex="0"]' : 'td[tabindex="0"]',
854
- ),
855
- )
523
+ const list: HTMLElement[] = getSelectableDates(calendarRef.current as HTMLDivElement)
856
524
 
857
525
  const index = list.indexOf(element)
858
526
  const first = index === 0
@@ -889,19 +557,14 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
889
557
  }
890
558
 
891
559
  setTimeout(() => {
892
- const _list: HTMLElement[] = [].concat(
893
- ...Element.prototype.querySelectorAll.call(
894
- element.parentNode?.parentNode,
895
- 'td[tabindex="0"], tr[tabindex="0"]',
896
- ),
897
- )
560
+ const _list = getSelectableDates(element.parentNode?.parentNode as HTMLDivElement)
898
561
 
899
562
  if (_list.length > 0 && event.key === 'ArrowRight') {
900
563
  _list[0].focus()
901
564
  }
902
565
 
903
566
  if (_list.length > 0 && event.key === 'ArrowLeft') {
904
- _list[_list.length - 1].focus()
567
+ _list.at(-1)?.focus()
905
568
  }
906
569
 
907
570
  if (_list.length > 0 && event.key === 'ArrowDown') {
@@ -941,12 +604,14 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
941
604
 
942
605
  setHoverDate(date)
943
606
 
944
- date && onDateHover && onDateHover(getDateBySelectionType(date, selectionType))
607
+ if (date) {
608
+ onDateHover?.(getDateBySelectionType(date, selectionType))
609
+ }
945
610
  }
946
611
 
947
612
  const handleCalendarMouseLeave = () => {
948
613
  setHoverDate(null)
949
- onDateHover && onDateHover(null)
614
+ onDateHover?.(null)
950
615
  }
951
616
 
952
617
  const handleNavigationOnClick = (direction: string, double = false) => {
@@ -998,7 +663,7 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
998
663
  const calendarDate = getCalendarDate(_calendarDate, index, view)
999
664
  return (
1000
665
  <div className={classNames('calendar', view)} key={index}>
1001
- <Navigation
666
+ <CCalendarNavigation
1002
667
  ariaNavNextMonthLabel={ariaNavNextMonthLabel}
1003
668
  ariaNavNextYearLabel={ariaNavNextYearLabel}
1004
669
  ariaNavPrevMonthLabel={ariaNavPrevMonthLabel}
@@ -1016,7 +681,7 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
1016
681
  onYearClick={() => setView('years')}
1017
682
  view={view}
1018
683
  />
1019
- <Calendar
684
+ <CCalendarPanel
1020
685
  calendarDate={calendarDate}
1021
686
  dayFormat={dayFormat}
1022
687
  disabledDates={disabledDates}