@instructure/ui-calendar 11.6.0 → 11.6.1-snapshot-129

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 (95) hide show
  1. package/CHANGELOG.md +36 -286
  2. package/es/Calendar/{Day → v1/Day}/index.js +2 -2
  3. package/es/Calendar/{index.js → v1/index.js} +4 -4
  4. package/es/Calendar/v2/Day/index.js +148 -0
  5. package/{src/index.ts → es/Calendar/v2/Day/props.js} +2 -4
  6. package/es/Calendar/v2/Day/styles.js +78 -0
  7. package/es/Calendar/v2/index.js +407 -0
  8. package/es/Calendar/v2/props.js +26 -0
  9. package/es/Calendar/v2/styles.js +66 -0
  10. package/es/{index.js → exports/a.js} +2 -2
  11. package/es/exports/b.js +25 -0
  12. package/lib/Calendar/{Day → v1/Day}/index.js +4 -4
  13. package/lib/Calendar/{index.js → v1/index.js} +12 -12
  14. package/lib/Calendar/v2/Day/index.js +155 -0
  15. package/lib/Calendar/v2/Day/props.js +31 -0
  16. package/lib/Calendar/v2/Day/styles.js +84 -0
  17. package/lib/Calendar/v2/index.js +416 -0
  18. package/lib/Calendar/v2/props.js +31 -0
  19. package/lib/Calendar/v2/styles.js +72 -0
  20. package/lib/{index.js → exports/a.js} +3 -3
  21. package/lib/exports/b.js +19 -0
  22. package/package.json +45 -23
  23. package/src/Calendar/{Day → v1/Day}/index.tsx +3 -3
  24. package/src/Calendar/{Day → v1/Day}/props.ts +1 -1
  25. package/src/Calendar/{index.tsx → v1/index.tsx} +4 -4
  26. package/src/Calendar/v2/Day/index.tsx +176 -0
  27. package/src/Calendar/v2/Day/props.ts +126 -0
  28. package/src/Calendar/v2/Day/styles.ts +96 -0
  29. package/src/Calendar/v2/README.md +255 -0
  30. package/src/Calendar/v2/index.tsx +560 -0
  31. package/src/Calendar/v2/props.ts +207 -0
  32. package/src/Calendar/v2/styles.ts +75 -0
  33. package/src/exports/a.ts +28 -0
  34. package/src/exports/b.ts +28 -0
  35. package/tsconfig.build.tsbuildinfo +1 -1
  36. package/types/Calendar/{Day → v1/Day}/index.d.ts +1 -1
  37. package/types/Calendar/v1/Day/index.d.ts.map +1 -0
  38. package/types/Calendar/{Day → v1/Day}/props.d.ts +1 -1
  39. package/types/Calendar/v1/Day/props.d.ts.map +1 -0
  40. package/types/Calendar/v1/Day/styles.d.ts.map +1 -0
  41. package/types/Calendar/v1/Day/theme.d.ts.map +1 -0
  42. package/types/Calendar/{index.d.ts → v1/index.d.ts} +1 -1
  43. package/types/Calendar/v1/index.d.ts.map +1 -0
  44. package/types/Calendar/v1/props.d.ts.map +1 -0
  45. package/types/Calendar/v1/styles.d.ts.map +1 -0
  46. package/types/Calendar/v1/theme.d.ts.map +1 -0
  47. package/types/Calendar/v2/Day/index.d.ts +49 -0
  48. package/types/Calendar/v2/Day/index.d.ts.map +1 -0
  49. package/types/Calendar/v2/Day/props.d.ts +76 -0
  50. package/types/Calendar/v2/Day/props.d.ts.map +1 -0
  51. package/types/Calendar/v2/Day/styles.d.ts +15 -0
  52. package/types/Calendar/v2/Day/styles.d.ts.map +1 -0
  53. package/types/Calendar/v2/index.d.ts +84 -0
  54. package/types/Calendar/v2/index.d.ts.map +1 -0
  55. package/types/Calendar/v2/props.d.ts +135 -0
  56. package/types/Calendar/v2/props.d.ts.map +1 -0
  57. package/types/Calendar/v2/styles.d.ts +13 -0
  58. package/types/Calendar/v2/styles.d.ts.map +1 -0
  59. package/types/exports/a.d.ts +5 -0
  60. package/types/exports/a.d.ts.map +1 -0
  61. package/types/exports/b.d.ts +5 -0
  62. package/types/exports/b.d.ts.map +1 -0
  63. package/types/Calendar/Day/index.d.ts.map +0 -1
  64. package/types/Calendar/Day/props.d.ts.map +0 -1
  65. package/types/Calendar/Day/styles.d.ts.map +0 -1
  66. package/types/Calendar/Day/theme.d.ts.map +0 -1
  67. package/types/Calendar/index.d.ts.map +0 -1
  68. package/types/Calendar/props.d.ts.map +0 -1
  69. package/types/Calendar/styles.d.ts.map +0 -1
  70. package/types/Calendar/theme.d.ts.map +0 -1
  71. package/types/index.d.ts +0 -5
  72. package/types/index.d.ts.map +0 -1
  73. /package/es/Calendar/{Day → v1/Day}/props.js +0 -0
  74. /package/es/Calendar/{Day → v1/Day}/styles.js +0 -0
  75. /package/es/Calendar/{Day → v1/Day}/theme.js +0 -0
  76. /package/es/Calendar/{props.js → v1/props.js} +0 -0
  77. /package/es/Calendar/{styles.js → v1/styles.js} +0 -0
  78. /package/es/Calendar/{theme.js → v1/theme.js} +0 -0
  79. /package/lib/Calendar/{Day → v1/Day}/props.js +0 -0
  80. /package/lib/Calendar/{Day → v1/Day}/styles.js +0 -0
  81. /package/lib/Calendar/{Day → v1/Day}/theme.js +0 -0
  82. /package/lib/Calendar/{props.js → v1/props.js} +0 -0
  83. /package/lib/Calendar/{styles.js → v1/styles.js} +0 -0
  84. /package/lib/Calendar/{theme.js → v1/theme.js} +0 -0
  85. /package/src/Calendar/{Day → v1/Day}/styles.ts +0 -0
  86. /package/src/Calendar/{Day → v1/Day}/theme.ts +0 -0
  87. /package/src/Calendar/{README.md → v1/README.md} +0 -0
  88. /package/src/Calendar/{props.ts → v1/props.ts} +0 -0
  89. /package/src/Calendar/{styles.ts → v1/styles.ts} +0 -0
  90. /package/src/Calendar/{theme.ts → v1/theme.ts} +0 -0
  91. /package/types/Calendar/{Day → v1/Day}/styles.d.ts +0 -0
  92. /package/types/Calendar/{Day → v1/Day}/theme.d.ts +0 -0
  93. /package/types/Calendar/{props.d.ts → v1/props.d.ts} +0 -0
  94. /package/types/Calendar/{styles.d.ts → v1/styles.d.ts} +0 -0
  95. /package/types/Calendar/{theme.d.ts → v1/theme.d.ts} +0 -0
