@chassis-ui/react 0.0.0-20260831090525 → 0.1.1

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 (106) hide show
  1. package/README.md +11 -0
  2. package/dist/index.d.ts +682 -237
  3. package/dist/index.js +2 -2
  4. package/dist/style.css +1 -1
  5. package/package.json +53 -46
  6. package/src/components/accordion/Accordion.tsx +2 -2
  7. package/src/components/accordion/AccordionItem.tsx +6 -6
  8. package/src/components/autocomplete/Autocomplete.tsx +4 -4
  9. package/src/components/avatar/Avatar.tsx +5 -2
  10. package/src/components/breadcrumb/Breadcrumb.tsx +11 -2
  11. package/src/components/button/Button.tsx +7 -4
  12. package/src/components/button-group/ButtonGroup.tsx +2 -2
  13. package/src/components/calendar/Calendar.tsx +56 -42
  14. package/src/components/calendar/CalendarMonthGrid.tsx +3 -1
  15. package/src/components/calendar/CalendarMonthYearPicker.tsx +4 -2
  16. package/src/components/calendar/CalendarYearGrid.tsx +5 -3
  17. package/src/components/calendar/RangeCalendar.scss +8 -8
  18. package/src/components/calendar/RangeCalendar.tsx +56 -44
  19. package/src/components/calendar/index.ts +1 -0
  20. package/src/components/calendar/labels.tsx +71 -0
  21. package/src/components/card/Card.tsx +4 -4
  22. package/src/components/card/CardGroup.tsx +1 -1
  23. package/src/components/card/CardImage.tsx +1 -1
  24. package/src/components/carousel/Carousel.tsx +20 -9
  25. package/src/components/carousel/CarouselControlButton.tsx +1 -1
  26. package/src/components/carousel/CarouselPlayPause.tsx +1 -1
  27. package/src/components/carousel/carouselEngine.ts +3 -3
  28. package/src/components/checkbox/Checkbox.tsx +12 -11
  29. package/src/components/checkbox/CheckboxGroup.tsx +1 -1
  30. package/src/components/chip/Chip.tsx +5 -4
  31. package/src/components/chip-input/ChipInput.tsx +12 -4
  32. package/src/components/chip-input/ChipList.tsx +2 -2
  33. package/src/components/close-button/CloseButton.tsx +2 -2
  34. package/src/components/color-input/ColorInput.tsx +2 -2
  35. package/src/components/combobox/Combobox.tsx +2 -2
  36. package/src/components/datagrid/DataGrid.scss +203 -0
  37. package/src/components/datagrid/DataGrid.tsx +259 -0
  38. package/src/components/datagrid/DataGridBody.tsx +162 -0
  39. package/src/components/datagrid/DataGridCell.tsx +47 -0
  40. package/src/components/datagrid/DataGridColumn.tsx +109 -0
  41. package/src/components/datagrid/DataGridHeader.tsx +47 -0
  42. package/src/components/datagrid/DataGridPinBehavior.tsx +225 -0
  43. package/src/components/datagrid/DataGridRow.tsx +48 -0
  44. package/src/components/datagrid/DataGridSelectionCell.tsx +74 -0
  45. package/src/components/datagrid/DataGridTableLayout.ts +30 -0
  46. package/src/components/datagrid/index.ts +14 -0
  47. package/src/components/datagrid/useColumnGeometry.ts +161 -0
  48. package/src/components/datepicker/ClearButton.tsx +4 -1
  49. package/src/components/datepicker/DatePicker.scss +2 -2
  50. package/src/components/datepicker/DatePicker.tsx +179 -147
  51. package/src/components/datepicker/DateRangePicker.tsx +107 -89
  52. package/src/components/datepicker/renderDatePickerShell.tsx +1 -1
  53. package/src/components/drawer/Drawer.tsx +3 -1
  54. package/src/components/file-input/FileInput.tsx +2 -2
  55. package/src/components/floating-input/FloatingInput.tsx +8 -8
  56. package/src/components/form/renderFormCheck.tsx +3 -3
  57. package/src/components/form-field/FormField.tsx +8 -8
  58. package/src/components/form-field/renderFormField.tsx +14 -3
  59. package/src/components/grid/Col.tsx +1 -1
  60. package/src/components/grid/GridItem.tsx +1 -1
  61. package/src/components/grid/Row.tsx +1 -1
  62. package/src/components/input-group/InputGroup.tsx +2 -2
  63. package/src/components/list/List.tsx +5 -5
  64. package/src/components/menu/Menu.tsx +10 -0
  65. package/src/components/menu/MenuSubmenu.tsx +3 -3
  66. package/src/components/menu/MenuSubmenuBack.tsx +1 -1
  67. package/src/components/menu/MenuToggle.tsx +2 -2
  68. package/src/components/modal/Modal.tsx +5 -3
  69. package/src/components/navbar/Navbar.tsx +1 -1
  70. package/src/components/navbar/NavbarBrand.tsx +5 -2
  71. package/src/components/notification/NotificationStack.tsx +1 -1
  72. package/src/components/otp-input/OtpBox.tsx +1 -1
  73. package/src/components/otp-input/OtpInput.tsx +12 -11
  74. package/src/components/pagination/Pagination.tsx +2 -2
  75. package/src/components/pagination/PaginationItem.tsx +23 -4
  76. package/src/components/popover/Popover.tsx +40 -14
  77. package/src/components/progress/Progress.tsx +1 -1
  78. package/src/components/progress/ProgressBar.tsx +1 -1
  79. package/src/components/radio/Radio.tsx +2 -2
  80. package/src/components/radio/RadioGroup.tsx +1 -1
  81. package/src/components/select/Select.tsx +8 -8
  82. package/src/components/skeleton/Skeleton.tsx +1 -1
  83. package/src/components/spinner/Spinner.tsx +2 -2
  84. package/src/components/stepper/Stepper.tsx +5 -5
  85. package/src/components/switch/Switch.tsx +3 -3
  86. package/src/components/table/Table.css +2 -2
  87. package/src/components/table/Table.tsx +12 -6
  88. package/src/components/text-input/TextInput.tsx +2 -2
  89. package/src/components/textarea/Textarea.tsx +2 -2
  90. package/src/components/toast/ToastBody.tsx +1 -1
  91. package/src/components/toast/ToastHeader.tsx +2 -2
  92. package/src/components/toast/Toaster.tsx +1 -1
  93. package/src/components/tooltip/Tooltip.tsx +23 -7
  94. package/src/hooks/useAutoDismiss.ts +4 -1
  95. package/src/hooks/useDialogElement.ts +10 -0
  96. package/src/hooks/useFloatingOverlay.ts +12 -2
  97. package/src/hooks/usePagination.ts +6 -2
  98. package/src/index.ts +226 -0
  99. package/src/types.ts +26 -26
  100. package/src/utils/breakpoints.ts +1 -1
  101. package/src/utils/devWarning.ts +58 -0
  102. package/src/utils/direction.ts +14 -0
  103. package/src/utils/focusRedirect.ts +10 -16
  104. package/src/utils/polymorphic.ts +18 -1
  105. package/src/utils/suppressFocusRingGlobally.ts +22 -7
  106. package/src/utils/triggerElement.ts +25 -0
