@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,145 @@
1
+ import React, { forwardRef, ReactNode, TextareaHTMLAttributes, useRef } from 'react'
2
+ import classNames from 'classnames'
3
+ import { AriaTextFieldOptions, 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 TextareaProps extends Omit<
10
+ TextareaHTMLAttributes<HTMLTextAreaElement>,
11
+ 'defaultValue' | 'onChange' | 'value'
12
+ > {
13
+ /**
14
+ * A string of all className you want applied to the component.
15
+ */
16
+ className?: string
17
+ /**
18
+ * The value of the textarea, uncontrolled.
19
+ */
20
+ defaultValue?: string
21
+ /**
22
+ * Toggle the disabled state for the component.
23
+ */
24
+ disabled?: boolean
25
+ /**
26
+ * A description for the field, rendered below the textarea.
27
+ */
28
+ help?: ReactNode
29
+ /**
30
+ * Set component validation state to invalid.
31
+ */
32
+ invalid?: boolean
33
+ /**
34
+ * An error message for the field, rendered below the textarea when `invalid` is set.
35
+ */
36
+ invalidFeedback?: ReactNode
37
+ /**
38
+ * The field's caption, rendered as a `FormLabel` associated with this textarea.
39
+ */
40
+ label?: ReactNode
41
+ /**
42
+ * Handler that is called when the value changes.
43
+ */
44
+ onChange?: (value: string) => void
45
+ /**
46
+ * 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`.
47
+ */
48
+ plainText?: boolean
49
+ /**
50
+ * Toggle the readonly state for the component.
51
+ */
52
+ readOnly?: boolean
53
+ /**
54
+ * The number of visible text lines for the control.
55
+ */
56
+ rows?: number
57
+ /**
58
+ * Size the component small or large.
59
+ */
60
+ size?: 'small' | 'large'
61
+ /**
62
+ * Set component validation state to valid.
63
+ */
64
+ valid?: boolean
65
+ /**
66
+ * A success message for the field, rendered below the textarea when `valid` is set.
67
+ */
68
+ validFeedback?: ReactNode
69
+ /**
70
+ * The value of the textarea, controlled.
71
+ */
72
+ value?: string
73
+ }
74
+
75
+ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
76
+ (
77
+ {
78
+ className,
79
+ disabled,
80
+ help,
81
+ id,
82
+ invalid,
83
+ invalidFeedback,
84
+ label,
85
+ plainText,
86
+ readOnly,
87
+ rows,
88
+ size,
89
+ valid,
90
+ validFeedback,
91
+ ...rest
92
+ },
93
+ ref
94
+ ) => {
95
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
96
+ const forkedRef = useForkedRef(ref, textareaRef)
97
+
98
+ const { describedBy, feedbackId, helpId, inputId, labelId, labelledBy } = useFormField({
99
+ ariaDescribedBy: rest['aria-describedby'],
100
+ ariaLabelledBy: rest['aria-labelledby'],
101
+ help,
102
+ id,
103
+ invalid,
104
+ invalidFeedback,
105
+ label,
106
+ valid,
107
+ validFeedback
108
+ })
109
+
110
+ const { inputProps } = useTextField<'textarea'>(
111
+ {
112
+ ...rest,
113
+ 'aria-describedby': describedBy,
114
+ 'aria-labelledby': labelledBy,
115
+ id: inputId,
116
+ inputElementType: 'textarea',
117
+ isDisabled: disabled,
118
+ isInvalid: invalid,
119
+ isReadOnly: readOnly
120
+ } as AriaTextFieldOptions<'textarea'>,
121
+ textareaRef
122
+ )
123
+
124
+ const _className = classNames(
125
+ 'form-input',
126
+ plainText && 'plaintext',
127
+ size,
128
+ validationClassName(invalid, valid),
129
+ className
130
+ )
131
+
132
+ return renderFormField({
133
+ children: <textarea {...inputProps} rows={rows} className={_className} ref={forkedRef} />,
134
+ help,
135
+ ids: { feedback: feedbackId, help: helpId, input: inputId, label: labelId },
136
+ invalid,
137
+ invalidFeedback,
138
+ label,
139
+ valid,
140
+ validFeedback
141
+ })
142
+ }
143
+ )
144
+
145
+ Textarea.displayName = 'Textarea'
@@ -0,0 +1,2 @@
1
+ export { Textarea } from './Textarea'
2
+ export type { TextareaProps } from './Textarea'
@@ -0,0 +1,219 @@
1
+ import React, { forwardRef, HTMLAttributes, ReactNode } from 'react'
2
+ import { mergeProps } from 'react-aria'
3
+ import { Transition } from 'react-transition-group'
4
+ import classNames from 'classnames'
5
+
6
+ import { ContextColor } from '../../types'
7
+ import { useAutoDismiss, useDismissibleTransition } from '../../hooks'
8
+ import { ToastContext } from './context'
9
+ import { ToastBody } from './ToastBody'
10
+ import { ToastFooter } from './ToastFooter'
11
+ import { ToastHeader } from './ToastHeader'
12
+
13
+ export interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
14
+ /**
15
+ * Apply a CSS fade transition to the toast.
16
+ */
17
+ animation?: boolean
18
+ /**
19
+ * Auto hide the toast. The timer starts once the show transition completes and pauses
20
+ * while the pointer is over the toast or focus is within it.
21
+ */
22
+ autohide?: boolean
23
+ /**
24
+ * A string of all className you want applied to the base component.
25
+ */
26
+ className?: string
27
+ /**
28
+ * Adds a close button — shorthand for `ToastHeader`'s/`ToastBody`'s `closeButton` prop.
29
+ * Placed in the header when `icon`/`title`/`time` is set; otherwise placed in the body
30
+ * alongside `message`, or in a header containing only the close button if `message` is
31
+ * also unset.
32
+ */
33
+ closeButton?: boolean
34
+ /**
35
+ * Overrides the close button's accessible name (defaults to `'Close'`). Set this for
36
+ * non-English UIs. Shorthand for `ToastHeader`'s `closeLabel` prop.
37
+ */
38
+ closeLabel?: string
39
+ /**
40
+ * Sets the color of the component to one of Chassis context colors.
41
+ */
42
+ color?: ContextColor
43
+ /**
44
+ * Delay hiding the toast (ms).
45
+ */
46
+ delay?: number
47
+ /**
48
+ * Trailing content — typically a row of `Button`s — rendered via a single `ToastFooter`,
49
+ * after `message`/`children`. Pass a function to receive `close` directly, instead of
50
+ * calling `useToast()` from a child component to wire up a "Close" action.
51
+ */
52
+ footer?: ReactNode | ((close: () => void) => ReactNode)
53
+ /**
54
+ * Leading icon for the header. A string is rendered as `<ToastIcon name={icon} />`; pass
55
+ * any other node for a fully custom icon (typically a logo or avatar). Shorthand for
56
+ * `ToastHeader`'s `icon` prop; hidden from assistive technology by default, since it
57
+ * duplicates `title` visually.
58
+ */
59
+ icon?: string | ReactNode
60
+ /**
61
+ * Message body, rendered via a single `ToastBody`. For multi-block content, compose
62
+ * `children` manually instead — `message` wraps everything in one element.
63
+ */
64
+ message?: ReactNode
65
+ /**
66
+ * Header timestamp, rendered after `title`. Shorthand for `ToastHeader`'s `time` prop.
67
+ */
68
+ time?: ReactNode
69
+ /**
70
+ * Header heading, rendered before `time`. Shorthand for `ToastHeader`'s children. When set
71
+ * alongside `message`, wires the toast's `aria-labelledby`/`aria-describedby` to them
72
+ * automatically.
73
+ */
74
+ title?: ReactNode
75
+ /**
76
+ * Callback fired when the component requests to be closed.
77
+ */
78
+ onClose?: () => void
79
+ /**
80
+ * Callback fired when the component requests to be shown.
81
+ */
82
+ onShow?: () => void
83
+ /**
84
+ * ARIA live-region role. Use `status` (the default) for confirmation, progress, and
85
+ * informational messages, which announce politely. Use `alert` for messages that need
86
+ * immediate attention — validation errors, failed operations — which interrupt speech.
87
+ */
88
+ role?: 'status' | 'alert'
89
+ /**
90
+ * Apply a full-color background with inverted text. Only meaningful alongside `color`.
91
+ */
92
+ solid?: boolean
93
+ /**
94
+ * Apply a semi-transparent background.
95
+ */
96
+ translucent?: boolean
97
+ /**
98
+ * Toggle the visibility of component.
99
+ */
100
+ visible?: boolean
101
+ }
102
+
103
+ export const Toast = forwardRef<HTMLDivElement, ToastProps>(
104
+ (
105
+ {
106
+ children,
107
+ animation = true,
108
+ autohide = true,
109
+ className,
110
+ closeButton,
111
+ closeLabel,
112
+ color,
113
+ delay = 5000,
114
+ footer,
115
+ icon,
116
+ message,
117
+ role = 'status',
118
+ solid,
119
+ time,
120
+ title,
121
+ translucent,
122
+ visible = false,
123
+ onClose,
124
+ onShow,
125
+ ...rest
126
+ },
127
+ ref
128
+ ) => {
129
+ const {
130
+ close,
131
+ entered,
132
+ forkedRef,
133
+ getTransitionClass,
134
+ textId,
135
+ titleId,
136
+ transitionProps,
137
+ visible: _visible
138
+ } = useDismissibleTransition({ onClose, onShow, ref, visible })
139
+
140
+ // The autohide timer is only meaningful once the show transition has actually finished (see
141
+ // `autohide`'s JSDoc) — gating on `_visible` alone would start it the instant `visible` flips
142
+ // true, while the toast is still fading/sliding in.
143
+ const autoDismissProps = useAutoDismiss({
144
+ enabled: autohide,
145
+ delay,
146
+ visible: _visible && entered,
147
+ onHide: close
148
+ })
149
+
150
+ const contextValues = {
151
+ visible: _visible,
152
+ close
153
+ }
154
+
155
+ const _className = classNames(
156
+ 'toast',
157
+ {
158
+ fade: animation,
159
+ context: !!color,
160
+ solid: Boolean(solid && color),
161
+ translucent
162
+ },
163
+ color,
164
+ className
165
+ )
166
+
167
+ const hasHeaderShorthand = icon != null || title != null || time != null
168
+ const headerGetsCloseButton = closeButton && (hasHeaderShorthand || message == null)
169
+ const bodyGetsCloseButton = closeButton && !hasHeaderShorthand && message != null
170
+ const hasHeaderContent = hasHeaderShorthand || headerGetsCloseButton
171
+
172
+ return (
173
+ <Transition {...transitionProps} timeout={250}>
174
+ {(state) => {
175
+ const transitionClass = getTransitionClass(state)
176
+ return (
177
+ <ToastContext.Provider value={contextValues}>
178
+ <div
179
+ className={classNames(_className, transitionClass)}
180
+ role={role}
181
+ aria-labelledby={title != null ? titleId : undefined}
182
+ aria-describedby={title != null && message != null ? textId : undefined}
183
+ {...mergeProps(rest, autoDismissProps)}
184
+ ref={forkedRef}
185
+ >
186
+ {hasHeaderContent && (
187
+ <ToastHeader
188
+ icon={icon}
189
+ time={time}
190
+ titleId={title != null ? titleId : undefined}
191
+ closeButton={headerGetsCloseButton}
192
+ closeLabel={closeLabel}
193
+ >
194
+ {title}
195
+ </ToastHeader>
196
+ )}
197
+ {message != null && (
198
+ <ToastBody
199
+ id={title != null && message != null ? textId : undefined}
200
+ closeButton={bodyGetsCloseButton}
201
+ closeLabel={closeLabel}
202
+ >
203
+ {message}
204
+ </ToastBody>
205
+ )}
206
+ {children}
207
+ {footer != null && (
208
+ <ToastFooter>{typeof footer === 'function' ? footer(close) : footer}</ToastFooter>
209
+ )}
210
+ </div>
211
+ </ToastContext.Provider>
212
+ )
213
+ }}
214
+ </Transition>
215
+ )
216
+ }
217
+ )
218
+
219
+ Toast.displayName = 'Toast'
@@ -0,0 +1,64 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { CloseButton } from '../close-button/CloseButton'
5
+ import { useToast } from '../../hooks'
6
+ import {
7
+ createPolymorphicComponent,
8
+ PolymorphicComponentProps,
9
+ PolymorphicRef
10
+ } from '../../utils/polymorphic'
11
+
12
+ type ToastBodyOwnProps<C extends ElementType> = {
13
+ /**
14
+ * A string of all className you want applied to the base component.
15
+ */
16
+ className?: string
17
+ /**
18
+ * Adds a close button alongside the body content, so a toast composed without a
19
+ * `ToastHeader` still gets a dismiss control without any manual layout markup.
20
+ */
21
+ closeButton?: boolean
22
+ /**
23
+ * Overrides the close button's accessible name (defaults to `'Close'`). Set this for
24
+ * non-English UIs.
25
+ */
26
+ closeLabel?: string
27
+ /**
28
+ * Component used for the root node. Either a string to use a HTML element or a component.
29
+ */
30
+ component?: C
31
+ }
32
+
33
+ export type ToastBodyProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
34
+ C,
35
+ ToastBodyOwnProps<C>
36
+ >
37
+
38
+ type ToastBodyComponent = (<C extends ElementType = 'div'>(
39
+ props: ToastBodyProps<C> & { ref?: PolymorphicRef<C> }
40
+ ) => ReactElement | null) & { displayName?: string }
41
+
42
+ function ToastBodyRender<C extends ElementType = 'div'>(
43
+ { children, className, closeButton, closeLabel, component, ...rest }: ToastBodyProps<C>,
44
+ ref: PolymorphicRef<C>
45
+ ) {
46
+ const { close } = useToast()
47
+ const Component = component ?? 'div'
48
+ const _className = classNames(
49
+ 'toast-body',
50
+ { 'd-flex align-items-start justify-content-between gap-small': closeButton },
51
+ className
52
+ )
53
+ return (
54
+ <Component className={_className} {...rest} ref={ref}>
55
+ {closeButton ? <div>{children}</div> : children}
56
+ {closeButton && <CloseButton label={closeLabel} onClick={close} />}
57
+ </Component>
58
+ )
59
+ }
60
+
61
+ export const ToastBody = createPolymorphicComponent<ToastBodyComponent>(
62
+ ToastBodyRender as ForwardRefRenderFunction<Element, ToastBodyProps<ElementType>>,
63
+ 'ToastBody'
64
+ )
@@ -0,0 +1,47 @@
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
+
10
+ type ToastFooterOwnProps<C extends ElementType> = {
11
+ /**
12
+ * A string of all className you want applied to the base component.
13
+ */
14
+ className?: string
15
+ /**
16
+ * Component used for the root node. Either a string to use a HTML element or a component.
17
+ */
18
+ component?: C
19
+ }
20
+
21
+ export type ToastFooterProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
22
+ C,
23
+ ToastFooterOwnProps<C>
24
+ >
25
+
26
+ type ToastFooterComponent = (<C extends ElementType = 'div'>(
27
+ props: ToastFooterProps<C> & { ref?: PolymorphicRef<C> }
28
+ ) => ReactElement | null) & { displayName?: string }
29
+
30
+ function ToastFooterRender<C extends ElementType = 'div'>(
31
+ { children, className, component, ...rest }: ToastFooterProps<C>,
32
+ ref: PolymorphicRef<C>
33
+ ) {
34
+ const Component = component ?? 'div'
35
+ const _className = classNames('toast-footer', className)
36
+
37
+ return (
38
+ <Component className={_className} {...rest} ref={ref}>
39
+ {children}
40
+ </Component>
41
+ )
42
+ }
43
+
44
+ export const ToastFooter = createPolymorphicComponent<ToastFooterComponent>(
45
+ ToastFooterRender as ForwardRefRenderFunction<Element, ToastFooterProps<ElementType>>,
46
+ 'ToastFooter'
47
+ )
@@ -0,0 +1,104 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement, ReactNode } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { CloseButton } from '../close-button/CloseButton'
5
+ import { useToast } from '../../hooks'
6
+ import {
7
+ createPolymorphicComponent,
8
+ PolymorphicComponentProps,
9
+ PolymorphicRef
10
+ } from '../../utils/polymorphic'
11
+ import { ToastIcon } from './ToastIcon'
12
+
13
+ type ToastHeaderOwnProps<C extends ElementType> = {
14
+ /**
15
+ * Heading, rendered before `time` as a `<strong>`.
16
+ */
17
+ children?: ReactNode
18
+ /**
19
+ * A string of all className you want applied to the base component.
20
+ */
21
+ className?: string
22
+ /**
23
+ * Automatically add a close button to the header.
24
+ */
25
+ closeButton?: boolean
26
+ /**
27
+ * Overrides the close button's accessible name (defaults to `'Close'`). Set this for
28
+ * non-English UIs.
29
+ */
30
+ closeLabel?: string
31
+ /**
32
+ * Component used for the root node. Either a string to use a HTML element or a component.
33
+ */
34
+ component?: C
35
+ /**
36
+ * Leading icon. A string is rendered as `<ToastIcon name={icon} />` and hidden from
37
+ * assistive technology by default, since it duplicates the heading visually. Pass any other
38
+ * node for a fully custom icon (typically a logo or avatar) — a custom node is left as-is,
39
+ * since it may carry its own meaningful accessible name (e.g. an avatar's `alt` text).
40
+ */
41
+ icon?: string | ReactNode
42
+ /**
43
+ * Trailing timestamp, rendered after the heading.
44
+ */
45
+ time?: ReactNode
46
+ /**
47
+ * Sets the `id` on the rendered heading element, for `aria-labelledby` wiring. Set
48
+ * automatically by `Toast` when both `title` and `message` are used together; only needed
49
+ * here for manual wiring in a fully custom composition.
50
+ */
51
+ titleId?: string
52
+ }
53
+
54
+ export type ToastHeaderProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
55
+ C,
56
+ ToastHeaderOwnProps<C>
57
+ >
58
+
59
+ type ToastHeaderComponent = (<C extends ElementType = 'div'>(
60
+ props: ToastHeaderProps<C> & { ref?: PolymorphicRef<C> }
61
+ ) => ReactElement | null) & { displayName?: string }
62
+
63
+ function ToastHeaderRender<C extends ElementType = 'div'>(
64
+ {
65
+ children,
66
+ className,
67
+ closeButton,
68
+ closeLabel,
69
+ component,
70
+ icon,
71
+ time,
72
+ titleId,
73
+ ...rest
74
+ }: ToastHeaderProps<C>,
75
+ ref: PolymorphicRef<C>
76
+ ) {
77
+ const { close } = useToast()
78
+ const Component = component ?? 'div'
79
+ const _className = classNames('toast-header', className)
80
+ return (
81
+ <Component className={_className} {...rest} ref={ref}>
82
+ {icon != null &&
83
+ (typeof icon === 'string' ? (
84
+ <span aria-hidden="true" className="me-small">
85
+ <ToastIcon name={icon} />
86
+ </span>
87
+ ) : (
88
+ <span className="me-small">{icon}</span>
89
+ ))}
90
+ {children != null && (
91
+ <strong id={titleId} className="me-auto">
92
+ {children}
93
+ </strong>
94
+ )}
95
+ {time != null && <small>{time}</small>}
96
+ {closeButton && <CloseButton label={closeLabel} onClick={close} />}
97
+ </Component>
98
+ )
99
+ }
100
+
101
+ export const ToastHeader = createPolymorphicComponent<ToastHeaderComponent>(
102
+ ToastHeaderRender as ForwardRefRenderFunction<Element, ToastHeaderProps<ElementType>>,
103
+ 'ToastHeader'
104
+ )
@@ -0,0 +1,21 @@
1
+ import React, { forwardRef } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { Icon, IconProps } from '../icon/Icon'
5
+
6
+ export type ToastIconProps = IconProps & {
7
+ /**
8
+ * A string of all className you want applied to the base component.
9
+ */
10
+ className?: string
11
+ }
12
+
13
+ export const ToastIcon = forwardRef<HTMLSpanElement | SVGSVGElement, ToastIconProps>(
14
+ ({ className, ...rest }, ref) => {
15
+ const _className = classNames('toast-icon', className)
16
+
17
+ return <Icon className={_className} {...rest} ref={ref} />
18
+ }
19
+ )
20
+
21
+ ToastIcon.displayName = 'ToastIcon'
@@ -0,0 +1,87 @@
1
+ import React, { forwardRef, HTMLAttributes } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import classNames from 'classnames'
4
+
5
+ import { useToastRegionQueue } from '../../hooks'
6
+ import { Toast } from './Toast'
7
+ import { toastQueue } from './toastQueue'
8
+
9
+ export interface ToasterProps extends HTMLAttributes<HTMLDivElement> {
10
+ /**
11
+ * Overrides the toast region's accessible name (defaults to `'Notifications'`, per
12
+ * react-aria). Set this for non-English UIs, or to distinguish multiple toasters on the
13
+ * same page.
14
+ */
15
+ 'aria-label'?: string
16
+ /**
17
+ * A string of all className you want applied to the base component.
18
+ */
19
+ className?: string
20
+ /**
21
+ * Describes the placement of your component.
22
+ *
23
+ * @type 'top-start' | 'top-center' | 'top-end' | 'middle-start' | 'middle-center' | 'middle-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' | string
24
+ */
25
+ placement?:
26
+ | 'top-start'
27
+ | 'top-center'
28
+ | 'top-end'
29
+ | 'middle-start'
30
+ | 'middle-center'
31
+ | 'middle-end'
32
+ | 'bottom-start'
33
+ | 'bottom-center'
34
+ | 'bottom-end'
35
+ | string
36
+ }
37
+
38
+ // Renders the shared `toastQueue` (see `toastQueue.ts` — `addToast()` is how toasts get added
39
+ // to it from anywhere in the app) plus any statically-passed `children`, e.g. a permanently
40
+ // pinned toast alongside dynamic ones.
41
+ export const Toaster = forwardRef<HTMLDivElement, ToasterProps>(
42
+ ({ children, className, placement, ...rest }, ref) => {
43
+ const { forkedRef, regionProps, state } = useToastRegionQueue(toastQueue, ref)
44
+
45
+ const _className = classNames(
46
+ 'toaster toast-container p-medium',
47
+ {
48
+ 'position-fixed': placement,
49
+ 'position-static': !placement,
50
+ 'top-0': placement && placement.includes('top'),
51
+ 'top-50 translate-middle-y': placement && placement.includes('middle'),
52
+ 'bottom-0': placement && placement.includes('bottom'),
53
+ 'start-0': placement && placement.includes('start'),
54
+ 'start-50 translate-middle-x': placement && placement.includes('center'),
55
+ 'end-0': placement && placement.includes('end')
56
+ },
57
+ className
58
+ )
59
+
60
+ const toaster = (toasterRef?: React.Ref<HTMLDivElement>) => {
61
+ return state.visibleToasts.length > 0 || children ? (
62
+ <div className={_className} {...regionProps} {...rest} ref={toasterRef}>
63
+ {children}
64
+ {state.visibleToasts.map((queued) => {
65
+ const { children: toastChildren, ...toastProps } = queued.content
66
+ return (
67
+ <Toast
68
+ key={queued.key}
69
+ visible
70
+ {...toastProps}
71
+ onClose={() => state.close(queued.key)}
72
+ >
73
+ {toastChildren}
74
+ </Toast>
75
+ )
76
+ })}
77
+ </div>
78
+ ) : null
79
+ }
80
+
81
+ return typeof window !== 'undefined' && placement
82
+ ? createPortal(toaster(forkedRef), document.body)
83
+ : toaster(forkedRef)
84
+ }
85
+ )
86
+
87
+ Toaster.displayName = 'Toaster'