@chassis-ui/react 0.0.0-20260831090525

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/index.d.ts +5909 -0
  4. package/dist/index.js +2 -0
  5. package/dist/style.css +1 -0
  6. package/package.json +114 -0
  7. package/src/components/accordion/Accordion.tsx +190 -0
  8. package/src/components/accordion/AccordionBody.tsx +46 -0
  9. package/src/components/accordion/AccordionHeader.tsx +21 -0
  10. package/src/components/accordion/AccordionItem.tsx +59 -0
  11. package/src/components/accordion/context.ts +8 -0
  12. package/src/components/accordion/index.ts +8 -0
  13. package/src/components/autocomplete/Autocomplete.tsx +464 -0
  14. package/src/components/autocomplete/AutocompleteGroup.tsx +20 -0
  15. package/src/components/autocomplete/AutocompleteItem.tsx +18 -0
  16. package/src/components/autocomplete/index.ts +6 -0
  17. package/src/components/avatar/Avatar.tsx +134 -0
  18. package/src/components/avatar/AvatarImage.tsx +19 -0
  19. package/src/components/avatar/AvatarStack.tsx +98 -0
  20. package/src/components/avatar/index.ts +6 -0
  21. package/src/components/badge/Badge.tsx +94 -0
  22. package/src/components/badge/index.ts +2 -0
  23. package/src/components/breadcrumb/Breadcrumb.tsx +59 -0
  24. package/src/components/breadcrumb/BreadcrumbItem.tsx +38 -0
  25. package/src/components/breadcrumb/index.ts +4 -0
  26. package/src/components/button/Button.tsx +229 -0
  27. package/src/components/button/index.ts +2 -0
  28. package/src/components/button-group/ButtonGroup.tsx +55 -0
  29. package/src/components/button-group/ButtonToolbar.tsx +47 -0
  30. package/src/components/button-group/index.ts +4 -0
  31. package/src/components/calendar/Calendar.scss +57 -0
  32. package/src/components/calendar/Calendar.tsx +324 -0
  33. package/src/components/calendar/CalendarMonthBlock.tsx +57 -0
  34. package/src/components/calendar/CalendarMonthGrid.tsx +92 -0
  35. package/src/components/calendar/CalendarMonthYearPicker.tsx +162 -0
  36. package/src/components/calendar/CalendarNavButton.tsx +24 -0
  37. package/src/components/calendar/CalendarWeekGrid.tsx +58 -0
  38. package/src/components/calendar/CalendarYearGrid.tsx +126 -0
  39. package/src/components/calendar/RangeCalendar.scss +95 -0
  40. package/src/components/calendar/RangeCalendar.tsx +361 -0
  41. package/src/components/calendar/index.ts +5 -0
  42. package/src/components/card/Card.tsx +173 -0
  43. package/src/components/card/CardBody.tsx +75 -0
  44. package/src/components/card/CardFooter.tsx +47 -0
  45. package/src/components/card/CardGroup.tsx +51 -0
  46. package/src/components/card/CardHeader.tsx +47 -0
  47. package/src/components/card/CardImage.tsx +70 -0
  48. package/src/components/card/CardImageOverlay.tsx +47 -0
  49. package/src/components/card/CardLink.tsx +40 -0
  50. package/src/components/card/CardSubtitle.tsx +47 -0
  51. package/src/components/card/CardText.tsx +47 -0
  52. package/src/components/card/CardTitle.tsx +47 -0
  53. package/src/components/card/index.ts +22 -0
  54. package/src/components/carousel/Carousel.tsx +742 -0
  55. package/src/components/carousel/CarouselControlButton.tsx +74 -0
  56. package/src/components/carousel/CarouselControlNext.tsx +22 -0
  57. package/src/components/carousel/CarouselControlPrev.tsx +22 -0
  58. package/src/components/carousel/CarouselIndicators.tsx +44 -0
  59. package/src/components/carousel/CarouselInner.tsx +56 -0
  60. package/src/components/carousel/CarouselItem.tsx +51 -0
  61. package/src/components/carousel/CarouselOverlay.tsx +51 -0
  62. package/src/components/carousel/CarouselPlayPause.tsx +61 -0
  63. package/src/components/carousel/carouselEngine.ts +170 -0
  64. package/src/components/carousel/context.ts +29 -0
  65. package/src/components/carousel/index.ts +21 -0
  66. package/src/components/checkbox/Checkbox.tsx +236 -0
  67. package/src/components/checkbox/CheckboxGroup.tsx +145 -0
  68. package/src/components/checkbox/context.ts +14 -0
  69. package/src/components/checkbox/index.ts +4 -0
  70. package/src/components/chip/Chip.tsx +183 -0
  71. package/src/components/chip/index.ts +2 -0
  72. package/src/components/chip-input/ChipInput.tsx +363 -0
  73. package/src/components/chip-input/ChipList.tsx +95 -0
  74. package/src/components/chip-input/index.ts +2 -0
  75. package/src/components/close-button/CloseButton.tsx +205 -0
  76. package/src/components/close-button/index.ts +2 -0
  77. package/src/components/collapse/Collapse.tsx +128 -0
  78. package/src/components/collapse/index.ts +2 -0
  79. package/src/components/color-input/ColorInput.tsx +106 -0
  80. package/src/components/color-input/index.ts +2 -0
  81. package/src/components/combobox/Combobox.tsx +320 -0
  82. package/src/components/combobox/ComboboxGroup.tsx +27 -0
  83. package/src/components/combobox/ComboboxItem.tsx +44 -0
  84. package/src/components/combobox/ComboboxListBox.tsx +98 -0
  85. package/src/components/combobox/index.ts +6 -0
  86. package/src/components/datepicker/CalendarToggleButton.tsx +58 -0
  87. package/src/components/datepicker/ClearButton.tsx +35 -0
  88. package/src/components/datepicker/DateField.tsx +54 -0
  89. package/src/components/datepicker/DatePicker.scss +51 -0
  90. package/src/components/datepicker/DatePicker.tsx +574 -0
  91. package/src/components/datepicker/DateRangePicker.scss +11 -0
  92. package/src/components/datepicker/DateRangePicker.tsx +343 -0
  93. package/src/components/datepicker/index.ts +4 -0
  94. package/src/components/datepicker/renderDatePickerShell.tsx +104 -0
  95. package/src/components/drawer/Drawer.tsx +211 -0
  96. package/src/components/drawer/DrawerBody.tsx +47 -0
  97. package/src/components/drawer/DrawerFooter.tsx +51 -0
  98. package/src/components/drawer/DrawerHeader.tsx +60 -0
  99. package/src/components/drawer/DrawerTitle.tsx +49 -0
  100. package/src/components/drawer/index.ts +10 -0
  101. package/src/components/file-input/FileInput.tsx +100 -0
  102. package/src/components/file-input/index.ts +2 -0
  103. package/src/components/flex/Flex.tsx +134 -0
  104. package/src/components/flex/index.ts +2 -0
  105. package/src/components/floating-input/FloatingInput.tsx +94 -0
  106. package/src/components/floating-input/index.ts +2 -0
  107. package/src/components/form/Form.tsx +26 -0
  108. package/src/components/form/FormFeedback.tsx +64 -0
  109. package/src/components/form/FormHelp.tsx +46 -0
  110. package/src/components/form/FormLabel.tsx +26 -0
  111. package/src/components/form/index.ts +8 -0
  112. package/src/components/form/renderFormCheck.tsx +86 -0
  113. package/src/components/form-field/FormField.tsx +84 -0
  114. package/src/components/form-field/index.ts +2 -0
  115. package/src/components/form-field/renderFormField.tsx +74 -0
  116. package/src/components/grid/Col.tsx +69 -0
  117. package/src/components/grid/Container.tsx +59 -0
  118. package/src/components/grid/Grid.tsx +86 -0
  119. package/src/components/grid/GridItem.tsx +123 -0
  120. package/src/components/grid/Row.tsx +71 -0
  121. package/src/components/grid/gap.ts +8 -0
  122. package/src/components/grid/index.ts +10 -0
  123. package/src/components/icon/Icon.tsx +74 -0
  124. package/src/components/icon/index.ts +2 -0
  125. package/src/components/input-adorn/InputAdorn.tsx +55 -0
  126. package/src/components/input-adorn/index.ts +2 -0
  127. package/src/components/input-group/InputGroup.tsx +50 -0
  128. package/src/components/input-group/InputGroupAddon.tsx +50 -0
  129. package/src/components/input-group/index.ts +4 -0
  130. package/src/components/link/Link.tsx +148 -0
  131. package/src/components/link/index.ts +2 -0
  132. package/src/components/list/List.tsx +193 -0
  133. package/src/components/list/ListItem.tsx +91 -0
  134. package/src/components/list/index.ts +4 -0
  135. package/src/components/menu/Menu.tsx +398 -0
  136. package/src/components/menu/MenuDivider.tsx +19 -0
  137. package/src/components/menu/MenuHeader.tsx +55 -0
  138. package/src/components/menu/MenuItem.tsx +85 -0
  139. package/src/components/menu/MenuItemDef.ts +83 -0
  140. package/src/components/menu/MenuList.tsx +142 -0
  141. package/src/components/menu/MenuSubmenu.tsx +284 -0
  142. package/src/components/menu/MenuSubmenuBack.tsx +39 -0
  143. package/src/components/menu/MenuText.tsx +47 -0
  144. package/src/components/menu/MenuToggle.tsx +148 -0
  145. package/src/components/menu/index.ts +19 -0
  146. package/src/components/menu/menuNavigation.ts +106 -0
  147. package/src/components/menu/submenuGroup.ts +43 -0
  148. package/src/components/modal/Modal.tsx +162 -0
  149. package/src/components/modal/ModalBody.tsx +47 -0
  150. package/src/components/modal/ModalFooter.tsx +51 -0
  151. package/src/components/modal/ModalHeader.tsx +60 -0
  152. package/src/components/modal/ModalTitle.tsx +49 -0
  153. package/src/components/modal/index.ts +10 -0
  154. package/src/components/nav/Nav.tsx +97 -0
  155. package/src/components/nav/NavItem.tsx +42 -0
  156. package/src/components/nav/NavLink.tsx +48 -0
  157. package/src/components/nav/NavTitle.tsx +22 -0
  158. package/src/components/nav/index.ts +7 -0
  159. package/src/components/navbar/Navbar.tsx +112 -0
  160. package/src/components/navbar/NavbarBrand.tsx +55 -0
  161. package/src/components/navbar/NavbarNav.tsx +47 -0
  162. package/src/components/navbar/NavbarText.tsx +47 -0
  163. package/src/components/navbar/NavbarToggler.tsx +34 -0
  164. package/src/components/navbar/index.ts +10 -0
  165. package/src/components/notification/Notification.scss +15 -0
  166. package/src/components/notification/Notification.tsx +187 -0
  167. package/src/components/notification/NotificationIcon.tsx +21 -0
  168. package/src/components/notification/NotificationStack.tsx +66 -0
  169. package/src/components/notification/NotificationText.tsx +47 -0
  170. package/src/components/notification/NotificationTitle.tsx +47 -0
  171. package/src/components/notification/context.ts +14 -0
  172. package/src/components/notification/index.ts +17 -0
  173. package/src/components/notification/notificationQueue.ts +48 -0
  174. package/src/components/otp-input/OtpBox.tsx +68 -0
  175. package/src/components/otp-input/OtpInput.tsx +341 -0
  176. package/src/components/otp-input/index.ts +2 -0
  177. package/src/components/pagination/Pagination.tsx +199 -0
  178. package/src/components/pagination/PaginationItem.tsx +133 -0
  179. package/src/components/pagination/index.ts +4 -0
  180. package/src/components/password-strength/PasswordStrength.tsx +155 -0
  181. package/src/components/password-strength/index.ts +2 -0
  182. package/src/components/password-strength/strengthScore.ts +79 -0
  183. package/src/components/placeholder/Placeholder.tsx +178 -0
  184. package/src/components/placeholder/index.ts +2 -0
  185. package/src/components/popover/Popover.tsx +243 -0
  186. package/src/components/popover/index.ts +2 -0
  187. package/src/components/progress/Progress.tsx +154 -0
  188. package/src/components/progress/ProgressBar.tsx +88 -0
  189. package/src/components/progress/index.ts +4 -0
  190. package/src/components/radio/Radio.tsx +88 -0
  191. package/src/components/radio/RadioGroup.tsx +142 -0
  192. package/src/components/radio/context.ts +14 -0
  193. package/src/components/radio/index.ts +4 -0
  194. package/src/components/range-input/RangeInput.tsx +109 -0
  195. package/src/components/range-input/index.ts +2 -0
  196. package/src/components/select/Select.tsx +277 -0
  197. package/src/components/select/index.ts +2 -0
  198. package/src/components/skeleton/Skeleton.tsx +89 -0
  199. package/src/components/skeleton/SkeletonLoader.tsx +91 -0
  200. package/src/components/skeleton/index.ts +4 -0
  201. package/src/components/spinner/Spinner.tsx +77 -0
  202. package/src/components/spinner/index.ts +2 -0
  203. package/src/components/stack/Stack.tsx +81 -0
  204. package/src/components/stack/index.ts +2 -0
  205. package/src/components/stepper/Stepper.tsx +172 -0
  206. package/src/components/stepper/StepperItem.tsx +66 -0
  207. package/src/components/stepper/index.ts +4 -0
  208. package/src/components/switch/Switch.tsx +246 -0
  209. package/src/components/switch/index.ts +2 -0
  210. package/src/components/table/Table.css +39 -0
  211. package/src/components/table/Table.tsx +390 -0
  212. package/src/components/table/TableBody.tsx +25 -0
  213. package/src/components/table/TableCell.tsx +27 -0
  214. package/src/components/table/TableColumn.tsx +31 -0
  215. package/src/components/table/TableHeader.tsx +26 -0
  216. package/src/components/table/TableRow.tsx +24 -0
  217. package/src/components/table/index.ts +12 -0
  218. package/src/components/tabs/Tab.tsx +36 -0
  219. package/src/components/tabs/TabList.tsx +76 -0
  220. package/src/components/tabs/TabPanel.tsx +68 -0
  221. package/src/components/tabs/Tabs.tsx +136 -0
  222. package/src/components/tabs/context.ts +24 -0
  223. package/src/components/tabs/index.ts +8 -0
  224. package/src/components/text-input/TextInput.tsx +188 -0
  225. package/src/components/text-input/index.ts +2 -0
  226. package/src/components/textarea/Textarea.tsx +145 -0
  227. package/src/components/textarea/index.ts +2 -0
  228. package/src/components/toast/Toast.tsx +219 -0
  229. package/src/components/toast/ToastBody.tsx +64 -0
  230. package/src/components/toast/ToastFooter.tsx +47 -0
  231. package/src/components/toast/ToastHeader.tsx +104 -0
  232. package/src/components/toast/ToastIcon.tsx +21 -0
  233. package/src/components/toast/Toaster.tsx +87 -0
  234. package/src/components/toast/context.ts +14 -0
  235. package/src/components/toast/index.ts +18 -0
  236. package/src/components/toast/toastQueue.ts +56 -0
  237. package/src/components/tooltip/Tooltip.tsx +155 -0
  238. package/src/components/tooltip/index.ts +2 -0
  239. package/src/global.d.ts +2 -0
  240. package/src/hooks/index.ts +57 -0
  241. package/src/hooks/useAutoDismiss.ts +88 -0
  242. package/src/hooks/useButtonSemantics.ts +33 -0
  243. package/src/hooks/useControllableState.ts +24 -0
  244. package/src/hooks/useDialogElement.ts +216 -0
  245. package/src/hooks/useDisabledAnchorGuard.ts +22 -0
  246. package/src/hooks/useDismissibleTransition.ts +126 -0
  247. package/src/hooks/useDrawer.ts +9 -0
  248. package/src/hooks/useFloatingOverlay.ts +115 -0
  249. package/src/hooks/useForkedRef.ts +50 -0
  250. package/src/hooks/useFormField.ts +89 -0
  251. package/src/hooks/useIsomorphicLayoutEffect.ts +9 -0
  252. package/src/hooks/useModal.ts +9 -0
  253. package/src/hooks/useNotification.ts +10 -0
  254. package/src/hooks/useOverlayPlacement.ts +93 -0
  255. package/src/hooks/usePagination.ts +85 -0
  256. package/src/hooks/useToast.ts +9 -0
  257. package/src/hooks/useToastRegionQueue.ts +27 -0
  258. package/src/index.ts +276 -0
  259. package/src/types.ts +133 -0
  260. package/src/utils/breakpoints.ts +43 -0
  261. package/src/utils/comboboxCollection.tsx +152 -0
  262. package/src/utils/dateRangePresets.ts +6 -0
  263. package/src/utils/dialogTransition.ts +39 -0
  264. package/src/utils/focusRedirect.ts +29 -0
  265. package/src/utils/isWholeUnitDisabled.ts +17 -0
  266. package/src/utils/mergeIsDateUnavailable.ts +15 -0
  267. package/src/utils/overlayPlacement.ts +93 -0
  268. package/src/utils/pointerInteraction.ts +21 -0
  269. package/src/utils/polymorphic.ts +36 -0
  270. package/src/utils/renderMenuItemContent.tsx +62 -0
  271. package/src/utils/setVisibleRangeStart.ts +64 -0
  272. package/src/utils/spacingClassName.ts +14 -0
  273. package/src/utils/suppressFocusRingGlobally.ts +69 -0
  274. package/src/utils/validationClassName.ts +8 -0
  275. package/src/utils/virtualFocusStyle.ts +16 -0
@@ -0,0 +1,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,4 @@
1
+ export { Checkbox } from './Checkbox'
2
+ export type { CheckboxProps, ButtonObject } from './Checkbox'
3
+ export { CheckboxGroup } from './CheckboxGroup'
4
+ export type { CheckboxGroupProps } from './CheckboxGroup'
@@ -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
+ )
@@ -0,0 +1,2 @@
1
+ export { Chip } from './Chip'
2
+ export type { ChipProps } from './Chip'