@@ -0,0 +1,161 @@
1
+ import { RefObject, useEffect, useMemo, useState } from 'react'
2
+ import { Key } from 'react-aria-components'
3
+ import { ColumnSize, ColumnStaticSize } from 'react-stately'
4
+
5
+ export interface ColumnGeometryColumn {
6
+ /**
7
+ * The column's unique id — matches `DataGridColumn`'s `id`/collection key.
8
+ */
9
+ id: Key
10
+ /**
11
+ * Mirrors `DataGridColumn`'s prop of the same name.
12
+ */
13
+ defaultWidth?: ColumnSize | null
14
+ /**
15
+ * Mirrors `DataGridColumn`'s prop of the same name.
16
+ */
17
+ minWidth?: ColumnStaticSize | null
18
+ /**
19
+ * Mirrors `DataGridColumn`'s prop of the same name.
20
+ */
21
+ maxWidth?: ColumnStaticSize | null
22
+ }
23
+
24
+ export interface ColumnOffset {
25
+ id: Key
26
+ /**
27
+ * Cumulative distance in pixels from the start edge of the row to this column's start edge.
28
+ */
29
+ offset: number
30
+ width: number
31
+ }
32
+
33
+ const STATIC_SIZE_PATTERN = /^\d+(\.\d+)?%?$/
34
+
35
+ /**
36
+ * Whether a `ColumnSize` resolves to a definite pixel value without waiting for layout: a plain
37
+ * number, a numeric string (`'150'`), or a percentage string (`'50%'`) — resolvable as soon as
38
+ * the container's own width is known. An `'<n>fr'` string is proportional — it only resolves to
39
+ * pixels once the layout engine has distributed the container's remaining space among every
40
+ * fractional column, so it's excluded here.
41
+ */
42
+ export const isStaticColumnSize = (size: ColumnSize | null | undefined): size is ColumnStaticSize =>
43
+ size != null && (typeof size === 'number' || STATIC_SIZE_PATTERN.test(size))
44
+
45
+ const resolveStaticWidth = (size: ColumnStaticSize, containerWidth: number): number => {
46
+ if (typeof size === 'number') return size
47
+ return size.endsWith('%') ? (parseFloat(size) / 100) * containerWidth : parseFloat(size)
48
+ }
49
+
50
+ /**
51
+ * Computes cumulative left-edge offsets for a list of columns from their declared
52
+ * `defaultWidth`/`minWidth`/`maxWidth` props, without reading the DOM. Only correct for a column
53
+ * whose own width — and every preceding column's width — is statically resolvable (see
54
+ * `isStaticColumnSize`): a column at or after the first `fr`-sized column has no offset that can
55
+ * be known before layout runs, so it resolves to `null`. Callers should fall back to
56
+ * `useColumnGeometry`'s DOM measurement for those.
57
+ */
58
+ export function calculateStaticColumnOffsets(
59
+ columns: readonly ColumnGeometryColumn[],
60
+ containerWidth: number
61
+ ): Map<Key, ColumnOffset | null> {
62
+ const offsets = new Map<Key, ColumnOffset | null>()
63
+ let offset = 0
64
+ let resolvable = true
65
+
66
+ for (const column of columns) {
67
+ if (!resolvable || !isStaticColumnSize(column.defaultWidth)) {
68
+ resolvable = false
69
+ offsets.set(column.id, null)
70
+ continue
71
+ }
72
+
73
+ let width = resolveStaticWidth(column.defaultWidth, containerWidth)
74
+ if (isStaticColumnSize(column.minWidth)) {
75
+ width = Math.max(width, resolveStaticWidth(column.minWidth, containerWidth))
76
+ }
77
+ if (isStaticColumnSize(column.maxWidth)) {
78
+ width = Math.min(width, resolveStaticWidth(column.maxWidth, containerWidth))
79
+ }
80
+
81
+ offsets.set(column.id, { id: column.id, offset, width })
82
+ offset += width
83
+ }
84
+
85
+ return offsets
86
+ }
87
+
88
+ interface MeasuredRect {
89
+ offset: number
90
+ width: number
91
+ }
92
+
93
+ /**
94
+ * Measures the mounted column-header cells, keyed by their 0-based column position.
95
+ *
96
+ * Keyed off each cell's own `aria-colindex` (1-based, assigned from the column's position in the
97
+ * resolved collection) rather than its position among the cells that happen to be in the DOM: the
98
+ * layout engine windows columns horizontally as well as rows, so `querySelectorAll` returns only
99
+ * the headers intersecting the horizontal viewport. Matching those positionally to the full column
100
+ * list — as this used to — mapped every column past the first unmounted one onto the wrong rect.
101
+ * A header without the attribute is skipped rather than guessed at.
102
+ */
103
+ const measureColumnHeaders = (container: HTMLElement): Map<number, MeasuredRect> => {
104
+ const measured = new Map<number, MeasuredRect>()
105
+ container.querySelectorAll<HTMLElement>('[role="columnheader"]').forEach((cell) => {
106
+ const colIndex = Number(cell.getAttribute('aria-colindex'))
107
+ if (!Number.isInteger(colIndex) || colIndex < 1) return
108
+ measured.set(colIndex - 1, { offset: cell.offsetLeft, width: cell.offsetWidth })
109
+ })
110
+ return measured
111
+ }
112
+
113
+ /**
114
+ * Resolves each column's rendered left-offset and width, independent of the virtualizer's
115
+ * private layout internals. Statically-sized columns (see `isStaticColumnSize`) resolve
116
+ * immediately from their declared props; `fr`-sized (proportional) columns fall back to
117
+ * `ResizeObserver`-measured DOM rects of the mounted `[role="columnheader"]` cells inside
118
+ * `containerRef`, matched to `columns` by `aria-colindex` (see `measureColumnHeaders`), since
119
+ * those only resolve to real pixels after the layout engine has actually distributed space.
120
+ */
121
+ export function useColumnGeometry(
122
+ columns: readonly ColumnGeometryColumn[],
123
+ containerRef: RefObject<HTMLElement | null>
124
+ ): Map<Key, ColumnOffset> {
125
+ const [containerWidth, setContainerWidth] = useState(0)
126
+ const [measured, setMeasured] = useState<Map<number, MeasuredRect>>(() => new Map())
127
+
128
+ useEffect(() => {
129
+ const container = containerRef.current
130
+ if (!container || typeof ResizeObserver === 'undefined') return
131
+
132
+ const measure = () => {
133
+ setContainerWidth(container.clientWidth)
134
+ setMeasured(measureColumnHeaders(container))
135
+ }
136
+
137
+ measure()
138
+ const observer = new ResizeObserver(measure)
139
+ observer.observe(container)
140
+ return () => observer.disconnect()
141
+ }, [containerRef])
142
+
143
+ const staticOffsets = useMemo(
144
+ () => calculateStaticColumnOffsets(columns, containerWidth),
145
+ [columns, containerWidth]
146
+ )
147
+
148
+ return useMemo(() => {
149
+ const result = new Map<Key, ColumnOffset>()
150
+ columns.forEach((column, index) => {
151
+ const staticOffset = staticOffsets.get(column.id)
152
+ const measuredRect = measured.get(index)
153
+ if (staticOffset) {
154
+ result.set(column.id, staticOffset)
155
+ } else if (measuredRect) {
156
+ result.set(column.id, { id: column.id, ...measuredRect })
157
+ }
158
+ })
159
+ return result
160
+ }, [columns, staticOffsets, measured])
161
+ }
@@ -1,6 +1,8 @@
1
1
  import React, { useRef } from 'react'
