@coreui/react-pro 5.0.0-alpha.0 → 5.0.0-alpha.2

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 (92) hide show
  1. package/README.md +1 -1
  2. package/dist/components/avatar/CAvatar.d.ts +1 -1
  3. package/dist/components/badge/CBadge.d.ts +1 -1
  4. package/dist/components/calendar/CCalendar.d.ts +25 -7
  5. package/dist/components/calendar/utils.d.ts +12 -3
  6. package/dist/components/card/CCard.d.ts +1 -1
  7. package/dist/components/close-button/CCloseButton.d.ts +6 -0
  8. package/dist/components/dropdown/CDropdown.d.ts +1 -0
  9. package/dist/components/modal/CModal.d.ts +6 -0
  10. package/dist/components/multi-select/types.d.ts +2 -2
  11. package/dist/components/multi-select/utils.d.ts +2 -2
  12. package/dist/components/nav/CNav.d.ts +1 -1
  13. package/dist/components/navbar/CNavbar.d.ts +1 -1
  14. package/dist/components/offcanvas/COffcanvas.d.ts +4 -0
  15. package/dist/components/smart-table/CSmartTableBody.d.ts +2 -1
  16. package/dist/components/smart-table/CSmartTableHead.d.ts +1 -1
  17. package/dist/components/smart-table/CSmartTableInterface.d.ts +1 -3
  18. package/dist/components/smart-table/types.d.ts +2 -2
  19. package/dist/components/smart-table/utils.d.ts +3 -1
  20. package/dist/index.es.js +1468 -1130
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/index.js +1468 -1130
  23. package/dist/index.js.map +1 -1
  24. package/dist/types.d.ts +1 -1
  25. package/dist/utils/executeAfterTransition.d.ts +2 -0
  26. package/dist/utils/getTransitionDurationFromElement.d.ts +2 -0
  27. package/dist/utils/index.d.ts +3 -1
  28. package/package.json +2 -2
  29. package/src/components/avatar/CAvatar.tsx +1 -1
  30. package/src/components/badge/CBadge.tsx +1 -1
  31. package/src/components/button/CButton.tsx +1 -1
  32. package/src/components/button/__tests__/__snapshots__/CButton.spec.tsx.snap +0 -1
  33. package/src/components/calendar/CCalendar.tsx +396 -193
  34. package/src/components/calendar/utils.ts +91 -4
  35. package/src/components/card/CCard.tsx +1 -1
  36. package/src/components/carousel/CCarousel.tsx +9 -5
  37. package/src/components/carousel/__tests__/__snapshots__/CCarousel.spec.tsx.snap +9 -5
  38. package/src/components/close-button/CCloseButton.tsx +9 -1
  39. package/src/components/date-range-picker/CDateRangePicker.tsx +49 -45
  40. package/src/components/dropdown/CDropdown.tsx +43 -1
  41. package/src/components/dropdown/CDropdownMenu.tsx +1 -1
  42. package/src/components/dropdown/CDropdownToggle.tsx +3 -12
  43. package/src/components/modal/CModal.tsx +25 -9
  44. package/src/components/multi-select/CMultiSelect.tsx +3 -3
  45. package/src/components/multi-select/CMultiSelectNativeSelect.tsx +1 -1
  46. package/src/components/multi-select/CMultiSelectOptions.tsx +1 -1
  47. package/src/components/multi-select/CMultiSelectSelection.tsx +3 -3
  48. package/src/components/multi-select/types.ts +2 -2
  49. package/src/components/multi-select/utils.ts +5 -5
  50. package/src/components/nav/CNav.tsx +2 -2
  51. package/src/components/nav/CNavGroup.tsx +9 -2
  52. package/src/components/nav/__tests__/__snapshots__/CNav.spec.tsx.snap +5 -4
  53. package/src/components/navbar/CNavbar.tsx +2 -2
  54. package/src/components/navbar/__tests__/CNavbar.spec.tsx +1 -1
  55. package/src/components/navbar/__tests__/__snapshots__/CNavbar.spec.tsx.snap +2 -1
  56. package/src/components/offcanvas/COffcanvas.tsx +8 -1
  57. package/src/components/popover/CPopover.tsx +2 -2
  58. package/src/components/progress/__tests__/__snapshots__/CProgress.spec.tsx.snap +10 -5
  59. package/src/components/progress/__tests__/__snapshots__/CProgressBar.spec.tsx.snap +0 -8
  60. package/src/components/sidebar/CSidebar.tsx +1 -1
  61. package/src/components/smart-table/CSmartTable.tsx +13 -6
  62. package/src/components/smart-table/CSmartTableBody.tsx +11 -5
  63. package/src/components/smart-table/CSmartTableHead.tsx +31 -11
  64. package/src/components/smart-table/CSmartTableInterface.tsx +1 -3
  65. package/src/components/smart-table/types.ts +2 -2
  66. package/src/components/smart-table/utils.ts +40 -5
  67. package/src/components/time-picker/CTimePicker.tsx +2 -2
  68. package/src/components/time-picker/utils.ts +5 -1
  69. package/src/components/tooltip/CTooltip.tsx +2 -2
  70. package/src/components/widgets/CWidgetStatsA.tsx +1 -1
  71. package/src/components/widgets/CWidgetStatsB.tsx +2 -2
  72. package/src/components/widgets/CWidgetStatsC.tsx +4 -12
  73. package/src/components/widgets/CWidgetStatsD.tsx +1 -1
  74. package/src/components/widgets/CWidgetStatsE.tsx +1 -1
  75. package/src/components/widgets/CWidgetStatsF.tsx +1 -1
  76. package/src/components/widgets/__tests__/CWidgetStatsA.spec.tsx +1 -1
  77. package/src/components/widgets/__tests__/CWidgetStatsB.spec.tsx +1 -1
  78. package/src/components/widgets/__tests__/CWidgetStatsC.spec.tsx +1 -1
  79. package/src/components/widgets/__tests__/CWidgetStatsD.spec.tsx +1 -1
  80. package/src/components/widgets/__tests__/CWidgetStatsE.spec.tsx +1 -1
  81. package/src/components/widgets/__tests__/CWidgetStatsF.spec.tsx +1 -1
  82. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsA.spec.tsx.snap +1 -1
  83. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsB.spec.tsx.snap +12 -7
  84. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsC.spec.tsx.snap +14 -9
  85. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsD.spec.tsx.snap +2 -2
  86. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsE.spec.tsx.snap +1 -1
  87. package/src/components/widgets/__tests__/__snapshots__/CWidgetStatsF.spec.tsx.snap +2 -2
  88. package/src/hooks/usePopper.ts +10 -2
  89. package/src/types.ts +14 -7
  90. package/src/utils/executeAfterTransition.ts +46 -0
  91. package/src/utils/getTransitionDurationFromElement.ts +24 -0
  92. package/src/utils/index.ts +10 -1
