@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,27 @@
1
+ import { ReactElement, ReactNode } from 'react'
2
+ import { Cell } from 'react-stately'
3
+
4
+ export interface TableCellProps {
5
+ /**
6
+ * The contents of the cell.
7
+ */
8
+ children: ReactNode
9
+ /**
10
+ * A string of all className you want applied to the cell.
11
+ */
12
+ className?: string
13
+ /**
14
+ * Indicates how many columns the cell spans.
15
+ */
16
+ colSpan?: number
17
+ /**
18
+ * A string representation of the cell's contents, used for typeahead.
19
+ */
20
+ textValue?: string
21
+ }
22
+
23
+ /**
24
+ * Collection node, data-only — see `TableHeader`. Read by `Table` to build a cell in the
25
+ * table's collection; never rendered directly.
26
+ */
27
+ export const TableCell = Cell as unknown as (props: TableCellProps) => ReactElement
@@ -0,0 +1,31 @@
1
+ import { ReactElement, ReactNode } from 'react'
2
+ import { Column } from 'react-stately'
3
+
4
+ export interface TableColumnProps {
5
+ /**
6
+ * Whether the column allows sorting. Adds a sort indicator and makes the header
7
+ * clickable/keyboard-activatable.
8
+ */
9
+ allowsSorting?: boolean
10
+ /**
11
+ * Rendered contents of the column header.
12
+ */
13
+ children: ReactNode
14
+ /**
15
+ * A string of all className you want applied to the column header.
16
+ */
17
+ className?: string
18
+ /**
19
+ * A string representation of the column header, used for accessibility announcements and,
20
+ * when `Table`'s `stacked` prop is set, as the label shown before each row's value for this
21
+ * column. Defaults to `children` when it's a plain string — set this explicitly when the
22
+ * header contains anything else (an icon, a `Tooltip`, etc.).
23
+ */
24
+ textValue?: string
25
+ }
26
+
27
+ /**
28
+ * Collection node, data-only — see `TableHeader`. Read by `Table` to build a column in the
29
+ * table's collection; never rendered directly.
30
+ */
31
+ export const TableColumn = Column as unknown as (props: TableColumnProps) => ReactElement
@@ -0,0 +1,26 @@
1
+ import { ReactElement } from 'react'
2
+ import { TableHeader as StatelyTableHeader } from 'react-stately'
3
+
4
+ export interface TableHeaderProps<T> {
5
+ /**
6
+ * `TableColumn` elements, or a render function paired with `columns` for dynamic column
7
+ * generation.
8
+ */
9
+ children: ReactElement | ReactElement[] | ((column: T) => ReactElement)
10
+ /**
11
+ * A string of all className you want applied to the `<thead>` element.
12
+ */
13
+ className?: string
14
+ /**
15
+ * A list of column data objects, rendered via the function form of `children`.
16
+ */
17
+ columns?: readonly T[]
18
+ }
19
+
20
+ /**
21
+ * Collection node, data-only — read by `Table` to build the table's column collection. Never
22
+ * rendered directly.
23
+ */
24
+ export const TableHeader = StatelyTableHeader as unknown as <T>(
25
+ props: TableHeaderProps<T>
26
+ ) => ReactElement
@@ -0,0 +1,24 @@
1
+ import { Key, ReactElement } from 'react'
2
+ import { Row } from 'react-stately'
3
+
4
+ export interface TableRowProps {
5
+ /**
6
+ * `TableCell` elements, or a render function called once per column with that column's key —
7
+ * required when the row's parent `TableBody` uses the `items`/render-function form.
8
+ */
9
+ children: ReactElement | ReactElement[] | ((columnKey: Key) => ReactElement)
10
+ /**
11
+ * A string of all className you want applied to the row.
12
+ */
13
+ className?: string
14
+ /**
15
+ * A string representation of the row's contents, used for typeahead.
16
+ */
17
+ textValue?: string
18
+ }
19
+
20
+ /**
21
+ * Collection node, data-only — see `TableHeader`. Read by `Table` to build a row in the
22
+ * table's collection; never rendered directly.
23
+ */
24
+ export const TableRow = Row as unknown as (props: TableRowProps) => ReactElement
@@ -0,0 +1,12 @@
1
+ export { Table } from './Table'
2
+ export type { TableProps } from './Table'
3
+ export { TableBody } from './TableBody'
4
+ export type { TableBodyProps } from './TableBody'
5
+ export { TableCell } from './TableCell'
6
+ export type { TableCellProps } from './TableCell'
7
+ export { TableColumn } from './TableColumn'
8
+ export type { TableColumnProps } from './TableColumn'
9
+ export { TableHeader } from './TableHeader'
10
+ export type { TableHeaderProps } from './TableHeader'
11
+ export { TableRow } from './TableRow'
12
+ export type { TableRowProps } from './TableRow'
@@ -0,0 +1,36 @@
1
+ import { ReactNode } from 'react'
2
+ import { Key } from 'react-stately'
3
+
4
+ export interface TabProps {
5
+ /**
6
+ * Label content for the tab. Must be a plain string for it to be used as the tab's accessible
7
+ * name when the rendered content itself doesn't expose one (e.g. an icon-only tab).
8
+ */
9
+ children: ReactNode
10
+ /**
11
+ * Prevents the tab from being selected, focused, or otherwise interacted with.
12
+ */
13
+ disabled?: boolean
14
+ /**
15
+ * Identifies this tab and pairs it with the `TabPanel` of the same `id`.
16
+ */
17
+ id: Key
18
+ }
19
+
20
+ // `Tab` is never actually mounted — it's read as data by `Tabs`, which builds react-aria's tab
21
+ // collection from each `Tab`'s `id` and `children` (see `Tabs.tsx`), and `TabList` renders the
22
+ // real, focusable tab elements from that collection instead. This keeps the public authoring
23
+ // shape as plain composed JSX:
24
+ //
25
+ // <TabList aria-label="...">
26
+ // <Tab id="home">Home</Tab>
27
+ // <Tab id="profile">Profile</Tab>
28
+ // </TabList>
29
+ // <TabPanel id="home">...</TabPanel>
30
+ // <TabPanel id="profile">...</TabPanel>
31
+ //
32
+ // `id` (not React's own `key`) is what pairs a tab with its panel — still give each `Tab`/
33
+ // `TabPanel` a React `key` too, as you would for any list.
34
+ export const Tab = (_props: TabProps): null => null
35
+
36
+ Tab.displayName = 'Tab'
@@ -0,0 +1,76 @@
1
+ import React, { AriaAttributes, forwardRef, ReactElement, ReactNode, useRef } from 'react'
2
+ import classNames from 'classnames'
3
+ import { useTab, useTabList } from 'react-aria'
4
+ import { Node } from 'react-stately'
5
+
6
+ import { useForkedRef } from '../../hooks'
7
+ import { TabProps } from './Tab'
8
+ import { useTabsContext } from './context'
9
+
10
+ export interface TabListProps extends AriaAttributes {
11
+ /**
12
+ * `Tab` elements — read as data by `Tabs` to build the tab collection (see `Tabs.tsx`). Not
13
+ * rendered directly.
14
+ */
15
+ children: ReactNode
16
+ /**
17
+ * A string of all className you want applied to the base component.
18
+ */
19
+ className?: string
20
+ /**
21
+ * Set the tab list variant to tabs or pills.
22
+ */
23
+ variant?: 'tabs' | 'pills'
24
+ }
25
+
26
+ // Renders the actual, focusable tabs from `state.collection` — built by the ancestor `Tabs`
27
+ // from this component's own `children` (see the comment there). This component's own `children`
28
+ // prop is intentionally unused for rendering.
29
+ export const TabList = forwardRef<HTMLUListElement, TabListProps>(
30
+ ({ className, variant = 'tabs', ...rest }, ref) => {
31
+ const { keyboardActivation, orientation, state } = useTabsContext()
32
+ const listRef = useRef<HTMLUListElement>(null)
33
+ const forkedRef = useForkedRef(ref, listRef)
34
+ const { tabListProps } = useTabList(
35
+ { keyboardActivation, orientation, ...rest },
36
+ state,
37
+ listRef
38
+ )
39
+
40
+ const _className = classNames('nav', `nav-${variant}`, className)
41
+
42
+ return (
43
+ <ul className={_className} {...tabListProps} ref={forkedRef}>
44
+ {[...state.collection].map((item) => (
45
+ <TabItem key={item.key} item={item} />
46
+ ))}
47
+ </ul>
48
+ )
49
+ }
50
+ )
51
+
52
+ TabList.displayName = 'TabList'
53
+
54
+ interface TabItemProps {
55
+ item: Node<ReactElement<TabProps>>
56
+ }
57
+
58
+ const TabItem = ({ item }: TabItemProps) => {
59
+ const { state } = useTabsContext()
60
+ const ref = useRef<HTMLAnchorElement>(null)
61
+ const { tabProps, isSelected, isDisabled } = useTab({ key: item.key }, state, ref)
62
+
63
+ return (
64
+ // ARIA's `tablist` role only permits `tab`-role children — `role="presentation"` opts this
65
+ // `<li>` wrapper out without hiding its content, same pattern as `MenuHeader`.
66
+ <li className="nav-item" role="presentation">
67
+ <a
68
+ className={classNames('nav-link', { active: isSelected, disabled: isDisabled })}
69
+ {...tabProps}
70
+ ref={ref}
71
+ >
72
+ {item.rendered}
73
+ </a>
74
+ </li>
75
+ )
76
+ }
@@ -0,0 +1,68 @@
1
+ import React, { forwardRef, HTMLAttributes, ReactNode, useRef } from 'react'
2
+ import classNames from 'classnames'
3
+ import { useTabPanel } from 'react-aria'
4
+ import { Key } from 'react-stately'
5
+ import { Transition } from 'react-transition-group'
6
+
7
+ import { useForkedRef } from '../../hooks'
8
+ import { useTabsContext } from './context'
9
+
10
+ export interface TabPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id'> {
11
+ /**
12
+ * Content of the panel, shown while the `Tab` of the same `id` is selected.
13
+ */
14
+ children: ReactNode
15
+ /**
16
+ * A string of all className you want applied to the base component.
17
+ */
18
+ className?: string
19
+ /**
20
+ * Pairs this panel with the `Tab` of the same `id`.
21
+ */
22
+ id: Key
23
+ }
24
+
25
+ // The counterpart to `Tab` — unlike `Tab`, `TabPanel` renders for real, but only while its `id`
26
+ // matches the currently selected tab, and unmounts immediately (not staggered) once it isn't.
27
+ // chassis-css's `.tab-content > .tab-pane`/`.active` rule (`_transitions.scss`) assumes exactly
28
+ // one visible block-level panel at a time — there's no overlap/positioning support for a second
29
+ // panel to occupy the same space while it exits, so staggering the *outgoing* panel's removal
30
+ // would leave two full-height panels stacked in flow simultaneously and visibly shove the layout
31
+ // around. Only the *incoming* panel gets a transition: `Transition`'s `appear` flag makes its
32
+ // mount start a render frame without the `show` class — `fade` alone means `opacity: 0` —
33
+ // before adding it a tick later, so the panel fades in instead of popping straight to full
34
+ // opacity. `appear` is gated on `isInitialSelectionRef` so only an actual tab *switch* animates,
35
+ // not whichever panel happens to be selected on first paint.
36
+ export const TabPanel = forwardRef<HTMLDivElement, TabPanelProps>(
37
+ ({ children, className, id, ...rest }, ref) => {
38
+ const { state, isInitialSelectionRef } = useTabsContext()
39
+ const panelRef = useRef<HTMLDivElement>(null)
40
+ const forkedRef = useForkedRef(ref, panelRef)
41
+ const { tabPanelProps } = useTabPanel({ id }, state, panelRef)
42
+
43
+ if (state.selectedKey !== id) return null
44
+
45
+ return (
46
+ <Transition in appear={!isInitialSelectionRef.current} nodeRef={panelRef} timeout={150}>
47
+ {(transitionState) => (
48
+ <div
49
+ className={classNames(
50
+ 'tab-pane',
51
+ 'fade',
52
+ 'active',
53
+ { show: transitionState === 'entered' },
54
+ className
55
+ )}
56
+ {...tabPanelProps}
57
+ {...rest}
58
+ ref={forkedRef}
59
+ >
60
+ {children}
61
+ </div>
62
+ )}
63
+ </Transition>
64
+ )
65
+ }
66
+ )
67
+
68
+ TabPanel.displayName = 'TabPanel'
@@ -0,0 +1,136 @@
1
+ import React, {
2
+ ElementType,
3
+ ForwardRefRenderFunction,
4
+ ReactElement,
5
+ ReactNode,
6
+ useEffect,
7
+ useRef
8
+ } from 'react'
9
+ import { Item, Key, useTabListState } from 'react-stately'
10
+
11
+ import {
12
+ createPolymorphicComponent,
13
+ PolymorphicComponentProps,
14
+ PolymorphicRef
15
+ } from '../../utils/polymorphic'
16
+ import { TabProps } from './Tab'
17
+ import { TabList } from './TabList'
18
+ import { TabsContext } from './context'
19
+
20
+ type TabsOwnProps<C extends ElementType> = {
21
+ /**
22
+ * A `TabList` (containing `Tab` children) followed by one `TabPanel` per tab.
23
+ */
24
+ children?: ReactNode
25
+ /**
26
+ * A string of all className you want applied to the base component.
27
+ */
28
+ className?: string
29
+ /**
30
+ * Component used for the root node. Either a string to use a HTML element or a component.
31
+ */
32
+ component?: C
33
+ /**
34
+ * The initially selected tab's key (uncontrolled).
35
+ */
36
+ defaultSelectedKey?: Key
37
+ /**
38
+ * The keys of tabs that cannot be selected, focused, or otherwise interacted with.
39
+ */
40
+ disabledKeys?: Iterable<Key>
41
+ /**
42
+ * Whether tabs are selected automatically on arrow-key focus (`'automatic'`, the default) or
43
+ * only on explicit activation — Enter/Space or click (`'manual'`).
44
+ */
45
+ keyboardActivation?: 'automatic' | 'manual'
46
+ /**
47
+ * Callback fired when the selected tab changes.
48
+ */
49
+ onSelectionChange?: (key: Key) => void
50
+ /**
51
+ * The orientation of the tab list.
52
+ */
53
+ orientation?: 'horizontal' | 'vertical'
54
+ /**
55
+ * The selected tab's key (controlled).
56
+ */
57
+ selectedKey?: Key
58
+ }
59
+
60
+ export type TabsProps<C extends ElementType = 'div'> = PolymorphicComponentProps<C, TabsOwnProps<C>>
61
+
62
+ type TabsComponent = (<C extends ElementType = 'div'>(
63
+ props: TabsProps<C> & { ref?: PolymorphicRef<C> }
64
+ ) => ReactElement | null) & { displayName?: string }
65
+
66
+ function TabsRender<C extends ElementType = 'div'>(
67
+ {
68
+ children,
69
+ className,
70
+ component,
71
+ defaultSelectedKey,
72
+ disabledKeys,
73
+ keyboardActivation,
74
+ onSelectionChange,
75
+ orientation,
76
+ selectedKey,
77
+ ...rest
78
+ }: TabsProps<C>,
79
+ ref: PolymorphicRef<C>
80
+ ) {
81
+ const Component = component ?? 'div'
82
+ // `TabList`'s own children (the `Tab`s) are the source of truth for the collection —
83
+ // `TabList` never renders them directly (see its own comment), it only reads them here,
84
+ // at the point react-stately actually needs a collection to build `state` from. This keeps
85
+ // the public authoring shape as plain composed JSX (matching every other chassis-react
86
+ // component) instead of exposing react-aria's raw `items`/`Item` collection API directly.
87
+ const childArray = React.Children.toArray(children)
88
+ const tabListChild = childArray.find(
89
+ (child): child is ReactElement<{ children?: ReactNode }> =>
90
+ React.isValidElement(child) && child.type === TabList
91
+ )
92
+ const panelChildren = childArray.filter((child) => child !== tabListChild)
93
+ const tabs = (tabListChild ? React.Children.toArray(tabListChild.props.children) : []).filter(
94
+ (child): child is ReactElement<TabProps> => React.isValidElement(child)
95
+ )
96
+
97
+ const tabDisabledKeys = tabs.filter((tab) => tab.props.disabled).map((tab) => tab.props.id)
98
+ const allDisabledKeys = disabledKeys ? [...disabledKeys, ...tabDisabledKeys] : tabDisabledKeys
99
+
100
+ const state = useTabListState<ReactElement<TabProps>>({
101
+ children: (tab) => (
102
+ <Item
103
+ key={tab.props.id}
104
+ textValue={typeof tab.props.children === 'string' ? tab.props.children : undefined}
105
+ >
106
+ {tab.props.children}
107
+ </Item>
108
+ ),
109
+ items: tabs,
110
+ defaultSelectedKey,
111
+ disabledKeys: allDisabledKeys,
112
+ selectedKey,
113
+ onSelectionChange
114
+ })
115
+
116
+ // Flipped to `false` once the initial commit has painted, so `TabPanel` can tell "selected on
117
+ // load" apart from "selected by switching" and only fade in the latter.
118
+ const isInitialSelectionRef = useRef(true)
119
+ useEffect(() => {
120
+ isInitialSelectionRef.current = false
121
+ }, [])
122
+
123
+ return (
124
+ <TabsContext.Provider value={{ keyboardActivation, orientation, state, isInitialSelectionRef }}>
125
+ <Component className={className} {...rest} ref={ref}>
126
+ {tabListChild}
127
+ <div className="tab-content">{panelChildren}</div>
128
+ </Component>
129
+ </TabsContext.Provider>
130
+ )
131
+ }
132
+
133
+ export const Tabs = createPolymorphicComponent<TabsComponent>(
134
+ TabsRender as ForwardRefRenderFunction<Element, TabsProps<ElementType>>,
135
+ 'Tabs'
136
+ )
@@ -0,0 +1,24 @@
1
+ import { createContext, MutableRefObject, ReactElement, useContext } from 'react'
2
+ import { TabListState } from 'react-stately'
3
+
4
+ import { TabProps } from './Tab'
5
+
6
+ export interface TabsContextValue {
7
+ keyboardActivation?: 'automatic' | 'manual'
8
+ orientation?: 'horizontal' | 'vertical'
9
+ state: TabListState<ReactElement<TabProps>>
10
+ // Read by `TabPanel` at mount time to skip its fade-in for whichever panel is selected on the
11
+ // very first render — only a tab *switch* should animate, not the page settling in. `Tabs`
12
+ // flips this to `false` in an effect once the initial commit has painted.
13
+ isInitialSelectionRef: MutableRefObject<boolean>
14
+ }
15
+
16
+ export const TabsContext = createContext<TabsContextValue | null>(null)
17
+
18
+ export const useTabsContext = (): TabsContextValue => {
19
+ const context = useContext(TabsContext)
20
+ if (!context) {
21
+ throw new Error('TabList and TabPanel must be rendered inside a Tabs')
22
+ }
23
+ return context
24
+ }
@@ -0,0 +1,8 @@
1
+ export { Tabs } from './Tabs'
2
+ export type { TabsProps } from './Tabs'
3
+ export { Tab } from './Tab'
4
+ export type { TabProps } from './Tab'
5
+ export { TabList } from './TabList'
6
+ export type { TabListProps } from './TabList'
7
+ export { TabPanel } from './TabPanel'
8
+ export type { TabPanelProps } from './TabPanel'
@@ -0,0 +1,188 @@
1
+ import React, { forwardRef, InputHTMLAttributes, ReactNode, useRef } from 'react'
2
+ import classNames from 'classnames'
3
+ import { AriaTextFieldProps, useTextField } from 'react-aria'
4
+
5
+ import { useForkedRef, useFormField } from '../../hooks'
6
+ import { validationClassName } from '../../utils/validationClassName'
7
+ import { renderFormField } from '../form-field/renderFormField'
8
+
9
+ export interface TextInputProps extends Omit<
10
+ InputHTMLAttributes<HTMLInputElement>,
11
+ 'defaultValue' | 'onChange' | 'size' | 'value'
12
+ > {
13
+ /**
14
+ * Content rendered at the input's trailing edge, e.g. a `InputAdorn` icon, text, or button.
15
+ * Setting either `adornStart` or `adornEnd` renders a `.form-input` wrapper around a
16
+ * `.ghost-input`, matching chassis-css's [input help](https://chassis-ui.com/css/docs/forms/input-adorn) pattern.
17
+ */
18
+ adornEnd?: ReactNode
19
+ /**
20
+ * Content rendered at the input's leading edge, e.g. a `InputAdorn` icon, text, or button.
21
+ * Setting either `adornStart` or `adornEnd` renders a `.form-input` wrapper around a
22
+ * `.ghost-input`, matching chassis-css's [input help](https://chassis-ui.com/css/docs/forms/input-adorn) pattern.
23
+ */
24
+ adornStart?: ReactNode
25
+ /**
26
+ * A string of all className you want applied to the component.
27
+ */
28
+ className?: string
29
+ /**
30
+ * The value of the input, uncontrolled.
31
+ */
32
+ defaultValue?: string
33
+ /**
34
+ * Toggle the disabled state for the component.
35
+ */
36
+ disabled?: boolean
37
+ /**
38
+ * A description for the field, rendered below the input.
39
+ */
40
+ help?: ReactNode
41
+ /**
42
+ * Set component validation state to invalid.
43
+ */
44
+ invalid?: boolean
45
+ /**
46
+ * An error message for the field, rendered below the input when `invalid` is set.
47
+ */
48
+ invalidFeedback?: ReactNode
49
+ /**
50
+ * The field's caption, rendered as a `FormLabel` associated with this input.
51
+ */
52
+ label?: ReactNode
53
+ /**
54
+ * Handler that is called when the value changes.
55
+ */
56
+ onChange?: (value: string) => void
57
+ /**
58
+ * Render the component styled as plain text. Removes the default form field styling and preserve the correct margin and padding. Recommend to use only along side `readonly` [docs]
59
+ */
60
+ plainText?: boolean
61
+ /**
62
+ * Toggle the readonly state for the component.
63
+ */
64
+ readOnly?: boolean
65
+ /**
66
+ * Size the component small or large.
67
+ */
68
+ size?: 'small' | 'large'
69
+ /**
70
+ * Specifies the type of component. For `color` or `file` inputs, use the dedicated `ColorInput` or `FileInput` components instead.
71
+ */
72
+ type?:
73
+ | 'date'
74
+ | 'datetime-local'
75
+ | 'email'
76
+ | 'month'
77
+ | 'password'
78
+ | 'search'
79
+ | 'tel'
80
+ | 'text'
81
+ | 'time'
82
+ | 'url'
83
+ | 'week'
84
+ | (string & {})
85
+ /**
86
+ * Set component validation state to valid.
87
+ */
88
+ valid?: boolean
89
+ /**
90
+ * A success message for the field, rendered below the input when `valid` is set.
91
+ */
92
+ validFeedback?: ReactNode
93
+ /**
94
+ * The value of the input, controlled.
95
+ */
96
+ value?: string
97
+ }
98
+
99
+ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
100
+ (
101
+ {
102
+ adornEnd,
103
+ adornStart,
104
+ className,
105
+ disabled,
106
+ help,
107
+ id,
108
+ invalid,
109
+ invalidFeedback,
110
+ label,
111
+ plainText,
112
+ readOnly,
113
+ size,
114
+ type = 'text',
115
+ valid,
116
+ validFeedback,
117
+ ...rest
118
+ },
119
+ ref
120
+ ) => {
121
+ const inputRef = useRef<HTMLInputElement>(null)
122
+ const forkedRef = useForkedRef(ref, inputRef)
123
+
124
+ const { describedBy, feedbackId, helpId, inputId, labelId, labelledBy } = useFormField({
125
+ ariaDescribedBy: rest['aria-describedby'],
126
+ ariaLabelledBy: rest['aria-labelledby'],
127
+ help,
128
+ id,
129
+ invalid,
130
+ invalidFeedback,
131
+ label,
132
+ valid,
133
+ validFeedback
134
+ })
135
+
136
+ const { inputProps } = useTextField(
137
+ {
138
+ ...rest,
139
+ 'aria-describedby': describedBy,
140
+ 'aria-labelledby': labelledBy,
141
+ id: inputId,
142
+ isDisabled: disabled,
143
+ isInvalid: invalid,
144
+ isReadOnly: readOnly,
145
+ type
146
+ } as AriaTextFieldProps,
147
+ inputRef
148
+ )
149
+
150
+ const hasAdorn = adornStart != null || adornEnd != null
151
+
152
+ // chassis-css's `.form-input:has(.ghost-input.is-valid)` selector reads validation state off
153
+ // the inner input, not the wrapper, once adorns turn `.form-input` into a flex container - see
154
+ // https://chassis-ui.com/css/docs/forms/input-adorn.
155
+ const inputClassName = classNames(
156
+ hasAdorn ? 'ghost-input' : 'form-input',
157
+ !hasAdorn && plainText && 'plaintext',
158
+ !hasAdorn && size,
159
+ validationClassName(invalid, valid),
160
+ !hasAdorn && className
161
+ )
162
+
163
+ const input = <input {...inputProps} className={inputClassName} ref={forkedRef} />
164
+
165
+ const children = hasAdorn ? (
166
+ <div className={classNames('form-input', plainText && 'plaintext', size, className)}>
167
+ {adornStart}
168
+ {input}
169
+ {adornEnd}
170
+ </div>
171
+ ) : (
172
+ input
173
+ )
174
+
175
+ return renderFormField({
176
+ children,
177
+ help,
178
+ ids: { feedback: feedbackId, help: helpId, input: inputId, label: labelId },
179
+ invalid,
180
+ invalidFeedback,
181
+ label,
182
+ valid,
183
+ validFeedback
184
+ })
185
+ }
186
+ )
187
+
188
+ TextInput.displayName = 'TextInput'
@@ -0,0 +1,2 @@
1
+ export { TextInput } from './TextInput'
2
+ export type { TextInputProps } from './TextInput'