@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,178 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { ContextColor, ContextStyle } from '../../types'
5
+ import {
6
+ createPolymorphicComponent,
7
+ PolymorphicComponentProps,
8
+ PolymorphicRef
9
+ } from '../../utils/polymorphic'
10
+
11
+ type PlaceholderAlign = 'start' | 'center' | 'end'
12
+
13
+ type PlaceholderOwnProps<C extends ElementType> = {
14
+ /**
15
+ * Set the horizontal alignment. Only applies when `src` is set.
16
+ */
17
+ align?: PlaceholderAlign
18
+ /**
19
+ * A string of all className you want applied to the component.
20
+ */
21
+ className?: string
22
+ /**
23
+ * Sets the color of the generated placeholder graphic to one of Chassis context colors. Has no
24
+ * effect when `src` is set.
25
+ */
26
+ color?: ContextColor
27
+ /**
28
+ * Sets the variant of the generated placeholder graphic to one of Chassis context styles. Has no
29
+ * effect when `src` is set.
30
+ */
31
+ variant?: Exclude<ContextStyle, 'basic' | 'outline'>
32
+ /**
33
+ * Component used for the root node when `src` is set. Either a string to use an HTML element
34
+ * or a component — e.g. a framework's own `Image` component. Its own props (`src`, `fill`,
35
+ * `priority`, etc.) are type-checked at the call site once passed here.
36
+ */
37
+ component?: C
38
+ /**
39
+ * Make the image responsive, so it never grows larger than its parent. Only applies when `src`
40
+ * is set.
41
+ */
42
+ fluid?: boolean
43
+ /**
44
+ * Placeholder height.
45
+ */
46
+ height?: number | string
47
+ /**
48
+ * Give the image a rounded border-radius. Only applies when `src` is set.
49
+ */
50
+ rounded?: boolean
51
+ /**
52
+ * Renders a real image instead of the generated placeholder graphic, e.g. a default asset or a
53
+ * `placehold.co` URL.
54
+ */
55
+ src?: string
56
+ /**
57
+ * Text shown in the generated placeholder graphic. Defaults to `{width}x{height}`. Pass `false`
58
+ * to hide it. Has no effect when `src` is set.
59
+ */
60
+ text?: string | false
61
+ /**
62
+ * Give the image a thumbnail appearance (padding, background, border, box-shadow). Only
63
+ * applies when `src` is set.
64
+ */
65
+ thumbnail?: boolean
66
+ /**
67
+ * Accessible title for the generated placeholder graphic, rendered as an SVG `<title>`. Pass
68
+ * `false` to hide it. Has no effect when `src` is set.
69
+ *
70
+ * @default 'Placeholder'
71
+ */
72
+ title?: string | false
73
+ /**
74
+ * Placeholder width.
75
+ */
76
+ width?: number | string
77
+ }
78
+
79
+ export type PlaceholderProps<C extends ElementType = 'img'> = PolymorphicComponentProps<
80
+ C,
81
+ PlaceholderOwnProps<C>
82
+ >
83
+
84
+ type PlaceholderComponent = (<C extends ElementType = 'img'>(
85
+ props: PlaceholderProps<C> & { ref?: PolymorphicRef<C> }
86
+ ) => ReactElement | null) & { displayName?: string }
87
+
88
+ const alignClassNames = (align: PlaceholderAlign | undefined) => [
89
+ align === 'start' || align === 'end' ? `float-${align}` : null,
90
+ align === 'center' ? 'd-block mx-auto' : null
91
+ ]
92
+
93
+ function PlaceholderRender<C extends ElementType = 'img'>(
94
+ {
95
+ align,
96
+ alt,
97
+ className,
98
+ color,
99
+ component,
100
+ fluid,
101
+ height,
102
+ rounded,
103
+ src,
104
+ text,
105
+ thumbnail,
106
+ title = 'Placeholder',
107
+ variant,
108
+ width,
109
+ ...rest
110
+ }: PlaceholderProps<C>,
111
+ ref: PolymorphicRef<C>
112
+ ) {
113
+ const showTitle = title !== false
114
+ const displayText =
115
+ text === false
116
+ ? undefined
117
+ : (text ?? (width !== undefined && height !== undefined ? `${width}x${height}` : undefined))
118
+ const showText = displayText !== undefined
119
+ const label =
120
+ [showTitle && title, showText && displayText].filter(Boolean).join(': ') || undefined
121
+
122
+ if (src) {
123
+ const Component = component ?? 'img'
124
+ const _className = classNames(
125
+ { image: fluid || thumbnail, fluid, rounded, thumbnail },
126
+ alignClassNames(align),
127
+ className
128
+ )
129
+
130
+ return (
131
+ <Component
132
+ src={src}
133
+ width={width}
134
+ height={height}
135
+ alt={alt ?? label ?? ''}
136
+ className={_className}
137
+ {...rest}
138
+ ref={ref}
139
+ />
140
+ )
141
+ }
142
+
143
+ const _className = classNames(
144
+ 'image',
145
+ { context: Boolean(color || variant) },
146
+ color,
147
+ variant ? variant : 'bg-evident',
148
+ 'fg-subtle',
149
+ className
150
+ )
151
+
152
+ return (
153
+ <svg
154
+ xmlns="http://www.w3.org/2000/svg"
155
+ width={width}
156
+ height={height}
157
+ aria-label={label}
158
+ aria-hidden={label ? undefined : true}
159
+ className={_className}
160
+ preserveAspectRatio="xMidYMid slice"
161
+ role={label ? 'img' : undefined}
162
+ {...rest}
163
+ ref={ref as PolymorphicRef<'svg'>}
164
+ >
165
+ {showTitle && <title>{title}</title>}
166
+ {showText && (
167
+ <text dy=".3em" fill="currentColor" textAnchor="middle" x="50%" y="50%">
168
+ {displayText}
169
+ </text>
170
+ )}
171
+ </svg>
172
+ )
173
+ }
174
+
175
+ export const Placeholder = createPolymorphicComponent<PlaceholderComponent>(
176
+ PlaceholderRender as ForwardRefRenderFunction<Element, PlaceholderProps<ElementType>>,
177
+ 'Placeholder'
178
+ )
@@ -0,0 +1,2 @@
1
+ export { Placeholder } from './Placeholder'
2
+ export type { PlaceholderProps } from './Placeholder'
@@ -0,0 +1,243 @@
1
+ import React, {
2
+ FC,
3
+ HTMLAttributes,
4
+ ReactElement,
5
+ ReactNode,
6
+ RefObject,
7
+ useEffect,
8
+ useRef
9
+ } from 'react'
10
+ import { createPortal } from 'react-dom'
11
+ import classNames from 'classnames'
12
+ import { mergeProps, useDialog, useOverlayPosition, useOverlayTrigger } from 'react-aria'
13
+ import { useOverlayTriggerState } from 'react-stately'
14
+ import { Transition } from 'react-transition-group'
15
+
16
+ import { getOverlayArrowStyle, getOverlayTransitionClass, useFloatingOverlay } from '../../hooks'
17
+ import { Placement, resolveDataPlacement, toAriaPlacement } from '../../utils/overlayPlacement'
18
+
19
+ interface PopoverPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
20
+ arrowProps: HTMLAttributes<HTMLDivElement>
21
+ content: ReactNode | string
22
+ overlayRef: RefObject<HTMLDivElement>
23
+ overlayTriggerProps: HTMLAttributes<HTMLDivElement>
24
+ title?: ReactNode | string
25
+ }
26
+
27
+ // A distinct component (rather than inline JSX from the `Transition` render prop) so
28
+ // `useDialog`'s own mount-focus effect — which only fires on this component's mount, not on
29
+ // `overlayRef.current` merely becoming non-null — actually runs each time the popover opens.
30
+ // `Transition`'s `unmountOnExit` genuinely unmounts and remounts this component on every
31
+ // open, giving `useDialog` a fresh mount each time.
32
+ const PopoverPanel = ({
33
+ arrowProps,
34
+ content,
35
+ overlayRef,
36
+ overlayTriggerProps,
37
+ title,
38
+ ...rest
39
+ }: PopoverPanelProps) => {
40
+ const { dialogProps, titleProps } = useDialog({}, overlayRef)
41
+
42
+ return (
43
+ <div {...mergeProps(overlayTriggerProps, dialogProps, rest)} ref={overlayRef}>
44
+ <div className="popover-arrow" {...arrowProps} style={getOverlayArrowStyle(arrowProps)}></div>
45
+ {title && (
46
+ <div className="popover-header" {...titleProps}>
47
+ {title}
48
+ </div>
49
+ )}
50
+ <div className="popover-body">{content}</div>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ export interface PopoverProps extends Pick<
56
+ HTMLAttributes<HTMLDivElement>,
57
+ 'aria-label' | 'aria-labelledby'
58
+ > {
59
+ children: ReactElement
60
+ /**
61
+ * Content node for your component.
62
+ */
63
+ content: ReactNode | string
64
+ /**
65
+ * Offset of the popover relative to its target, as `[crossAxis, mainAxis]`.
66
+ */
67
+ offset?: [number, number]
68
+ /**
69
+ * Callback fired when the component requests to be hidden.
70
+ */
71
+ onHide?: () => void
72
+ /**
73
+ * Callback fired when the component requests to be shown.
74
+ */
75
+ onShow?: () => void
76
+ /**
77
+ * Title node for your component.
78
+ */
79
+ title?: ReactNode | string
80
+ /**
81
+ * Describes the preferred placement of your component. Chassis will flip it to keep it in
82
+ * view.
83
+ */
84
+ placement?: Placement
85
+ /**
86
+ * Toggle the visibility of popover component.
87
+ */
88
+ visible?: boolean
89
+ }
90
+
91
+ export const Popover: FC<PopoverProps> = ({
92
+ children,
93
+ content,
94
+ placement = 'right',
95
+ offset: offsetProp = [0, 8],
96
+ onHide,
97
+ onShow,
98
+ title,
99
+ visible,
100
+ ...rest
101
+ }) => {
102
+ const arrowRef = useRef<HTMLDivElement>(null)
103
+ const triggerRef = useRef<HTMLElement | null>(null)
104
+ const floatingRef = useRef<HTMLDivElement>(null)
105
+
106
+ const state = useOverlayTriggerState({ defaultOpen: !!visible })
107
+ const {
108
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
109
+ triggerProps: { onPress: _onPress, ...triggerProps },
110
+ overlayProps: overlayTriggerProps
111
+ } = useOverlayTrigger({ type: 'dialog' }, state, triggerRef)
112
+
113
+ const portalContainer = useFloatingOverlay({
114
+ close: state.close,
115
+ isOpen: state.isOpen,
116
+ open: state.open,
117
+ onHide,
118
+ onShow,
119
+ triggerRef,
120
+ visible
121
+ })
122
+
123
+ // Escape should always close the popover, regardless of where focus currently is — a
124
+ // mouse-opened popover leaves focus on the trigger, not inside the panel. Mirrors
125
+ // `Menu.tsx`'s own window-level Escape handling. `onExited` below already returns focus to the
126
+ // trigger once the exit transition finishes, so this doesn't need to do that itself.
127
+ useEffect(() => {
128
+ if (!state.isOpen) return undefined
129
+
130
+ const handleEscape = (event: KeyboardEvent) => {
131
+ if (event.key !== 'Escape') return
132
+ state.close()
133
+ }
134
+
135
+ window.addEventListener('keydown', handleEscape)
136
+ return () => window.removeEventListener('keydown', handleEscape)
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps
138
+ }, [state.isOpen, state.close])
139
+
140
+ // Deferred so the click that opened the popover doesn't immediately close it again — mirrors
141
+ // `Menu.tsx`'s own outside-click handling.
142
+ useEffect(() => {
143
+ if (!state.isOpen) return undefined
144
+
145
+ const handleDismiss = (event: MouseEvent) => {
146
+ const target = event.target as Node
147
+ if (triggerRef.current?.contains(target)) return
148
+ if (floatingRef.current?.contains(target)) return
149
+ state.close()
150
+ }
151
+
152
+ const id = window.setTimeout(() => {
153
+ window.addEventListener('click', handleDismiss)
154
+ })
155
+
156
+ return () => {
157
+ window.clearTimeout(id)
158
+ window.removeEventListener('click', handleDismiss)
159
+ }
160
+ // eslint-disable-next-line react-hooks/exhaustive-deps
161
+ }, [state.isOpen, state.close])
162
+
163
+ const {
164
+ overlayProps,
165
+ arrowProps,
166
+ placement: resolvedPlacement
167
+ } = useOverlayPosition({
168
+ targetRef: triggerRef,
169
+ overlayRef: floatingRef,
170
+ placement: toAriaPlacement(placement),
171
+ offset: offsetProp[1],
172
+ crossOffset: offsetProp[0],
173
+ arrowSize: 8,
174
+ arrowRef,
175
+ isOpen: state.isOpen,
176
+ // `useOverlayPosition` closes on any window scroll via a backward-compat `WeakMap` that
177
+ // `useOverlayTrigger` populates for `triggerRef.current` above. Passing `null` opts out so
178
+ // the popover repositions with its trigger instead of vanishing — matching
179
+ // `Autocomplete`/`Combobox`, neither of which is wired into that map.
180
+ onClose: null
181
+ })
182
+
183
+ const floatingStyle: React.CSSProperties = {
184
+ position: overlayProps.style?.position as React.CSSProperties['position'],
185
+ top: overlayProps.style?.top,
186
+ left: overlayProps.style?.left
187
+ }
188
+ const placementAttr = resolveDataPlacement(placement, resolvedPlacement)
189
+
190
+ return (
191
+ <>
192
+ {React.cloneElement(children, {
193
+ ref: (node: HTMLElement | null) => {
194
+ triggerRef.current = node
195
+ },
196
+ ...triggerProps,
197
+ onClick: (event: React.MouseEvent) => {
198
+ children.props.onClick?.(event)
199
+ state.toggle()
200
+ }
201
+ })}
202
+ {typeof window !== 'undefined' &&
203
+ createPortal(
204
+ <Transition
205
+ in={state.isOpen}
206
+ mountOnEnter
207
+ nodeRef={floatingRef}
208
+ onExited={() => {
209
+ const trigger = triggerRef.current
210
+ if (trigger && document.contains(trigger)) {
211
+ trigger.focus()
212
+ }
213
+ }}
214
+ timeout={{
215
+ enter: 0,
216
+ exit: 200
217
+ }}
218
+ unmountOnExit
219
+ >
220
+ {(transitionState) => {
221
+ const transitionClass = getOverlayTransitionClass(transitionState)
222
+ return (
223
+ <PopoverPanel
224
+ className={classNames('popover cx-popover-auto', transitionClass)}
225
+ data-cx-placement={placementAttr}
226
+ style={floatingStyle}
227
+ overlayTriggerProps={overlayTriggerProps}
228
+ overlayRef={floatingRef}
229
+ arrowProps={arrowProps}
230
+ title={title}
231
+ content={content}
232
+ {...rest}
233
+ />
234
+ )
235
+ }}
236
+ </Transition>,
237
+ portalContainer ?? document.body
238
+ )}
239
+ </>
240
+ )
241
+ }
242
+
243
+ Popover.displayName = 'Popover'
@@ -0,0 +1,2 @@
1
+ export { Popover } from './Popover'
2
+ export type { PopoverProps } from './Popover'
@@ -0,0 +1,154 @@
1
+ import React, {
2
+ CSSProperties,
3
+ ElementType,
4
+ ForwardRefRenderFunction,
5
+ ReactElement,
6
+ ReactNode,
7
+ useId
8
+ } from 'react'
9
+ import classNames from 'classnames'
10
+
11
+ import { ContextColor } from '../../types'
12
+ import {
13
+ createPolymorphicComponent,
14
+ PolymorphicComponentProps,
15
+ PolymorphicRef
16
+ } from '../../utils/polymorphic'
17
+ import { ProgressBar } from './ProgressBar'
18
+
19
+ type ProgressStyle = CSSProperties & {
20
+ '--cx-height'?: string
21
+ }
22
+
23
+ type ProgressOwnProps<C extends ElementType> = {
24
+ /**
25
+ * Use to animate the stripes right to left via CSS3 animations.
26
+ */
27
+ animated?: boolean
28
+ /**
29
+ * A string of all className you want applied to the component.
30
+ */
31
+ className?: string
32
+ /**
33
+ * Sets the color of the component to one of Chassis context colors.
34
+ */
35
+ color?: ContextColor
36
+ /**
37
+ * Component used for the root node. Either a string to use a HTML element or a component.
38
+ */
39
+ component?: C
40
+ /**
41
+ * Sets the height of the component, via the `--cx-height` custom property. If you set that
42
+ * value the inner bar (and the striped pattern's tile size) automatically resizes accordingly.
43
+ */
44
+ height?: number
45
+ /**
46
+ * Shows the current value as text inside the bar, independently of `showValue` — combine both
47
+ * to get a `label` + `showValue` caption above the bar alongside an `inlineValue` reading
48
+ * inside it.
49
+ */
50
+ inlineValue?: boolean
51
+ /**
52
+ * A text label describing the progress. Rendered in a caption row above the bar. Also used as
53
+ * the default accessible name when no `aria-label`/`aria-labelledby` is supplied.
54
+ */
55
+ label?: ReactNode
56
+ /**
57
+ * Shows the current value as text in the caption row above the bar, alongside `label` when
58
+ * both are set, or alone otherwise. Use `inlineValue` to show it inside the bar instead.
59
+ */
60
+ showValue?: boolean
61
+ /**
62
+ * Adds a diagonal stripe pattern over the bar's background.
63
+ */
64
+ striped?: boolean
65
+ /**
66
+ * The percent to progress the ProgressBar (out of 100).
67
+ */
68
+ value?: number
69
+ }
70
+
71
+ export type ProgressProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
72
+ C,
73
+ ProgressOwnProps<C>
74
+ >
75
+
76
+ type ProgressComponent = (<C extends ElementType = 'div'>(
77
+ props: ProgressProps<C> & { ref?: PolymorphicRef<C> }
78
+ ) => ReactElement | null) & { displayName?: string }
79
+
80
+ function ProgressRender<C extends ElementType = 'div'>(
81
+ {
82
+ 'aria-label': ariaLabel,
83
+ animated,
84
+ children,
85
+ className,
86
+ color,
87
+ component,
88
+ height,
89
+ inlineValue,
90
+ label,
91
+ showValue,
92
+ striped,
93
+ style,
94
+ value = 0,
95
+ ...rest
96
+ }: ProgressProps<C>,
97
+ ref: PolymorphicRef<C>
98
+ ) {
99
+ const Component = component ?? 'div'
100
+ const _className = classNames('progress', className)
101
+ const clampedValue = Math.min(100, Math.max(0, value))
102
+
103
+ const barChildren =
104
+ children !== undefined ? children : inlineValue ? `${clampedValue}%` : undefined
105
+ const hasCaption = Boolean(label || showValue)
106
+
107
+ const labelId = useId()
108
+ const isNodeLabel = label != null && typeof label !== 'string'
109
+
110
+ const _style: ProgressStyle = { ...style }
111
+ if (height !== undefined) _style['--cx-height'] = `${height}px`
112
+
113
+ const progressElement = (
114
+ <Component
115
+ {...rest}
116
+ className={_className}
117
+ role="progressbar"
118
+ aria-valuenow={clampedValue}
119
+ aria-valuemin={0}
120
+ aria-valuemax={100}
121
+ aria-label={ariaLabel ?? (typeof label === 'string' ? label : undefined)}
122
+ aria-labelledby={!ariaLabel && isNodeLabel ? labelId : undefined}
123
+ style={_style}
124
+ ref={ref}
125
+ >
126
+ <ProgressBar animated={animated} color={color} striped={striped} value={clampedValue}>
127
+ {barChildren}
128
+ </ProgressBar>
129
+ </Component>
130
+ )
131
+
132
+ if (!hasCaption) return progressElement
133
+
134
+ return (
135
+ <div className="d-flex flex-column gap-xsmall">
136
+ <div
137
+ className={classNames('d-flex', label ? 'justify-content-between' : 'justify-content-end')}
138
+ >
139
+ {label ? (
140
+ <span className="font-strong" id={isNodeLabel ? labelId : undefined}>
141
+ {label}
142
+ </span>
143
+ ) : null}
144
+ {showValue ? <span className="font-strong">{clampedValue}%</span> : null}
145
+ </div>
146
+ {progressElement}
147
+ </div>
148
+ )
149
+ }
150
+
151
+ export const Progress = createPolymorphicComponent<ProgressComponent>(
152
+ ProgressRender as ForwardRefRenderFunction<Element, ProgressProps<ElementType>>,
153
+ 'Progress'
154
+ )
@@ -0,0 +1,88 @@
1
+ import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import { ContextColor } from '../../types'
5
+ import {
6
+ createPolymorphicComponent,
7
+ PolymorphicComponentProps,
8
+ PolymorphicRef
9
+ } from '../../utils/polymorphic'
10
+
11
+ type ProgressBarOwnProps<C extends ElementType> = {
12
+ /**
13
+ * Use to animate the stripes right to left via CSS3 animations.
14
+ */
15
+ animated?: boolean
16
+ /**
17
+ * A string of all className you want applied to the component.
18
+ */
19
+ className?: string
20
+ /**
21
+ * Sets the color of the component to one of Chassis context colors.
22
+ */
23
+ color?: ContextColor
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
+ * Adds a diagonal stripe pattern over the bar's background.
30
+ */
31
+ striped?: boolean
32
+ /**
33
+ * The percent to progress the ProgressBar.
34
+ */
35
+ value?: number
36
+ }
37
+
38
+ export type ProgressBarProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
39
+ C,
40
+ ProgressBarOwnProps<C>
41
+ >
42
+
43
+ type ProgressBarComponent = (<C extends ElementType = 'div'>(
44
+ props: ProgressBarProps<C> & { ref?: PolymorphicRef<C> }
45
+ ) => ReactElement | null) & { displayName?: string }
46
+
47
+ function ProgressBarRender<C extends ElementType = 'div'>(
48
+ {
49
+ children,
50
+ animated,
51
+ className,
52
+ color,
53
+ component,
54
+ striped,
55
+ style,
56
+ value = 0,
57
+ ...rest
58
+ }: ProgressBarProps<C>,
59
+ ref: PolymorphicRef<C>
60
+ ) {
61
+ const Component = component ?? 'div'
62
+ const clampedValue = Math.min(100, Math.max(0, value))
63
+ const _className = classNames(
64
+ 'progress-bar',
65
+ color && `bg-${color} fg-contrast`,
66
+ {
67
+ striped,
68
+ animated
69
+ },
70
+ className
71
+ )
72
+
73
+ return (
74
+ <Component
75
+ {...rest}
76
+ className={_className}
77
+ style={{ width: `${clampedValue}%`, ...style }}
78
+ ref={ref}
79
+ >
80
+ <span className="mx-2xsmall">{children}</span>
81
+ </Component>
82
+ )
83
+ }
84
+
85
+ export const ProgressBar = createPolymorphicComponent<ProgressBarComponent>(
86
+ ProgressBarRender as ForwardRefRenderFunction<Element, ProgressBarProps<ElementType>>,
87
+ 'ProgressBar'
88
+ )
@@ -0,0 +1,4 @@
1
+ export { Progress } from './Progress'
2
+ export type { ProgressProps } from './Progress'
3
+ export { ProgressBar } from './ProgressBar'
4
+ export type { ProgressBarProps } from './ProgressBar'