@chassis-ui/react 0.1.0 → 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.
- package/README.md +11 -0
- package/dist/index.d.ts +682 -237
- package/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +32 -25
- package/src/components/accordion/Accordion.tsx +2 -2
- package/src/components/accordion/AccordionItem.tsx +6 -6
- package/src/components/autocomplete/Autocomplete.tsx +4 -4
- package/src/components/avatar/Avatar.tsx +5 -2
- package/src/components/breadcrumb/Breadcrumb.tsx +11 -2
- package/src/components/button/Button.tsx +7 -4
- package/src/components/button-group/ButtonGroup.tsx +2 -2
- package/src/components/calendar/Calendar.tsx +56 -42
- package/src/components/calendar/CalendarMonthGrid.tsx +3 -1
- package/src/components/calendar/CalendarMonthYearPicker.tsx +4 -2
- package/src/components/calendar/CalendarYearGrid.tsx +5 -3
- package/src/components/calendar/RangeCalendar.scss +8 -8
- package/src/components/calendar/RangeCalendar.tsx +56 -44
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/labels.tsx +71 -0
- package/src/components/card/Card.tsx +4 -4
- package/src/components/card/CardGroup.tsx +1 -1
- package/src/components/card/CardImage.tsx +1 -1
- package/src/components/carousel/Carousel.tsx +20 -9
- package/src/components/carousel/CarouselControlButton.tsx +1 -1
- package/src/components/carousel/CarouselPlayPause.tsx +1 -1
- package/src/components/carousel/carouselEngine.ts +3 -3
- package/src/components/checkbox/Checkbox.tsx +12 -11
- package/src/components/checkbox/CheckboxGroup.tsx +1 -1
- package/src/components/chip/Chip.tsx +5 -4
- package/src/components/chip-input/ChipInput.tsx +12 -4
- package/src/components/chip-input/ChipList.tsx +2 -2
- package/src/components/close-button/CloseButton.tsx +2 -2
- package/src/components/color-input/ColorInput.tsx +2 -2
- package/src/components/combobox/Combobox.tsx +2 -2
- package/src/components/datagrid/DataGrid.scss +203 -0
- package/src/components/datagrid/DataGrid.tsx +259 -0
- package/src/components/datagrid/DataGridBody.tsx +162 -0
- package/src/components/datagrid/DataGridCell.tsx +47 -0
- package/src/components/datagrid/DataGridColumn.tsx +109 -0
- package/src/components/datagrid/DataGridHeader.tsx +47 -0
- package/src/components/datagrid/DataGridPinBehavior.tsx +225 -0
- package/src/components/datagrid/DataGridRow.tsx +48 -0
- package/src/components/datagrid/DataGridSelectionCell.tsx +74 -0
- package/src/components/datagrid/DataGridTableLayout.ts +30 -0
- package/src/components/datagrid/index.ts +14 -0
- package/src/components/datagrid/useColumnGeometry.ts +161 -0
- package/src/components/datepicker/ClearButton.tsx +4 -1
- package/src/components/datepicker/DatePicker.scss +2 -2
- package/src/components/datepicker/DatePicker.tsx +179 -147
- package/src/components/datepicker/DateRangePicker.tsx +107 -89
- package/src/components/datepicker/renderDatePickerShell.tsx +1 -1
- package/src/components/drawer/Drawer.tsx +3 -1
- package/src/components/file-input/FileInput.tsx +2 -2
- package/src/components/floating-input/FloatingInput.tsx +8 -8
- package/src/components/form/renderFormCheck.tsx +3 -3
- package/src/components/form-field/FormField.tsx +8 -8
- package/src/components/form-field/renderFormField.tsx +14 -3
- package/src/components/grid/Col.tsx +1 -1
- package/src/components/grid/GridItem.tsx +1 -1
- package/src/components/grid/Row.tsx +1 -1
- package/src/components/input-group/InputGroup.tsx +2 -2
- package/src/components/list/List.tsx +5 -5
- package/src/components/menu/Menu.tsx +10 -0
- package/src/components/menu/MenuSubmenu.tsx +3 -3
- package/src/components/menu/MenuSubmenuBack.tsx +1 -1
- package/src/components/menu/MenuToggle.tsx +2 -2
- package/src/components/modal/Modal.tsx +5 -3
- package/src/components/navbar/Navbar.tsx +1 -1
- package/src/components/navbar/NavbarBrand.tsx +5 -2
- package/src/components/notification/NotificationStack.tsx +1 -1
- package/src/components/otp-input/OtpBox.tsx +1 -1
- package/src/components/otp-input/OtpInput.tsx +12 -11
- package/src/components/pagination/Pagination.tsx +2 -2
- package/src/components/pagination/PaginationItem.tsx +23 -4
- package/src/components/popover/Popover.tsx +40 -14
- package/src/components/progress/Progress.tsx +1 -1
- package/src/components/progress/ProgressBar.tsx +1 -1
- package/src/components/radio/Radio.tsx +2 -2
- package/src/components/radio/RadioGroup.tsx +1 -1
- package/src/components/select/Select.tsx +8 -8
- package/src/components/skeleton/Skeleton.tsx +1 -1
- package/src/components/spinner/Spinner.tsx +2 -2
- package/src/components/stepper/Stepper.tsx +5 -5
- package/src/components/switch/Switch.tsx +3 -3
- package/src/components/table/Table.css +2 -2
- package/src/components/table/Table.tsx +12 -6
- package/src/components/text-input/TextInput.tsx +2 -2
- package/src/components/textarea/Textarea.tsx +2 -2
- package/src/components/toast/ToastBody.tsx +1 -1
- package/src/components/toast/ToastHeader.tsx +2 -2
- package/src/components/toast/Toaster.tsx +1 -1
- package/src/components/tooltip/Tooltip.tsx +23 -7
- package/src/hooks/useAutoDismiss.ts +4 -1
- package/src/hooks/useDialogElement.ts +10 -0
- package/src/hooks/useFloatingOverlay.ts +12 -2
- package/src/hooks/usePagination.ts +6 -2
- package/src/index.ts +226 -0
- package/src/types.ts +26 -26
- package/src/utils/breakpoints.ts +1 -1
- package/src/utils/devWarning.ts +58 -0
- package/src/utils/direction.ts +14 -0
- package/src/utils/focusRedirect.ts +10 -16
- package/src/utils/polymorphic.ts +18 -1
- package/src/utils/suppressFocusRingGlobally.ts +22 -7
- 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
|
|
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">
|
|
@@ -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
|
|
112
|
+
* Size the component sm or lg.
|
|
104
113
|
*/
|
|
105
|
-
size?: '
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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':
|
|
487
|
+
'aria-label': mergedLabels.calendar,
|
|
464
488
|
isDisabled: disabled
|
|
465
489
|
}
|
|
466
490
|
|
|
467
|
-
return
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
|