@@ -5,7 +5,10 @@ import classNames from 'classnames'
5
5
  import { CButton } from '../button/CButton'
6
6
 
7
7
  import {
8
+ convertToDateObject,
8
9
  createGroupsInArray,
10
+ getCalendarDate,
11
+ getDateBySelectionType,
9
12
  getMonthDetails,
10
13
  getMonthsNames,
11
14
  getYears,
@@ -13,20 +16,17 @@ import {
13
16
  isDateInRange,
14
17
  isDateSelected,
15
18
  isDisableDateInRange,
16
- isLastDayOfMonth,
17
19
  isSameDateAs,
18
20
  isToday,
19
- isStartDate,
20
- isEndDate,
21
21
  } from './utils'
22
22
 
23
- import { useStateWithCallback } from '../../hooks'
23
+ import { useForkedRef, useStateWithCallback } from '../../hooks'
24
24
 
25
25
  export interface CCalendarProps {
26
26
  /**
27
27
  * Default date of the component
28
28
  */
29
- calendarDate?: Date | string
29
+ calendarDate?: Date | string | null
30
30
  /**
31
31
  * The number of calendars that render on desktop devices.
32
32
  */
@@ -103,6 +103,10 @@ export interface CCalendarProps {
103
103
  * @since 4.3.0
104
104
  */
105
105
  navYearFirst?: boolean
106
+ /**
107
+ * Allow range selection.
108
+ */
109
+ range?: boolean
106
110
  /**
107
111
  * 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.
108
112
  *
@@ -116,13 +120,21 @@ export interface CCalendarProps {
116
120
  */
117
121
  showAdjacementDays?: boolean
118
122
  /**
119
- * Allow range selection.
123
+ * Set whether to display week numbers in the calendar.
124
+ *
125
+ * @since 5.0.0-alpha.1
120
126
  */
121
- range?: boolean
127
+ showWeekNumber?: boolean
122
128
  /**
123
129
  * Toggle select mode between start and end date.
124
130
  */
125
131
  selectEndDate?: boolean
132
+ /**
133
+ * Specify the type of date selection as day, week, month, or year.
134
+ *
135
+ * @since 5.0.0-alpha.1
136
+ */
137
+ selectionType?: 'day' | 'week' | 'month' | 'year'
126
138
  /**
127
139
  * Initial selected date.
128
140
  */
@@ -133,22 +145,28 @@ export interface CCalendarProps {
133
145
  * @default 2
134
146
  */
135
147
  weekdayFormat?: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
148
+ /**
149
+ * Label displayed over week numbers in the calendar.
150
+ *
151
+ * @since 5.0.0-alpha.1
152
+ */
153
+ weekNumbersLabel?: string
136
154
  /**
137
155
  * Callback fired when the calendar date changed.
138
156
  */
139
- onCalendarDateChange?: (date: Date) => void
157
+ onCalendarDateChange?: (date: Date | string) => void
140
158
  /**
141
159
  * Callback fired when the user hovers over the calendar cell.
142
160
  */
143
- onDayHover?: (date: Date | null) => void
161
+ onDateHover?: (date: Date | string | null) => void
144
162
  /**
145
163
  * Callback fired when the start date changed.
146
164
  */
147
- onStartDateChange?: (date: Date | null, formatedDate?: string | undefined) => void
165
+ onStartDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void
148
166
  /**
149
167
  * Callback fired when the end date changed.
150
168
  */
151
- onEndDateChange?: (date: Date | null, formatedDate?: string | undefined) => void
169
+ onEndDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void
152
170
  /**
153
171
  * Callback fired when the selection type changed.
154
172
  */
@@ -160,7 +178,6 @@ export interface CCalendarProps {
160
178
  }
161
179
 
162
180
  const Calendar = (props: {
163
- addMonths: number
164
181
  calendarDate: Date
165
182
  dayFormat?: 'numeric' | '2-digit' | ((date: Date) => string | number)
166
183
  disabledDates?: Date[] | Date[][] | (Date | Date[])[]
@@ -170,23 +187,22 @@ const Calendar = (props: {
170
187
  locale: string
171
188
  maxDate: Date | null
172
189
  minDate: Date | null
173
- onDayClick: (date: Date) => void
174
- onDayKeyDown: (event: KeyboardEvent<HTMLDivElement>, date: Date) => void
175
- onDayMouseEnter: (date: Date) => void
176
- onDayMouseLeave: () => void
177
- onMonthClick: (month: number) => void
178
- onMonthKeyDown: (event: KeyboardEvent<HTMLDivElement>, month: number) => void
179
- onYearClick: (date: Date) => void
180
- onYearKeyDown: (event: KeyboardEvent<HTMLDivElement>, date: Date) => void
190
+ onCalendarClick: (date: Date) => void
191
+ onCalendarKeyDown: (event: KeyboardEvent<HTMLDivElement>, date: Date) => void
192
+ onCalendarMouseEnter: (date: Date) => void
193
+ onCalendarMouseLeave: () => void
194
+ order: number
181
195
  selectAdjacementDays: boolean
182
196
  selectEndDate: boolean | undefined
197
+ selectionType: 'day' | 'week' | 'month' | 'year'
183
198
  showAdjacementDays: boolean
199
+ showWeekNumber: boolean
184
200
  startDate: Date | null
185
- view: string
201
+ view: 'days' | 'months' | 'years'
186
202
  weekdayFormat: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
203
+ weekNumbersLabel: string | undefined
187
204
  }) => {
188
205
  const {
189
- addMonths,
190
206
  calendarDate,
191
207
  dayFormat,
192
208
  disabledDates,
@@ -196,54 +212,52 @@ const Calendar = (props: {
196
212
  locale,
197
213
  maxDate,
198
214
  minDate,
199
- onDayClick,
200
- onDayKeyDown,
201
- onDayMouseEnter,
202
- onDayMouseLeave,
203
- onMonthClick,
204
- onMonthKeyDown,
205
- onYearClick,
206
- onYearKeyDown,
215
+ onCalendarClick,
216
+ onCalendarKeyDown,
217
+ onCalendarMouseEnter,
218
+ onCalendarMouseLeave,
207
219
  selectAdjacementDays,
208
220
  selectEndDate,
221
+ selectionType,
222
+ showWeekNumber,
209
223
  showAdjacementDays,
210
224
  startDate,
211
225
  view,
212
226
  weekdayFormat,
227
+ weekNumbersLabel,
213
228
  } = props
214
- const [date, setDate] = useState<Date>(calendarDate)
215
229
  const [listOfMonths, setListOfMonths] = useState<string[][]>([])
216
230
 
217
- useEffect(() => {
218
- if (addMonths !== 0) {
219
- setDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() + addMonths, 1))
220
- return
221
- }
222
-
223
- setDate(calendarDate)
224
- }, [calendarDate])
225
-
226
231
  useEffect(() => {
227
232
  setListOfMonths(createGroupsInArray(getMonthsNames(locale), 4))
228
233
  }, [])
229
234
 
230
- const monthDetails = getMonthDetails(date.getFullYear(), date.getMonth(), firstDayOfWeek)
231
- const listOfYears = createGroupsInArray(getYears(date.getFullYear()), 4)
232
- const weekDays = monthDetails[0]
235
+ const monthDetails = getMonthDetails(
236
+ calendarDate.getFullYear(),
237
+ calendarDate.getMonth(),
238
+ firstDayOfWeek,
239
+ )
240
+ const listOfYears = createGroupsInArray(getYears(calendarDate.getFullYear()), 4)
241
+ const weekDays = monthDetails[0].days
233
242
 
234
243
  return (
235
244
  <table>
236
245
  {view === 'days' && (
237
246
  <thead>
238
247
  <tr>
248
+ {showWeekNumber && (
249
+ <th className="calendar-cell">
250
+ <div className="calendar-header-cell-inner">{weekNumbersLabel}</div>
251
+ </th>
252
+ )}
239
253
  {weekDays.map(({ date }: { date: Date }, idx: number) => (
240
254
  <th key={idx} className="calendar-cell">
241
255
  <div className="calendar-header-cell-inner">
242
256
  {typeof weekdayFormat === 'function'
243
257
  ? weekdayFormat(date)
244
- : (typeof weekdayFormat === 'string'
258
+ : typeof weekdayFormat === 'string'
245
259
  ? date.toLocaleDateString(locale, { weekday: weekdayFormat })
246
- : date.toLocaleDateString(locale, { weekday: 'long' }).slice(0, weekdayFormat))}
260
+ : date.toLocaleDateString(locale, { weekday: 'long' }).slice(0, weekdayFormat)}
247
261
  </div>
248
262
  </th>
249
263
  ))}
@@ -253,46 +267,81 @@ const Calendar = (props: {
253
267
  <tbody>
254
268
  {view === 'days' &&
255
269
  monthDetails.map((week, index) => {
270
+ const date = convertToDateObject(
271
+ week.weekNumber === 0
272
+ ? `${calendarDate.getFullYear()}W53`
273
+ : `${calendarDate.getFullYear()}W${week.weekNumber}`,
274
+ selectionType,
275
+ )
276
+ const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
256
277
  return (
257
- <tr key={index}>
258
- {week.map(({ date, month }: { date: Date; month: string }, idx: number) =>
259
- month === 'current' || showAdjacementDays ? (
278
+ <tr
279
+ className={classNames('calendar-row', {
280
+ disabled: isDisabled,
281
+ range: selectionType === 'week' && isDateInRange(date, startDate, endDate),
282
+ 'range-hover':
283
+ selectionType === 'week' && hoverDate && selectEndDate
284
+ ? isDateInRange(date, startDate, hoverDate)
285
+ : isDateInRange(date, hoverDate, endDate),
286
+ selected: isDateSelected(date, startDate, endDate),
287
+ })}
288
+ key={index}
289
+ tabIndex={selectionType === 'week' && !isDisabled ? 0 : -1}
290
+ {...(selectionType === 'week' &&
291
+ !isDisabled && {
292
+ onBlur: () => onCalendarMouseLeave(),
293
+ onClick: () => onCalendarClick(date),
294
+ onFocus: () => onCalendarMouseEnter(date),
295
+ onKeyDown: (event) => onCalendarKeyDown(event, date),
296
+ onMouseEnter: () => onCalendarMouseEnter(date),
297
+ onMouseLeave: () => onCalendarMouseLeave(),
298
+ })}
299
+ >
300
+ {showWeekNumber && (
301
+ <th className="calendar-cell-week-number">
302
+ {week.weekNumber === 0 ? 53 : week.weekNumber}
303
+ </th>
304
+ )}
305
+ {week.days.map(({ date, month }: { date: Date; month: string }, idx: number) => {
306
+ const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
307
+ return month === 'current' || showAdjacementDays ? (
260
308
  <td
261
309
  className={classNames('calendar-cell', {
262
- today: month === 'current' && isToday(date),
263
- disabled: isDateDisabled(date, minDate, maxDate, disabledDates),
310
+ ...(selectionType === 'day' && {
311
+ clickable: month !== 'current' && selectAdjacementDays,
312
+ disabled: isDisabled,
313
+ 'range-hover':
314
+ month === 'current' &&
315
+ (hoverDate && selectEndDate
316
+ ? isDateInRange(date, startDate, hoverDate)
317
+ : isDateInRange(date, hoverDate, endDate)),
318
+ range: month === 'current' && isDateInRange(date, startDate, endDate),
319
+ selected: isDateSelected(date, startDate, endDate),
320
+ }),
264
321
  [month]: true,
265
- clickable: month !== 'current' && selectAdjacementDays,
266
- last: isLastDayOfMonth(date),
267
- 'range-hover':
268
- month === 'current' &&
269
- (hoverDate && selectEndDate
270
- ? isDateInRange(date, startDate, hoverDate)
271
- : isDateInRange(date, hoverDate, endDate)),
272
- range: month === 'current' && isDateInRange(date, startDate, endDate),
273
- selected: isDateSelected(date, startDate, endDate),
274
- start: isStartDate(date, startDate, endDate),
275
- end: isEndDate(date, startDate, endDate),
322
+ today: month === 'current' && isToday(date),
276
323
  })}
277
324
  key={idx}
278
325
  tabIndex={
326
+ selectionType === 'day' &&
279
327
  (month === 'current' || selectAdjacementDays) &&
280
- !isDateDisabled(date, minDate, maxDate, disabledDates)
328
+ !isDisabled
281
329
  ? 0
282
330
  : -1
283
331
  }
284
332
  title={date.toLocaleDateString(locale)}
285
- {...((month === 'current' || selectAdjacementDays) && {
286
- onBlur: () => onDayMouseLeave(),
287
- onClick: () => onDayClick(date),
288
- onFocus: () => onDayMouseEnter(date),
289
- onKeyDown: (event) => onDayKeyDown(event, date),
290
- onMouseEnter: () => onDayMouseEnter(date),
291
- onMouseLeave: () => onDayMouseLeave(),
292
- })}
333
+ {...(selectionType === 'day' &&
334
+ (month === 'current' || selectAdjacementDays) && {
335
+ onBlur: () => onCalendarMouseLeave(),
336
+ onClick: () => onCalendarClick(date),
337
+ onFocus: () => onCalendarMouseEnter(date),
338
+ onKeyDown: (event) => onCalendarKeyDown(event, date),
339
+ onMouseEnter: () => onCalendarMouseEnter(date),
340
+ onMouseLeave: () => onCalendarMouseLeave(),
341
+ })}
293
342
  {...(month !== 'current' &&
294
343
  !selectAdjacementDays && {
295
- onMouseEnter: () => onDayMouseLeave(),
344
+ onMouseEnter: () => onCalendarMouseLeave(),
296
345
  })}
297
346
  >
298
347
  <div className="calendar-cell-inner">
@@ -303,8 +352,8 @@ const Calendar = (props: {
303
352
  </td>
304
353
  ) : (
305
354
  <td key={idx}></td>
306
- ),
307
- )}
355
+ )
356
+ })}
308
357
  </tr>
309
358
  )
310
359
  })}
@@ -313,13 +362,31 @@ const Calendar = (props: {
313
362
  return (
314
363
  <tr key={index}>
315
364
  {row.map((month, idx) => {
365
+ const monthNumber = index * 3 + idx
366
+ const date = new Date(Date.UTC(calendarDate.getFullYear(), monthNumber, 1))
367
+ const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
316
368
  return (
317
369
  <td
318
- className="calendar-cell month"
370
+ className={classNames('calendar-cell', {
371
+ disabled: isDisabled,
372
+ selected: isDateSelected(date, startDate, endDate),
373
+ 'range-hover':
374
+ selectionType === 'month' &&
375
+ (hoverDate && selectEndDate
376
+ ? isDateInRange(date, startDate, hoverDate)
377
+ : isDateInRange(date, hoverDate, endDate)),
378
+ range: isDateInRange(date, startDate, endDate),
379
+ })}
319
380
  key={idx}
320
- onClick={() => onMonthClick(index * 3 + idx - addMonths)}
321
- onKeyDown={(event) => onMonthKeyDown(event, index * 3 + idx - addMonths)}
322
- tabIndex={0}
381
+ tabIndex={!isDisabled ? 0 : -1}
382
+ {...(!isDisabled && {
383
+ onBlur: () => onCalendarMouseLeave(),
384
+ onClick: () => onCalendarClick(date),
385
+ onFocus: () => onCalendarMouseEnter(date),
386
+ onKeyDown: (event) => onCalendarKeyDown(event, date),
387
+ onMouseEnter: () => onCalendarMouseEnter(date),
388
+ onMouseLeave: () => onCalendarMouseLeave(),
389
+ })}
323
390
  >
324
391
  <div className="calendar-cell-inner">{month}</div>
325
392
  </td>
@@ -333,20 +400,30 @@ const Calendar = (props: {
333
400
  return (
334
401
  <tr key={index}>
335
402
  {row.map((year, idx) => {
403
+ const date = new Date(Date.UTC(year, 0, 1))
404
+ const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
336
405
  return (
337
406
  <td
338
- className="calendar-cell year"
407
+ className={classNames('calendar-cell year', {
408
+ disabled: isDisabled,
409
+ selected: isDateSelected(date, startDate, endDate),
410
+ 'range-hover':
411
+ selectionType === 'year' &&
412
+ (hoverDate && selectEndDate
413
+ ? isDateInRange(date, startDate, hoverDate)
414
+ : isDateInRange(date, hoverDate, endDate)),
415
+ range: isDateInRange(date, startDate, endDate),
416
+ })}
339
417
  key={idx}
340
- onClick={() =>
341
- onYearClick(new Date(year, date.getMonth() - addMonths, date.getDate()))
342
- }
343
- onKeyDown={(event) =>
344
- onYearKeyDown(
345
- event,
346
- new Date(year, date.getMonth() - addMonths, date.getDate()),
347
- )
348
- }
349
- tabIndex={0}
418
+ tabIndex={!isDisabled ? 0 : -1}
419
+ {...(!isDisabled && {
420
+ onBlur: () => onCalendarMouseLeave(),
421
+ onClick: () => onCalendarClick(date),
422
+ onFocus: () => onCalendarMouseEnter(date),
423
+ onKeyDown: (event) => onCalendarKeyDown(event, date),
424
+ onMouseEnter: () => onCalendarMouseEnter(date),
425
+ onMouseLeave: () => onCalendarMouseLeave(),
426
+ })}
350
427
  >
351
428
  <div className="calendar-cell-inner">
352
429
  {new Date(year, 0, 1).toLocaleDateString(locale, { year: 'numeric' })}
@@ -363,7 +440,6 @@ const Calendar = (props: {
363
440
  }
364
441
 
365
442
  const Navigation = (props: {
366
- addMonths: number
367
443
  calendarDate: Date
368
444
  locale: string
369
445
  navigation: boolean
@@ -375,10 +451,9 @@ const Navigation = (props: {
375
451
  onMonthClick: () => void
376
452
  onNavigationClick: (direction: string, double?: boolean) => void
377
453
  onYearClick: () => void
378
- view: string
454
+ view: 'days' | 'months' | 'years'
379
455
  }) => {
380
456
  const {
381
- addMonths,
382
457
  calendarDate,
383
458
  locale,
384
459
  navigation,
@@ -393,16 +468,6 @@ const Navigation = (props: {
393
468
  view,
394
469
  } = props
395
470
 
396
- const [date, setDate] = useState<Date | null>()
397
- useEffect(() => {
398
- if (addMonths !== 0) {
399
- setDate(new Date(calendarDate.getFullYear(), calendarDate.getMonth() + addMonths, 1))
400
- return
401
- }
402
-
403
- setDate(calendarDate)
404
- }, [calendarDate])
405
-
406
471
  return (
407
472
  <div className="calendar-nav">
408
473
  {navigation && (
@@ -425,7 +490,7 @@ const Navigation = (props: {
425
490
  >
426
491
  {view === 'days' && (
427
492
  <CButton color="transparent" size="sm" onClick={() => navigation && onMonthClick()}>
428
- {date && date.toLocaleDateString(locale, { month: 'long' })}
493
+ {calendarDate && calendarDate.toLocaleDateString(locale, { month: 'long' })}
429
494
  </CButton>
430
495
  )}
431
496
  <CButton
@@ -434,7 +499,7 @@ const Navigation = (props: {
434
499
  onClick={() => navigation && onYearClick()}
435
500
  {...(navYearFirst && { style: { order: '-1' } })}
436
501
  >
437
- {date && date.toLocaleDateString(locale, { year: 'numeric' })}
502
+ {calendarDate && calendarDate.toLocaleDateString(locale, { year: 'numeric' })}
438
503
  </CButton>
439
504
  </div>
440
505
  {navigation && (
@@ -478,10 +543,13 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
478
543
  range,
479
544
  selectAdjacementDays = false,
480
545
  selectEndDate,
546
+ selectionType = 'day',
481
547
  showAdjacementDays = true,
548
+ showWeekNumber = false,
482
549
  weekdayFormat = 2,
483
- onDayHover,
550
+ weekNumbersLabel,
484
551
  onCalendarDateChange,
552
+ onDateHover,
485
553
  onEndDateChange,
486
554
  onStartDateChange,
487
555
  onSelectEndChange,
@@ -489,9 +557,28 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
489
557
  },
490
558
  ref,
491
559
  ) => {
560
+ const calendarRef = useRef<HTMLDivElement>(null)
561
+ const forkedRef = useForkedRef(ref, calendarRef)
492
562
  const isInitialMount = useRef(true)
493
563
  const [_calendarDate, setCalendarDate] = useState<Date | null>(null)
494
564
 
565
+ useEffect(() => {
566
+ if (selectionType === 'day' || selectionType === 'week') {
567
+ setView('days')
568
+ return
569
+ }
570
+
571
+ if (selectionType === 'month') {
572
+ setView('months')
573
+ return
574
+ }
575
+
576
+ if (selectionType === 'year') {
577
+ setView('years')
578
+ return
579
+ }
580
+ }, [selectionType])
581
+
495
582
  useEffect(() => {
496
583
  if (calendarDate === null) {
497
584
  setCalendarDate(new Date())
@@ -499,30 +586,31 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
499
586
  }
500
587
 
501
588
  if (calendarDate) {
502
- const date = new Date(calendarDate)
589
+ const date = convertToDateObject(calendarDate, selectionType)
503
590
  !isSameDateAs(_calendarDate, date) && setCalendarDate(date)
504
591
  }
505
592
  }, [calendarDate])
506
593
 
507
594
  const [_startDate, setStartDate] = useStateWithCallback<Date | null>(
508
- startDate ? new Date(startDate) : null,
509
- onStartDateChange,
595
+ startDate ? convertToDateObject(startDate, selectionType) : null,
596
+ () =>
597
+ onStartDateChange && onStartDateChange(getDateBySelectionType(_startDate, selectionType)),
510
598
  !isInitialMount.current,
511
599
  )
512
600
  useEffect(() => {
513
- const date = startDate ? new Date(startDate) : null
601
+ const date = startDate ? convertToDateObject(startDate, selectionType) : null
514
602
  if (!isSameDateAs(date, _startDate)) {
515
603
  setStartDate(date)
516
604
  }
517
605
  }, [startDate])
518
606
 
519
607
  const [_endDate, setEndDate] = useStateWithCallback<Date | null>(
520
- endDate ? new Date(endDate) : null,
521
- onEndDateChange,
608
+ endDate ? convertToDateObject(endDate, selectionType) : null,
609
+ () => onEndDateChange && onEndDateChange(getDateBySelectionType(_endDate, selectionType)),
522
610
  !isInitialMount.current,
523
611
  )
524
612
  useEffect(() => {
525
- const date = endDate ? new Date(endDate) : null
613
+ const date = endDate ? convertToDateObject(endDate, selectionType) : null
526
614
  if (!isSameDateAs(date, _endDate)) {
527
615
  setEndDate(date)
528
616
  }
@@ -530,14 +618,18 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
530
618
 
531
619
  const [_hoverDate, setHoverDate] = useState<Date | null>(null)
532
620
 
533
- const [_maxDate, setMaxDate] = useState<Date | null>(maxDate ? new Date(maxDate) : null)
621
+ const [_maxDate, setMaxDate] = useState<Date | null>(
622
+ maxDate ? convertToDateObject(maxDate, selectionType) : null,
623
+ )
534
624
  useEffect(() => {
535
- maxDate && setMaxDate(new Date(maxDate))
625
+ maxDate && setMaxDate(convertToDateObject(maxDate, selectionType))
536
626
  }, [maxDate])
537
627
 
538
- const [_minDate, setMinDate] = useState<Date | null>(minDate ? new Date(minDate) : null)
628
+ const [_minDate, setMinDate] = useState<Date | null>(
629
+ minDate ? convertToDateObject(minDate, selectionType) : null,
630
+ )
539
631
  useEffect(() => {
540
- minDate && setMinDate(new Date(minDate))
632
+ minDate && setMinDate(convertToDateObject(minDate, selectionType))
541
633
  }, [minDate])
542
634
 
543
635
  const [_selectEndDate, setSelectEndDate] = useStateWithCallback(
@@ -555,7 +647,7 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
555
647
  onSelectEndChange(_selectEndDate)
556
648
  }, [_selectEndDate])
557
649
 
558
- const [view, setView] = useStateWithCallback('days', onViewChanged)
650
+ const [view, setView] = useStateWithCallback<'days' | 'months' | 'years'>('days', onViewChanged)
559
651
 
560
652
  useEffect(() => {
561
653
  isInitialMount.current = false
@@ -578,13 +670,28 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
578
670
  onCalendarDateChange && onCalendarDateChange(d)
579
671
  }
580
672
 
581
- const handleDayClick = (date: Date, index?: number) => {
673
+ const handleCalendarClick = (date: Date, index?: number) => {
582
674
  if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
583
675
  return
584
676
  }
585
677
 
586
678
  const _date = new Date(date)
587
- setCalendarDate(index ? new Date(_date.setMonth(_date.getMonth() - index)) : _date)
679
+
680
+ if (view === 'days') {
681
+ setCalendarDate(index ? new Date(_date.setMonth(_date.getMonth() - index)) : _date)
682
+ }
683
+
684
+ if (view === 'months' && selectionType !== 'month') {
685
+ setCalendarDate(index ? new Date(_date.setMonth(_date.getMonth() - index)) : _date)
686
+ setView('days')
687
+ return
688
+ }
689
+
690
+ if (view === 'years' && selectionType !== 'year') {
691
+ setCalendarDate(index ? new Date(_date.setFullYear(_date.getFullYear() - index)) : _date)
692
+ setView('months')
693
+ return
694
+ }
588
695
 
589
696
  if (range) {
590
697
  if (_selectEndDate) {
@@ -626,40 +733,132 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
626
733
  setStartDate(date)
627
734
  }
628
735
 
629
- const handleDayKeyDown = (
736
+ const handleCalendarKeyDown = (
630
737
  event: React.KeyboardEvent<HTMLDivElement>,
631
738
  date: Date,
632
739
  index?: number,
633
740
  ) => {
634
741
  if (event.code === 'Space' || event.key === 'Enter') {
635
742
  event.preventDefault()
636
- handleDayClick(date, index)
743
+ handleCalendarClick(date, index)
744
+ }
745
+
746
+ if (
747
+ event.key === 'ArrowRight' ||
748
+ event.key === 'ArrowLeft' ||
749
+ event.key === 'ArrowUp' ||
750
+ event.key === 'ArrowDown'
751
+ ) {
752
+ event.preventDefault()
753
+
754
+ if (
755
+ maxDate &&
756
+ date >= convertToDateObject(maxDate, selectionType) &&
757
+ (event.key === 'ArrowRight' || event.key === 'ArrowDown')
758
+ ) {
759
+ return
760
+ }
761
+
762
+ if (
763
+ minDate &&
764
+ date <= convertToDateObject(minDate, selectionType) &&
765
+ (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
766
+ ) {
767
+ return
768
+ }
769
+
770
+ let element = event.target as HTMLElement
771
+
772
+ if (selectionType === 'week' && element.tabIndex === -1) {
773
+ element = element.closest('tr[tabindex="0"]') as HTMLElement
774
+ }
775
+
776
+ const list: HTMLElement[] = [].concat(
777
+ ...Element.prototype.querySelectorAll.call(
778
+ calendarRef.current,
779
+ 'td[tabindex="0"], tr[tabindex="0"]',
780
+ ),
781
+ )
782
+
783
+ const index = list.indexOf(element)
784
+ const first = index === 0
785
+ const last = index === list.length - 1
786
+
787
+ const toBoundary = {
788
+ start: index,
789
+ end: list.length - (index + 1),
790
+ }
791
+
792
+ const gap = {
793
+ ArrowRight: 1,
794
+ ArrowLeft: -1,
795
+ ArrowUp: selectionType === 'week' && view === 'days' ? -1 : view === 'days' ? -7 : -3,
796
+ ArrowDown: selectionType === 'week' && view === 'days' ? 1 : view === 'days' ? 7 : 3,
797
+ }
798
+
799
+ if (
800
+ (event.key === 'ArrowRight' && last) ||
801
+ (event.key === 'ArrowDown' && toBoundary['end'] < gap['ArrowDown']) ||
802
+ (event.key === 'ArrowLeft' && first) ||
803
+ (event.key === 'ArrowUp' && toBoundary['start'] < Math.abs(gap['ArrowUp']))
804
+ ) {
805
+ if (view === 'days') {
806
+ setCalendarPage(0, event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1)
807
+ }
808
+
809
+ if (view === 'months') {
810
+ setCalendarPage(event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1)
811
+ }
812
+
813
+ if (view === 'years') {
814
+ setCalendarPage(event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 10 : -10)
815
+ }
816
+
817
+ setTimeout(() => {
818
+ const _list: HTMLElement[] = [].concat(
819
+ ...Element.prototype.querySelectorAll.call(
820
+ element.parentNode?.parentNode,
821
+ 'td[tabindex="0"], tr[tabindex="0"]',
822
+ ),
823
+ )
824
+
825
+ if (_list.length && event.key === 'ArrowRight') {
826
+ _list[0].focus()
827
+ }
828
+
829
+ if (_list.length && event.key === 'ArrowLeft') {
830
+ _list[_list.length - 1].focus()
831
+ }
832
+
833
+ if (_list.length && event.key === 'ArrowDown') {
834
+ _list[gap['ArrowDown'] - (list.length - index)].focus()
835
+ }
836
+
837
+ if (_list.length && event.key === 'ArrowUp') {
838
+ _list[_list.length - (Math.abs(gap['ArrowUp']) + 1 - (index + 1))].focus()
839
+ }
840
+ }, 1)
841
+
842
+ return
843
+ }
844
+
845
+ list[index + gap[event.key]].focus()
637
846
  }
638
847
  }
639
848
 
640
- const handleDayMouseEnter = (date: Date) => {
849
+ const handleCalendarMouseEnter = (date: Date) => {
641
850
  if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
642
851
  return
643
852
  }
644
853
 
645
854
  setHoverDate(date)
646
- onDayHover && onDayHover(date)
647
- }
648
-
649
- const handleDayMouseLeave = () => {
650
- setHoverDate(null)
651
- onDayHover && onDayHover(null)
652
- }
653
855
 
654
- const handleMonthClick = (month: number) => {
655
- setCalendarPage(0, 0, month)
656
- setView('days')
856
+ date && onDateHover && onDateHover(getDateBySelectionType(date, selectionType))
657
857
  }
658
858
 
659
- const handleMonthKeyDown = (event: React.KeyboardEvent<HTMLDivElement>, month: number) => {
660
- if (event.code === 'Space' || event.key === 'Enter') {
661
- handleMonthClick(month)
662
- }
859
+ const handleCalendarMouseLeave = () => {
860
+ setHoverDate(null)
861
+ onDateHover && onDateHover(null)
663
862
  }
664
863
 
665
864
  const handleNavigationOnClick = (direction: string, double = false) => {
@@ -677,6 +876,7 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
677
876
  setCalendarPage(0, -1)
678
877
  return
679
878
  }
879
+
680
880
  if (direction === 'next') {
681
881
  if (double) {
682
882
  setCalendarPage(view === 'years' ? 10 : 1)
@@ -693,65 +893,65 @@ export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
693
893
  }
694
894
  }
695
895
 
696
- const handleYearClick = (date: Date) => {
697
- setCalendarDate(date)
698
- setView('months')
699
- }
700
-
701
- const handleYearKeyDown = (event: React.KeyboardEvent<HTMLDivElement>, date: Date) => {
702
- if (event.code === 'Space' || event.key === 'Enter') {
703
- handleYearClick(date)
704
- }
705
- }
706
-
707
896
  return (
708
- <div className={classNames('calendars', className)}>
897
+ <div
898
+ className={classNames(
899
+ 'calendars',
900
+ {
901
+ [`select-${selectionType}`]: selectionType && view === 'days',
902
+ 'show-week-numbers': showWeekNumber,
903
+ },
904
+ className,
905
+ )}
906
+ ref={forkedRef}
907
+ >
709
908
  {_calendarDate &&
710
- Array.from({ length: calendars }, (_, index) => (
711
- <div className={classNames('calendar', view)} key={index} ref={ref}>
712
- <Navigation
713
- addMonths={index}
714
- calendarDate={_calendarDate}
715
- locale={locale}
716
- navigation={navigation}
717
- navNextDoubleIcon={navNextDoubleIcon}
718
- navNextIcon={navNextIcon}
719
- navPrevDoubleIcon={navPrevDoubleIcon}
720
- navPrevIcon={navPrevIcon}
721
- navYearFirst={navYearFirst}
722
- onMonthClick={() => setView('months')}
723
- onNavigationClick={handleNavigationOnClick}
724
- onYearClick={() => setView('years')}
725
- view={view}
726
- />
727
- <Calendar
728
- addMonths={index}
729
- calendarDate={_calendarDate}
730
- dayFormat={dayFormat}
731
- disabledDates={disabledDates}
732
- endDate={_endDate}
733
- firstDayOfWeek={firstDayOfWeek}
734
- hoverDate={_hoverDate}
735
- locale={locale}
736
- maxDate={_maxDate}
737
- minDate={_minDate}
738
- onDayClick={(date) => handleDayClick(date, index)}
739
- onDayKeyDown={(event, date) => handleDayKeyDown(event, date, index)}
740
- onDayMouseEnter={handleDayMouseEnter}
741
- onDayMouseLeave={handleDayMouseLeave}
742
- onMonthClick={handleMonthClick}
743
- onMonthKeyDown={handleMonthKeyDown}
744
- onYearClick={handleYearClick}
745
- onYearKeyDown={handleYearKeyDown}
746
- selectEndDate={_selectEndDate}
747
- selectAdjacementDays={selectAdjacementDays}
748
- showAdjacementDays={showAdjacementDays}
749
- startDate={_startDate}
750
- view={view}
751
- weekdayFormat={weekdayFormat}
752
- />
753
- </div>
754
- ))}
909
+ Array.from({ length: calendars }, (_, index) => {
910
+ const calendarDate = getCalendarDate(_calendarDate, index, view)
911
+ return (
912
+ <div className={classNames('calendar', view)} key={index}>
913
+ <Navigation
914
+ calendarDate={calendarDate}
915
+ locale={locale}
916
+ navigation={navigation}
917
+ navNextDoubleIcon={navNextDoubleIcon}
918
+ navNextIcon={navNextIcon}
919
+ navPrevDoubleIcon={navPrevDoubleIcon}
920
+ navPrevIcon={navPrevIcon}
921
+ navYearFirst={navYearFirst}
922
+ onMonthClick={() => setView('months')}
923
+ onNavigationClick={handleNavigationOnClick}
924
+ onYearClick={() => setView('years')}
925
+ view={view}
926
+ />
927
+ <Calendar
928
+ calendarDate={calendarDate}
929
+ dayFormat={dayFormat}
930
+ disabledDates={disabledDates}
931
+ endDate={_endDate}
932
+ firstDayOfWeek={firstDayOfWeek}
933
+ hoverDate={_hoverDate}
934
+ locale={locale}
935
+ maxDate={_maxDate}
936
+ minDate={_minDate}
937
+ onCalendarClick={(date) => handleCalendarClick(date, index)}
938
+ onCalendarKeyDown={(event, date) => handleCalendarKeyDown(event, date, index)}
939
+ onCalendarMouseEnter={handleCalendarMouseEnter}
940
+ onCalendarMouseLeave={handleCalendarMouseLeave}
941
+ order={index}
942
+ selectAdjacementDays={selectAdjacementDays}
943
+ selectEndDate={_selectEndDate}
944
+ selectionType={selectionType}
945
+ showAdjacementDays={showAdjacementDays}
946
+ showWeekNumber={showWeekNumber}
947
+ startDate={_startDate}
948
+ view={view}
949
+ weekdayFormat={weekdayFormat}
950
+ weekNumbersLabel={weekNumbersLabel}
951
+ />
952
+ </div>
953
+ )
954
+ })}
755
955
  </div>
756
956
  )
757
957
  },
@@ -780,14 +980,17 @@ CCalendar.propTypes = {
780
980
  range: PropTypes.bool,
781
981
  selectAdjacementDays: PropTypes.bool,
782
982
  selectEndDate: PropTypes.bool,
983
+ selectionType: PropTypes.oneOf(['day', 'week', 'month', 'year']),
783
984
  showAdjacementDays: PropTypes.bool,
985
+ showWeekNumber: PropTypes.bool,
784
986
  startDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
785
987
  weekdayFormat: PropTypes.oneOfType([
786
988
  PropTypes.func,
787
989
  PropTypes.number,
788
990
  PropTypes.oneOf<'long' | 'narrow' | 'short'>(['long', 'narrow', 'short']),
789
991
  ]),
790
- onDayHover: PropTypes.func,
992
+ weekNumbersLabel: PropTypes.string,
993
+ onDateHover: PropTypes.func,
791
994
  onCalendarDateChange: PropTypes.func,
792
995
  onEndDateChange: PropTypes.func,
793
996
  onSelectEndChange: PropTypes.func,