@chassis-ui/react 0.0.0-20260831090525

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 (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/index.d.ts +5909 -0
  4. package/dist/index.js +2 -0
  5. package/dist/style.css +1 -0
  6. package/package.json +114 -0
  7. package/src/components/accordion/Accordion.tsx +190 -0
  8. package/src/components/accordion/AccordionBody.tsx +46 -0
  9. package/src/components/accordion/AccordionHeader.tsx +21 -0
  10. package/src/components/accordion/AccordionItem.tsx +59 -0
  11. package/src/components/accordion/context.ts +8 -0
  12. package/src/components/accordion/index.ts +8 -0
  13. package/src/components/autocomplete/Autocomplete.tsx +464 -0
  14. package/src/components/autocomplete/AutocompleteGroup.tsx +20 -0
  15. package/src/components/autocomplete/AutocompleteItem.tsx +18 -0
  16. package/src/components/autocomplete/index.ts +6 -0
  17. package/src/components/avatar/Avatar.tsx +134 -0
  18. package/src/components/avatar/AvatarImage.tsx +19 -0
  19. package/src/components/avatar/AvatarStack.tsx +98 -0
  20. package/src/components/avatar/index.ts +6 -0
  21. package/src/components/badge/Badge.tsx +94 -0
  22. package/src/components/badge/index.ts +2 -0
  23. package/src/components/breadcrumb/Breadcrumb.tsx +59 -0
  24. package/src/components/breadcrumb/BreadcrumbItem.tsx +38 -0
  25. package/src/components/breadcrumb/index.ts +4 -0
  26. package/src/components/button/Button.tsx +229 -0
  27. package/src/components/button/index.ts +2 -0
  28. package/src/components/button-group/ButtonGroup.tsx +55 -0
  29. package/src/components/button-group/ButtonToolbar.tsx +47 -0
  30. package/src/components/button-group/index.ts +4 -0
  31. package/src/components/calendar/Calendar.scss +57 -0
  32. package/src/components/calendar/Calendar.tsx +324 -0
  33. package/src/components/calendar/CalendarMonthBlock.tsx +57 -0
  34. package/src/components/calendar/CalendarMonthGrid.tsx +92 -0
  35. package/src/components/calendar/CalendarMonthYearPicker.tsx +162 -0
  36. package/src/components/calendar/CalendarNavButton.tsx +24 -0
  37. package/src/components/calendar/CalendarWeekGrid.tsx +58 -0
  38. package/src/components/calendar/CalendarYearGrid.tsx +126 -0
  39. package/src/components/calendar/RangeCalendar.scss +95 -0
  40. package/src/components/calendar/RangeCalendar.tsx +361 -0
  41. package/src/components/calendar/index.ts +5 -0
  42. package/src/components/card/Card.tsx +173 -0
  43. package/src/components/card/CardBody.tsx +75 -0
  44. package/src/components/card/CardFooter.tsx +47 -0
  45. package/src/components/card/CardGroup.tsx +51 -0
  46. package/src/components/card/CardHeader.tsx +47 -0
  47. package/src/components/card/CardImage.tsx +70 -0
  48. package/src/components/card/CardImageOverlay.tsx +47 -0
  49. package/src/components/card/CardLink.tsx +40 -0
  50. package/src/components/card/CardSubtitle.tsx +47 -0
  51. package/src/components/card/CardText.tsx +47 -0
  52. package/src/components/card/CardTitle.tsx +47 -0
  53. package/src/components/card/index.ts +22 -0
  54. package/src/components/carousel/Carousel.tsx +742 -0
  55. package/src/components/carousel/CarouselControlButton.tsx +74 -0
  56. package/src/components/carousel/CarouselControlNext.tsx +22 -0
  57. package/src/components/carousel/CarouselControlPrev.tsx +22 -0
  58. package/src/components/carousel/CarouselIndicators.tsx +44 -0
  59. package/src/components/carousel/CarouselInner.tsx +56 -0
  60. package/src/components/carousel/CarouselItem.tsx +51 -0
  61. package/src/components/carousel/CarouselOverlay.tsx +51 -0
  62. package/src/components/carousel/CarouselPlayPause.tsx +61 -0
  63. package/src/components/carousel/carouselEngine.ts +170 -0
  64. package/src/components/carousel/context.ts +29 -0
  65. package/src/components/carousel/index.ts +21 -0
  66. package/src/components/checkbox/Checkbox.tsx +236 -0
  67. package/src/components/checkbox/CheckboxGroup.tsx +145 -0
  68. package/src/components/checkbox/context.ts +14 -0
  69. package/src/components/checkbox/index.ts +4 -0
  70. package/src/components/chip/Chip.tsx +183 -0
  71. package/src/components/chip/index.ts +2 -0
  72. package/src/components/chip-input/ChipInput.tsx +363 -0
  73. package/src/components/chip-input/ChipList.tsx +95 -0
  74. package/src/components/chip-input/index.ts +2 -0
  75. package/src/components/close-button/CloseButton.tsx +205 -0
  76. package/src/components/close-button/index.ts +2 -0
  77. package/src/components/collapse/Collapse.tsx +128 -0
  78. package/src/components/collapse/index.ts +2 -0
  79. package/src/components/color-input/ColorInput.tsx +106 -0
  80. package/src/components/color-input/index.ts +2 -0
  81. package/src/components/combobox/Combobox.tsx +320 -0
  82. package/src/components/combobox/ComboboxGroup.tsx +27 -0
  83. package/src/components/combobox/ComboboxItem.tsx +44 -0
  84. package/src/components/combobox/ComboboxListBox.tsx +98 -0
  85. package/src/components/combobox/index.ts +6 -0
  86. package/src/components/datepicker/CalendarToggleButton.tsx +58 -0
  87. package/src/components/datepicker/ClearButton.tsx +35 -0
  88. package/src/components/datepicker/DateField.tsx +54 -0
  89. package/src/components/datepicker/DatePicker.scss +51 -0
  90. package/src/components/datepicker/DatePicker.tsx +574 -0
  91. package/src/components/datepicker/DateRangePicker.scss +11 -0
  92. package/src/components/datepicker/DateRangePicker.tsx +343 -0
  93. package/src/components/datepicker/index.ts +4 -0
  94. package/src/components/datepicker/renderDatePickerShell.tsx +104 -0
  95. package/src/components/drawer/Drawer.tsx +211 -0
  96. package/src/components/drawer/DrawerBody.tsx +47 -0
  97. package/src/components/drawer/DrawerFooter.tsx +51 -0
  98. package/src/components/drawer/DrawerHeader.tsx +60 -0
  99. package/src/components/drawer/DrawerTitle.tsx +49 -0
  100. package/src/components/drawer/index.ts +10 -0
  101. package/src/components/file-input/FileInput.tsx +100 -0
  102. package/src/components/file-input/index.ts +2 -0
  103. package/src/components/flex/Flex.tsx +134 -0
  104. package/src/components/flex/index.ts +2 -0
  105. package/src/components/floating-input/FloatingInput.tsx +94 -0
  106. package/src/components/floating-input/index.ts +2 -0
  107. package/src/components/form/Form.tsx +26 -0
  108. package/src/components/form/FormFeedback.tsx +64 -0
  109. package/src/components/form/FormHelp.tsx +46 -0
  110. package/src/components/form/FormLabel.tsx +26 -0
  111. package/src/components/form/index.ts +8 -0
  112. package/src/components/form/renderFormCheck.tsx +86 -0
  113. package/src/components/form-field/FormField.tsx +84 -0
  114. package/src/components/form-field/index.ts +2 -0
  115. package/src/components/form-field/renderFormField.tsx +74 -0
  116. package/src/components/grid/Col.tsx +69 -0
  117. package/src/components/grid/Container.tsx +59 -0
  118. package/src/components/grid/Grid.tsx +86 -0
  119. package/src/components/grid/GridItem.tsx +123 -0
  120. package/src/components/grid/Row.tsx +71 -0
  121. package/src/components/grid/gap.ts +8 -0
  122. package/src/components/grid/index.ts +10 -0
  123. package/src/components/icon/Icon.tsx +74 -0
  124. package/src/components/icon/index.ts +2 -0
  125. package/src/components/input-adorn/InputAdorn.tsx +55 -0
  126. package/src/components/input-adorn/index.ts +2 -0
  127. package/src/components/input-group/InputGroup.tsx +50 -0
  128. package/src/components/input-group/InputGroupAddon.tsx +50 -0
  129. package/src/components/input-group/index.ts +4 -0
  130. package/src/components/link/Link.tsx +148 -0
  131. package/src/components/link/index.ts +2 -0
  132. package/src/components/list/List.tsx +193 -0
  133. package/src/components/list/ListItem.tsx +91 -0
  134. package/src/components/list/index.ts +4 -0
  135. package/src/components/menu/Menu.tsx +398 -0
  136. package/src/components/menu/MenuDivider.tsx +19 -0
  137. package/src/components/menu/MenuHeader.tsx +55 -0
  138. package/src/components/menu/MenuItem.tsx +85 -0
  139. package/src/components/menu/MenuItemDef.ts +83 -0
  140. package/src/components/menu/MenuList.tsx +142 -0
  141. package/src/components/menu/MenuSubmenu.tsx +284 -0
  142. package/src/components/menu/MenuSubmenuBack.tsx +39 -0
  143. package/src/components/menu/MenuText.tsx +47 -0
  144. package/src/components/menu/MenuToggle.tsx +148 -0
  145. package/src/components/menu/index.ts +19 -0
  146. package/src/components/menu/menuNavigation.ts +106 -0
  147. package/src/components/menu/submenuGroup.ts +43 -0
  148. package/src/components/modal/Modal.tsx +162 -0
  149. package/src/components/modal/ModalBody.tsx +47 -0
  150. package/src/components/modal/ModalFooter.tsx +51 -0
  151. package/src/components/modal/ModalHeader.tsx +60 -0
  152. package/src/components/modal/ModalTitle.tsx +49 -0
  153. package/src/components/modal/index.ts +10 -0
  154. package/src/components/nav/Nav.tsx +97 -0
  155. package/src/components/nav/NavItem.tsx +42 -0
  156. package/src/components/nav/NavLink.tsx +48 -0
  157. package/src/components/nav/NavTitle.tsx +22 -0
  158. package/src/components/nav/index.ts +7 -0
  159. package/src/components/navbar/Navbar.tsx +112 -0
  160. package/src/components/navbar/NavbarBrand.tsx +55 -0
  161. package/src/components/navbar/NavbarNav.tsx +47 -0
  162. package/src/components/navbar/NavbarText.tsx +47 -0
  163. package/src/components/navbar/NavbarToggler.tsx +34 -0
  164. package/src/components/navbar/index.ts +10 -0
  165. package/src/components/notification/Notification.scss +15 -0
  166. package/src/components/notification/Notification.tsx +187 -0
  167. package/src/components/notification/NotificationIcon.tsx +21 -0
  168. package/src/components/notification/NotificationStack.tsx +66 -0
  169. package/src/components/notification/NotificationText.tsx +47 -0
  170. package/src/components/notification/NotificationTitle.tsx +47 -0
  171. package/src/components/notification/context.ts +14 -0
  172. package/src/components/notification/index.ts +17 -0
  173. package/src/components/notification/notificationQueue.ts +48 -0
  174. package/src/components/otp-input/OtpBox.tsx +68 -0
  175. package/src/components/otp-input/OtpInput.tsx +341 -0
  176. package/src/components/otp-input/index.ts +2 -0
  177. package/src/components/pagination/Pagination.tsx +199 -0
  178. package/src/components/pagination/PaginationItem.tsx +133 -0
  179. package/src/components/pagination/index.ts +4 -0
  180. package/src/components/password-strength/PasswordStrength.tsx +155 -0
  181. package/src/components/password-strength/index.ts +2 -0
  182. package/src/components/password-strength/strengthScore.ts +79 -0
  183. package/src/components/placeholder/Placeholder.tsx +178 -0
  184. package/src/components/placeholder/index.ts +2 -0
  185. package/src/components/popover/Popover.tsx +243 -0
  186. package/src/components/popover/index.ts +2 -0
  187. package/src/components/progress/Progress.tsx +154 -0
  188. package/src/components/progress/ProgressBar.tsx +88 -0
  189. package/src/components/progress/index.ts +4 -0
  190. package/src/components/radio/Radio.tsx +88 -0
  191. package/src/components/radio/RadioGroup.tsx +142 -0
  192. package/src/components/radio/context.ts +14 -0
  193. package/src/components/radio/index.ts +4 -0
  194. package/src/components/range-input/RangeInput.tsx +109 -0
  195. package/src/components/range-input/index.ts +2 -0
  196. package/src/components/select/Select.tsx +277 -0
  197. package/src/components/select/index.ts +2 -0
  198. package/src/components/skeleton/Skeleton.tsx +89 -0
  199. package/src/components/skeleton/SkeletonLoader.tsx +91 -0
  200. package/src/components/skeleton/index.ts +4 -0
  201. package/src/components/spinner/Spinner.tsx +77 -0
  202. package/src/components/spinner/index.ts +2 -0
  203. package/src/components/stack/Stack.tsx +81 -0
  204. package/src/components/stack/index.ts +2 -0
  205. package/src/components/stepper/Stepper.tsx +172 -0
  206. package/src/components/stepper/StepperItem.tsx +66 -0
  207. package/src/components/stepper/index.ts +4 -0
  208. package/src/components/switch/Switch.tsx +246 -0
  209. package/src/components/switch/index.ts +2 -0
  210. package/src/components/table/Table.css +39 -0
  211. package/src/components/table/Table.tsx +390 -0
  212. package/src/components/table/TableBody.tsx +25 -0
  213. package/src/components/table/TableCell.tsx +27 -0
  214. package/src/components/table/TableColumn.tsx +31 -0
  215. package/src/components/table/TableHeader.tsx +26 -0
  216. package/src/components/table/TableRow.tsx +24 -0
  217. package/src/components/table/index.ts +12 -0
  218. package/src/components/tabs/Tab.tsx +36 -0
  219. package/src/components/tabs/TabList.tsx +76 -0
  220. package/src/components/tabs/TabPanel.tsx +68 -0
  221. package/src/components/tabs/Tabs.tsx +136 -0
  222. package/src/components/tabs/context.ts +24 -0
  223. package/src/components/tabs/index.ts +8 -0
  224. package/src/components/text-input/TextInput.tsx +188 -0
  225. package/src/components/text-input/index.ts +2 -0
  226. package/src/components/textarea/Textarea.tsx +145 -0
  227. package/src/components/textarea/index.ts +2 -0
  228. package/src/components/toast/Toast.tsx +219 -0
  229. package/src/components/toast/ToastBody.tsx +64 -0
  230. package/src/components/toast/ToastFooter.tsx +47 -0
  231. package/src/components/toast/ToastHeader.tsx +104 -0
  232. package/src/components/toast/ToastIcon.tsx +21 -0
  233. package/src/components/toast/Toaster.tsx +87 -0
  234. package/src/components/toast/context.ts +14 -0
  235. package/src/components/toast/index.ts +18 -0
  236. package/src/components/toast/toastQueue.ts +56 -0
  237. package/src/components/tooltip/Tooltip.tsx +155 -0
  238. package/src/components/tooltip/index.ts +2 -0
  239. package/src/global.d.ts +2 -0
  240. package/src/hooks/index.ts +57 -0
  241. package/src/hooks/useAutoDismiss.ts +88 -0
  242. package/src/hooks/useButtonSemantics.ts +33 -0
  243. package/src/hooks/useControllableState.ts +24 -0
  244. package/src/hooks/useDialogElement.ts +216 -0
  245. package/src/hooks/useDisabledAnchorGuard.ts +22 -0
  246. package/src/hooks/useDismissibleTransition.ts +126 -0
  247. package/src/hooks/useDrawer.ts +9 -0
  248. package/src/hooks/useFloatingOverlay.ts +115 -0
  249. package/src/hooks/useForkedRef.ts +50 -0
  250. package/src/hooks/useFormField.ts +89 -0
  251. package/src/hooks/useIsomorphicLayoutEffect.ts +9 -0
  252. package/src/hooks/useModal.ts +9 -0
  253. package/src/hooks/useNotification.ts +10 -0
  254. package/src/hooks/useOverlayPlacement.ts +93 -0
  255. package/src/hooks/usePagination.ts +85 -0
  256. package/src/hooks/useToast.ts +9 -0
  257. package/src/hooks/useToastRegionQueue.ts +27 -0
  258. package/src/index.ts +276 -0
  259. package/src/types.ts +133 -0
  260. package/src/utils/breakpoints.ts +43 -0
  261. package/src/utils/comboboxCollection.tsx +152 -0
  262. package/src/utils/dateRangePresets.ts +6 -0
  263. package/src/utils/dialogTransition.ts +39 -0
  264. package/src/utils/focusRedirect.ts +29 -0
  265. package/src/utils/isWholeUnitDisabled.ts +17 -0
  266. package/src/utils/mergeIsDateUnavailable.ts +15 -0
  267. package/src/utils/overlayPlacement.ts +93 -0
  268. package/src/utils/pointerInteraction.ts +21 -0
  269. package/src/utils/polymorphic.ts +36 -0
  270. package/src/utils/renderMenuItemContent.tsx +62 -0
  271. package/src/utils/setVisibleRangeStart.ts +64 -0
  272. package/src/utils/spacingClassName.ts +14 -0
  273. package/src/utils/suppressFocusRingGlobally.ts +69 -0
  274. package/src/utils/validationClassName.ts +8 -0
  275. package/src/utils/virtualFocusStyle.ts +16 -0
@@ -0,0 +1,574 @@
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useMemo, useRef, useState } from 'react'
2
+ import { AriaButtonProps, mergeProps, useDateFormatter, useDatePicker, useDialog } from 'react-aria'
3
+ import {
4
+ DateValue,
5
+ OverlayTriggerState,
6
+ useDatePickerState,
7
+ useOverlayTriggerState
8
+ } from 'react-stately'
9
+ import { getLocalTimeZone } from '@internationalized/date'
10
+
11
+ import { useForkedRef, useFormField, useOverlayPlacement } from '../../hooks'
12
+ import { mergeIsDateUnavailable } from '../../utils/mergeIsDateUnavailable'
13
+ import { renderFormField } from '../form-field/renderFormField'
14
+ import { Calendar } from '../calendar/Calendar'
15
+ import { CalendarToggleButton } from './CalendarToggleButton'
16
+ import { ClearButton } from './ClearButton'
17
+ import { DateField } from './DateField'
18
+ import { renderDatePickerShell } from './renderDatePickerShell'
19
+ import './DatePicker.scss'
20
+
21
+ interface DatePickerBaseProps extends Omit<
22
+ HTMLAttributes<HTMLDivElement>,
23
+ 'onChange' | 'defaultValue'
24
+ > {
25
+ /**
26
+ * An accessible label for the date picker, used when there's no visible `<label>`.
27
+ */
28
+ 'aria-label'?: string
29
+ /**
30
+ * Identifies a visible `<label>` element for the date picker.
31
+ */
32
+ 'aria-labelledby'?: string
33
+ /**
34
+ * A string of all className you want applied to the base component.
35
+ */
36
+ className?: string
37
+ /**
38
+ * Whether the calendar popover is open by default (uncontrolled).
39
+ *
40
+ * @default false
41
+ */
42
+ defaultOpen?: boolean
43
+ /**
44
+ * Prevents the date picker from being focused or interacted with.
45
+ */
46
+ disabled?: boolean
47
+ /**
48
+ * The day that starts the week in the calendar overlay, overriding the default set by the
49
+ * active locale.
50
+ *
51
+ * @default 'mon'
52
+ */
53
+ firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
54
+ /**
55
+ * A description for the field, rendered below the date picker.
56
+ */
57
+ help?: ReactNode
58
+ /**
59
+ * `id` forwarded to the field's grouping element — useful for pairing with a `<label for>`.
60
+ */
61
+ id?: string
62
+ /**
63
+ * Set component validation state to invalid.
64
+ */
65
+ invalid?: boolean
66
+ /**
67
+ * An error message for the field, rendered below the date picker when `invalid` is set.
68
+ */
69
+ invalidFeedback?: ReactNode
70
+ /**
71
+ * Callback that is called for each date in the calendar. If it returns `true`, that date is
72
+ * shown but cannot be selected.
73
+ */
74
+ isDateUnavailable?: (date: DateValue) => boolean
75
+ /**
76
+ * Whether the calendar popover is open (controlled).
77
+ */
78
+ isOpen?: boolean
79
+ /**
80
+ * The field's caption, rendered as a `FormLabel` associated with the field group.
81
+ */
82
+ label?: ReactNode
83
+ /**
84
+ * The maximum allowed date that a user may select.
85
+ */
86
+ maxValue?: DateValue | null
87
+ /**
88
+ * The minimum allowed date that a user may select.
89
+ */
90
+ minValue?: DateValue | null
91
+ /**
92
+ * `name` of an auto-created hidden input, kept in sync with the selection, for native form
93
+ * submission — one input when `selectionMode` is `'single'`, one per selected date when it's
94
+ * `'multiple'` (same `name` on each, which browsers serialize as multiple form values). Omit to
95
+ * skip creating any.
96
+ */
97
+ name?: string
98
+ /**
99
+ * Callback fired when the calendar popover's open state changes.
100
+ */
101
+ onOpenChange?: (isOpen: boolean) => void
102
+ /**
103
+ * Size the component small or large.
104
+ */
105
+ size?: 'small' | 'large'
106
+ /**
107
+ * ISO 8601 dates (`YYYY-MM-DD`) to mark unselectable, as a convenience alternative to
108
+ * `isDateUnavailable` for data-driven cases (e.g. booked dates fetched from an API). Composed
109
+ * with `isDateUnavailable` when both are given — a date unavailable by either is unavailable.
110
+ * Applies to both the calendar overlay and typing a date directly into the field.
111
+ */
112
+ unavailableDates?: string[]
113
+ /**
114
+ * Set component validation state to valid.
115
+ */
116
+ valid?: boolean
117
+ /**
118
+ * A success message for the field, rendered below the date picker when `valid` is set.
119
+ */
120
+ validFeedback?: ReactNode
121
+ /**
122
+ * Number of months to display side by side in the calendar overlay.
123
+ *
124
+ * @default 1
125
+ */
126
+ visibleMonths?: number
127
+ }
128
+
129
+ export interface DatePickerSingleProps extends DatePickerBaseProps {
130
+ /**
131
+ * The initial selected date (uncontrolled).
132
+ */
133
+ defaultValue?: DateValue | null
134
+ /**
135
+ * Callback fired when the selected date changes.
136
+ */
137
+ onChange?: (value: DateValue | null) => void
138
+ /**
139
+ * Whether a single date or multiple, independently toggled dates can be selected. Multiple
140
+ * selection replaces the editable segmented field with a read-only, comma-separated list of
141
+ * the selected dates — a segmented day/month/year field has no way to represent more than one
142
+ * date.
143
+ *
144
+ * @default 'single'
145
+ */
146
+ selectionMode?: 'single'
147
+ /**
148
+ * The selected date (controlled).
149
+ */
150
+ value?: DateValue | null
151
+ }
152
+
153
+ export interface DatePickerMultipleProps extends DatePickerBaseProps {
154
+ /**
155
+ * The initial selected dates (uncontrolled).
156
+ */
157
+ defaultValue?: DateValue[] | null
158
+ /**
159
+ * Callback fired when the set of selected dates changes.
160
+ */
161
+ onChange?: (value: DateValue[]) => void
162
+ /**
163
+ * Whether a single date or multiple, independently toggled dates can be selected. Multiple
164
+ * selection replaces the editable segmented field with a read-only, comma-separated list of
165
+ * the selected dates — a segmented day/month/year field has no way to represent more than one
166
+ * date.
167
+ *
168
+ * @default 'single'
169
+ */
170
+ selectionMode: 'multiple'
171
+ /**
172
+ * The selected dates (controlled).
173
+ */
174
+ value?: DateValue[] | null
175
+ }
176
+
177
+ export type DatePickerProps = DatePickerSingleProps | DatePickerMultipleProps
178
+
179
+ // Dispatches on `selectionMode` between two internal implementations that share little beyond
180
+ // the field/overlay shell and `useFormField` — `selectionMode: 'multiple'` has no
181
+ // `useDatePickerState`/`useDatePicker` equivalent to build on (see `DatePickerMultiple`'s own
182
+ // comment), so bolting an array value onto the single-value hook pair isn't an option.
183
+ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => {
184
+ if (props.selectionMode === 'multiple') return <DatePickerMultiple {...props} ref={ref} />
185
+ return <DatePickerSingle {...props} ref={ref} />
186
+ })
187
+
188
+ DatePicker.displayName = 'DatePicker'
189
+
190
+ const DatePickerSingle = forwardRef<HTMLDivElement, DatePickerSingleProps>(
191
+ (
192
+ {
193
+ className,
194
+ defaultOpen,
195
+ defaultValue,
196
+ disabled,
197
+ firstDayOfWeek,
198
+ help,
199
+ id,
200
+ invalid,
201
+ invalidFeedback,
202
+ isDateUnavailable,
203
+ isOpen,
204
+ label,
205
+ maxValue,
206
+ minValue,
207
+ name,
208
+ onChange,
209
+ onOpenChange,
210
+ selectionMode: _selectionMode,
211
+ size,
212
+ unavailableDates,
213
+ valid,
214
+ validFeedback,
215
+ value,
216
+ visibleMonths,
217
+ ...rest
218
+ }: DatePickerSingleProps,
219
+ ref
220
+ ) => {
221
+ const combinedIsDateUnavailable = useMemo(
222
+ () => mergeIsDateUnavailable(unavailableDates, isDateUnavailable),
223
+ [unavailableDates, isDateUnavailable]
224
+ )
225
+
226
+ const state = useDatePickerState({
227
+ defaultOpen,
228
+ defaultValue,
229
+ isDateUnavailable: combinedIsDateUnavailable,
230
+ isDisabled: disabled,
231
+ isOpen,
232
+ maxValue,
233
+ minValue,
234
+ onChange,
235
+ onOpenChange,
236
+ value
237
+ })
238
+
239
+ const groupRef = useRef<HTMLDivElement>(null)
240
+ const forkedGroupRef = useForkedRef(ref, groupRef)
241
+ const calendarRef = useRef<HTMLDivElement>(null)
242
+ const overlayRef = useRef<HTMLDivElement>(null)
243
+ const toggleButtonRef = useRef<HTMLButtonElement>(null)
244
+
245
+ const {
246
+ describedBy,
247
+ feedbackId,
248
+ helpId,
249
+ inputId: groupId,
250
+ labelId,
251
+ labelledBy
252
+ } = useFormField({
253
+ ariaDescribedBy: rest['aria-describedby'],
254
+ ariaLabelledBy: rest['aria-labelledby'],
255
+ help,
256
+ id,
257
+ invalid,
258
+ invalidFeedback,
259
+ label,
260
+ valid,
261
+ validFeedback
262
+ })
263
+
264
+ const { groupProps, fieldProps, buttonProps, calendarProps, dialogProps } = useDatePicker(
265
+ {
266
+ 'aria-label': rest['aria-label'],
267
+ 'aria-labelledby': labelledBy,
268
+ defaultValue,
269
+ id: groupId,
270
+ isDateUnavailable: combinedIsDateUnavailable,
271
+ isDisabled: disabled,
272
+ isInvalid: invalid,
273
+ maxValue,
274
+ minValue,
275
+ onChange,
276
+ value
277
+ },
278
+ state,
279
+ groupRef
280
+ )
281
+
282
+ const { overlayStyle, placementAttr, overlayDismissProps } = useOverlayPlacement({
283
+ overlayRef,
284
+ state,
285
+ triggerRef: groupRef
286
+ })
287
+
288
+ // `Calendar` is dialog-agnostic by design (see its own comment) — applying `role="dialog"`
289
+ // etc. is this component's concern, layered on via the generic HTML-attribute passthrough
290
+ // `Calendar` merges onto its root.
291
+ const { dialogProps: domDialogProps } = useDialog(dialogProps, calendarRef)
292
+
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
+ })
363
+ }
364
+ )
365
+
366
+ DatePickerSingle.displayName = 'DatePickerSingle'
367
+
368
+ // `selectionMode: 'multiple'` counterpart to `DatePickerSingle` — deliberately not built on
369
+ // `useDatePickerState`/`useDatePicker`, since neither has any multi-value concept (a single
370
+ // `DateValue`, edited through one segmented day/month/year field that has no way to represent
371
+ // more than one date — see react-stately's own `useDatePickerState` types). Instead this is
372
+ // assembled from the same lower-level pieces already used elsewhere in this codebase:
373
+ // `useOverlayTriggerState` for open/close (the same `OverlayTriggerState` shape
374
+ // `useOverlayPlacement`/`CalendarToggleButton` already expect, so both are reused unchanged), the
375
+ // manual controlled/uncontrolled pattern `ChipInput` already uses for its own array value, and
376
+ // a read-only comma-separated field in place of `DateField`'s editable segments.
377
+ const DatePickerMultiple = forwardRef<HTMLDivElement, DatePickerMultipleProps>(
378
+ (
379
+ {
380
+ className,
381
+ defaultOpen,
382
+ defaultValue,
383
+ disabled,
384
+ firstDayOfWeek,
385
+ help,
386
+ id,
387
+ invalid,
388
+ invalidFeedback,
389
+ isDateUnavailable,
390
+ isOpen,
391
+ label,
392
+ maxValue,
393
+ minValue,
394
+ name,
395
+ onChange,
396
+ onOpenChange,
397
+ selectionMode: _selectionMode,
398
+ size,
399
+ unavailableDates,
400
+ valid,
401
+ validFeedback,
402
+ value,
403
+ visibleMonths,
404
+ ...rest
405
+ }: DatePickerMultipleProps,
406
+ ref
407
+ ) => {
408
+ const combinedIsDateUnavailable = useMemo(
409
+ () => mergeIsDateUnavailable(unavailableDates, isDateUnavailable),
410
+ [unavailableDates, isDateUnavailable]
411
+ )
412
+
413
+ const isControlled = value !== undefined
414
+ const [uncontrolledValues, setUncontrolledValues] = useState<DateValue[]>(defaultValue ?? [])
415
+ const values = isControlled ? (value ?? []) : uncontrolledValues
416
+
417
+ const setValues = (next: DateValue[]) => {
418
+ if (!isControlled) setUncontrolledValues(next)
419
+ onChange?.(next)
420
+ }
421
+
422
+ const state: OverlayTriggerState = useOverlayTriggerState({ defaultOpen, isOpen, onOpenChange })
423
+
424
+ const groupRef = useRef<HTMLDivElement>(null)
425
+ const forkedGroupRef = useForkedRef(ref, groupRef)
426
+ const calendarRef = useRef<HTMLDivElement>(null)
427
+ const overlayRef = useRef<HTMLDivElement>(null)
428
+ const toggleButtonRef = useRef<HTMLButtonElement>(null)
429
+
430
+ const {
431
+ describedBy,
432
+ feedbackId,
433
+ helpId,
434
+ inputId: groupId,
435
+ labelId,
436
+ labelledBy
437
+ } = useFormField({
438
+ ariaDescribedBy: rest['aria-describedby'],
439
+ ariaLabelledBy: rest['aria-labelledby'],
440
+ help,
441
+ id,
442
+ invalid,
443
+ invalidFeedback,
444
+ label,
445
+ valid,
446
+ validFeedback
447
+ })
448
+
449
+ const { overlayStyle, placementAttr, overlayDismissProps } = useOverlayPlacement({
450
+ overlayRef,
451
+ state,
452
+ triggerRef: groupRef
453
+ })
454
+
455
+ const { dialogProps: domDialogProps } = useDialog(
456
+ { 'aria-label': rest['aria-label'], 'aria-labelledby': labelledBy },
457
+ calendarRef
458
+ )
459
+
460
+ const buttonProps: AriaButtonProps = {
461
+ 'aria-expanded': state.isOpen,
462
+ 'aria-haspopup': 'dialog',
463
+ 'aria-label': 'Calendar',
464
+ isDisabled: disabled
465
+ }
466
+
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
+ })
541
+ }
542
+ )
543
+
544
+ DatePickerMultiple.displayName = 'DatePickerMultiple'
545
+
546
+ interface MultiDateFieldProps {
547
+ values: DateValue[]
548
+ }
549
+
550
+ // `ZonedDateTime` carries its own time zone (`toDate()` takes no argument); `CalendarDate`/
551
+ // `CalendarDateTime` don't, so they need the viewer's local one supplied explicitly.
552
+ const toJsDate = (date: DateValue): Date =>
553
+ 'timeZone' in date ? date.toDate() : date.toDate(getLocalTimeZone())
554
+
555
+ // Read-only stand-in for `DateField`'s editable segments — a segmented day/month/year field can
556
+ // only ever represent one date, so multiple selection instead shows every selected date, formatted
557
+ // per the active locale and joined with commas.
558
+ const MultiDateField = ({ values }: MultiDateFieldProps) => {
559
+ const formatter = useDateFormatter({ dateStyle: 'medium' })
560
+
561
+ return (
562
+ <div className="datepicker-field">
563
+ {values.length > 0 && (
564
+ <>
565
+ {values
566
+ .slice()
567
+ .sort((a, b) => a.compare(b))
568
+ .map((date) => formatter.format(toJsDate(date)))
569
+ .join(', ')}
570
+ </>
571
+ )}
572
+ </div>
573
+ )
574
+ }
@@ -0,0 +1,11 @@
1
+ /*
2
+ * DateRangePicker reuses DatePicker.scss's `.datepicker-field`/`.datepicker-segment`
3
+ * classes for each of its two segmented fields, and its chassis-css `.datepicker` popover shell
4
+ * (plus the `[hidden]` visibility fix) for the popover positioning box (both imported alongside
5
+ * this file) — the separator between the two fields is the only new visual territory here.
6
+ */
7
+
8
+ .daterangepicker-separator {
9
+ margin-inline: var(--cx-gap);
10
+ color: var(--cx-fg-subtle);
11
+ }