@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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/index.d.ts +5909 -0
- package/dist/index.js +2 -0
- package/dist/style.css +1 -0
- package/package.json +114 -0
- package/src/components/accordion/Accordion.tsx +190 -0
- package/src/components/accordion/AccordionBody.tsx +46 -0
- package/src/components/accordion/AccordionHeader.tsx +21 -0
- package/src/components/accordion/AccordionItem.tsx +59 -0
- package/src/components/accordion/context.ts +8 -0
- package/src/components/accordion/index.ts +8 -0
- package/src/components/autocomplete/Autocomplete.tsx +464 -0
- package/src/components/autocomplete/AutocompleteGroup.tsx +20 -0
- package/src/components/autocomplete/AutocompleteItem.tsx +18 -0
- package/src/components/autocomplete/index.ts +6 -0
- package/src/components/avatar/Avatar.tsx +134 -0
- package/src/components/avatar/AvatarImage.tsx +19 -0
- package/src/components/avatar/AvatarStack.tsx +98 -0
- package/src/components/avatar/index.ts +6 -0
- package/src/components/badge/Badge.tsx +94 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/breadcrumb/Breadcrumb.tsx +59 -0
- package/src/components/breadcrumb/BreadcrumbItem.tsx +38 -0
- package/src/components/breadcrumb/index.ts +4 -0
- package/src/components/button/Button.tsx +229 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/button-group/ButtonGroup.tsx +55 -0
- package/src/components/button-group/ButtonToolbar.tsx +47 -0
- package/src/components/button-group/index.ts +4 -0
- package/src/components/calendar/Calendar.scss +57 -0
- package/src/components/calendar/Calendar.tsx +324 -0
- package/src/components/calendar/CalendarMonthBlock.tsx +57 -0
- package/src/components/calendar/CalendarMonthGrid.tsx +92 -0
- package/src/components/calendar/CalendarMonthYearPicker.tsx +162 -0
- package/src/components/calendar/CalendarNavButton.tsx +24 -0
- package/src/components/calendar/CalendarWeekGrid.tsx +58 -0
- package/src/components/calendar/CalendarYearGrid.tsx +126 -0
- package/src/components/calendar/RangeCalendar.scss +95 -0
- package/src/components/calendar/RangeCalendar.tsx +361 -0
- package/src/components/calendar/index.ts +5 -0
- package/src/components/card/Card.tsx +173 -0
- package/src/components/card/CardBody.tsx +75 -0
- package/src/components/card/CardFooter.tsx +47 -0
- package/src/components/card/CardGroup.tsx +51 -0
- package/src/components/card/CardHeader.tsx +47 -0
- package/src/components/card/CardImage.tsx +70 -0
- package/src/components/card/CardImageOverlay.tsx +47 -0
- package/src/components/card/CardLink.tsx +40 -0
- package/src/components/card/CardSubtitle.tsx +47 -0
- package/src/components/card/CardText.tsx +47 -0
- package/src/components/card/CardTitle.tsx +47 -0
- package/src/components/card/index.ts +22 -0
- package/src/components/carousel/Carousel.tsx +742 -0
- package/src/components/carousel/CarouselControlButton.tsx +74 -0
- package/src/components/carousel/CarouselControlNext.tsx +22 -0
- package/src/components/carousel/CarouselControlPrev.tsx +22 -0
- package/src/components/carousel/CarouselIndicators.tsx +44 -0
- package/src/components/carousel/CarouselInner.tsx +56 -0
- package/src/components/carousel/CarouselItem.tsx +51 -0
- package/src/components/carousel/CarouselOverlay.tsx +51 -0
- package/src/components/carousel/CarouselPlayPause.tsx +61 -0
- package/src/components/carousel/carouselEngine.ts +170 -0
- package/src/components/carousel/context.ts +29 -0
- package/src/components/carousel/index.ts +21 -0
- package/src/components/checkbox/Checkbox.tsx +236 -0
- package/src/components/checkbox/CheckboxGroup.tsx +145 -0
- package/src/components/checkbox/context.ts +14 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/chip/Chip.tsx +183 -0
- package/src/components/chip/index.ts +2 -0
- package/src/components/chip-input/ChipInput.tsx +363 -0
- package/src/components/chip-input/ChipList.tsx +95 -0
- package/src/components/chip-input/index.ts +2 -0
- package/src/components/close-button/CloseButton.tsx +205 -0
- package/src/components/close-button/index.ts +2 -0
- package/src/components/collapse/Collapse.tsx +128 -0
- package/src/components/collapse/index.ts +2 -0
- package/src/components/color-input/ColorInput.tsx +106 -0
- package/src/components/color-input/index.ts +2 -0
- package/src/components/combobox/Combobox.tsx +320 -0
- package/src/components/combobox/ComboboxGroup.tsx +27 -0
- package/src/components/combobox/ComboboxItem.tsx +44 -0
- package/src/components/combobox/ComboboxListBox.tsx +98 -0
- package/src/components/combobox/index.ts +6 -0
- package/src/components/datepicker/CalendarToggleButton.tsx +58 -0
- package/src/components/datepicker/ClearButton.tsx +35 -0
- package/src/components/datepicker/DateField.tsx +54 -0
- package/src/components/datepicker/DatePicker.scss +51 -0
- package/src/components/datepicker/DatePicker.tsx +574 -0
- package/src/components/datepicker/DateRangePicker.scss +11 -0
- package/src/components/datepicker/DateRangePicker.tsx +343 -0
- package/src/components/datepicker/index.ts +4 -0
- package/src/components/datepicker/renderDatePickerShell.tsx +104 -0
- package/src/components/drawer/Drawer.tsx +211 -0
- package/src/components/drawer/DrawerBody.tsx +47 -0
- package/src/components/drawer/DrawerFooter.tsx +51 -0
- package/src/components/drawer/DrawerHeader.tsx +60 -0
- package/src/components/drawer/DrawerTitle.tsx +49 -0
- package/src/components/drawer/index.ts +10 -0
- package/src/components/file-input/FileInput.tsx +100 -0
- package/src/components/file-input/index.ts +2 -0
- package/src/components/flex/Flex.tsx +134 -0
- package/src/components/flex/index.ts +2 -0
- package/src/components/floating-input/FloatingInput.tsx +94 -0
- package/src/components/floating-input/index.ts +2 -0
- package/src/components/form/Form.tsx +26 -0
- package/src/components/form/FormFeedback.tsx +64 -0
- package/src/components/form/FormHelp.tsx +46 -0
- package/src/components/form/FormLabel.tsx +26 -0
- package/src/components/form/index.ts +8 -0
- package/src/components/form/renderFormCheck.tsx +86 -0
- package/src/components/form-field/FormField.tsx +84 -0
- package/src/components/form-field/index.ts +2 -0
- package/src/components/form-field/renderFormField.tsx +74 -0
- package/src/components/grid/Col.tsx +69 -0
- package/src/components/grid/Container.tsx +59 -0
- package/src/components/grid/Grid.tsx +86 -0
- package/src/components/grid/GridItem.tsx +123 -0
- package/src/components/grid/Row.tsx +71 -0
- package/src/components/grid/gap.ts +8 -0
- package/src/components/grid/index.ts +10 -0
- package/src/components/icon/Icon.tsx +74 -0
- package/src/components/icon/index.ts +2 -0
- package/src/components/input-adorn/InputAdorn.tsx +55 -0
- package/src/components/input-adorn/index.ts +2 -0
- package/src/components/input-group/InputGroup.tsx +50 -0
- package/src/components/input-group/InputGroupAddon.tsx +50 -0
- package/src/components/input-group/index.ts +4 -0
- package/src/components/link/Link.tsx +148 -0
- package/src/components/link/index.ts +2 -0
- package/src/components/list/List.tsx +193 -0
- package/src/components/list/ListItem.tsx +91 -0
- package/src/components/list/index.ts +4 -0
- package/src/components/menu/Menu.tsx +398 -0
- package/src/components/menu/MenuDivider.tsx +19 -0
- package/src/components/menu/MenuHeader.tsx +55 -0
- package/src/components/menu/MenuItem.tsx +85 -0
- package/src/components/menu/MenuItemDef.ts +83 -0
- package/src/components/menu/MenuList.tsx +142 -0
- package/src/components/menu/MenuSubmenu.tsx +284 -0
- package/src/components/menu/MenuSubmenuBack.tsx +39 -0
- package/src/components/menu/MenuText.tsx +47 -0
- package/src/components/menu/MenuToggle.tsx +148 -0
- package/src/components/menu/index.ts +19 -0
- package/src/components/menu/menuNavigation.ts +106 -0
- package/src/components/menu/submenuGroup.ts +43 -0
- package/src/components/modal/Modal.tsx +162 -0
- package/src/components/modal/ModalBody.tsx +47 -0
- package/src/components/modal/ModalFooter.tsx +51 -0
- package/src/components/modal/ModalHeader.tsx +60 -0
- package/src/components/modal/ModalTitle.tsx +49 -0
- package/src/components/modal/index.ts +10 -0
- package/src/components/nav/Nav.tsx +97 -0
- package/src/components/nav/NavItem.tsx +42 -0
- package/src/components/nav/NavLink.tsx +48 -0
- package/src/components/nav/NavTitle.tsx +22 -0
- package/src/components/nav/index.ts +7 -0
- package/src/components/navbar/Navbar.tsx +112 -0
- package/src/components/navbar/NavbarBrand.tsx +55 -0
- package/src/components/navbar/NavbarNav.tsx +47 -0
- package/src/components/navbar/NavbarText.tsx +47 -0
- package/src/components/navbar/NavbarToggler.tsx +34 -0
- package/src/components/navbar/index.ts +10 -0
- package/src/components/notification/Notification.scss +15 -0
- package/src/components/notification/Notification.tsx +187 -0
- package/src/components/notification/NotificationIcon.tsx +21 -0
- package/src/components/notification/NotificationStack.tsx +66 -0
- package/src/components/notification/NotificationText.tsx +47 -0
- package/src/components/notification/NotificationTitle.tsx +47 -0
- package/src/components/notification/context.ts +14 -0
- package/src/components/notification/index.ts +17 -0
- package/src/components/notification/notificationQueue.ts +48 -0
- package/src/components/otp-input/OtpBox.tsx +68 -0
- package/src/components/otp-input/OtpInput.tsx +341 -0
- package/src/components/otp-input/index.ts +2 -0
- package/src/components/pagination/Pagination.tsx +199 -0
- package/src/components/pagination/PaginationItem.tsx +133 -0
- package/src/components/pagination/index.ts +4 -0
- package/src/components/password-strength/PasswordStrength.tsx +155 -0
- package/src/components/password-strength/index.ts +2 -0
- package/src/components/password-strength/strengthScore.ts +79 -0
- package/src/components/placeholder/Placeholder.tsx +178 -0
- package/src/components/placeholder/index.ts +2 -0
- package/src/components/popover/Popover.tsx +243 -0
- package/src/components/popover/index.ts +2 -0
- package/src/components/progress/Progress.tsx +154 -0
- package/src/components/progress/ProgressBar.tsx +88 -0
- package/src/components/progress/index.ts +4 -0
- package/src/components/radio/Radio.tsx +88 -0
- package/src/components/radio/RadioGroup.tsx +142 -0
- package/src/components/radio/context.ts +14 -0
- package/src/components/radio/index.ts +4 -0
- package/src/components/range-input/RangeInput.tsx +109 -0
- package/src/components/range-input/index.ts +2 -0
- package/src/components/select/Select.tsx +277 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/skeleton/Skeleton.tsx +89 -0
- package/src/components/skeleton/SkeletonLoader.tsx +91 -0
- package/src/components/skeleton/index.ts +4 -0
- package/src/components/spinner/Spinner.tsx +77 -0
- package/src/components/spinner/index.ts +2 -0
- package/src/components/stack/Stack.tsx +81 -0
- package/src/components/stack/index.ts +2 -0
- package/src/components/stepper/Stepper.tsx +172 -0
- package/src/components/stepper/StepperItem.tsx +66 -0
- package/src/components/stepper/index.ts +4 -0
- package/src/components/switch/Switch.tsx +246 -0
- package/src/components/switch/index.ts +2 -0
- package/src/components/table/Table.css +39 -0
- package/src/components/table/Table.tsx +390 -0
- package/src/components/table/TableBody.tsx +25 -0
- package/src/components/table/TableCell.tsx +27 -0
- package/src/components/table/TableColumn.tsx +31 -0
- package/src/components/table/TableHeader.tsx +26 -0
- package/src/components/table/TableRow.tsx +24 -0
- package/src/components/table/index.ts +12 -0
- package/src/components/tabs/Tab.tsx +36 -0
- package/src/components/tabs/TabList.tsx +76 -0
- package/src/components/tabs/TabPanel.tsx +68 -0
- package/src/components/tabs/Tabs.tsx +136 -0
- package/src/components/tabs/context.ts +24 -0
- package/src/components/tabs/index.ts +8 -0
- package/src/components/text-input/TextInput.tsx +188 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/components/textarea/Textarea.tsx +145 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/toast/Toast.tsx +219 -0
- package/src/components/toast/ToastBody.tsx +64 -0
- package/src/components/toast/ToastFooter.tsx +47 -0
- package/src/components/toast/ToastHeader.tsx +104 -0
- package/src/components/toast/ToastIcon.tsx +21 -0
- package/src/components/toast/Toaster.tsx +87 -0
- package/src/components/toast/context.ts +14 -0
- package/src/components/toast/index.ts +18 -0
- package/src/components/toast/toastQueue.ts +56 -0
- package/src/components/tooltip/Tooltip.tsx +155 -0
- package/src/components/tooltip/index.ts +2 -0
- package/src/global.d.ts +2 -0
- package/src/hooks/index.ts +57 -0
- package/src/hooks/useAutoDismiss.ts +88 -0
- package/src/hooks/useButtonSemantics.ts +33 -0
- package/src/hooks/useControllableState.ts +24 -0
- package/src/hooks/useDialogElement.ts +216 -0
- package/src/hooks/useDisabledAnchorGuard.ts +22 -0
- package/src/hooks/useDismissibleTransition.ts +126 -0
- package/src/hooks/useDrawer.ts +9 -0
- package/src/hooks/useFloatingOverlay.ts +115 -0
- package/src/hooks/useForkedRef.ts +50 -0
- package/src/hooks/useFormField.ts +89 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +9 -0
- package/src/hooks/useModal.ts +9 -0
- package/src/hooks/useNotification.ts +10 -0
- package/src/hooks/useOverlayPlacement.ts +93 -0
- package/src/hooks/usePagination.ts +85 -0
- package/src/hooks/useToast.ts +9 -0
- package/src/hooks/useToastRegionQueue.ts +27 -0
- package/src/index.ts +276 -0
- package/src/types.ts +133 -0
- package/src/utils/breakpoints.ts +43 -0
- package/src/utils/comboboxCollection.tsx +152 -0
- package/src/utils/dateRangePresets.ts +6 -0
- package/src/utils/dialogTransition.ts +39 -0
- package/src/utils/focusRedirect.ts +29 -0
- package/src/utils/isWholeUnitDisabled.ts +17 -0
- package/src/utils/mergeIsDateUnavailable.ts +15 -0
- package/src/utils/overlayPlacement.ts +93 -0
- package/src/utils/pointerInteraction.ts +21 -0
- package/src/utils/polymorphic.ts +36 -0
- package/src/utils/renderMenuItemContent.tsx +62 -0
- package/src/utils/setVisibleRangeStart.ts +64 -0
- package/src/utils/spacingClassName.ts +14 -0
- package/src/utils/suppressFocusRingGlobally.ts +69 -0
- package/src/utils/validationClassName.ts +8 -0
- 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
|
+
}
|