@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,343 @@
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useMemo, useRef } from 'react'
2
+ import { mergeProps, RangeValue, useDateRangePicker, useDialog } from 'react-aria'
3
+ import { DateValue, useDateRangePickerState } from 'react-stately'
4
+
5
+ import { useForkedRef, useFormField, useOverlayPlacement } from '../../hooks'
6
+ import { DateRangePreset } from '../../utils/dateRangePresets'
7
+ import { mergeIsDateUnavailable } from '../../utils/mergeIsDateUnavailable'
8
+ import { renderFormField } from '../form-field/renderFormField'
9
+ import { RangeCalendar } from '../calendar/RangeCalendar'
10
+ import { CalendarToggleButton } from './CalendarToggleButton'
11
+ import { ClearButton } from './ClearButton'
12
+ import { DateField } from './DateField'
13
+ import { renderDatePickerShell } from './renderDatePickerShell'
14
+ import './DatePicker.scss'
15
+ import './DateRangePicker.scss'
16
+
17
+ export interface DateRangePickerProps extends Omit<
18
+ HTMLAttributes<HTMLDivElement>,
19
+ 'onChange' | 'defaultValue'
20
+ > {
21
+ /**
22
+ * An accessible label for the date range picker, used when there's no visible `<label>`.
23
+ */
24
+ 'aria-label'?: string
25
+ /**
26
+ * Identifies a visible `<label>` element for the date range picker.
27
+ */
28
+ 'aria-labelledby'?: string
29
+ /**
30
+ * A string of all className you want applied to the base component.
31
+ */
32
+ className?: string
33
+ /**
34
+ * Whether the calendar popover is open by default (uncontrolled).
35
+ *
36
+ * @default false
37
+ */
38
+ defaultOpen?: boolean
39
+ /**
40
+ * The initial selected date range (uncontrolled).
41
+ */
42
+ defaultValue?: RangeValue<DateValue> | null
43
+ /**
44
+ * Prevents the date range 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 range 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 range 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
+ * Base `name` for a pair of auto-created hidden inputs, kept in sync with the selection, for
93
+ * native form submission — rendered as `${name}Start` and `${name}End`. Omit to skip creating
94
+ * them.
95
+ */
96
+ name?: string
97
+ /**
98
+ * Callback fired when the selected date range changes.
99
+ */
100
+ onChange?: (value: RangeValue<DateValue> | null) => void
101
+ /**
102
+ * Callback fired when the calendar popover's open state changes.
103
+ */
104
+ onOpenChange?: (isOpen: boolean) => void
105
+ /**
106
+ * A list of quick-select range presets shown in the overlay next to the calendar. Selecting a
107
+ * preset commits its range immediately, the same as picking a start and end date from the
108
+ * calendar. The preset matching the current selection (if any) is marked selected. Omit to not
109
+ * show a preset list.
110
+ */
111
+ presets?: DateRangePreset[]
112
+ /**
113
+ * Size the component small or large.
114
+ */
115
+ size?: 'small' | 'large'
116
+ /**
117
+ * ISO 8601 dates (`YYYY-MM-DD`) to mark unselectable, as a convenience alternative to
118
+ * `isDateUnavailable` for data-driven cases (e.g. booked dates fetched from an API). Composed
119
+ * with `isDateUnavailable` when both are given — a date unavailable by either is unavailable.
120
+ * Applies to both the calendar overlay and typing a date directly into either field.
121
+ */
122
+ unavailableDates?: string[]
123
+ /**
124
+ * Set component validation state to valid.
125
+ */
126
+ valid?: boolean
127
+ /**
128
+ * A success message for the field, rendered below the date range picker when `valid` is set.
129
+ */
130
+ validFeedback?: ReactNode
131
+ /**
132
+ * The selected date range (controlled).
133
+ */
134
+ value?: RangeValue<DateValue> | null
135
+ /**
136
+ * Number of months to display side by side in the calendar overlay.
137
+ *
138
+ * @default 1
139
+ */
140
+ visibleMonths?: number
141
+ }
142
+
143
+ // Mirrors `DatePicker` closely — same field/overlay/dialog composition, just with two
144
+ // segmented fields (`startFieldProps`/`endFieldProps` in place of a single `fieldProps`) and
145
+ // `RangeCalendar` in place of `Calendar` in the overlay, dialog role/ref landing directly on
146
+ // it exactly as `Calendar` does for `DatePicker`. `presets` is passed straight through —
147
+ // `RangeCalendar` owns rendering and selecting them (it's also usable standalone), so completing
148
+ // one goes through the same `state.setValue`/`onChange` path a two-click grid selection does,
149
+ // which is what closes this overlay automatically.
150
+ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
151
+ (
152
+ {
153
+ className,
154
+ defaultOpen,
155
+ defaultValue,
156
+ disabled,
157
+ firstDayOfWeek,
158
+ help,
159
+ id,
160
+ invalid,
161
+ invalidFeedback,
162
+ isDateUnavailable,
163
+ isOpen,
164
+ label,
165
+ maxValue,
166
+ minValue,
167
+ name,
168
+ onChange,
169
+ onOpenChange,
170
+ presets,
171
+ size,
172
+ unavailableDates,
173
+ valid,
174
+ validFeedback,
175
+ value,
176
+ visibleMonths,
177
+ ...rest
178
+ }: DateRangePickerProps,
179
+ ref
180
+ ): ReactNode => {
181
+ const combinedIsDateUnavailable = useMemo(
182
+ () => mergeIsDateUnavailable(unavailableDates, isDateUnavailable),
183
+ [unavailableDates, isDateUnavailable]
184
+ )
185
+
186
+ const state = useDateRangePickerState({
187
+ defaultOpen,
188
+ defaultValue,
189
+ isDateUnavailable: combinedIsDateUnavailable,
190
+ isDisabled: disabled,
191
+ isOpen,
192
+ maxValue,
193
+ minValue,
194
+ onChange,
195
+ onOpenChange,
196
+ value
197
+ })
198
+
199
+ const groupRef = useRef<HTMLDivElement>(null)
200
+ const forkedGroupRef = useForkedRef(ref, groupRef)
201
+ const calendarRef = useRef<HTMLDivElement>(null)
202
+ const overlayRef = useRef<HTMLDivElement>(null)
203
+ const toggleButtonRef = useRef<HTMLButtonElement>(null)
204
+
205
+ const {
206
+ describedBy,
207
+ feedbackId,
208
+ helpId,
209
+ inputId: groupId,
210
+ labelId,
211
+ labelledBy
212
+ } = useFormField({
213
+ ariaDescribedBy: rest['aria-describedby'],
214
+ ariaLabelledBy: rest['aria-labelledby'],
215
+ help,
216
+ id,
217
+ invalid,
218
+ invalidFeedback,
219
+ label,
220
+ valid,
221
+ validFeedback
222
+ })
223
+
224
+ const { groupProps, startFieldProps, endFieldProps, buttonProps, calendarProps, dialogProps } =
225
+ useDateRangePicker(
226
+ {
227
+ 'aria-label': rest['aria-label'],
228
+ 'aria-labelledby': labelledBy,
229
+ defaultValue,
230
+ id: groupId,
231
+ isDateUnavailable: combinedIsDateUnavailable,
232
+ isDisabled: disabled,
233
+ isInvalid: invalid,
234
+ maxValue,
235
+ minValue,
236
+ onChange,
237
+ value
238
+ },
239
+ state,
240
+ groupRef
241
+ )
242
+
243
+ const { overlayStyle, placementAttr, overlayDismissProps } = useOverlayPlacement({
244
+ overlayRef,
245
+ state,
246
+ triggerRef: groupRef
247
+ })
248
+
249
+ const { dialogProps: domDialogProps } = useDialog(dialogProps, calendarRef)
250
+
251
+ return renderFormField({
252
+ children: (
253
+ <>
254
+ {renderDatePickerShell({
255
+ calendar: (
256
+ <RangeCalendar
257
+ {...domDialogProps}
258
+ autoFocus
259
+ disabled={disabled}
260
+ firstDayOfWeek={firstDayOfWeek}
261
+ isDateUnavailable={combinedIsDateUnavailable}
262
+ maxValue={maxValue}
263
+ minValue={minValue}
264
+ onChange={calendarProps.onChange}
265
+ presets={presets}
266
+ ref={calendarRef}
267
+ value={calendarProps.value}
268
+ visibleMonths={visibleMonths}
269
+ />
270
+ ),
271
+ className,
272
+ // `state.value` is always a `{ start, end }` object, never `null` itself — even with
273
+ // nothing picked yet — so the endpoints are what actually indicate a selection to
274
+ // clear (matching the hidden-input `value`s below, which check the same way).
275
+ clearButton: (state.value?.start || state.value?.end) && !disabled && (
276
+ <ClearButton
277
+ onPress={() => {
278
+ state.setValue(null)
279
+ // See `DatePicker`'s identical `ClearButton` usage for why this is needed —
280
+ // this button unmounts itself once cleared, so focus needs somewhere to land.
281
+ toggleButtonRef.current?.focus()
282
+ }}
283
+ />
284
+ ),
285
+ disabled,
286
+ field: (
287
+ <>
288
+ <DateField fieldProps={startFieldProps} />
289
+ <span aria-hidden="true" className="daterangepicker-separator">
290
+ –
291
+ </span>
292
+ <DateField fieldProps={endFieldProps} />
293
+ </>
294
+ ),
295
+ fieldClassName: 'd-flex w-100',
296
+ groupProps: {
297
+ ...mergeProps(groupProps, rest),
298
+ 'aria-describedby': describedBy,
299
+ 'aria-labelledby': labelledBy
300
+ },
301
+ groupRef: forkedGroupRef,
302
+ invalid,
303
+ isOpen: state.isOpen,
304
+ overlayDismissProps,
305
+ overlayRef,
306
+ overlayStyle,
307
+ placementAttr,
308
+ size,
309
+ toggleButton: (
310
+ <CalendarToggleButton buttonProps={buttonProps} ref={toggleButtonRef} state={state} />
311
+ ),
312
+ valid
313
+ })}
314
+ {name && (
315
+ <>
316
+ <input
317
+ disabled={disabled}
318
+ name={`${name}Start`}
319
+ type="hidden"
320
+ value={state.value?.start ? state.value.start.toString() : ''}
321
+ />
322
+ <input
323
+ disabled={disabled}
324
+ name={`${name}End`}
325
+ type="hidden"
326
+ value={state.value?.end ? state.value.end.toString() : ''}
327
+ />
328
+ </>
329
+ )}
330
+ </>
331
+ ),
332
+ help,
333
+ ids: { feedback: feedbackId, help: helpId, label: labelId },
334
+ invalid,
335
+ invalidFeedback,
336
+ label,
337
+ valid,
338
+ validFeedback
339
+ })
340
+ }
341
+ )
342
+
343
+ DateRangePicker.displayName = 'DateRangePicker'
@@ -0,0 +1,4 @@
1
+ export { DatePicker } from './DatePicker'
2
+ export type { DatePickerProps, DatePickerSingleProps, DatePickerMultipleProps } from './DatePicker'
3
+ export { DateRangePicker } from './DateRangePicker'
4
+ export type { DateRangePickerProps } from './DateRangePicker'
@@ -0,0 +1,104 @@
1
+ import React, { CSSProperties, HTMLAttributes, ReactNode, Ref } from 'react'
2
+ import classNames from 'classnames'
3
+ import { FocusScope } from 'react-aria'
4
+
5
+ import { validationClassName } from '../../utils/validationClassName'
6
+
7
+ export interface RenderDatePickerShellOptions {
8
+ /**
9
+ * The fully-built calendar element (`Calendar`/`RangeCalendar`, with its own dialog role/ref
10
+ * already applied) — only ever mounted while `isOpen`, inside a `FocusScope`.
11
+ */
12
+ calendar: ReactNode
13
+ className?: string
14
+ /**
15
+ * The `ClearButton` element, or `null`/`undefined` when there's nothing to clear.
16
+ */
17
+ clearButton?: ReactNode
18
+ disabled?: boolean
19
+ /**
20
+ * The field itself — a single segmented `DateField`, a read-only `MultiDateField`, or a pair
21
+ * of `DateField`s either side of a separator.
22
+ */
23
+ field: ReactNode
24
+ /**
25
+ * className for the wrapper directly around `field` — differs slightly by variant
26
+ * (`DateRangePicker`'s two side-by-side fields need `d-flex`, the single-field variants don't).
27
+ */
28
+ fieldClassName: string
29
+ /**
30
+ * Already-merged props (react-aria's `groupProps`/`mergeProps(rest)`, plus
31
+ * `aria-describedby`/`aria-labelledby` and any variant-specific ARIA attributes) for the
32
+ * `.form-input` wrapper — assembling this is the caller's job since react-aria only returns a
33
+ * `groupProps` shape for the single-select hooks, not the manually-assembled multi-select case.
34
+ */
35
+ groupProps: HTMLAttributes<HTMLDivElement>
36
+ groupRef: Ref<HTMLDivElement>
37
+ invalid?: boolean
38
+ isOpen: boolean
39
+ overlayDismissProps: HTMLAttributes<HTMLElement>
40
+ overlayRef: Ref<HTMLDivElement>
41
+ overlayStyle?: CSSProperties
42
+ placementAttr?: string
43
+ size?: 'small' | 'large'
44
+ toggleButton: ReactNode
45
+ valid?: boolean
46
+ }
47
+
48
+ // Shared `.form-input` adorn-wrapper + popover-overlay shell for `DatePicker`'s two internal
49
+ // variants (single/multiple selection) and `DateRangePicker` — otherwise duplicated near-verbatim
50
+ // three times. What's left in each caller is only the genuinely variant-specific pieces: which
51
+ // field(s) render inside the wrapper, which calendar (`Calendar`/`RangeCalendar`, single- vs.
52
+ // range-typed props) renders inside the overlay, and how each variant's own `groupProps`/hidden
53
+ // inputs get built.
54
+ export const renderDatePickerShell = ({
55
+ calendar,
56
+ className,
57
+ clearButton,
58
+ disabled,
59
+ field,
60
+ fieldClassName,
61
+ groupProps,
62
+ groupRef,
63
+ invalid,
64
+ isOpen,
65
+ overlayDismissProps,
66
+ overlayRef,
67
+ overlayStyle,
68
+ placementAttr,
69
+ size,
70
+ toggleButton,
71
+ valid
72
+ }: RenderDatePickerShellOptions): ReactNode => (
73
+ <>
74
+ <div
75
+ {...groupProps}
76
+ className={classNames(
77
+ 'form-input',
78
+ size,
79
+ { disabled },
80
+ validationClassName(invalid, valid),
81
+ className
82
+ )}
83
+ ref={groupRef}
84
+ >
85
+ <div className={fieldClassName}>{field}</div>
86
+ {clearButton}
87
+ {toggleButton}
88
+ </div>
89
+ <div
90
+ className="datepicker"
91
+ data-cx-placement={placementAttr}
92
+ hidden={!isOpen}
93
+ ref={overlayRef}
94
+ {...overlayDismissProps}
95
+ style={overlayStyle}
96
+ >
97
+ {isOpen && (
98
+ <FocusScope contain restoreFocus>
99
+ {calendar}
100
+ </FocusScope>
101
+ )}
102
+ </div>
103
+ </>
104
+ )
@@ -0,0 +1,211 @@
1
+ import React, {
2
+ createContext,
3
+ DialogHTMLAttributes,
4
+ forwardRef,
5
+ useEffect,
6
+ useId,
7
+ useRef
8
+ } from 'react'
9
+ import classNames from 'classnames'
10
+
11
+ import { useDialogElement } from '../../hooks'
12
+
13
+ export interface DrawerProps extends Omit<
14
+ DialogHTMLAttributes<HTMLDialogElement>,
15
+ 'onCancel' | 'onClose'
16
+ > {
17
+ /**
18
+ * Show a backdrop while the drawer is open. `'static'` blocks closing on backdrop click
19
+ * (the drawer nudges instead).
20
+ */
21
+ backdrop?: boolean | 'static'
22
+ /**
23
+ * A string of all className you want applied to the base component.
24
+ */
25
+ className?: string
26
+ /**
27
+ * Size the height to the drawer's content instead of `--drawer-height`. Meaningful for
28
+ * `placement="bottom"`.
29
+ */
30
+ fitContent?: boolean
31
+ /**
32
+ * Expand the panel to fill the viewport, animating in from the direction of `placement`.
33
+ */
34
+ fullscreen?: boolean
35
+ /**
36
+ * Disable the open/close transition entirely.
37
+ */
38
+ instant?: boolean
39
+ /**
40
+ * Closes the drawer when the escape key is pressed.
41
+ */
42
+ keyboard?: boolean
43
+ /**
44
+ * Callback fired when the drawer requests to be closed (escape, backdrop click, close button,
45
+ * or another drawer opening).
46
+ */
47
+ onClose?: () => void
48
+ /**
49
+ * Callback fired when a close attempt is blocked (static backdrop click, or escape with `keyboard=false`).
50
+ */
51
+ onClosePrevented?: () => void
52
+ /**
53
+ * Callback fired after the exit transition completes and the drawer is fully hidden.
54
+ */
55
+ onHidden?: () => void
56
+ /**
57
+ * Callback fired when the drawer starts to open.
58
+ */
59
+ onShow?: () => void
60
+ /**
61
+ * Callback fired after the entry transition completes and the drawer is fully visible.
62
+ */
63
+ onShown?: () => void
64
+ /**
65
+ * Which viewport edge the panel slides in from. Always required — there is no default
66
+ * off-screen transform without one.
67
+ */
68
+ placement: 'start' | 'end' | 'top' | 'bottom'
69
+ /**
70
+ * Renders as a drawer only below this breakpoint — inline as a flex container above it.
71
+ */
72
+ responsive?: 'small' | 'medium' | 'large' | 'xlarge' | '2xlarge'
73
+ /**
74
+ * Allow the page behind the drawer to scroll while it's open.
75
+ */
76
+ scroll?: boolean
77
+ /**
78
+ * Remove the inset gap, border radius, and border so the panel sits flush against the
79
+ * viewport edge.
80
+ */
81
+ sheet?: boolean
82
+ /**
83
+ * Apply a frosted-glass background to the panel.
84
+ */
85
+ translucent?: boolean
86
+ /**
87
+ * Toggle the visibility of the drawer component.
88
+ */
89
+ visible?: boolean
90
+ }
91
+
92
+ export interface DrawerContextProps {
93
+ /**
94
+ * Requests the drawer be closed — fires `onClose`. Wire this to any element's `onClick`; see
95
+ * `useDrawer`.
96
+ */
97
+ close: () => void
98
+ /**
99
+ * Id generated by `Drawer` and applied automatically by `DrawerTitle` to itself, so the
100
+ * dialog's `aria-labelledby` resolves to it without any manual wiring.
101
+ */
102
+ titleId?: string
103
+ }
104
+
105
+ export const DrawerContext = createContext<DrawerContextProps>({ close: () => {} })
106
+
107
+ // Currently-open drawers, so opening one can auto-close any other open drawer
108
+ // ("When a second drawer opens while one is already open, the first closes automatically").
109
+ const openDrawers = new Set<{ dialog: HTMLDialogElement; close: () => void }>()
110
+
111
+ export const Drawer = forwardRef<HTMLDialogElement, DrawerProps>(
112
+ (
113
+ {
114
+ children,
115
+ backdrop = true,
116
+ className,
117
+ fitContent,
118
+ fullscreen,
119
+ instant,
120
+ keyboard = true,
121
+ onClose,
122
+ onClosePrevented,
123
+ onHidden,
124
+ onShow,
125
+ onShown,
126
+ placement,
127
+ responsive,
128
+ scroll = false,
129
+ sheet,
130
+ translucent,
131
+ visible,
132
+ ...rest
133
+ },
134
+ ref
135
+ ) => {
136
+ const { close, dialogRef, forkedRef, handleBackdropClick, handleCancel, hiding, staticBounce } =
137
+ useDialogElement({
138
+ backdrop,
139
+ instant,
140
+ isModal: Boolean(backdrop) || !scroll,
141
+ keyboard,
142
+ onBeforeShow: (dialog) => {
143
+ for (const entry of openDrawers) {
144
+ if (entry.dialog !== dialog) entry.close()
145
+ }
146
+ },
147
+ onClose,
148
+ onClosePrevented,
149
+ onHidden,
150
+ onShow,
151
+ onShown,
152
+ ref,
153
+ visible
154
+ })
155
+
156
+ const titleId = useId()
157
+
158
+ const closeRef = useRef(close)
159
+ closeRef.current = close
160
+
161
+ // Register in the cross-instance registry so other drawers can auto-close this one.
162
+ useEffect(() => {
163
+ const dialog = dialogRef.current
164
+ if (!dialog) return undefined
165
+ const entry = { dialog, close: () => closeRef.current() }
166
+ openDrawers.add(entry)
167
+ return () => {
168
+ openDrawers.delete(entry)
169
+ }
170
+ }, [dialogRef])
171
+
172
+ // `hiding` has to stay on the class list for the whole exit transition: the native `open`
173
+ // attribute — and with it, the `[class*="drawer"]:not([open], .hiding)` escape hatch
174
+ // chassis-css's navbar styles use to suppress the drawer-flash transition when the navbar
175
+ // itself crosses its `expand` breakpoint — would otherwise get stripped the instant `open`
176
+ // is removed, which would also strip the *intentional* close transition this class exists
177
+ // to protect.
178
+ const _className = classNames(
179
+ responsive ? `max-${responsive}:drawer` : 'drawer',
180
+ `drawer-${placement}`,
181
+ {
182
+ fullscreen,
183
+ sheet,
184
+ translucent,
185
+ instant,
186
+ 'drawer-fit-content': fitContent,
187
+ nonmodal: !(Boolean(backdrop) || !scroll),
188
+ static: staticBounce,
189
+ hiding
190
+ },
191
+ className
192
+ )
193
+
194
+ return (
195
+ <DrawerContext.Provider value={{ close, titleId }}>
196
+ <dialog
197
+ aria-labelledby={titleId}
198
+ {...rest}
199
+ className={_className}
200
+ onCancel={handleCancel}
201
+ onClick={handleBackdropClick}
202
+ ref={forkedRef}
203
+ >
204
+ {children}
205
+ </dialog>
206
+ </DrawerContext.Provider>
207
+ )
208
+ }
209
+ )
210
+
211
+ Drawer.displayName = 'Drawer'
@@ -0,0 +1,47 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import {
5
+ createPolymorphicComponent,
6
+ PolymorphicComponentProps,
7
+ PolymorphicRef
8
+ } from '../../utils/polymorphic'
9
+
10
+ type DrawerBodyOwnProps<C extends ElementType> = {
11
+ /**
12
+ * A string of all className you want applied to the base component.
13
+ */
14
+ className?: string
15
+ /**
16
+ * Component used for the root node. Either a string to use a HTML element or a component.
17
+ */
18
+ component?: C
19
+ }
20
+
21
+ export type DrawerBodyProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
22
+ C,
23
+ DrawerBodyOwnProps<C>
24
+ >
25
+
26
+ type DrawerBodyComponent = (<C extends ElementType = 'div'>(
27
+ props: DrawerBodyProps<C> & { ref?: PolymorphicRef<C> }
28
+ ) => ReactElement | null) & { displayName?: string }
29
+
30
+ function DrawerBodyRender<C extends ElementType = 'div'>(
31
+ { children, className, component, ...rest }: DrawerBodyProps<C>,
32
+ ref: PolymorphicRef<C>
33
+ ) {
34
+ const Component = component ?? 'div'
35
+ const _className = classNames('drawer-body', className)
36
+
37
+ return (
38
+ <Component className={_className} {...rest} ref={ref}>
39
+ {children}
40
+ </Component>
41
+ )
42
+ }
43
+
44
+ export const DrawerBody = createPolymorphicComponent<DrawerBodyComponent>(
45
+ DrawerBodyRender as ForwardRefRenderFunction<Element, DrawerBodyProps<ElementType>>,
46
+ 'DrawerBody'
47
+ )