@@ -0,0 +1,560 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Children, Component, ReactElement, MouseEvent } from 'react'
26
+
27
+ import { View } from '@instructure/ui-view/latest'
28
+ import {
29
+ safeCloneElement,
30
+ callRenderProp,
31
+ omitProps,
32
+ withDeterministicId
33
+ } from '@instructure/ui-react-utils'
34
+ import { createChainedFunction } from '@instructure/ui-utils'
35
+ import { logError as error } from '@instructure/console'
36
+ import { AccessibleContent } from '@instructure/ui-a11y-content'
37
+
38
+ import { withStyle } from '@instructure/emotion'
39
+
40
+ import { Locale, DateTime, ApplyLocaleContext } from '@instructure/ui-i18n'
41
+ import type { Moment } from '@instructure/ui-i18n'
42
+
43
+ import generateStyle from './styles'
44
+
45
+ import { Day } from './Day'
46
+
47
+ import { allowedProps } from './props'
48
+ import type { CalendarProps, CalendarState } from './props'
49
+ import { Renderable } from '@instructure/shared-types'
50
+
51
+ import { IconButton } from '@instructure/ui-buttons/latest'
52
+ import {
53
+ ChevronLeftInstUIIcon,
54
+ ChevronRightInstUIIcon
55
+ } from '@instructure/ui-icons'
56
+
57
+ import { SimpleSelect } from '@instructure/ui-simple-select/latest'
58
+
59
+ /**
60
+ ---
61
+ category: components
62
+ ---
63
+ **/
64
+ @withDeterministicId()
65
+ @withStyle(generateStyle)
66
+ class Calendar extends Component<CalendarProps, CalendarState> {
67
+ static readonly componentId = 'Calendar'
68
+
69
+ declare context: React.ContextType<typeof ApplyLocaleContext>
70
+ static contextType = ApplyLocaleContext
71
+ static Day = Day
72
+ static DAY_COUNT = 42 // 6 weeks visible
73
+
74
+ static allowedProps = allowedProps
75
+ static defaultProps = {
76
+ as: 'span',
77
+ role: 'table'
78
+ }
79
+
80
+ ref: Element | null = null
81
+ private _weekdayHeaderIds
82
+
83
+ constructor(props: CalendarProps) {
84
+ super(props)
85
+ this._weekdayHeaderIds = (
86
+ this.props.renderWeekdayLabels || this.defaultWeekdays
87
+ ).reduce((ids: Record<number, string>, _label, i) => {
88
+ return { ...ids, [i]: this.props.deterministicId!('weekday-header') }
89
+ }, {})
90
+ this.state = this.calculateState(
91
+ this.locale(),
92
+ this.timezone(),
93
+ props.currentDate
94
+ )
95
+ }
96
+
97
+ handleRef = (el: Element | null) => {
98
+ this.ref = el
99
+ }
100
+
101
+ componentDidMount() {
102
+ this.props.makeStyles?.()
103
+ }
104
+
105
+ componentDidUpdate(prevProps: CalendarProps) {
106
+ this.props.makeStyles?.()
107
+
108
+ const isUpdated =
109
+ prevProps.locale !== this.props.locale ||
110
+ prevProps.timezone !== this.props.timezone ||
111
+ prevProps.visibleMonth !== this.props.visibleMonth
112
+
113
+ if (isUpdated) {
114
+ this.setState(() => {
115
+ return {
116
+ ...this.calculateState(
117
+ this.locale(),
118
+ this.timezone(),
119
+ this.props.currentDate
120
+ )
121
+ }
122
+ })
123
+ }
124
+ }
125
+
126
+ calculateState = (locale: string, timezone: string, currentDate?: string) => {
127
+ const visibleMonth = this.props.visibleMonth || currentDate
128
+
129
+ return {
130
+ visibleMonth: visibleMonth
131
+ ? DateTime.parse(visibleMonth, locale, timezone)
132
+ : DateTime.now(locale, timezone),
133
+ today: currentDate
134
+ ? DateTime.parse(currentDate, locale, timezone)
135
+ : DateTime.now(locale, timezone)
136
+ }
137
+ }
138
+
139
+ get role() {
140
+ return this.props.role === 'listbox' ? this.props.role : undefined
141
+ }
142
+
143
+ get hasPrevMonth() {
144
+ // this is needed for locales that doesn't use the latin script for numbers e.g.: arabic
145
+ const yearNumber = Number(
146
+ this.state.visibleMonth
147
+ .clone()
148
+ .locale('en')
149
+ .subtract({ months: 1 })
150
+ .format('YYYY')
151
+ )
152
+ return (
153
+ !this.props.withYearPicker ||
154
+ (this.props.withYearPicker &&
155
+ yearNumber >= this.props.withYearPicker.startYear)
156
+ )
157
+ }
158
+
159
+ get hasNextMonth() {
160
+ // this is needed for locales that doesn't use the latin script for numbers e.g.: arabic
161
+ const yearNumber = Number(
162
+ this.state.visibleMonth
163
+ .clone()
164
+ .locale('en')
165
+ .add({ months: 1 })
166
+ .format('YYYY')
167
+ )
168
+ return (
169
+ !this.props.withYearPicker ||
170
+ (this.props.withYearPicker &&
171
+ yearNumber <= this.props.withYearPicker.endYear)
172
+ )
173
+ }
174
+
175
+ renderMonthNavigationButtons = () => {
176
+ const { renderNextMonthButton, renderPrevMonthButton } = this.props
177
+
178
+ return {
179
+ prevButton: renderPrevMonthButton ? (
180
+ callRenderProp(renderPrevMonthButton)
181
+ ) : (
182
+ <IconButton
183
+ size="small"
184
+ withBackground={false}
185
+ withBorder={false}
186
+ renderIcon={<ChevronLeftInstUIIcon color="baseColor" />}
187
+ screenReaderLabel="Previous month"
188
+ />
189
+ ),
190
+ nextButton: renderNextMonthButton ? (
191
+ callRenderProp(renderNextMonthButton)
192
+ ) : (
193
+ <IconButton
194
+ size="small"
195
+ withBackground={false}
196
+ withBorder={false}
197
+ renderIcon={<ChevronRightInstUIIcon color="baseColor" />}
198
+ screenReaderLabel="Next month"
199
+ />
200
+ )
201
+ }
202
+ }
203
+
204
+ handleMonthChange = (direction: 'prev' | 'next') => (e: React.MouseEvent) => {
205
+ const { onRequestRenderNextMonth, onRequestRenderPrevMonth } = this.props
206
+ const { visibleMonth } = this.state
207
+ const newDate = visibleMonth.clone()
208
+ if (direction === 'prev') {
209
+ if (!this.hasPrevMonth) {
210
+ return
211
+ }
212
+ if (onRequestRenderPrevMonth) {
213
+ onRequestRenderPrevMonth(
214
+ e,
215
+ newDate.subtract({ months: 1 }).format('YYYY-MM')
216
+ )
217
+ return
218
+ }
219
+ newDate.subtract({ months: 1 })
220
+ } else {
221
+ if (!this.hasNextMonth) {
222
+ return
223
+ }
224
+ if (onRequestRenderNextMonth) {
225
+ onRequestRenderNextMonth(
226
+ e,
227
+ newDate.add({ months: 1 }).format('YYYY-MM')
228
+ )
229
+ return
230
+ }
231
+ newDate.add({ months: 1 })
232
+ }
233
+ this.setState({ visibleMonth: newDate })
234
+ }
235
+
236
+ handleYearChange = (
237
+ e: React.SyntheticEvent<Element, Event>,
238
+ year: string
239
+ ) => {
240
+ const { withYearPicker } = this.props
241
+ const { visibleMonth } = this.state
242
+ const yearNumber = Number(
243
+ DateTime.parse(year, this.locale(), this.timezone())
244
+ .locale('en')
245
+ .format('YYYY')
246
+ )
247
+ const newDate = visibleMonth.clone()
248
+ if (withYearPicker?.onRequestYearChange) {
249
+ withYearPicker.onRequestYearChange(e, yearNumber)
250
+ return
251
+ }
252
+ newDate.year(yearNumber)
253
+ this.setState({ visibleMonth: newDate })
254
+ }
255
+
256
+ renderHeader() {
257
+ const { renderNavigationLabel, styles, withYearPicker } = this.props
258
+ const { visibleMonth } = this.state
259
+ const { prevButton, nextButton } = this.renderMonthNavigationButtons()
260
+
261
+ const cloneButton = (
262
+ button: ReactElement<any>,
263
+ onClick?: (e: React.MouseEvent) => void
264
+ ) =>
265
+ safeCloneElement(button, {
266
+ onClick: createChainedFunction(
267
+ button.props.onClick as React.MouseEventHandler,
268
+ onClick
269
+ )
270
+ })
271
+
272
+ const style = [
273
+ styles?.navigation,
274
+ ...(prevButton || nextButton ? [styles?.navigationWithButtons] : [])
275
+ ]
276
+
277
+ const yearList: string[] = []
278
+
279
+ if (withYearPicker) {
280
+ const { startYear, endYear } = withYearPicker
281
+ for (let year = endYear; year >= startYear!; year--) {
282
+ // add the years to the list with the correct locale
283
+ yearList.push(
284
+ DateTime.parse(
285
+ year.toString(),
286
+ this.locale(),
287
+ this.timezone()
288
+ ).format('YYYY')
289
+ )
290
+ }
291
+ }
292
+
293
+ return (
294
+ <div>
295
+ <div css={style}>
296
+ {prevButton &&
297
+ cloneButton(prevButton, this.handleMonthChange('prev'))}
298
+ {renderNavigationLabel ? (
299
+ callRenderProp(renderNavigationLabel)
300
+ ) : (
301
+ <span>
302
+ <div>{visibleMonth.format('MMMM')}</div>
303
+ {!withYearPicker ? (
304
+ <div>{visibleMonth.format('YYYY')}</div>
305
+ ) : null}
306
+ </span>
307
+ )}
308
+ {nextButton &&
309
+ cloneButton(nextButton, this.handleMonthChange('next'))}
310
+ </div>
311
+
312
+ {withYearPicker ? (
313
+ <div css={styles?.yearPicker}>
314
+ <SimpleSelect
315
+ width="90px"
316
+ renderLabel=""
317
+ placeholder="--"
318
+ assistiveText={withYearPicker.screenReaderLabel}
319
+ value={visibleMonth.format('YYYY')}
320
+ onChange={(
321
+ e: React.SyntheticEvent<Element, Event>,
322
+ {
323
+ value
324
+ }: {
325
+ value?: string | number | undefined
326
+ id?: string | undefined
327
+ }
328
+ ) => this.handleYearChange(e, `${value}`)}
329
+ >
330
+ {yearList.map((year) => (
331
+ <SimpleSelect.Option key={year} id={`opt-${year}`} value={year}>
332
+ {`${year}`}
333
+ </SimpleSelect.Option>
334
+ ))}
335
+ </SimpleSelect>
336
+ </div>
337
+ ) : null}
338
+ </div>
339
+ )
340
+ }
341
+
342
+ renderBody() {
343
+ return (
344
+ <table role={this.role}>
345
+ <thead>{this.renderWeekdayHeaders()}</thead>
346
+ <tbody>{this.renderDays()}</tbody>
347
+ </table>
348
+ )
349
+ }
350
+
351
+ renderWeekdayHeaders() {
352
+ const { styles } = this.props
353
+ const renderWeekdayLabels =
354
+ this.props.renderWeekdayLabels || this.defaultWeekdays
355
+ const { length } = renderWeekdayLabels
356
+ error(
357
+ length === 7,
358
+ `[Calendar] \`renderWeekdayLabels\` should be an array with 7 labels (one for each weekday). ${length} provided.`
359
+ )
360
+ return (
361
+ <tr>
362
+ {renderWeekdayLabels.map((label, i) => {
363
+ return (
364
+ <th
365
+ key={i}
366
+ scope="col"
367
+ css={styles?.weekdayHeader}
368
+ id={this._weekdayHeaderIds[i]}
369
+ >
370
+ {callRenderProp(label)}
371
+ </th>
372
+ )
373
+ })}
374
+ </tr>
375
+ )
376
+ }
377
+
378
+ get defaultWeekdays() {
379
+ const shortDayNames = DateTime.getLocalDayNamesOfTheWeek(
380
+ this.locale(),
381
+ 'short'
382
+ )
383
+ const longDayNames = DateTime.getLocalDayNamesOfTheWeek(
384
+ this.locale(),
385
+ 'long'
386
+ )
387
+ return [
388
+ <AccessibleContent key={1} alt={longDayNames[0]}>
389
+ {shortDayNames[0]}
390
+ </AccessibleContent>,
391
+ <AccessibleContent key={2} alt={longDayNames[1]}>
392
+ {shortDayNames[1]}
393
+ </AccessibleContent>,
394
+ <AccessibleContent key={3} alt={longDayNames[2]}>
395
+ {shortDayNames[2]}
396
+ </AccessibleContent>,
397
+ <AccessibleContent key={4} alt={longDayNames[3]}>
398
+ {shortDayNames[3]}
399
+ </AccessibleContent>,
400
+ <AccessibleContent key={5} alt={longDayNames[4]}>
401
+ {shortDayNames[4]}
402
+ </AccessibleContent>,
403
+ <AccessibleContent key={6} alt={longDayNames[5]}>
404
+ {shortDayNames[5]}
405
+ </AccessibleContent>,
406
+ <AccessibleContent key={7} alt={longDayNames[6]}>
407
+ {shortDayNames[6]}
408
+ </AccessibleContent>
409
+ ] as Renderable<never>[]
410
+ }
411
+
412
+ renderDays() {
413
+ const { children } = this.props
414
+ const childrenArr = Children.toArray(
415
+ children ? children : this.renderDefaultdays()
416
+ ) as ReactElement[]
417
+ const { length } = childrenArr
418
+ const role = this.role === 'listbox' ? 'presentation' : undefined
419
+
420
+ error(
421
+ length === Calendar.DAY_COUNT,
422
+ `[Calendar] should have exactly ${Calendar.DAY_COUNT} children. ${length} provided.`
423
+ )
424
+
425
+ return childrenArr
426
+ .reduce((days: ReactElement<any>[][], day, i) => {
427
+ const index = Math.floor(i / 7)
428
+ if (!days[index]) days.push([])
429
+ days[index].push(day)
430
+ return days // 7xN 2D array of `Day`s
431
+ }, [])
432
+ .map((row) => (
433
+ <tr key={`row${row[0].props.date}`} role={role}>
434
+ {row.map((day, i) => (
435
+ <td key={day.props.date} role={role}>
436
+ {role === 'presentation'
437
+ ? safeCloneElement(day, {
438
+ 'aria-describedby': this._weekdayHeaderIds[i]
439
+ })
440
+ : day}
441
+ </td>
442
+ ))}
443
+ </tr>
444
+ ))
445
+ }
446
+
447
+ locale(): string {
448
+ if (this.props.locale) {
449
+ return this.props.locale
450
+ } else if (this.context && this.context.locale) {
451
+ return this.context.locale
452
+ }
453
+ return Locale.browserLocale()
454
+ }
455
+
456
+ timezone() {
457
+ if (this.props.timezone) {
458
+ return this.props.timezone
459
+ } else if (this.context && this.context.timezone) {
460
+ return this.context.timezone
461
+ }
462
+ return DateTime.browserTimeZone()
463
+ }
464
+
465
+ // date is returned as an ISO string, like 2021-09-14T22:00:00.000Z
466
+ handleDayClick = (event: MouseEvent<any>, { date }: { date: string }) => {
467
+ if (this.props.onDateSelected) {
468
+ const parsedDate = DateTime.parse(date, this.locale(), this.timezone())
469
+ this.props.onDateSelected(parsedDate.toISOString(), parsedDate, event)
470
+ }
471
+ }
472
+
473
+ isDisabledDate(date: Moment) {
474
+ const disabledDates = this.props.disabledDates
475
+ if (!disabledDates) {
476
+ return false
477
+ }
478
+ if (Array.isArray(disabledDates)) {
479
+ for (const aDisabledDate of disabledDates) {
480
+ if (date.isSame(aDisabledDate, 'day')) {
481
+ return true
482
+ }
483
+ }
484
+ return false
485
+ }
486
+ return disabledDates(date.toISOString())
487
+ }
488
+
489
+ renderDefaultdays() {
490
+ const { selectedDate } = this.props
491
+ const { visibleMonth, today } = this.state
492
+ // Sets it to the first local day of the week counting back from the start of the month.
493
+ // Note that first day depends on the locale, e.g. it's Sunday in the US and
494
+ // Monday in most of the EU.
495
+ const currDate = DateTime.getFirstDayOfWeek(
496
+ visibleMonth.clone().startOf('month')
497
+ )
498
+
499
+ const arr: Moment[] = []
500
+ for (let i = 0; i < Calendar.DAY_COUNT; i++) {
501
+ // This workaround is needed because moment's `.add({days: 1})` function has a bug that happens when the date added lands perfectly onto the DST cutoff,
502
+ // in these cases adding 1 day results in 23 hours added instead,
503
+ // so `moment.tz('2024-09-07T00:00:00', 'America/Santiago').add({days: 1})` results
504
+ // in "Sat Sep 07 2024 23:00:00 GMT-0400" instead of "Sun Sep 08 2024 00:00:00 GMT-0400".
505
+ // which would cause duplicate dates in the calendar.
506
+ // More info on the bug: https://github.com/moment/moment/issues/4743
507
+ // Please note that this causes one hour of time difference in the affected timezones/dates and to
508
+ // fully solve this bug we need to change to something like luxon which handles this properly
509
+ if (currDate.clone().format('HH') === '23') {
510
+ arr.push(currDate.clone().add({ hours: 1 }))
511
+ } else {
512
+ arr.push(currDate.clone())
513
+ }
514
+
515
+ currDate.add({ days: 1 })
516
+ }
517
+ return arr.map((date) => {
518
+ const dateStr = date.toISOString()
519
+ return (
520
+ <Calendar.Day
521
+ key={dateStr}
522
+ date={dateStr}
523
+ isSelected={selectedDate ? date.isSame(selectedDate, 'day') : false}
524
+ isToday={date.isSame(today, 'day')}
525
+ isOutsideMonth={!date.isSame(visibleMonth, 'month')}
526
+ label={date.format('D MMMM YYYY')} // used by screen readers
527
+ onClick={this.handleDayClick}
528
+ interaction={this.isDisabledDate(date) ? 'disabled' : 'enabled'}
529
+ >
530
+ {date.format('DD')}
531
+ </Calendar.Day>
532
+ )
533
+ })
534
+ }
535
+
536
+ render() {
537
+ const passthroughProps = View.omitViewProps(
538
+ omitProps(this.props, Calendar.allowedProps),
539
+ Calendar
540
+ )
541
+
542
+ return (
543
+ <View
544
+ {...passthroughProps}
545
+ as={this.props.as}
546
+ display="inline-block"
547
+ padding="small"
548
+ background="primary"
549
+ elementRef={this.handleRef}
550
+ data-cid="Calendar"
551
+ >
552
+ {this.renderHeader()}
553
+ {this.renderBody()}
554
+ </View>
555
+ )
556
+ }
557
+ }
558
+
559
+ export default Calendar
560
+ export { Calendar }