@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,236 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
InputHTMLAttributes,
|
|
4
|
+
ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef
|
|
8
|
+
} from 'react'
|
|
9
|
+
import classNames from 'classnames'
|
|
10
|
+
import {
|
|
11
|
+
AriaCheckboxGroupItemProps,
|
|
12
|
+
AriaCheckboxProps,
|
|
13
|
+
useCheckbox,
|
|
14
|
+
useCheckboxGroupItem
|
|
15
|
+
} from 'react-aria'
|
|
16
|
+
import { CheckboxGroupState, useToggleState } from 'react-stately'
|
|
17
|
+
|
|
18
|
+
import { useForkedRef } from '../../hooks'
|
|
19
|
+
import { ContextColor } from '../../types'
|
|
20
|
+
import { validationClassName } from '../../utils/validationClassName'
|
|
21
|
+
|
|
22
|
+
import { CheckboxGroupContext } from './context'
|
|
23
|
+
import { ButtonObject, renderFormCheck } from '../form/renderFormCheck'
|
|
24
|
+
|
|
25
|
+
export type { ButtonObject } from '../form/renderFormCheck'
|
|
26
|
+
|
|
27
|
+
export interface CheckboxProps extends Omit<
|
|
28
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
29
|
+
'checked' | 'defaultChecked' | 'onChange' | 'size'
|
|
30
|
+
> {
|
|
31
|
+
/**
|
|
32
|
+
* Create button-like checkboxes.
|
|
33
|
+
*/
|
|
34
|
+
button?: ButtonObject
|
|
35
|
+
/**
|
|
36
|
+
* A string of all className you want applied to the component.
|
|
37
|
+
*/
|
|
38
|
+
className?: string
|
|
39
|
+
/**
|
|
40
|
+
* Sets the color of the check indicator to one of Chassis context colors. Ignored when `button` is set.
|
|
41
|
+
*/
|
|
42
|
+
color?: ContextColor
|
|
43
|
+
/**
|
|
44
|
+
* Whether the checkbox is selected, uncontrolled. Ignored when rendered inside a `<CheckboxGroup>` —
|
|
45
|
+
* the group's `value`/`defaultValue` owns selection there.
|
|
46
|
+
*/
|
|
47
|
+
defaultSelected?: boolean
|
|
48
|
+
/**
|
|
49
|
+
* The id global attribute defines an identifier (ID) that must be unique in the whole document.
|
|
50
|
+
*/
|
|
51
|
+
id?: string
|
|
52
|
+
/**
|
|
53
|
+
* Checkbox indeterminate property.
|
|
54
|
+
*/
|
|
55
|
+
indeterminate?: boolean
|
|
56
|
+
/**
|
|
57
|
+
* Set component validation state to invalid.
|
|
58
|
+
*/
|
|
59
|
+
invalid?: boolean
|
|
60
|
+
/**
|
|
61
|
+
* Whether the checkbox is selected, controlled. Ignored when rendered inside a `<CheckboxGroup>` —
|
|
62
|
+
* the group's `value`/`defaultValue` owns selection there.
|
|
63
|
+
*/
|
|
64
|
+
isSelected?: boolean
|
|
65
|
+
/**
|
|
66
|
+
* The element represents a caption for a component.
|
|
67
|
+
*/
|
|
68
|
+
label?: ReactNode
|
|
69
|
+
/**
|
|
70
|
+
* Callback fired when the selected state changes. Ignored when rendered inside a `<CheckboxGroup>` —
|
|
71
|
+
* use the group's `onChange` instead.
|
|
72
|
+
*/
|
|
73
|
+
onChange?: (isSelected: boolean) => void
|
|
74
|
+
/**
|
|
75
|
+
* Size the component small or large.
|
|
76
|
+
*/
|
|
77
|
+
size?: 'small' | 'large'
|
|
78
|
+
/**
|
|
79
|
+
* Set component validation state to valid.
|
|
80
|
+
*/
|
|
81
|
+
valid?: boolean
|
|
82
|
+
/**
|
|
83
|
+
* The value of the checkbox, used when submitting an HTML form. Required when rendered inside a
|
|
84
|
+
* `<CheckboxGroup>` — it identifies this item within the group's selected values.
|
|
85
|
+
*/
|
|
86
|
+
value?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const CheckboxStandalone = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
90
|
+
(
|
|
91
|
+
{
|
|
92
|
+
button,
|
|
93
|
+
className,
|
|
94
|
+
color,
|
|
95
|
+
defaultSelected,
|
|
96
|
+
disabled,
|
|
97
|
+
id,
|
|
98
|
+
indeterminate,
|
|
99
|
+
invalid,
|
|
100
|
+
isSelected,
|
|
101
|
+
label,
|
|
102
|
+
onChange,
|
|
103
|
+
size,
|
|
104
|
+
valid,
|
|
105
|
+
...rest
|
|
106
|
+
},
|
|
107
|
+
ref
|
|
108
|
+
) => {
|
|
109
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
110
|
+
const forkedRef = useForkedRef(ref, inputRef)
|
|
111
|
+
|
|
112
|
+
const toggleState = useToggleState({
|
|
113
|
+
defaultSelected,
|
|
114
|
+
isDisabled: disabled,
|
|
115
|
+
isSelected,
|
|
116
|
+
onChange
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const { inputProps } = useCheckbox(
|
|
120
|
+
{
|
|
121
|
+
...rest,
|
|
122
|
+
children: label,
|
|
123
|
+
isDisabled: disabled,
|
|
124
|
+
isIndeterminate: indeterminate,
|
|
125
|
+
isInvalid: invalid,
|
|
126
|
+
value: rest.value as string | undefined
|
|
127
|
+
} as AriaCheckboxProps,
|
|
128
|
+
toggleState,
|
|
129
|
+
inputRef
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const inputClassName = classNames(validationClassName(invalid, valid))
|
|
133
|
+
|
|
134
|
+
return renderFormCheck({
|
|
135
|
+
button,
|
|
136
|
+
className,
|
|
137
|
+
color,
|
|
138
|
+
input: <input {...inputProps} className={inputClassName} id={id} ref={forkedRef} />,
|
|
139
|
+
invalid,
|
|
140
|
+
label,
|
|
141
|
+
size,
|
|
142
|
+
valid
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
CheckboxStandalone.displayName = 'CheckboxStandalone'
|
|
147
|
+
|
|
148
|
+
interface CheckboxGroupItemProps extends CheckboxProps {
|
|
149
|
+
groupState: CheckboxGroupState
|
|
150
|
+
groupValid?: boolean
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const CheckboxGroupItem = forwardRef<HTMLInputElement, CheckboxGroupItemProps>(
|
|
154
|
+
(
|
|
155
|
+
{
|
|
156
|
+
button,
|
|
157
|
+
className,
|
|
158
|
+
color,
|
|
159
|
+
defaultSelected: _defaultSelected,
|
|
160
|
+
disabled,
|
|
161
|
+
groupState,
|
|
162
|
+
groupValid,
|
|
163
|
+
id,
|
|
164
|
+
indeterminate,
|
|
165
|
+
invalid: itemInvalid,
|
|
166
|
+
isSelected: _isSelected,
|
|
167
|
+
label,
|
|
168
|
+
onChange: _onChange,
|
|
169
|
+
size,
|
|
170
|
+
valid: itemValid,
|
|
171
|
+
...rest
|
|
172
|
+
},
|
|
173
|
+
ref
|
|
174
|
+
) => {
|
|
175
|
+
// The group's own invalid/valid cascades to every item, same as RadioGroup does for Radio —
|
|
176
|
+
// but an item can still override it with its own invalid/valid, since (unlike a lone Radio) a
|
|
177
|
+
// lone Checkbox's validity is independently meaningful (see FORMS.md).
|
|
178
|
+
const invalid = itemInvalid ?? groupState.isInvalid
|
|
179
|
+
const valid = itemValid ?? groupValid
|
|
180
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
181
|
+
const forkedRef = useForkedRef(ref, inputRef)
|
|
182
|
+
|
|
183
|
+
// Once per mount, not on every render — these are dev-time misuse warnings, not something
|
|
184
|
+
// that needs to re-fire for every keystroke a parent's re-render happens to cause.
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!rest.value) {
|
|
187
|
+
console.error('Checkbox: a `value` prop is required when rendered inside a CheckboxGroup.')
|
|
188
|
+
}
|
|
189
|
+
if (_defaultSelected !== undefined || _isSelected !== undefined || _onChange !== undefined) {
|
|
190
|
+
console.warn(
|
|
191
|
+
'Checkbox: `defaultSelected`, `isSelected`, and `onChange` are ignored inside a ' +
|
|
192
|
+
"CheckboxGroup — selection is owned by the group's `value`/`defaultValue`/`onChange`."
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
198
|
+
const { inputProps } = useCheckboxGroupItem(
|
|
199
|
+
{
|
|
200
|
+
...rest,
|
|
201
|
+
children: label,
|
|
202
|
+
isDisabled: disabled,
|
|
203
|
+
isIndeterminate: indeterminate,
|
|
204
|
+
isInvalid: invalid,
|
|
205
|
+
value: rest.value as string
|
|
206
|
+
} as AriaCheckboxGroupItemProps,
|
|
207
|
+
groupState,
|
|
208
|
+
inputRef
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
const inputClassName = classNames(validationClassName(invalid, valid))
|
|
212
|
+
|
|
213
|
+
return renderFormCheck({
|
|
214
|
+
button,
|
|
215
|
+
className,
|
|
216
|
+
color,
|
|
217
|
+
input: <input {...inputProps} className={inputClassName} id={id} ref={forkedRef} />,
|
|
218
|
+
invalid,
|
|
219
|
+
label,
|
|
220
|
+
size,
|
|
221
|
+
valid
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
CheckboxGroupItem.displayName = 'CheckboxGroupItem'
|
|
226
|
+
|
|
227
|
+
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {
|
|
228
|
+
const group = useContext(CheckboxGroupContext)
|
|
229
|
+
return group ? (
|
|
230
|
+
<CheckboxGroupItem {...props} groupState={group.state} groupValid={group.valid} ref={ref} />
|
|
231
|
+
) : (
|
|
232
|
+
<CheckboxStandalone {...props} ref={ref} />
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
Checkbox.displayName = 'Checkbox'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { forwardRef, HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { AriaCheckboxGroupProps, useCheckboxGroup } from 'react-aria'
|
|
4
|
+
import { useCheckboxGroupState } from 'react-stately'
|
|
5
|
+
|
|
6
|
+
import { validationClassName } from '../../utils/validationClassName'
|
|
7
|
+
|
|
8
|
+
import { CheckboxGroupContext } from './context'
|
|
9
|
+
import { FormFeedback } from '../form/FormFeedback'
|
|
10
|
+
import { FormHelp } from '../form/FormHelp'
|
|
11
|
+
import { Flex } from '../flex'
|
|
12
|
+
|
|
13
|
+
export interface CheckboxGroupProps extends Omit<
|
|
14
|
+
HTMLAttributes<HTMLFieldSetElement>,
|
|
15
|
+
'defaultValue' | 'onChange'
|
|
16
|
+
> {
|
|
17
|
+
/**
|
|
18
|
+
* One or more `<Checkbox>` elements.
|
|
19
|
+
*/
|
|
20
|
+
children: ReactNode
|
|
21
|
+
/**
|
|
22
|
+
* A string of all className you want applied to the component.
|
|
23
|
+
*/
|
|
24
|
+
className?: string
|
|
25
|
+
/**
|
|
26
|
+
* The selected values, uncontrolled.
|
|
27
|
+
*/
|
|
28
|
+
defaultValue?: string[]
|
|
29
|
+
/**
|
|
30
|
+
* A description for the group, rendered below the options.
|
|
31
|
+
*/
|
|
32
|
+
description?: ReactNode
|
|
33
|
+
/**
|
|
34
|
+
* Disables every checkbox in the group.
|
|
35
|
+
*/
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
/**
|
|
38
|
+
* An error message for the group, rendered below the options when `invalid` is set.
|
|
39
|
+
*/
|
|
40
|
+
errorMessage?: ReactNode
|
|
41
|
+
/**
|
|
42
|
+
* Set group validation state to invalid.
|
|
43
|
+
*/
|
|
44
|
+
invalid?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* The group's caption, rendered as a `<legend>`.
|
|
47
|
+
*/
|
|
48
|
+
label?: ReactNode
|
|
49
|
+
/**
|
|
50
|
+
* The name for the group, used for native form submission.
|
|
51
|
+
*/
|
|
52
|
+
name?: string
|
|
53
|
+
/**
|
|
54
|
+
* Callback fired when the selected values change.
|
|
55
|
+
*/
|
|
56
|
+
onChange?: (value: string[]) => void
|
|
57
|
+
/**
|
|
58
|
+
* Lay the group's checkboxes out on the same horizontal row instead of stacking them.
|
|
59
|
+
*/
|
|
60
|
+
orientation?: 'horizontal' | 'vertical'
|
|
61
|
+
/**
|
|
62
|
+
* Marks the group as required.
|
|
63
|
+
*/
|
|
64
|
+
required?: boolean
|
|
65
|
+
/**
|
|
66
|
+
* Set group validation state to valid.
|
|
67
|
+
*/
|
|
68
|
+
valid?: boolean
|
|
69
|
+
/**
|
|
70
|
+
* The selected values, controlled.
|
|
71
|
+
*/
|
|
72
|
+
value?: string[]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const CheckboxGroup = forwardRef<HTMLFieldSetElement, CheckboxGroupProps>(
|
|
76
|
+
(
|
|
77
|
+
{
|
|
78
|
+
children,
|
|
79
|
+
className,
|
|
80
|
+
defaultValue,
|
|
81
|
+
description,
|
|
82
|
+
disabled,
|
|
83
|
+
errorMessage,
|
|
84
|
+
invalid,
|
|
85
|
+
label,
|
|
86
|
+
name,
|
|
87
|
+
onChange,
|
|
88
|
+
orientation,
|
|
89
|
+
required,
|
|
90
|
+
valid,
|
|
91
|
+
value,
|
|
92
|
+
...rest
|
|
93
|
+
},
|
|
94
|
+
ref
|
|
95
|
+
) => {
|
|
96
|
+
const groupProps = {
|
|
97
|
+
...rest,
|
|
98
|
+
defaultValue,
|
|
99
|
+
description,
|
|
100
|
+
errorMessage,
|
|
101
|
+
isDisabled: disabled,
|
|
102
|
+
isInvalid: invalid,
|
|
103
|
+
isRequired: required,
|
|
104
|
+
label,
|
|
105
|
+
name,
|
|
106
|
+
onChange,
|
|
107
|
+
value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const state = useCheckboxGroupState(groupProps)
|
|
111
|
+
const {
|
|
112
|
+
groupProps: ariaGroupProps,
|
|
113
|
+
labelProps,
|
|
114
|
+
descriptionProps,
|
|
115
|
+
errorMessageProps
|
|
116
|
+
} = useCheckboxGroup(groupProps as AriaCheckboxGroupProps, state)
|
|
117
|
+
|
|
118
|
+
const _className = classNames('form-field', validationClassName(invalid, valid), className)
|
|
119
|
+
|
|
120
|
+
const items = (
|
|
121
|
+
<CheckboxGroupContext.Provider value={{ state, valid }}>
|
|
122
|
+
{children}
|
|
123
|
+
</CheckboxGroupContext.Provider>
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<fieldset {...rest} {...ariaGroupProps} className={_className} ref={ref}>
|
|
128
|
+
{label && (
|
|
129
|
+
<legend className="form-label" {...labelProps}>
|
|
130
|
+
{label}
|
|
131
|
+
</legend>
|
|
132
|
+
)}
|
|
133
|
+
{orientation === 'horizontal' ? <Flex gap="medium">{items}</Flex> : items}
|
|
134
|
+
{description && <FormHelp {...descriptionProps}>{description}</FormHelp>}
|
|
135
|
+
{invalid && errorMessage && (
|
|
136
|
+
<FormFeedback invalid {...errorMessageProps}>
|
|
137
|
+
{errorMessage}
|
|
138
|
+
</FormFeedback>
|
|
139
|
+
)}
|
|
140
|
+
</fieldset>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
CheckboxGroup.displayName = 'CheckboxGroup'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext } from 'react'
|
|
2
|
+
import { CheckboxGroupState } from 'react-stately'
|
|
3
|
+
|
|
4
|
+
export interface CheckboxGroupContextValue {
|
|
5
|
+
state: CheckboxGroupState
|
|
6
|
+
/**
|
|
7
|
+
* `CheckboxGroup`'s own `valid` prop, mirrored here since it has no react-stately equivalent to
|
|
8
|
+
* read off `state` the way `isInvalid` does — each `Checkbox` needs it to apply its own
|
|
9
|
+
* `is-valid` class when the item doesn't set its own (same pattern as `RadioGroupContext`).
|
|
10
|
+
*/
|
|
11
|
+
valid?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const CheckboxGroupContext = createContext<CheckboxGroupContextValue | null>(null)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ElementType,
|
|
3
|
+
ForwardRefRenderFunction,
|
|
4
|
+
MouseEventHandler,
|
|
5
|
+
ReactElement,
|
|
6
|
+
Ref
|
|
7
|
+
} from 'react'
|
|
8
|
+
import classNames from 'classnames'
|
|
9
|
+
import { mergeProps } from 'react-aria'
|
|
10
|
+
|
|
11
|
+
import { ContextColor, ContextStyle } from '../../types'
|
|
12
|
+
import { useButtonSemantics, useDisabledAnchorGuard } from '../../hooks'
|
|
13
|
+
import {
|
|
14
|
+
createPolymorphicComponent,
|
|
15
|
+
PolymorphicComponentProps,
|
|
16
|
+
PolymorphicRef
|
|
17
|
+
} from '../../utils/polymorphic'
|
|
18
|
+
|
|
19
|
+
type ChipOwnProps<C extends ElementType> = {
|
|
20
|
+
/**
|
|
21
|
+
* A string of all className you want applied to the component.
|
|
22
|
+
*/
|
|
23
|
+
className?: string
|
|
24
|
+
/**
|
|
25
|
+
* Sets the color of the component to one of Chassis context colors.
|
|
26
|
+
*/
|
|
27
|
+
color?: ContextColor
|
|
28
|
+
/**
|
|
29
|
+
* Component used for the root node. Either a string to use a HTML element or a component.
|
|
30
|
+
* Defaults to `span`, or `a` when `href` is set.
|
|
31
|
+
*/
|
|
32
|
+
component?: C
|
|
33
|
+
/**
|
|
34
|
+
* Toggle the disabled state for the component. Applied as the native `disabled` attribute when
|
|
35
|
+
* `component` is `button`, or the `.disabled` class for every other element (a bare `<span>`/
|
|
36
|
+
* `<a>` has no real `disabled` attribute).
|
|
37
|
+
*/
|
|
38
|
+
disabled?: boolean
|
|
39
|
+
/**
|
|
40
|
+
* Renders the chip as a link to this URL. Defaults `component` to `a`.
|
|
41
|
+
*/
|
|
42
|
+
href?: string
|
|
43
|
+
/**
|
|
44
|
+
* Fires on click. Typed for every element `component` can actually render, rather than
|
|
45
|
+
* narrowed to whichever element `C` happens to be.
|
|
46
|
+
*/
|
|
47
|
+
onClick?: MouseEventHandler<HTMLElement>
|
|
48
|
+
/**
|
|
49
|
+
* Marks the chip as pressed for toggle-style usage (e.g. a filter chip). Applies the `.active`
|
|
50
|
+
* class and sets `aria-pressed` so assistive technology announces the toggle state.
|
|
51
|
+
*/
|
|
52
|
+
pressed?: boolean
|
|
53
|
+
/**
|
|
54
|
+
* Size the component small or large.
|
|
55
|
+
*/
|
|
56
|
+
size?: 'small' | 'large'
|
|
57
|
+
/**
|
|
58
|
+
* Specifies the type of button. Only applies when `component` is `button`. Different browsers
|
|
59
|
+
* may use different default types for the `<button>` element, so always specify it explicitly.
|
|
60
|
+
*/
|
|
61
|
+
type?: 'button' | 'submit' | 'reset'
|
|
62
|
+
/**
|
|
63
|
+
* Set the chip style variant. `solid`/`basic` render the default look with no extra class.
|
|
64
|
+
*/
|
|
65
|
+
variant?: ContextStyle
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type ChipProps<C extends ElementType = 'span'> = PolymorphicComponentProps<
|
|
69
|
+
C,
|
|
70
|
+
ChipOwnProps<C>
|
|
71
|
+
>
|
|
72
|
+
|
|
73
|
+
type ChipComponent = (<C extends ElementType = 'span'>(
|
|
74
|
+
props: ChipProps<C> & { ref?: PolymorphicRef<C> }
|
|
75
|
+
) => ReactElement | null) & { displayName?: string }
|
|
76
|
+
|
|
77
|
+
function ChipRender<C extends ElementType = 'span'>(
|
|
78
|
+
{
|
|
79
|
+
children,
|
|
80
|
+
className,
|
|
81
|
+
color,
|
|
82
|
+
component,
|
|
83
|
+
disabled,
|
|
84
|
+
href,
|
|
85
|
+
onClick,
|
|
86
|
+
pressed,
|
|
87
|
+
size,
|
|
88
|
+
type = 'button',
|
|
89
|
+
variant,
|
|
90
|
+
...rest
|
|
91
|
+
}: ChipProps<C>,
|
|
92
|
+
ref: PolymorphicRef<C>
|
|
93
|
+
) {
|
|
94
|
+
// Only defaults to `a` when `component` wasn't explicitly passed — an explicit `component`
|
|
95
|
+
// (even alongside `href`) always wins.
|
|
96
|
+
const Component = (component ?? (href ? 'a' : 'span')) as ElementType
|
|
97
|
+
const isButton = Component === 'button'
|
|
98
|
+
const isAnchor = Component === 'a'
|
|
99
|
+
|
|
100
|
+
const _className = classNames(
|
|
101
|
+
'chip',
|
|
102
|
+
color,
|
|
103
|
+
size,
|
|
104
|
+
{
|
|
105
|
+
outline: variant === 'outline',
|
|
106
|
+
smooth: variant === 'smooth',
|
|
107
|
+
active: pressed,
|
|
108
|
+
disabled: !isButton && disabled
|
|
109
|
+
},
|
|
110
|
+
className
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// `<a>`/`<span>`/`<div>` have no real `disabled` attribute, so a disabled non-button chip
|
|
114
|
+
// still fires click (and an anchor still navigates) unless it's blocked here, same guard
|
|
115
|
+
// `Button`/`CloseButton` apply.
|
|
116
|
+
const handleClick = useDisabledAnchorGuard<HTMLElement>(!isButton, disabled, onClick)
|
|
117
|
+
|
|
118
|
+
// A default (non-`button`/`a`) chip — the documented `pressed`/filter-chip use case — gets a
|
|
119
|
+
// raw `onClick` with no keyboard semantics otherwise, the same gap `Link` fills via
|
|
120
|
+
// `useButtonSemantics`. Skipped when the caller already supplied their own `role` via `rest`
|
|
121
|
+
// (e.g. `ChipList`'s `role="row"` grid semantics) — that caller is trusted to handle its own
|
|
122
|
+
// keyboard interaction, same as `CloseButton`'s `isComponentReference` escape hatch.
|
|
123
|
+
const hasExplicitRole = (rest as Record<string, unknown>).role !== undefined
|
|
124
|
+
const needsButtonSemantics = !isButton && !isAnchor && !!onClick && !hasExplicitRole
|
|
125
|
+
const { buttonProps, forkedRef } = useButtonSemantics<HTMLElement>(ref as Ref<HTMLElement>, {
|
|
126
|
+
disabled,
|
|
127
|
+
onClick
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
if (isButton) {
|
|
131
|
+
return (
|
|
132
|
+
<button
|
|
133
|
+
{...(rest as Record<string, unknown>)}
|
|
134
|
+
aria-pressed={pressed}
|
|
135
|
+
className={_className}
|
|
136
|
+
disabled={disabled}
|
|
137
|
+
onClick={handleClick as MouseEventHandler<HTMLButtonElement>}
|
|
138
|
+
ref={ref as Ref<HTMLButtonElement>}
|
|
139
|
+
type={type}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</button>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isAnchor) {
|
|
147
|
+
return (
|
|
148
|
+
<a
|
|
149
|
+
{...(rest as Record<string, unknown>)}
|
|
150
|
+
aria-pressed={pressed}
|
|
151
|
+
className={_className}
|
|
152
|
+
href={href}
|
|
153
|
+
onClick={handleClick as MouseEventHandler<HTMLAnchorElement>}
|
|
154
|
+
{...(disabled && { 'aria-disabled': true, tabIndex: -1 })}
|
|
155
|
+
ref={ref as Ref<HTMLAnchorElement>}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</a>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Component
|
|
164
|
+
{...(mergeProps(rest, needsButtonSemantics ? buttonProps : {}) as Record<string, unknown>)}
|
|
165
|
+
aria-pressed={pressed}
|
|
166
|
+
className={_className}
|
|
167
|
+
{...(!needsButtonSemantics && { onClick: handleClick })}
|
|
168
|
+
{...(disabled && { 'aria-disabled': true })}
|
|
169
|
+
// `href` was only ever forwarded when it also forced `Component` to `'a'` — now that an
|
|
170
|
+
// explicit `component` wins over that default (see above), it needs to keep reaching a
|
|
171
|
+
// custom `component`/HTML tag directly, same as `Avatar`'s equivalent fix.
|
|
172
|
+
{...(href && { href })}
|
|
173
|
+
ref={needsButtonSemantics ? forkedRef : ref}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</Component>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const Chip = createPolymorphicComponent<ChipComponent>(
|
|
181
|
+
ChipRender as ForwardRefRenderFunction<Element, ChipProps<ElementType>>,
|
|
182
|
+
'Chip'
|
|
183
|
+
)
|