@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,86 @@
1
+ import React, { ReactNode } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { ContextColor, Shapes } from '../../types'
5
+ import { validationClassName } from '../../utils/validationClassName'
6
+
7
+ import { FormLabel } from './FormLabel'
8
+
9
+ export type ButtonObject = {
10
+ /**
11
+ * Sets the color of the component to one of Chassis context colors.
12
+ */
13
+ color?: ContextColor
14
+ /**
15
+ * Select the shape of the component.
16
+ */
17
+ shape?: Shapes
18
+ /**
19
+ * Size the component small or large.
20
+ */
21
+ size?: 'small' | 'large'
22
+ /**
23
+ * Set the button variant to an outlined button or a ghost button.
24
+ */
25
+ variant?: 'outline' | 'ghost'
26
+ }
27
+
28
+ export interface RenderFormCheckOptions {
29
+ button?: ButtonObject
30
+ className?: string
31
+ color?: ContextColor
32
+ input: ReactNode
33
+ invalid?: boolean
34
+ label?: ReactNode
35
+ size?: 'small' | 'large'
36
+ valid?: boolean
37
+ }
38
+
39
+ // Shared nested, modern `.form-check`/`.check-input` markup for Checkbox and Radio —
40
+ // see https://chassis-ui.com/css/docs/forms/checkbox-radio/#modern-inputs. Everything renders
41
+ // nested inside a single <label> (or a bare <span class="check-input"> when there's no label);
42
+ // there is no sibling/`for`-linked layout.
43
+ export const renderFormCheck = ({
44
+ button,
45
+ className,
46
+ color,
47
+ input,
48
+ invalid,
49
+ label,
50
+ size,
51
+ valid
52
+ }: RenderFormCheckOptions) => {
53
+ if (button) {
54
+ const _className = classNames(
55
+ 'button',
56
+ 'button-check',
57
+ button.color,
58
+ button.variant,
59
+ button.size,
60
+ button.shape,
61
+ validationClassName(invalid, valid),
62
+ className
63
+ )
64
+ return (
65
+ <FormLabel customClassName={_className}>
66
+ {input}
67
+ {label}
68
+ </FormLabel>
69
+ )
70
+ }
71
+
72
+ const checkInputClassName = classNames('check-input', color, validationClassName(invalid, valid))
73
+
74
+ if (!label) {
75
+ return <span className={checkInputClassName}>{input}</span>
76
+ }
77
+
78
+ const _className = classNames('form-check', size, validationClassName(invalid, valid), className)
79
+
80
+ return (
81
+ <FormLabel customClassName={_className}>
82
+ <span className={checkInputClassName}>{input}</span>
83
+ {label}
84
+ </FormLabel>
85
+ )
86
+ }
@@ -0,0 +1,84 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ import { FormFieldIds, renderFormField } from './renderFormField'
4
+
5
+ export interface FormFieldProps {
6
+ /**
7
+ * The form control(s) this field wraps.
8
+ */
9
+ children: ReactNode
10
+ /**
11
+ * A string of all className you want applied to the component.
12
+ */
13
+ className?: string
14
+ /**
15
+ * A description for the field, rendered below the control(s).
16
+ */
17
+ help?: ReactNode
18
+ /**
19
+ * The DOM ids of the wrapped control(s), used to associate the label (`htmlFor`) and
20
+ * point your control's own `aria-describedby` at the rendered help/feedback text.
21
+ */
22
+ ids?: FormFieldIds
23
+ /**
24
+ * Set field validation state to invalid.
25
+ */
26
+ invalid?: boolean
27
+ /**
28
+ * An error message for the field, rendered below the control(s) when `invalid` is set.
29
+ */
30
+ invalidFeedback?: ReactNode
31
+ /**
32
+ * The field's caption, rendered as a `FormLabel` associated with `ids.input`.
33
+ */
34
+ label?: ReactNode
35
+ /**
36
+ * Set field validation state to valid.
37
+ */
38
+ valid?: boolean
39
+ /**
40
+ * A success message for the field, rendered below the control(s) when `valid` is set.
41
+ */
42
+ validFeedback?: ReactNode
43
+ }
44
+
45
+ // Standalone `.form-field` wrapper for the "wrap this yourself" case: a control with no field
46
+ // props of its own, or grouping more than one element under one label (e.g. an input plus a
47
+ // sibling status meter). Combobox/DatePicker/ChipInput/OtpInput do NOT use this — they
48
+ // call `renderFormField` directly and expose label/help/validFeedback/invalidFeedback as their
49
+ // own props (see FORMS.md). Wrapping any of those 10 components in `FormField` produces a
50
+ // nested, empty `.form-field` div — see FORMS.md's Gotchas section.
51
+ export const FormField = ({
52
+ children,
53
+ className,
54
+ help,
55
+ ids,
56
+ invalid,
57
+ invalidFeedback,
58
+ label,
59
+ valid,
60
+ validFeedback
61
+ }: FormFieldProps): ReactNode => {
62
+ if (label && !ids?.input && !ids?.label) {
63
+ console.warn(
64
+ 'FormField: `label` is set but `ids.input`/`ids.label` are not — the rendered label ' +
65
+ "won't be associated with your control (no `htmlFor`, no `aria-labelledby` target). " +
66
+ 'Pass `ids={{ input: yourControlId }}` (or `ids.label` for a group with no single ' +
67
+ 'input) so the label has something to point at.'
68
+ )
69
+ }
70
+
71
+ return renderFormField({
72
+ children,
73
+ className,
74
+ help,
75
+ ids,
76
+ invalid,
77
+ invalidFeedback,
78
+ label,
79
+ valid,
80
+ validFeedback
81
+ })
82
+ }
83
+
84
+ FormField.displayName = 'FormField'
@@ -0,0 +1,2 @@
1
+ export { FormField } from './FormField'
2
+ export type { FormFieldProps } from './FormField'
@@ -0,0 +1,74 @@
1
+ import React, { ReactNode } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { FormFeedback } from '../form/FormFeedback'
5
+ import { FormHelp } from '../form/FormHelp'
6
+ import { FormLabel } from '../form/FormLabel'
7
+
8
+ export interface FormFieldIds {
9
+ feedback?: string
10
+ help?: string
11
+ input?: string
12
+ /**
13
+ * The label's own id, for controls with no single input to associate via `htmlFor` (e.g. a
14
+ * multi-input group) — reference it from the control's own `aria-labelledby` instead.
15
+ */
16
+ label?: string
17
+ }
18
+
19
+ export interface RenderFormFieldOptions {
20
+ children: ReactNode
21
+ className?: string
22
+ help?: ReactNode
23
+ ids?: FormFieldIds
24
+ invalid?: boolean
25
+ invalidFeedback?: ReactNode
26
+ label?: ReactNode
27
+ valid?: boolean
28
+ validFeedback?: ReactNode
29
+ }
30
+
31
+ // Shared `.form-field` grid wrapper for label/control/help/feedback — see
32
+ // https://chassis-ui.com/css/docs/forms/form-field. Renders `children` bare when none of
33
+ // label/help/validFeedback/invalidFeedback are set, so field-capable leaves stay drop-in
34
+ // compatible until a consumer opts into the wrapping.
35
+ export const renderFormField = ({
36
+ children,
37
+ className,
38
+ help,
39
+ ids = {},
40
+ invalid,
41
+ invalidFeedback,
42
+ label,
43
+ valid,
44
+ validFeedback
45
+ }: RenderFormFieldOptions): ReactNode => {
46
+ const showInvalidFeedback = invalid && invalidFeedback
47
+ const showValidFeedback = valid && validFeedback
48
+
49
+ if (!label && !help && !showInvalidFeedback && !showValidFeedback) {
50
+ return children
51
+ }
52
+
53
+ return (
54
+ <div className={classNames('form-field', className)}>
55
+ {label && (
56
+ <FormLabel htmlFor={ids.input} id={ids.label}>
57
+ {label}
58
+ </FormLabel>
59
+ )}
60
+ {children}
61
+ {help && <FormHelp id={ids.help}>{help}</FormHelp>}
62
+ {showInvalidFeedback && (
63
+ <FormFeedback id={ids.feedback} invalid>
64
+ {invalidFeedback}
65
+ </FormFeedback>
66
+ )}
67
+ {showValidFeedback && (
68
+ <FormFeedback id={ids.feedback} valid>
69
+ {validFeedback}
70
+ </FormFeedback>
71
+ )}
72
+ </div>
73
+ )
74
+ }
@@ -0,0 +1,69 @@
1
+ import React, { forwardRef, HTMLAttributes } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { Span, buildResponsiveClassNames } from '../../utils/breakpoints'
5
+ import { Breakpoint } from '../../types'
6
+
7
+ export interface ColLayout {
8
+ /**
9
+ * Columns (of 12) this Col spans, or `'auto'`/`true` for a natural-width column.
10
+ *
11
+ * @type { 'auto' | number | string | boolean }
12
+ */
13
+ span?: Span
14
+ /**
15
+ * Columns to offset the start of this Col by.
16
+ */
17
+ offset?: number | string
18
+ /**
19
+ * Visual order relative to sibling columns.
20
+ *
21
+ * @type { 'first' | 'last' | number | string }
22
+ */
23
+ order?: 'first' | 'last' | number | string
24
+ }
25
+
26
+ export interface ColProps extends HTMLAttributes<HTMLDivElement>, ColLayout {
27
+ /**
28
+ * A string of all className you want applied to the base component.
29
+ */
30
+ className?: string
31
+ /**
32
+ * Overrides `span`/`offset`/`order` at a breakpoint and up.
33
+ *
34
+ * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { span?: 'auto' | number | string | boolean, offset?: number | string, order?: 'first' | 'last' | number | string }>> }
35
+ */
36
+ responsive?: Partial<Record<Breakpoint, ColLayout>>
37
+ }
38
+
39
+ const layoutClassNames = ({ span, offset, order }: ColLayout, prefix: string) => [
40
+ typeof span === 'number' || typeof span === 'string' ? `${prefix}col-${span}` : null,
41
+ span === true ? `${prefix}col` : null,
42
+ typeof offset === 'number' || typeof offset === 'string' ? `${prefix}offset-${offset}` : null,
43
+ typeof order === 'number' || typeof order === 'string' ? `${prefix}order-${order}` : null
44
+ ]
45
+
46
+ export const Col = forwardRef<HTMLDivElement, ColProps>(
47
+ ({ children, className, span, offset, order, responsive, ...rest }, ref) => {
48
+ const layoutClassList = buildResponsiveClassNames(
49
+ layoutClassNames,
50
+ { span, offset, order },
51
+ responsive
52
+ )
53
+
54
+ // The base breakpoint only gets its equal-width flex sizing from an explicit `span` (base or
55
+ // `col`'s own class) or from the fallback bare `col` below — `offset`/`order` alone (or a
56
+ // `span` set only inside `responsive`) don't provide it, so without this the column would
57
+ // render at `flex: 0 1 auto` instead of a proper flexible column below the first tier that
58
+ // sets a span.
59
+ const _className = classNames(span === undefined && 'col', layoutClassList, className)
60
+
61
+ return (
62
+ <div className={_className} {...rest} ref={ref}>
63
+ {children}
64
+ </div>
65
+ )
66
+ }
67
+ )
68
+
69
+ Col.displayName = 'Col'
@@ -0,0 +1,59 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import {
5
+ createPolymorphicComponent,
6
+ PolymorphicComponentProps,
7
+ PolymorphicRef
8
+ } from '../../utils/polymorphic'
9
+ import { Breakpoint } from '../../types'
10
+
11
+ type ContainerOwnProps<C extends ElementType> = {
12
+ /**
13
+ * A string of all className you want applied to the base component.
14
+ */
15
+ className?: string
16
+ /**
17
+ * Component used for the root node. Either a string to use a HTML element or a component.
18
+ */
19
+ component?: C
20
+ /**
21
+ * Set container 100% wide until the given breakpoint, after which it scales up with `max-width`
22
+ * at every larger breakpoint.
23
+ *
24
+ * @type Breakpoint
25
+ */
26
+ fluidUntil?: Breakpoint
27
+ /**
28
+ * Set container 100% wide, spanning the entire width of the viewport.
29
+ */
30
+ fluid?: boolean
31
+ }
32
+
33
+ export type ContainerProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
34
+ C,
35
+ ContainerOwnProps<C>
36
+ >
37
+
38
+ type ContainerComponent = (<C extends ElementType = 'div'>(
39
+ props: ContainerProps<C> & { ref?: PolymorphicRef<C> }
40
+ ) => ReactElement | null) & { displayName?: string }
41
+
42
+ function ContainerRender<C extends ElementType = 'div'>(
43
+ { children, className, component, fluidUntil, fluid, ...rest }: ContainerProps<C>,
44
+ ref: PolymorphicRef<C>
45
+ ) {
46
+ const Component = component ?? 'div'
47
+ const _className = classNames('container', fluid && 'fluid', fluidUntil, className)
48
+
49
+ return (
50
+ <Component className={_className} {...rest} ref={ref}>
51
+ {children}
52
+ </Component>
53
+ )
54
+ }
55
+
56
+ export const Container = createPolymorphicComponent<ContainerComponent>(
57
+ ContainerRender as ForwardRefRenderFunction<Element, ContainerProps<ElementType>>,
58
+ 'Container'
59
+ )
@@ -0,0 +1,86 @@
1
+ import React, { CSSProperties, ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import {
5
+ createPolymorphicComponent,
6
+ PolymorphicComponentProps,
7
+ PolymorphicRef
8
+ } from '../../utils/polymorphic'
9
+ import { Spacing } from '../../types'
10
+ import { resolveGap } from './gap'
11
+
12
+ type GridStyle = CSSProperties & {
13
+ '--cx-grid-columns'?: number
14
+ '--cx-grid-rows'?: number
15
+ '--cx-grid-gap'?: string
16
+ '--cx-gap'?: string
17
+ }
18
+
19
+ type GridOwnProps<C extends ElementType> = {
20
+ /**
21
+ * A string of all className you want applied to the base component.
22
+ */
23
+ className?: string
24
+ /**
25
+ * Component used for the root node. Either a string to use a HTML element or a component.
26
+ */
27
+ component?: C
28
+ /**
29
+ * Number of columns in the grid template, set via the `--cx-grid-columns` custom property
30
+ * (defaults to `12` in CSS when omitted). Has no effect when `fill` is set.
31
+ */
32
+ columns?: number
33
+ /**
34
+ * Number of rows in the grid template, set via the `--cx-grid-rows` custom property (defaults
35
+ * to `1` in CSS when omitted). Has no effect when `fill` is set.
36
+ */
37
+ rows?: number
38
+ /**
39
+ * Gap between grid items, set via the `--cx-grid-gap` custom property (or `--cx-gap` when
40
+ * `fill` is set). Accepts a `Spacing` token (mapped to the matching `--cx-space-*` custom
41
+ * property) or any raw CSS `gap` value, including a `"{row} {column}"` pair.
42
+ *
43
+ * @type { Spacing | string }
44
+ */
45
+ gap?: Spacing | (string & {})
46
+ /**
47
+ * Renders `.grid-fill` instead of `.grid` — columns expand equally to fill the available
48
+ * width, with the column count determined by the number of children rather than `columns`.
49
+ */
50
+ fill?: boolean
51
+ }
52
+
53
+ export type GridProps<C extends ElementType = 'div'> = PolymorphicComponentProps<C, GridOwnProps<C>>
54
+
55
+ type GridComponent = (<C extends ElementType = 'div'>(
56
+ props: GridProps<C> & { ref?: PolymorphicRef<C> }
57
+ ) => ReactElement | null) & { displayName?: string }
58
+
59
+ function GridRender<C extends ElementType = 'div'>(
60
+ { children, className, component, columns, rows, gap, fill, style, ...rest }: GridProps<C>,
61
+ ref: PolymorphicRef<C>
62
+ ) {
63
+ const Component = component ?? 'div'
64
+ const _className = classNames(fill ? 'grid-fill' : 'grid', className)
65
+
66
+ const _style: GridStyle = { ...style }
67
+
68
+ if (fill) {
69
+ if (gap !== undefined) _style['--cx-gap'] = resolveGap(gap)
70
+ } else {
71
+ if (columns !== undefined) _style['--cx-grid-columns'] = columns
72
+ if (rows !== undefined) _style['--cx-grid-rows'] = rows
73
+ if (gap !== undefined) _style['--cx-grid-gap'] = resolveGap(gap)
74
+ }
75
+
76
+ return (
77
+ <Component className={_className} style={_style} {...rest} ref={ref}>
78
+ {children}
79
+ </Component>
80
+ )
81
+ }
82
+
83
+ export const Grid = createPolymorphicComponent<GridComponent>(
84
+ GridRender as ForwardRefRenderFunction<Element, GridProps<ElementType>>,
85
+ 'Grid'
86
+ )
@@ -0,0 +1,123 @@
1
+ import React, { CSSProperties, ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import {
5
+ createPolymorphicComponent,
6
+ PolymorphicComponentProps,
7
+ PolymorphicRef
8
+ } from '../../utils/polymorphic'
9
+ import { buildResponsiveClassNames } from '../../utils/breakpoints'
10
+ import { Breakpoint, Spacing } from '../../types'
11
+ import { resolveGap } from './gap'
12
+
13
+ type GridItemStyle = CSSProperties & {
14
+ '--cx-grid-rows'?: number
15
+ '--cx-grid-gap'?: string
16
+ }
17
+
18
+ export interface GridItemLayout {
19
+ /**
20
+ * Number of grid column tracks (of the parent `<Grid>`'s `columns`) this item spans, mapped to
21
+ * the `g-col-{n}` class.
22
+ */
23
+ span?: number
24
+ /**
25
+ * Grid column line to start this item at, mapped to the `g-start-{n}` class.
26
+ */
27
+ start?: number
28
+ }
29
+
30
+ type GridItemOwnProps<C extends ElementType> = GridItemLayout & {
31
+ /**
32
+ * A string of all className you want applied to the base component.
33
+ */
34
+ className?: string
35
+ /**
36
+ * Component used for the root node. Either a string to use a HTML element or a component.
37
+ */
38
+ component?: C
39
+ /**
40
+ * Overrides `span`/`start` at a breakpoint and up.
41
+ *
42
+ * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { span?: number, start?: number }>> }
43
+ */
44
+ responsive?: Partial<Record<Breakpoint, GridItemLayout>>
45
+ /**
46
+ * Turns this item into a nested subgrid: adds `.grid`/`.grid-cols-subgrid` alongside its
47
+ * `g-col-{n}`/`g-start-{n}` placement classes, so its own children inherit the parent
48
+ * `<Grid>`'s column tracks instead of defining new ones. Combine with `span` — a subgrid
49
+ * item must itself be a grid item of the parent for `grid-template-columns: subgrid` to take
50
+ * effect, which is why `subgrid` lives on `<GridItem>` (the element actually placed as a grid
51
+ * item) rather than on a `<Grid>` nested inside it.
52
+ */
53
+ subgrid?: boolean
54
+ /**
55
+ * Number of rows in the subgrid's own row template, set via the `--cx-grid-rows` custom
56
+ * property (defaults to `1` in CSS when omitted). Only relevant when `subgrid` is set —
57
+ * subgrid only inherits the parent's column tracks, not its rows.
58
+ */
59
+ rows?: number
60
+ /**
61
+ * Gap between the subgrid's children, set via the `--cx-grid-gap` custom property. Only
62
+ * relevant when `subgrid` is set — a subgrid doesn't inherit its parent's gap.
63
+ *
64
+ * @type { Spacing | string }
65
+ */
66
+ gap?: Spacing | (string & {})
67
+ }
68
+
69
+ export type GridItemProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
70
+ C,
71
+ GridItemOwnProps<C>
72
+ >
73
+
74
+ type GridItemComponent = (<C extends ElementType = 'div'>(
75
+ props: GridItemProps<C> & { ref?: PolymorphicRef<C> }
76
+ ) => ReactElement | null) & { displayName?: string }
77
+
78
+ const layoutClassNames = ({ span, start }: GridItemLayout, prefix: string) => [
79
+ typeof span === 'number' ? `${prefix}g-col-${span}` : null,
80
+ typeof start === 'number' ? `${prefix}g-start-${start}` : null
81
+ ]
82
+
83
+ function GridItemRender<C extends ElementType = 'div'>(
84
+ {
85
+ children,
86
+ className,
87
+ component,
88
+ span,
89
+ start,
90
+ responsive,
91
+ subgrid,
92
+ rows,
93
+ gap,
94
+ style,
95
+ ...rest
96
+ }: GridItemProps<C>,
97
+ ref: PolymorphicRef<C>
98
+ ) {
99
+ const Component = component ?? 'div'
100
+ const _className = classNames(
101
+ buildResponsiveClassNames(layoutClassNames, { span, start }, responsive),
102
+ subgrid && 'grid grid-cols-subgrid',
103
+ className
104
+ )
105
+
106
+ const _style: GridItemStyle = { ...style }
107
+
108
+ if (subgrid) {
109
+ if (rows !== undefined) _style['--cx-grid-rows'] = rows
110
+ if (gap !== undefined) _style['--cx-grid-gap'] = resolveGap(gap)
111
+ }
112
+
113
+ return (
114
+ <Component className={_className} style={_style} {...rest} ref={ref}>
115
+ {children}
116
+ </Component>
117
+ )
118
+ }
119
+
120
+ export const GridItem = createPolymorphicComponent<GridItemComponent>(
121
+ GridItemRender as ForwardRefRenderFunction<Element, GridItemProps<ElementType>>,
122
+ 'GridItem'
123
+ )
@@ -0,0 +1,71 @@
1
+ import React, { forwardRef, HTMLAttributes } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { buildResponsiveClassNames } from '../../utils/breakpoints'
5
+ import { spacingClassName } from '../../utils/spacingClassName'
6
+ import { Breakpoint, Spacing } from '../../types'
7
+
8
+ export interface RowLayout {
9
+ /**
10
+ * Equal-width columns per row, or `'auto'` for content-sized columns.
11
+ *
12
+ * @type { 'auto' | number | string }
13
+ */
14
+ cols?: 'auto' | number | string
15
+ /**
16
+ * Gutter width on both axes.
17
+ *
18
+ * @type { Spacing | 0 }
19
+ */
20
+ gutter?: Spacing | 0
21
+ /**
22
+ * Horizontal gutter width.
23
+ *
24
+ * @type { Spacing | 0 }
25
+ */
26
+ gutterX?: Spacing | 0
27
+ /**
28
+ * Vertical gutter width.
29
+ *
30
+ * @type { Spacing | 0 }
31
+ */
32
+ gutterY?: Spacing | 0
33
+ }
34
+
35
+ export interface RowProps extends HTMLAttributes<HTMLDivElement>, RowLayout {
36
+ /**
37
+ * A string of all className you want applied to the base component.
38
+ */
39
+ className?: string
40
+ /**
41
+ * Overrides `cols`/`gutter`/`gutterX`/`gutterY` at a breakpoint and up.
42
+ *
43
+ * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { cols?: 'auto' | number | string, gutter?: Spacing | 0, gutterX?: Spacing | 0, gutterY?: Spacing | 0 }>> }
44
+ */
45
+ responsive?: Partial<Record<Breakpoint, RowLayout>>
46
+ }
47
+
48
+ const layoutClassNames = ({ cols, gutter, gutterX, gutterY }: RowLayout, prefix: string) => [
49
+ cols ? `${prefix}row-cols-${cols}` : null,
50
+ spacingClassName('g', gutter, prefix),
51
+ spacingClassName('gx', gutterX, prefix),
52
+ spacingClassName('gy', gutterY, prefix)
53
+ ]
54
+
55
+ export const Row = forwardRef<HTMLDivElement, RowProps>(
56
+ ({ children, className, cols, gutter, gutterX, gutterY, responsive, ...rest }, ref) => {
57
+ const _className = classNames(
58
+ 'row',
59
+ buildResponsiveClassNames(layoutClassNames, { cols, gutter, gutterX, gutterY }, responsive),
60
+ className
61
+ )
62
+
63
+ return (
64
+ <div className={_className} {...rest} ref={ref}>
65
+ {children}
66
+ </div>
67
+ )
68
+ }
69
+ )
70
+
71
+ Row.displayName = 'Row'
@@ -0,0 +1,8 @@
1
+ import { SPACING } from '../../types'
2
+
3
+ const SPACING_TOKENS = new Set<string>(SPACING)
4
+
5
+ // `Spacing` tokens map to the matching `--cx-space-*` custom property; anything else (a raw CSS
6
+ // value, e.g. `"1rem"` or `".25rem 1rem"`) passes through unchanged.
7
+ export const resolveGap = (gap: string) =>
8
+ SPACING_TOKENS.has(gap) ? `var(--cx-space-${gap})` : gap
@@ -0,0 +1,10 @@
1
+ export { Col } from './Col'
2
+ export type { ColProps } from './Col'
3
+ export { Container } from './Container'
4
+ export type { ContainerProps } from './Container'
5
+ export { Grid } from './Grid'
6
+ export type { GridProps } from './Grid'
7
+ export { GridItem } from './GridItem'
8
+ export type { GridItemProps, GridItemLayout } from './GridItem'
9
+ export { Row } from './Row'
10
+ export type { RowProps } from './Row'