2
2
  import { useButton } from 'react-aria'
3
3
 
4
+ import { useCalendarLabels } from '../calendar/labels'
5
+
4
6
  interface ClearButtonProps {
5
7
  isDisabled?: boolean
6
8
  onPress: () => void
@@ -12,7 +14,8 @@ interface ClearButtonProps {
12
14
  // press/keyboard semantics consistent with the rest of this library, rather than a raw `onClick`.
13
15
  export const ClearButton = ({ isDisabled, onPress }: ClearButtonProps) => {
14
16
  const ref = useRef<HTMLButtonElement>(null)
15
- const { buttonProps } = useButton({ 'aria-label': 'Clear', isDisabled, onPress }, ref)
17
+ const labels = useCalendarLabels()
18
+ const { buttonProps } = useButton({ 'aria-label': labels.clear, isDisabled, onPress }, ref)
16
19
 
17
20
  return (
18
21
  <button {...buttonProps} className="input-adorn" ref={ref} type="button">
@@ -35,8 +35,8 @@
35
35
  }
36
36
 
37
37
  .datepicker-segment {
38
- padding-inline: var(--cx-space-3xsmall);
39
- @include border-radius(var(--cx-border-radius-small));
38
+ padding-inline: var(--cx-space-3xs);
39
+ @include border-radius(var(--cx-border-radius-sm));
40
40
  outline: 0;
41
41
  }
42
42
 
@@ -17,6 +17,7 @@ import { ClearButton } from './ClearButton'
17
17
  import { DateField } from './DateField'
18
18
  import { renderDatePickerShell } from './renderDatePickerShell'
19
19
  import './DatePicker.scss'
20
+ import { CalendarLabels, CalendarLabelsProvider, DEFAULT_CALENDAR_LABELS } from '../calendar/labels'
20
21
 
21
22
  interface DatePickerBaseProps extends Omit<
22
23
  HTMLAttributes<HTMLDivElement>,
@@ -72,6 +73,14 @@ interface DatePickerBaseProps extends Omit<
72
73
  * shown but cannot be selected.
73
74
  */
74
75
  isDateUnavailable?: (date: DateValue) => boolean
76
+ /**
77
+ * Overrides for the strings this component and its calendar render themselves rather than
78
+ * getting from the active locale — the calendar trigger, the clear adornment, and the calendar's
79
+ * own year-view arrows/announcements. Date segment order, month and weekday names all follow
80
+ * `I18nProvider`'s locale via react-aria and need no override. Merged over the English defaults,
81
+ * so passing one key leaves the rest alone.
82
+ */
83
+ labels?: Partial<CalendarLabels>
75
84
  /**
76
85
  * Whether the calendar popover is open (controlled).
77
86
  */
@@ -100,9 +109,9 @@ interface DatePickerBaseProps extends Omit<
100
109
  */
101
110
  onOpenChange?: (isOpen: boolean) => void
102
111
  /**
103
- * Size the component small or large.
112
+ * Size the component sm or lg.
104
113
  */
105
- size?: 'small' | 'large'
114
+ size?: 'sm' | 'lg'
106
115
  /**
107
116
  * ISO 8601 dates (`YYYY-MM-DD`) to mark unselectable, as a convenience alternative to
108
117
  * `isDateUnavailable` for data-driven cases (e.g. booked dates fetched from an API). Composed
@@ -202,6 +211,7 @@ const DatePickerSingle = forwardRef<HTMLDivElement, DatePickerSingleProps>(
202
211
  isDateUnavailable,
203
212
  isOpen,
204
213
  label,
214
+ labels,
205
215
  maxValue,
206
216
  minValue,
207
217
  name,
@@ -290,76 +300,84 @@ const DatePickerSingle = forwardRef<HTMLDivElement, DatePickerSingleProps>(
290
300
  // `Calendar` merges onto its root.
291
301
  const { dialogProps: domDialogProps } = useDialog(dialogProps, calendarRef)
292
302
 
293
- return renderFormField({
294
- children: (
295
- <>
296
- {renderDatePickerShell({
297
- calendar: (
298
- <Calendar
299
- {...domDialogProps}
300
- autoFocus
301
- disabled={disabled}
302
- firstDayOfWeek={firstDayOfWeek}
303
- isDateUnavailable={combinedIsDateUnavailable}
304
- maxValue={maxValue}
305
- minValue={minValue}
306
- onChange={calendarProps.onChange}
307
- ref={calendarRef}
308
- value={calendarProps.value}
309
- visibleMonths={visibleMonths}
310
- />
311
- ),
312
- className,
313
- clearButton: state.value && !disabled && (
314
- <ClearButton
315
- onPress={() => {
316
- state.setValue(null)
317
- // `ClearButton` unmounts itself once `state.value` clears — without this, focus
318
- // would otherwise drop to `document.body` (same failure mode fixed for the
319
- // calendar's own month/year view switch — see `CalendarMonthYearPicker`'s comment).
320
- toggleButtonRef.current?.focus()
321
- }}
322
- />
323
- ),
324
- disabled,
325
- field: <DateField fieldProps={fieldProps} />,
326
- fieldClassName: 'w-100 overflow-x-scroll',
327
- groupProps: {
328
- ...mergeProps(groupProps, rest),
329
- 'aria-describedby': describedBy,
330
- 'aria-labelledby': labelledBy
331
- },
332
- groupRef: forkedGroupRef,
333
- invalid,
334
- isOpen: state.isOpen,
335
- overlayDismissProps,
336
- overlayRef,
337
- overlayStyle,
338
- placementAttr,
339
- size,
340
- toggleButton: (
341
- <CalendarToggleButton buttonProps={buttonProps} ref={toggleButtonRef} state={state} />
342
- ),
343
- valid
344
- })}
345
- {name && (
346
- <input
347
- disabled={disabled}
348
- name={name}
349
- type="hidden"
350
- value={state.value ? state.value.toString() : ''}
351
- />
352
- )}
353
- </>
354
- ),
355
- help,
356
- ids: { feedback: feedbackId, help: helpId, label: labelId },
357
- invalid,
358
- invalidFeedback,
359
- label,
360
- valid,
361
- validFeedback
362
- })
303
+ return (
304
+ <CalendarLabelsProvider labels={labels}>
305
+ {renderFormField({
306
+ children: (
307
+ <>
308
+ {renderDatePickerShell({
309
+ calendar: (
310
+ <Calendar
311
+ {...domDialogProps}
312
+ autoFocus
313
+ disabled={disabled}
314
+ firstDayOfWeek={firstDayOfWeek}
315
+ isDateUnavailable={combinedIsDateUnavailable}
316
+ maxValue={maxValue}
317
+ minValue={minValue}
318
+ onChange={calendarProps.onChange}
319
+ ref={calendarRef}
320
+ value={calendarProps.value}
321
+ visibleMonths={visibleMonths}
322
+ />
323
+ ),
324
+ className,
325
+ clearButton: state.value && !disabled && (
326
+ <ClearButton
327
+ onPress={() => {
328
+ state.setValue(null)
329
+ // `ClearButton` unmounts itself once `state.value` clears — without this, focus
330
+ // would otherwise drop to `document.body` (same failure mode fixed for the
331
+ // calendar's own month/year view switch — see `CalendarMonthYearPicker`'s comment).
332
+ toggleButtonRef.current?.focus()
333
+ }}
334
+ />
335
+ ),
336
+ disabled,
337
+ field: <DateField fieldProps={fieldProps} />,
338
+ fieldClassName: 'w-100 overflow-x-scroll',
339
+ groupProps: {
340
+ ...mergeProps(groupProps, rest),
341
+ 'aria-describedby': describedBy,
342
+ 'aria-labelledby': labelledBy
343
+ },
344
+ groupRef: forkedGroupRef,
345
+ invalid,
346
+ isOpen: state.isOpen,
347
+ overlayDismissProps,
348
+ overlayRef,
349
+ overlayStyle,
350
+ placementAttr,
351
+ size,
352
+ toggleButton: (
353
+ <CalendarToggleButton
354
+ buttonProps={buttonProps}
355
+ ref={toggleButtonRef}
356
+ state={state}
357
+ />
358
+ ),
359
+ valid
360
+ })}
361
+ {name && (
362
+ <input
363
+ disabled={disabled}
364
+ name={name}
365
+ type="hidden"
366
+ value={state.value ? state.value.toString() : ''}
367
+ />
368
+ )}
369
+ </>
370
+ ),
371
+ help,
372
+ ids: { feedback: feedbackId, help: helpId, label: labelId },
373
+ invalid,
374
+ invalidFeedback,
375
+ label,
376
+ valid,
377
+ validFeedback
378
+ })}
379
+ </CalendarLabelsProvider>
380
+ )
363
381
  }
364
382
  )
365
383
 
@@ -389,6 +407,7 @@ const DatePickerMultiple = forwardRef<HTMLDivElement, DatePickerMultipleProps>(
389
407
  isDateUnavailable,
390
408
  isOpen,
391
409
  label,
410
+ labels,
392
411
  maxValue,
393
412
  minValue,
394
413
  name,
@@ -457,87 +476,100 @@ const DatePickerMultiple = forwardRef<HTMLDivElement, DatePickerMultipleProps>(
457
476
  calendarRef
458
477
  )
459
478
 
479
+ // This variant builds its own trigger button props rather than getting them from
480
+ // `useDatePicker` (see the component comment), so it has to resolve the label itself — it's
481
+ // the provider here, so it can't read the context it's about to publish.
482
+ const mergedLabels = { ...DEFAULT_CALENDAR_LABELS, ...labels }
483
+
460
484
  const buttonProps: AriaButtonProps = {
461
485
  'aria-expanded': state.isOpen,
462
486
  'aria-haspopup': 'dialog',
463
- 'aria-label': 'Calendar',
487
+ 'aria-label': mergedLabels.calendar,
464
488
  isDisabled: disabled
465
489
  }
466
490
 
467
- return renderFormField({
468
- children: (
469
- <>
470
- {renderDatePickerShell({
471
- calendar: (
472
- <Calendar
473
- {...domDialogProps}
474
- autoFocus
475
- disabled={disabled}
476
- firstDayOfWeek={firstDayOfWeek}
477
- isDateUnavailable={combinedIsDateUnavailable}
478
- maxValue={maxValue}
479
- minValue={minValue}
480
- onChange={setValues}
481
- ref={calendarRef}
482
- selectionMode="multiple"
483
- value={values}
484
- visibleMonths={visibleMonths}
485
- />
486
- ),
487
- className,
488
- clearButton: values.length > 0 && !disabled && (
489
- <ClearButton
490
- onPress={() => {
491
- setValues([])
492
- // See the single-selection `ClearButton` usage above for why this is needed.
493
- toggleButtonRef.current?.focus()
494
- }}
495
- />
496
- ),
497
- disabled,
498
- field: <MultiDateField values={values} />,
499
- fieldClassName: 'w-100 overflow-x-scroll',
500
- groupProps: {
501
- ...mergeProps(rest),
502
- 'aria-describedby': describedBy,
503
- 'aria-disabled': disabled || undefined,
504
- 'aria-labelledby': labelledBy,
505
- id: groupId,
506
- role: 'group'
507
- },
508
- groupRef: forkedGroupRef,
509
- invalid,
510
- isOpen: state.isOpen,
511
- overlayDismissProps,
512
- overlayRef,
513
- overlayStyle,
514
- placementAttr,
515
- size,
516
- toggleButton: (
517
- <CalendarToggleButton buttonProps={buttonProps} ref={toggleButtonRef} state={state} />
518
- ),
519
- valid
520
- })}
521
- {name &&
522
- values.map((date) => (
523
- <input
524
- disabled={disabled}
525
- key={date.toString()}
526
- name={name}
527
- type="hidden"
528
- value={date.toString()}
529
- />
530
- ))}
531
- </>
532
- ),
533
- help,
534
- ids: { feedback: feedbackId, help: helpId, label: labelId },
535
- invalid,
536
- invalidFeedback,
537
- label,
538
- valid,
539
- validFeedback
540
- })
491
+ return (
492
+ <CalendarLabelsProvider labels={labels}>
493
+ {renderFormField({
494
+ children: (
495
+ <>
496
+ {renderDatePickerShell({
497
+ calendar: (
498
+ <Calendar
499
+ {...domDialogProps}
500
+ autoFocus
501
+ disabled={disabled}
502
+ firstDayOfWeek={firstDayOfWeek}
503
+ isDateUnavailable={combinedIsDateUnavailable}
504
+ maxValue={maxValue}
505
+ minValue={minValue}
506
+ onChange={setValues}
507
+ ref={calendarRef}
508
+ selectionMode="multiple"
509
+ value={values}
510
+ visibleMonths={visibleMonths}
511
+ />
512
+ ),
513
+ className,
514
+ clearButton: values.length > 0 && !disabled && (
515
+ <ClearButton
516
+ onPress={() => {
517
+ setValues([])
518
+ // See the single-selection `ClearButton` usage above for why this is needed.
519
+ toggleButtonRef.current?.focus()
520
+ }}
521
+ />
522
+ ),
523
+ disabled,
524
+ field: <MultiDateField values={values} />,
525
+ fieldClassName: 'w-100 overflow-x-scroll',
526
+ groupProps: {
527
+ ...mergeProps(rest),
528
+ 'aria-describedby': describedBy,
529
+ 'aria-disabled': disabled || undefined,
530
+ 'aria-labelledby': labelledBy,
531
+ id: groupId,
532
+ role: 'group'
533
+ },
534
+ groupRef: forkedGroupRef,
535
+ invalid,
536
+ isOpen: state.isOpen,
537
+ overlayDismissProps,
538
+ overlayRef,
539
+ overlayStyle,
540
+ placementAttr,
541
+ size,
542
+ toggleButton: (
543
+ <CalendarToggleButton
544
+ buttonProps={buttonProps}
545
+ ref={toggleButtonRef}
546
+ state={state}
547
+ />
548
+ ),
549
+ valid
550
+ })}
551
+ {name &&
552
+ values.map((date) => (
553
+ <input
554
+ disabled={disabled}
555
+ key={date.toString()}
556
+ name={name}
557
+ type="hidden"
558
+ value={date.toString()}
559
+ />
560
+ ))}
561
+ </>
562
+ ),
563
+ help,
564
+ ids: { feedback: feedbackId, help: helpId, label: labelId },
565
+ invalid,
566
+ invalidFeedback,
567
+ label,
568
+ valid,
569
+ validFeedback
570
+ })}
571
+ </CalendarLabelsProvider>
572
+ )
541
573
  }
542
574
  )
543
575