@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,128 @@
|
|
|
1
|
+
import React, { forwardRef, HTMLAttributes, useRef, useState } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { CSSTransition } from 'react-transition-group'
|
|
4
|
+
|
|
5
|
+
import { useForkedRef } from '../../hooks'
|
|
6
|
+
|
|
7
|
+
export interface CollapseProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/**
|
|
9
|
+
* A string of all className you want applied to the base component.
|
|
10
|
+
*/
|
|
11
|
+
className?: string
|
|
12
|
+
/**
|
|
13
|
+
* Set horizontal collapsing to transition the width instead of height.
|
|
14
|
+
*/
|
|
15
|
+
horizontal?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Callback fired when the component requests to be hidden.
|
|
18
|
+
*/
|
|
19
|
+
onHide?: () => void
|
|
20
|
+
/**
|
|
21
|
+
* Callback fired when the component requests to be shown.
|
|
22
|
+
*/
|
|
23
|
+
onShow?: () => void
|
|
24
|
+
/**
|
|
25
|
+
* Toggle the visibility of component.
|
|
26
|
+
*/
|
|
27
|
+
visible?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Collapse = forwardRef<HTMLDivElement, CollapseProps>(
|
|
31
|
+
({ children, className, horizontal, onHide, onShow, visible, ...rest }, ref) => {
|
|
32
|
+
const [height, setHeight] = useState<number>()
|
|
33
|
+
const [width, setWidth] = useState<number>()
|
|
34
|
+
const collapseRef = useRef<HTMLDivElement>(null)
|
|
35
|
+
const forkedRef = useForkedRef(ref, collapseRef)
|
|
36
|
+
|
|
37
|
+
const getTransitionClass = (state: string) => {
|
|
38
|
+
return state === 'entering'
|
|
39
|
+
? 'collapsing'
|
|
40
|
+
: state === 'entered'
|
|
41
|
+
? 'collapse show'
|
|
42
|
+
: state === 'exiting'
|
|
43
|
+
? 'collapsing'
|
|
44
|
+
: 'collapse'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const onEntering = () => {
|
|
48
|
+
onShow && onShow()
|
|
49
|
+
|
|
50
|
+
if (horizontal) {
|
|
51
|
+
collapseRef.current && setWidth(collapseRef.current.scrollWidth)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
collapseRef.current && setHeight(collapseRef.current.scrollHeight)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const onEntered = () => {
|
|
58
|
+
if (horizontal) {
|
|
59
|
+
setWidth(0)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
setHeight(0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const onExit = () => {
|
|
66
|
+
if (horizontal) {
|
|
67
|
+
collapseRef.current && setWidth(collapseRef.current.scrollWidth)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
collapseRef.current && setHeight(collapseRef.current.scrollHeight)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const onExiting = () => {
|
|
74
|
+
onHide && onHide()
|
|
75
|
+
if (horizontal) {
|
|
76
|
+
setWidth(0)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
setHeight(0)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const onExited = () => {
|
|
83
|
+
if (horizontal) {
|
|
84
|
+
setWidth(0)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
setHeight(0)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const _className = classNames(
|
|
91
|
+
{
|
|
92
|
+
'collapse-horizontal': horizontal
|
|
93
|
+
},
|
|
94
|
+
className
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<CSSTransition
|
|
99
|
+
in={visible}
|
|
100
|
+
nodeRef={collapseRef}
|
|
101
|
+
onEntering={onEntering}
|
|
102
|
+
onEntered={onEntered}
|
|
103
|
+
onExit={onExit}
|
|
104
|
+
onExiting={onExiting}
|
|
105
|
+
onExited={onExited}
|
|
106
|
+
timeout={350}
|
|
107
|
+
>
|
|
108
|
+
{(state) => {
|
|
109
|
+
const transitionClass = getTransitionClass(state)
|
|
110
|
+
const currentHeight = height === 0 ? null : { height }
|
|
111
|
+
const currentWidth = width === 0 ? null : { width }
|
|
112
|
+
return (
|
|
113
|
+
<div
|
|
114
|
+
className={classNames(_className, transitionClass)}
|
|
115
|
+
style={{ ...currentHeight, ...currentWidth }}
|
|
116
|
+
{...rest}
|
|
117
|
+
ref={forkedRef}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}}
|
|
123
|
+
</CSSTransition>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
Collapse.displayName = 'Collapse'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { ChangeEventHandler, forwardRef, InputHTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
|
|
4
|
+
import { useFormField } from '../../hooks'
|
|
5
|
+
import { validationClassName } from '../../utils/validationClassName'
|
|
6
|
+
import { renderFormField } from '../form-field/renderFormField'
|
|
7
|
+
|
|
8
|
+
export interface ColorInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
9
|
+
/**
|
|
10
|
+
* A string of all className you want applied to the component.
|
|
11
|
+
*/
|
|
12
|
+
className?: string
|
|
13
|
+
/**
|
|
14
|
+
* The value of the input, uncontrolled.
|
|
15
|
+
*/
|
|
16
|
+
defaultValue?: string
|
|
17
|
+
/**
|
|
18
|
+
* Toggle the disabled state for the component.
|
|
19
|
+
*/
|
|
20
|
+
disabled?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* A description for the field, rendered below the input.
|
|
23
|
+
*/
|
|
24
|
+
help?: ReactNode
|
|
25
|
+
/**
|
|
26
|
+
* Set component validation state to invalid.
|
|
27
|
+
*/
|
|
28
|
+
invalid?: boolean
|
|
29
|
+
/**
|
|
30
|
+
* An error message for the field, rendered below the input when `invalid` is set.
|
|
31
|
+
*/
|
|
32
|
+
invalidFeedback?: ReactNode
|
|
33
|
+
/**
|
|
34
|
+
* The field's caption, rendered as a `FormLabel` associated with this input.
|
|
35
|
+
*/
|
|
36
|
+
label?: ReactNode
|
|
37
|
+
/**
|
|
38
|
+
* Method called immediately after the `value` prop changes.
|
|
39
|
+
*/
|
|
40
|
+
onChange?: ChangeEventHandler<HTMLInputElement>
|
|
41
|
+
/**
|
|
42
|
+
* Size the component small or large.
|
|
43
|
+
*/
|
|
44
|
+
size?: 'small' | 'large'
|
|
45
|
+
/**
|
|
46
|
+
* Set component validation state to valid.
|
|
47
|
+
*/
|
|
48
|
+
valid?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* A success message for the field, rendered below the input when `valid` is set.
|
|
51
|
+
*/
|
|
52
|
+
validFeedback?: ReactNode
|
|
53
|
+
/**
|
|
54
|
+
* The `value` attribute of component.
|
|
55
|
+
*
|
|
56
|
+
* @controllable onChange
|
|
57
|
+
* */
|
|
58
|
+
value?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const ColorInput = forwardRef<HTMLInputElement, ColorInputProps>(
|
|
62
|
+
(
|
|
63
|
+
{ className, help, id, invalid, invalidFeedback, label, size, valid, validFeedback, ...rest },
|
|
64
|
+
ref
|
|
65
|
+
) => {
|
|
66
|
+
const { describedBy, feedbackId, helpId, inputId } = useFormField({
|
|
67
|
+
ariaDescribedBy: rest['aria-describedby'],
|
|
68
|
+
help,
|
|
69
|
+
id,
|
|
70
|
+
invalid,
|
|
71
|
+
invalidFeedback,
|
|
72
|
+
valid,
|
|
73
|
+
validFeedback
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const _className = classNames(
|
|
77
|
+
'form-input',
|
|
78
|
+
size,
|
|
79
|
+
validationClassName(invalid, valid),
|
|
80
|
+
className
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return renderFormField({
|
|
84
|
+
children: (
|
|
85
|
+
<input
|
|
86
|
+
{...rest}
|
|
87
|
+
aria-describedby={describedBy}
|
|
88
|
+
aria-invalid={invalid || undefined}
|
|
89
|
+
className={_className}
|
|
90
|
+
id={inputId}
|
|
91
|
+
ref={ref}
|
|
92
|
+
type="color"
|
|
93
|
+
/>
|
|
94
|
+
),
|
|
95
|
+
help,
|
|
96
|
+
ids: { feedback: feedbackId, help: helpId, input: inputId },
|
|
97
|
+
invalid,
|
|
98
|
+
invalidFeedback,
|
|
99
|
+
label,
|
|
100
|
+
valid,
|
|
101
|
+
validFeedback
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
ColorInput.displayName = 'ColorInput'
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
InputHTMLAttributes,
|
|
5
|
+
ReactNode,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { createPortal } from 'react-dom'
|
|
11
|
+
import classNames from 'classnames'
|
|
12
|
+
import { useComboBox, useFilter, useOverlayPosition } from 'react-aria'
|
|
13
|
+
import { Key, useComboBoxState } from 'react-stately'
|
|
14
|
+
|
|
15
|
+
import { useFloatingOverlay, useForkedRef, useFormField } from '../../hooks'
|
|
16
|
+
import {
|
|
17
|
+
buildEntriesFromChildren,
|
|
18
|
+
buildEntriesFromItemsDef,
|
|
19
|
+
ComboboxEntry,
|
|
20
|
+
comboboxCollectionChildren,
|
|
21
|
+
getDisabledKeys
|
|
22
|
+
} from '../../utils/comboboxCollection'
|
|
23
|
+
import {
|
|
24
|
+
COMBOBOX_MENU_OVERLAY_STYLE,
|
|
25
|
+
resolveMenuOverlayPositioning,
|
|
26
|
+
toAriaPlacement
|
|
27
|
+
} from '../../utils/overlayPlacement'
|
|
28
|
+
import { validationClassName } from '../../utils/validationClassName'
|
|
29
|
+
import { renderFormField } from '../form-field/renderFormField'
|
|
30
|
+
import { MenuItemsDef } from '../menu/MenuItemDef'
|
|
31
|
+
import { ComboboxGroup } from './ComboboxGroup'
|
|
32
|
+
import { ComboboxItem } from './ComboboxItem'
|
|
33
|
+
import { ComboboxListBox } from './ComboboxListBox'
|
|
34
|
+
|
|
35
|
+
export interface ComboboxProps extends Omit<
|
|
36
|
+
HTMLAttributes<HTMLDivElement>,
|
|
37
|
+
'onChange' | 'defaultValue'
|
|
38
|
+
> {
|
|
39
|
+
/**
|
|
40
|
+
* An accessible label for the combobox, used when there's no visible `<label>`.
|
|
41
|
+
*/
|
|
42
|
+
'aria-label'?: string
|
|
43
|
+
/**
|
|
44
|
+
* Identifies a visible `<label>` element for the combobox.
|
|
45
|
+
*/
|
|
46
|
+
'aria-labelledby'?: string
|
|
47
|
+
/**
|
|
48
|
+
* `ComboboxItem` elements, optionally wrapped in `ComboboxGroup` — read as data by
|
|
49
|
+
* `Combobox` to build the option list. Not rendered directly. Ignored when `items` is set.
|
|
50
|
+
*/
|
|
51
|
+
children?: ReactNode
|
|
52
|
+
/**
|
|
53
|
+
* A string of all className you want applied to the base component.
|
|
54
|
+
*/
|
|
55
|
+
className?: string
|
|
56
|
+
/**
|
|
57
|
+
* The initial selected option's id (uncontrolled).
|
|
58
|
+
*/
|
|
59
|
+
defaultValue?: Key | null
|
|
60
|
+
/**
|
|
61
|
+
* Prevents the combobox from being focused or interacted with.
|
|
62
|
+
*/
|
|
63
|
+
disabled?: boolean
|
|
64
|
+
/**
|
|
65
|
+
* A description for the field, rendered below the combobox.
|
|
66
|
+
*/
|
|
67
|
+
help?: ReactNode
|
|
68
|
+
/**
|
|
69
|
+
* `id` forwarded to the input element — useful for pairing with a `<label for>`.
|
|
70
|
+
*/
|
|
71
|
+
id?: string
|
|
72
|
+
/**
|
|
73
|
+
* Set component validation state to invalid.
|
|
74
|
+
*/
|
|
75
|
+
invalid?: boolean
|
|
76
|
+
/**
|
|
77
|
+
* An error message for the field, rendered below the combobox when `invalid` is set.
|
|
78
|
+
*/
|
|
79
|
+
invalidFeedback?: ReactNode
|
|
80
|
+
/**
|
|
81
|
+
* Array of item/header/divider definitions for data-driven rendering. When provided, children
|
|
82
|
+
* are ignored. A `'header'` entry starts a group that all following items join until the next
|
|
83
|
+
* header or the end of the array. `'divider'` entries are a no-op here (dividers aren't
|
|
84
|
+
* meaningful for a listbox/option collection) — use `ComboboxGroup` composition instead if
|
|
85
|
+
* you need finer control over grouping. An entry's `href`/`onClick` are `Menu`-only and are not
|
|
86
|
+
* read here — use `onChange` to react to the selection instead.
|
|
87
|
+
*/
|
|
88
|
+
items?: MenuItemsDef
|
|
89
|
+
/**
|
|
90
|
+
* The field's caption, rendered as a `FormLabel` associated with the input.
|
|
91
|
+
*/
|
|
92
|
+
label?: ReactNode
|
|
93
|
+
/**
|
|
94
|
+
* `name` of an auto-created hidden input, kept in sync with the selection, for native form
|
|
95
|
+
* submission. Omit to skip creating one.
|
|
96
|
+
*/
|
|
97
|
+
name?: string
|
|
98
|
+
/**
|
|
99
|
+
* Text shown in the listbox when no options match the current query.
|
|
100
|
+
*/
|
|
101
|
+
noResultsText?: ReactNode
|
|
102
|
+
/**
|
|
103
|
+
* Callback fired when the selected option changes.
|
|
104
|
+
*/
|
|
105
|
+
onChange?: (value: Key | null) => void
|
|
106
|
+
/**
|
|
107
|
+
* Placeholder shown in the input when nothing is selected.
|
|
108
|
+
*/
|
|
109
|
+
placeholder?: string
|
|
110
|
+
/**
|
|
111
|
+
* Size the component small or large.
|
|
112
|
+
*/
|
|
113
|
+
size?: 'small' | 'large'
|
|
114
|
+
/**
|
|
115
|
+
* Set component validation state to valid.
|
|
116
|
+
*/
|
|
117
|
+
valid?: boolean
|
|
118
|
+
/**
|
|
119
|
+
* A success message for the field, rendered below the combobox when `valid` is set.
|
|
120
|
+
*/
|
|
121
|
+
validFeedback?: ReactNode
|
|
122
|
+
/**
|
|
123
|
+
* The selected option's id (controlled).
|
|
124
|
+
*/
|
|
125
|
+
value?: Key | null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const Combobox = forwardRef<HTMLDivElement, ComboboxProps>(
|
|
129
|
+
(
|
|
130
|
+
{
|
|
131
|
+
'aria-describedby': ariaDescribedBy,
|
|
132
|
+
'aria-label': ariaLabel,
|
|
133
|
+
'aria-labelledby': ariaLabelledBy,
|
|
134
|
+
children,
|
|
135
|
+
className,
|
|
136
|
+
defaultValue,
|
|
137
|
+
disabled,
|
|
138
|
+
help,
|
|
139
|
+
id,
|
|
140
|
+
invalid,
|
|
141
|
+
invalidFeedback,
|
|
142
|
+
items,
|
|
143
|
+
label,
|
|
144
|
+
name,
|
|
145
|
+
noResultsText = 'No results found',
|
|
146
|
+
onChange,
|
|
147
|
+
placeholder,
|
|
148
|
+
size,
|
|
149
|
+
valid,
|
|
150
|
+
validFeedback,
|
|
151
|
+
value,
|
|
152
|
+
...rest
|
|
153
|
+
}: ComboboxProps,
|
|
154
|
+
ref
|
|
155
|
+
): ReactNode => {
|
|
156
|
+
// `entries`/`disabledKeys` only need to change when the data driving them does — without this,
|
|
157
|
+
// both re-derive from scratch on every render, including every keystroke while typing.
|
|
158
|
+
const entries = useMemo(
|
|
159
|
+
() =>
|
|
160
|
+
items
|
|
161
|
+
? buildEntriesFromItemsDef(items)
|
|
162
|
+
: buildEntriesFromChildren(children, { Group: ComboboxGroup, Item: ComboboxItem }),
|
|
163
|
+
[items, children]
|
|
164
|
+
)
|
|
165
|
+
const disabledKeys = useMemo(() => getDisabledKeys(entries), [entries])
|
|
166
|
+
|
|
167
|
+
// Case- and accent-insensitive substring matching, mirroring chassis-css's own
|
|
168
|
+
// always-case-insensitive combobox.js filtering.
|
|
169
|
+
const { contains } = useFilter({ sensitivity: 'base' })
|
|
170
|
+
|
|
171
|
+
const state = useComboBoxState<ComboboxEntry>({
|
|
172
|
+
children: comboboxCollectionChildren,
|
|
173
|
+
defaultItems: entries,
|
|
174
|
+
disabledKeys,
|
|
175
|
+
defaultFilter: contains,
|
|
176
|
+
defaultValue,
|
|
177
|
+
value,
|
|
178
|
+
onChange,
|
|
179
|
+
allowsEmptyCollection: true,
|
|
180
|
+
// Matches chassis-css's vanilla combobox.js: the menu opens on focus, not only once the
|
|
181
|
+
// user starts typing.
|
|
182
|
+
menuTrigger: 'focus'
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
186
|
+
const forkedWrapperRef = useForkedRef(ref, wrapperRef)
|
|
187
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
188
|
+
const listBoxRef = useRef<HTMLElement>(null)
|
|
189
|
+
const popoverRef = useRef<HTMLDivElement>(null)
|
|
190
|
+
const noResultsId = useId()
|
|
191
|
+
|
|
192
|
+
const { describedBy, feedbackId, helpId, inputId, labelId, labelledBy } = useFormField({
|
|
193
|
+
ariaDescribedBy,
|
|
194
|
+
ariaLabelledBy,
|
|
195
|
+
help,
|
|
196
|
+
id,
|
|
197
|
+
invalid,
|
|
198
|
+
invalidFeedback,
|
|
199
|
+
label,
|
|
200
|
+
valid,
|
|
201
|
+
validFeedback
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const { inputProps, listBoxProps } = useComboBox<ComboboxEntry>(
|
|
205
|
+
{
|
|
206
|
+
'aria-label': ariaLabel,
|
|
207
|
+
'aria-labelledby': labelledBy,
|
|
208
|
+
id: inputId,
|
|
209
|
+
inputRef,
|
|
210
|
+
listBoxRef,
|
|
211
|
+
popoverRef,
|
|
212
|
+
isDisabled: disabled,
|
|
213
|
+
placeholder
|
|
214
|
+
},
|
|
215
|
+
state
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const { overlayProps, placement: resolvedPlacement } = useOverlayPosition({
|
|
219
|
+
targetRef: wrapperRef,
|
|
220
|
+
overlayRef: popoverRef,
|
|
221
|
+
placement: toAriaPlacement('bottom-start'),
|
|
222
|
+
offset: 2,
|
|
223
|
+
isOpen: state.isOpen,
|
|
224
|
+
// Leaving `onClose` unset doesn't actually disable react-aria's close-on-scroll listener —
|
|
225
|
+
// only an explicit `null` does. Matches the fix already applied to `Autocomplete`'s identical
|
|
226
|
+
// positioning call.
|
|
227
|
+
onClose: null
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Portals the panel to `document.body` (or an enclosing open `<dialog>`) instead of rendering
|
|
231
|
+
// it inline, matching `Popover`/`Tooltip` — an inline-rendered panel gets clipped by any
|
|
232
|
+
// ancestor with `overflow: hidden`/`auto` (a `ModalBody`, a scrollable card, a table cell), and
|
|
233
|
+
// `Combobox` is commonly composed inside exactly those. See `COMBOBOX_MENU_OVERLAY_STYLE` for
|
|
234
|
+
// the styling this trades away by leaving the DOM position `.combobox + .menu` relies on.
|
|
235
|
+
const portalContainer = useFloatingOverlay({
|
|
236
|
+
close: state.close,
|
|
237
|
+
isOpen: state.isOpen,
|
|
238
|
+
open: state.open,
|
|
239
|
+
triggerRef: wrapperRef
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const { menuStyle, placementAttr } = resolveMenuOverlayPositioning(
|
|
243
|
+
overlayProps.style,
|
|
244
|
+
'bottom-start',
|
|
245
|
+
resolvedPlacement
|
|
246
|
+
)
|
|
247
|
+
const overlayStyle: React.CSSProperties = { ...COMBOBOX_MENU_OVERLAY_STYLE, ...menuStyle }
|
|
248
|
+
|
|
249
|
+
const inputHtmlProps = inputProps as InputHTMLAttributes<HTMLInputElement>
|
|
250
|
+
const showNoResults = state.collection.size === 0
|
|
251
|
+
// Merges the no-results message into the input's own description (for AT that reads
|
|
252
|
+
// descriptions on demand) in addition to the `aria-live` region below (for AT that announces
|
|
253
|
+
// live-region changes proactively as the user types) — belt and braces, since screen reader
|
|
254
|
+
// support for announcing a freshly-mounted live region is inconsistent.
|
|
255
|
+
const inputDescribedBy =
|
|
256
|
+
[describedBy, showNoResults && noResultsId].filter(Boolean).join(' ') || undefined
|
|
257
|
+
|
|
258
|
+
return renderFormField({
|
|
259
|
+
children: (
|
|
260
|
+
<>
|
|
261
|
+
<div
|
|
262
|
+
className={classNames(
|
|
263
|
+
'form-input',
|
|
264
|
+
'combobox',
|
|
265
|
+
size,
|
|
266
|
+
{ disabled },
|
|
267
|
+
validationClassName(invalid, valid),
|
|
268
|
+
className
|
|
269
|
+
)}
|
|
270
|
+
ref={forkedWrapperRef}
|
|
271
|
+
{...rest}
|
|
272
|
+
>
|
|
273
|
+
<input
|
|
274
|
+
autoComplete="off"
|
|
275
|
+
className="combobox-value"
|
|
276
|
+
{...inputHtmlProps}
|
|
277
|
+
aria-describedby={inputDescribedBy}
|
|
278
|
+
aria-invalid={invalid || undefined}
|
|
279
|
+
ref={inputRef}
|
|
280
|
+
/>
|
|
281
|
+
</div>
|
|
282
|
+
{typeof window !== 'undefined' &&
|
|
283
|
+
createPortal(
|
|
284
|
+
<div
|
|
285
|
+
className={classNames('menu', { show: state.isOpen })}
|
|
286
|
+
data-cx-placement={placementAttr}
|
|
287
|
+
style={overlayStyle}
|
|
288
|
+
hidden={!state.isOpen}
|
|
289
|
+
ref={popoverRef}
|
|
290
|
+
>
|
|
291
|
+
<ComboboxListBox
|
|
292
|
+
state={state}
|
|
293
|
+
listBoxProps={listBoxProps}
|
|
294
|
+
listBoxRef={listBoxRef}
|
|
295
|
+
/>
|
|
296
|
+
{showNoResults && (
|
|
297
|
+
<div className="combobox-no-results" id={noResultsId} role="status">
|
|
298
|
+
{noResultsText}
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
</div>,
|
|
302
|
+
portalContainer ?? document.body
|
|
303
|
+
)}
|
|
304
|
+
{name && (
|
|
305
|
+
<input type="hidden" name={name} value={state.value ?? ''} disabled={disabled} />
|
|
306
|
+
)}
|
|
307
|
+
</>
|
|
308
|
+
),
|
|
309
|
+
help,
|
|
310
|
+
ids: { feedback: feedbackId, help: helpId, input: inputId, label: labelId },
|
|
311
|
+
invalid,
|
|
312
|
+
invalidFeedback,
|
|
313
|
+
label,
|
|
314
|
+
valid,
|
|
315
|
+
validFeedback
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
Combobox.displayName = 'Combobox'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface ComboboxGroupProps {
|
|
4
|
+
/**
|
|
5
|
+
* Items belonging to this group.
|
|
6
|
+
*/
|
|
7
|
+
children: ReactNode
|
|
8
|
+
/**
|
|
9
|
+
* Header content for the group (`.menu-header`).
|
|
10
|
+
*/
|
|
11
|
+
label: ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// `ComboboxGroup` is never actually mounted — like `ComboboxItem`, it's read as data by
|
|
15
|
+
// `Combobox`, which splits its top-level children into groups (wrapping react-stately
|
|
16
|
+
// `Section` nodes) and bare items. Groups and bare items can be mixed at the top level:
|
|
17
|
+
//
|
|
18
|
+
// <Combobox aria-label="Language">
|
|
19
|
+
// <ComboboxGroup label="Frontend">
|
|
20
|
+
// <ComboboxItem id="html">HTML</ComboboxItem>
|
|
21
|
+
// <ComboboxItem id="css">CSS</ComboboxItem>
|
|
22
|
+
// </ComboboxGroup>
|
|
23
|
+
// <ComboboxItem id="python">Python</ComboboxItem>
|
|
24
|
+
// </Combobox>
|
|
25
|
+
export const ComboboxGroup = (_props: ComboboxGroupProps): null => null
|
|
26
|
+
|
|
27
|
+
ComboboxGroup.displayName = 'ComboboxGroup'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import { Key } from 'react-stately'
|
|
3
|
+
|
|
4
|
+
export interface ComboboxItemProps {
|
|
5
|
+
/**
|
|
6
|
+
* Content of the option. Used as the filter/typeahead text when `textValue` isn't set — pass
|
|
7
|
+
* `textValue` explicitly whenever `children` isn't a plain string (e.g. it wraps an icon or
|
|
8
|
+
* other rich markup), since it can't otherwise be derived from rich content.
|
|
9
|
+
*/
|
|
10
|
+
children: ReactNode
|
|
11
|
+
/**
|
|
12
|
+
* Secondary line of text rendered below `children` (`.menu-item-description`).
|
|
13
|
+
*/
|
|
14
|
+
description?: ReactNode
|
|
15
|
+
/**
|
|
16
|
+
* Prevents the option from being selected, focused, or otherwise interacted with.
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
/**
|
|
20
|
+
* Icon rendered at the option's leading edge (`.menu-item-icon`).
|
|
21
|
+
*/
|
|
22
|
+
icon?: ReactNode
|
|
23
|
+
/**
|
|
24
|
+
* Identifies this option. Submitted as the value when this option is selected.
|
|
25
|
+
*/
|
|
26
|
+
id: Key
|
|
27
|
+
/**
|
|
28
|
+
* Text used for filtering and typeahead. Required when `children` isn't a plain string —
|
|
29
|
+
* falls back to `children` itself when omitted and `children` is a string.
|
|
30
|
+
*/
|
|
31
|
+
textValue?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// `ComboboxItem` is never actually mounted — like `Tab`, it's read as data by `Combobox`,
|
|
35
|
+
// which builds react-aria's listbox collection from each item's `id` and `children`. This keeps
|
|
36
|
+
// the public authoring shape as plain composed JSX:
|
|
37
|
+
//
|
|
38
|
+
// <Combobox aria-label="Fruit">
|
|
39
|
+
// <ComboboxItem id="apple">Apple</ComboboxItem>
|
|
40
|
+
// <ComboboxItem id="banana">Banana</ComboboxItem>
|
|
41
|
+
// </Combobox>
|
|
42
|
+
export const ComboboxItem = (_props: ComboboxItemProps): null => null
|
|
43
|
+
|
|
44
|
+
ComboboxItem.displayName = 'ComboboxItem'
|