@primitiv-ui/react 0.1.0

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 (585) hide show
  1. package/README.md +79 -0
  2. package/package.json +59 -0
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
  4. package/src/AccessibleIcon/README.md +42 -0
  5. package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
  6. package/src/AccessibleIcon/index.ts +2 -0
  7. package/src/AccessibleIcon/types.ts +8 -0
  8. package/src/Accordion/Accordion.tsx +412 -0
  9. package/src/Accordion/AccordionContext.ts +12 -0
  10. package/src/Accordion/README.md +202 -0
  11. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
  12. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
  13. package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
  14. package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
  15. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
  16. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
  17. package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
  18. package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
  19. package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
  20. package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
  21. package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
  22. package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
  23. package/src/Accordion/hooks/index.ts +6 -0
  24. package/src/Accordion/hooks/useAccordionContext.ts +1 -0
  25. package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
  26. package/src/Accordion/hooks/useAccordionItem.ts +22 -0
  27. package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
  28. package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
  29. package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
  30. package/src/Accordion/index.ts +1 -0
  31. package/src/Accordion/types.ts +81 -0
  32. package/src/Alert/Alert.tsx +43 -0
  33. package/src/Alert/README.md +54 -0
  34. package/src/Alert/__tests__/Alert.test.tsx +28 -0
  35. package/src/Alert/index.ts +2 -0
  36. package/src/Alert/types.ts +5 -0
  37. package/src/Avatar/Avatar.tsx +149 -0
  38. package/src/Avatar/AvatarContext.ts +20 -0
  39. package/src/Avatar/README.md +116 -0
  40. package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
  41. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
  42. package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
  43. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
  44. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
  45. package/src/Avatar/hooks/index.ts +2 -0
  46. package/src/Avatar/hooks/useAvatarContext.ts +1 -0
  47. package/src/Avatar/hooks/useAvatarImage.ts +40 -0
  48. package/src/Avatar/index.ts +3 -0
  49. package/src/Avatar/types.ts +44 -0
  50. package/src/Breadcrumb/Breadcrumb.tsx +234 -0
  51. package/src/Breadcrumb/README.md +111 -0
  52. package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
  53. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
  54. package/src/Breadcrumb/index.ts +2 -0
  55. package/src/Breadcrumb/types.ts +22 -0
  56. package/src/Button/Button.tsx +95 -0
  57. package/src/Button/README.md +112 -0
  58. package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
  59. package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
  60. package/src/Button/__tests__/Button.contract.test.tsx +72 -0
  61. package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
  62. package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
  63. package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
  64. package/src/Button/index.ts +2 -0
  65. package/src/Button/types.ts +8 -0
  66. package/src/Carousel/Carousel.tsx +708 -0
  67. package/src/Carousel/CarouselContext.ts +11 -0
  68. package/src/Carousel/README.md +848 -0
  69. package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
  70. package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
  71. package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
  72. package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
  73. package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
  74. package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
  75. package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
  76. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
  77. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
  78. package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
  79. package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
  80. package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
  81. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
  82. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
  83. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
  84. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
  85. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
  86. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
  87. package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
  88. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
  89. package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
  90. package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
  91. package/src/Carousel/hooks/index.ts +4 -0
  92. package/src/Carousel/hooks/useCarouselContext.ts +13 -0
  93. package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
  94. package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
  95. package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
  96. package/src/Carousel/index.ts +3 -0
  97. package/src/Carousel/types.ts +400 -0
  98. package/src/Checkbox/Checkbox.tsx +228 -0
  99. package/src/Checkbox/CheckboxContext.ts +12 -0
  100. package/src/Checkbox/README.md +156 -0
  101. package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
  102. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
  103. package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
  104. package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
  105. package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
  106. package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
  107. package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
  108. package/src/Checkbox/hooks/index.ts +2 -0
  109. package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
  110. package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
  111. package/src/Checkbox/index.ts +1 -0
  112. package/src/Checkbox/types.ts +33 -0
  113. package/src/CheckboxCard/CheckboxCard.tsx +208 -0
  114. package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
  115. package/src/CheckboxCard/README.md +114 -0
  116. package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
  117. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
  118. package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
  119. package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
  120. package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
  121. package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
  122. package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
  123. package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
  124. package/src/CheckboxCard/hooks/index.ts +2 -0
  125. package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
  126. package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
  127. package/src/CheckboxCard/index.ts +3 -0
  128. package/src/CheckboxCard/types.ts +33 -0
  129. package/src/Collapsible/Collapsible.tsx +316 -0
  130. package/src/Collapsible/CollapsibleContext.ts +7 -0
  131. package/src/Collapsible/README.md +174 -0
  132. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
  133. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
  134. package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
  135. package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
  136. package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
  137. package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
  138. package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
  139. package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
  140. package/src/Collapsible/hooks/index.ts +2 -0
  141. package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
  142. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
  143. package/src/Collapsible/index.ts +1 -0
  144. package/src/Collapsible/types.ts +48 -0
  145. package/src/ContextMenu/ContextMenu.tsx +1004 -0
  146. package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
  147. package/src/ContextMenu/ContextMenuContext.ts +21 -0
  148. package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
  149. package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
  150. package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
  151. package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
  152. package/src/ContextMenu/README.md +275 -0
  153. package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
  154. package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
  155. package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
  156. package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
  157. package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
  158. package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
  159. package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
  160. package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
  161. package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
  162. package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
  163. package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
  164. package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
  165. package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
  166. package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
  167. package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
  168. package/src/ContextMenu/constants.ts +4 -0
  169. package/src/ContextMenu/index.ts +1 -0
  170. package/src/ContextMenu/types.ts +199 -0
  171. package/src/DirectionProvider/DirectionContext.ts +21 -0
  172. package/src/DirectionProvider/DirectionProvider.tsx +31 -0
  173. package/src/DirectionProvider/README.md +62 -0
  174. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
  175. package/src/DirectionProvider/index.ts +3 -0
  176. package/src/DirectionProvider/types.ts +10 -0
  177. package/src/Divider/Divider.tsx +57 -0
  178. package/src/Divider/README.md +57 -0
  179. package/src/Divider/__tests__/Divider.test.tsx +41 -0
  180. package/src/Divider/index.ts +1 -0
  181. package/src/Divider/types.ts +5 -0
  182. package/src/Dropdown/Dropdown.tsx +842 -0
  183. package/src/Dropdown/DropdownContentContext.ts +15 -0
  184. package/src/Dropdown/DropdownContext.ts +17 -0
  185. package/src/Dropdown/DropdownGroupContext.ts +8 -0
  186. package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
  187. package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
  188. package/src/Dropdown/DropdownSubContext.ts +15 -0
  189. package/src/Dropdown/README.md +284 -0
  190. package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
  191. package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
  192. package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
  193. package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
  194. package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
  195. package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
  196. package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
  197. package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
  198. package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
  199. package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
  200. package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
  201. package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
  202. package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
  203. package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
  204. package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
  205. package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
  206. package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
  207. package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
  208. package/src/Dropdown/constants.ts +4 -0
  209. package/src/Dropdown/hooks/index.ts +9 -0
  210. package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
  211. package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
  212. package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
  213. package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
  214. package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
  215. package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
  216. package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
  217. package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
  218. package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
  219. package/src/Dropdown/index.ts +1 -0
  220. package/src/Dropdown/types.ts +200 -0
  221. package/src/EmptyState/EmptyState.tsx +245 -0
  222. package/src/EmptyState/README.md +129 -0
  223. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
  224. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
  225. package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
  226. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
  227. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
  228. package/src/EmptyState/index.ts +2 -0
  229. package/src/EmptyState/types.ts +21 -0
  230. package/src/Field/Field.tsx +239 -0
  231. package/src/Field/FieldContext.ts +22 -0
  232. package/src/Field/README.md +167 -0
  233. package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
  234. package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
  235. package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
  236. package/src/Field/hooks/index.ts +2 -0
  237. package/src/Field/hooks/useFieldContext.ts +1 -0
  238. package/src/Field/hooks/useFieldProps.ts +57 -0
  239. package/src/Field/index.ts +2 -0
  240. package/src/Field/types.ts +33 -0
  241. package/src/Fieldset/Fieldset.tsx +104 -0
  242. package/src/Fieldset/README.md +74 -0
  243. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
  244. package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
  245. package/src/Fieldset/index.ts +2 -0
  246. package/src/Fieldset/types.ts +5 -0
  247. package/src/Input/Input.tsx +120 -0
  248. package/src/Input/README.md +180 -0
  249. package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
  250. package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
  251. package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
  252. package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
  253. package/src/Input/index.ts +2 -0
  254. package/src/Input/types.ts +7 -0
  255. package/src/InputGroup/InputGroup.tsx +228 -0
  256. package/src/InputGroup/README.md +178 -0
  257. package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
  258. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
  259. package/src/InputGroup/index.ts +2 -0
  260. package/src/InputGroup/types.ts +13 -0
  261. package/src/MillerColumns/MillerColumns.tsx +329 -0
  262. package/src/MillerColumns/MillerColumnsContext.ts +25 -0
  263. package/src/MillerColumns/README.md +278 -0
  264. package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
  265. package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
  266. package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
  267. package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
  268. package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
  269. package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
  270. package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
  271. package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
  272. package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
  273. package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
  274. package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
  275. package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
  276. package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
  277. package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
  278. package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
  279. package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
  280. package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
  281. package/src/MillerColumns/hooks/index.ts +7 -0
  282. package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
  283. package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
  284. package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
  285. package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
  286. package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
  287. package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
  288. package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
  289. package/src/MillerColumns/index.ts +3 -0
  290. package/src/MillerColumns/types.ts +93 -0
  291. package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
  292. package/src/MillerColumns/utils.ts +75 -0
  293. package/src/Modal/Modal.tsx +474 -0
  294. package/src/Modal/ModalContext.ts +13 -0
  295. package/src/Modal/README.md +207 -0
  296. package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
  297. package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
  298. package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
  299. package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
  300. package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
  301. package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
  302. package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
  303. package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
  304. package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
  305. package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
  306. package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
  307. package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
  308. package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
  309. package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
  310. package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
  311. package/src/Modal/hooks/index.ts +4 -0
  312. package/src/Modal/hooks/useModalContent.ts +62 -0
  313. package/src/Modal/hooks/useModalContext.ts +1 -0
  314. package/src/Modal/hooks/useModalRoot.ts +81 -0
  315. package/src/Modal/hooks/useModalTrigger.ts +25 -0
  316. package/src/Modal/index.ts +3 -0
  317. package/src/Modal/types.ts +76 -0
  318. package/src/Portal/Portal.tsx +28 -0
  319. package/src/Portal/README.md +70 -0
  320. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
  321. package/src/Portal/index.ts +2 -0
  322. package/src/Portal/types.ts +6 -0
  323. package/src/Progress/Progress.tsx +178 -0
  324. package/src/Progress/ProgressContext.ts +15 -0
  325. package/src/Progress/README.md +112 -0
  326. package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
  327. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
  328. package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
  329. package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
  330. package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
  331. package/src/Progress/hooks/index.ts +2 -0
  332. package/src/Progress/hooks/useProgressContext.ts +1 -0
  333. package/src/Progress/hooks/useProgressRoot.ts +45 -0
  334. package/src/Progress/index.ts +3 -0
  335. package/src/Progress/types.ts +43 -0
  336. package/src/RadioCard/README.md +133 -0
  337. package/src/RadioCard/RadioCard.tsx +334 -0
  338. package/src/RadioCard/RadioCardContext.ts +23 -0
  339. package/src/RadioCard/RadioCardItemContext.ts +10 -0
  340. package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
  341. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
  342. package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
  343. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
  344. package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
  345. package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
  346. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
  347. package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
  348. package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
  349. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
  350. package/src/RadioCard/hooks/index.ts +3 -0
  351. package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
  352. package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
  353. package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
  354. package/src/RadioCard/index.ts +4 -0
  355. package/src/RadioCard/types.ts +51 -0
  356. package/src/RadioGroup/README.md +185 -0
  357. package/src/RadioGroup/RadioGroup.tsx +353 -0
  358. package/src/RadioGroup/RadioGroupContext.ts +23 -0
  359. package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
  360. package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
  361. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
  362. package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
  363. package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
  364. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
  365. package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
  366. package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
  367. package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
  368. package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
  369. package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
  370. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
  371. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
  372. package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
  373. package/src/RadioGroup/hooks/index.ts +3 -0
  374. package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
  375. package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
  376. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
  377. package/src/RadioGroup/index.ts +1 -0
  378. package/src/RadioGroup/types.ts +51 -0
  379. package/src/Select/README.md +203 -0
  380. package/src/Select/Select.tsx +204 -0
  381. package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
  382. package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
  383. package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
  384. package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
  385. package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
  386. package/src/Select/__tests__/Select.group.test.tsx +42 -0
  387. package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
  388. package/src/Select/index.ts +2 -0
  389. package/src/Select/types.ts +89 -0
  390. package/src/SkipNav/README.md +87 -0
  391. package/src/SkipNav/SkipNav.tsx +116 -0
  392. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
  393. package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
  394. package/src/SkipNav/index.ts +1 -0
  395. package/src/SkipNav/types.ts +26 -0
  396. package/src/Slider/README.md +215 -0
  397. package/src/Slider/Slider.tsx +308 -0
  398. package/src/Slider/SliderContext.ts +24 -0
  399. package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
  400. package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
  401. package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
  402. package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
  403. package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
  404. package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
  405. package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
  406. package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
  407. package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
  408. package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
  409. package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
  410. package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
  411. package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
  412. package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
  413. package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
  414. package/src/Slider/hooks/index.ts +3 -0
  415. package/src/Slider/hooks/useSliderContext.ts +1 -0
  416. package/src/Slider/hooks/useSliderRoot.ts +197 -0
  417. package/src/Slider/hooks/useSliderThumb.ts +77 -0
  418. package/src/Slider/index.ts +3 -0
  419. package/src/Slider/types.ts +48 -0
  420. package/src/Slider/utils.ts +155 -0
  421. package/src/Slot/Slot.tsx +158 -0
  422. package/src/Slot/__tests__/Slot.test.tsx +163 -0
  423. package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
  424. package/src/Slot/composeEventHandlers.ts +38 -0
  425. package/src/Slot/index.ts +3 -0
  426. package/src/Slot/types.ts +5 -0
  427. package/src/Status/README.md +50 -0
  428. package/src/Status/Status.tsx +44 -0
  429. package/src/Status/__tests__/Status.test.tsx +28 -0
  430. package/src/Status/index.ts +2 -0
  431. package/src/Status/types.ts +5 -0
  432. package/src/Switch/README.md +121 -0
  433. package/src/Switch/Switch.tsx +167 -0
  434. package/src/Switch/SwitchContext.ts +10 -0
  435. package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
  436. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
  437. package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
  438. package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
  439. package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
  440. package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
  441. package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
  442. package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
  443. package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
  444. package/src/Switch/hooks/index.ts +2 -0
  445. package/src/Switch/hooks/useSwitchContext.ts +1 -0
  446. package/src/Switch/hooks/useSwitchRoot.ts +28 -0
  447. package/src/Switch/index.ts +3 -0
  448. package/src/Switch/types.ts +37 -0
  449. package/src/Table/README.md +205 -0
  450. package/src/Table/Table.tsx +380 -0
  451. package/src/Table/__tests__/Table.Body.test.tsx +61 -0
  452. package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
  453. package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
  454. package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
  455. package/src/Table/__tests__/Table.Head.test.tsx +61 -0
  456. package/src/Table/__tests__/Table.Header.test.tsx +73 -0
  457. package/src/Table/__tests__/Table.Root.test.tsx +49 -0
  458. package/src/Table/__tests__/Table.Row.test.tsx +67 -0
  459. package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
  460. package/src/Table/index.ts +1 -0
  461. package/src/Table/types.ts +63 -0
  462. package/src/Tabs/README.md +110 -0
  463. package/src/Tabs/Tabs.tsx +434 -0
  464. package/src/Tabs/TabsContext.ts +13 -0
  465. package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
  466. package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
  467. package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
  468. package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
  469. package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
  470. package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
  471. package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
  472. package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
  473. package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
  474. package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
  475. package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
  476. package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
  477. package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
  478. package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
  479. package/src/Tabs/hooks/index.ts +4 -0
  480. package/src/Tabs/hooks/useTabsContent.ts +27 -0
  481. package/src/Tabs/hooks/useTabsContext.ts +1 -0
  482. package/src/Tabs/hooks/useTabsRoot.ts +148 -0
  483. package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
  484. package/src/Tabs/index.ts +3 -0
  485. package/src/Tabs/types.ts +99 -0
  486. package/src/Tabs/utils.ts +8 -0
  487. package/src/Textarea/README.md +98 -0
  488. package/src/Textarea/Textarea.tsx +93 -0
  489. package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
  490. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
  491. package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
  492. package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
  493. package/src/Textarea/index.ts +2 -0
  494. package/src/Textarea/types.ts +7 -0
  495. package/src/Toggle/README.md +97 -0
  496. package/src/Toggle/Toggle.tsx +81 -0
  497. package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
  498. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
  499. package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
  500. package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
  501. package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
  502. package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
  503. package/src/Toggle/index.ts +2 -0
  504. package/src/Toggle/types.ts +23 -0
  505. package/src/ToggleGroup/README.md +137 -0
  506. package/src/ToggleGroup/ToggleGroup.tsx +298 -0
  507. package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
  508. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
  509. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
  510. package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
  511. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
  512. package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
  513. package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
  514. package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
  515. package/src/ToggleGroup/hooks/index.ts +2 -0
  516. package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
  517. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
  518. package/src/ToggleGroup/index.ts +2 -0
  519. package/src/ToggleGroup/types.ts +72 -0
  520. package/src/Tooltip/README.md +214 -0
  521. package/src/Tooltip/Tooltip.tsx +260 -0
  522. package/src/Tooltip/TooltipContext.ts +20 -0
  523. package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
  524. package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
  525. package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
  526. package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
  527. package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
  528. package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
  529. package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
  530. package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
  531. package/src/Tooltip/hooks/index.ts +4 -0
  532. package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
  533. package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
  534. package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
  535. package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
  536. package/src/Tooltip/index.ts +1 -0
  537. package/src/Tooltip/types.ts +64 -0
  538. package/src/Tree/README.md +339 -0
  539. package/src/Tree/Tree.tsx +571 -0
  540. package/src/Tree/TreeContext.ts +24 -0
  541. package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
  542. package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
  543. package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
  544. package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
  545. package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
  546. package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
  547. package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
  548. package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
  549. package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
  550. package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
  551. package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
  552. package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
  553. package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
  554. package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
  555. package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
  556. package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
  557. package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
  558. package/src/Tree/hooks/index.ts +3 -0
  559. package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
  560. package/src/Tree/hooks/useTreePath.ts +68 -0
  561. package/src/Tree/hooks/useTreeRoot.ts +279 -0
  562. package/src/Tree/index.ts +3 -0
  563. package/src/Tree/types.ts +224 -0
  564. package/src/Tree/utils.ts +59 -0
  565. package/src/VisuallyHidden/README.md +58 -0
  566. package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
  567. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
  568. package/src/VisuallyHidden/index.ts +2 -0
  569. package/src/VisuallyHidden/types.ts +5 -0
  570. package/src/hooks/index.ts +3 -0
  571. package/src/hooks/useCollection.ts +74 -0
  572. package/src/hooks/useControllableState.ts +81 -0
  573. package/src/hooks/useRovingTabindex.ts +178 -0
  574. package/src/index.ts +38 -0
  575. package/src/test/intersectionObserverPolyfill.ts +83 -0
  576. package/src/test/popoverPolyfill.ts +86 -0
  577. package/src/test/scrollPolyfill.ts +23 -0
  578. package/src/types.ts +13 -0
  579. package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
  580. package/src/utils/__tests__/deriveId.test.ts +28 -0
  581. package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
  582. package/src/utils/createStrictContext.ts +49 -0
  583. package/src/utils/deriveId.ts +31 -0
  584. package/src/utils/getKeyToActionMap.ts +95 -0
  585. package/src/utils/index.ts +3 -0
@@ -0,0 +1,569 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Carousel } from "..";
4
+
5
+ describe("Carousel basic rendering tests", () => {
6
+ describe("Carousel.Root", () => {
7
+ it("should render the Carousel.Root component", () => {
8
+ render(
9
+ <Carousel.Root ariaLabel="Featured products" data-testid="carousel-root" />,
10
+ );
11
+ const carouselRoot = screen.getByTestId("carousel-root");
12
+
13
+ expect(carouselRoot).toBeVisible();
14
+ });
15
+
16
+ it('should apply aria-roledescription="carousel" so assistive tech announces the widget as a carousel', () => {
17
+ render(
18
+ <Carousel.Root ariaLabel="Featured products" data-testid="carousel-root" />,
19
+ );
20
+ const carouselRoot = screen.getByTestId("carousel-root");
21
+
22
+ expect(carouselRoot).toHaveAttribute("aria-roledescription", "carousel");
23
+ });
24
+
25
+ it("should expose the required ariaLabel prop as aria-label on the rendered element", () => {
26
+ const label = "Featured products";
27
+ render(<Carousel.Root ariaLabel={label} data-testid="carousel-root" />);
28
+ const carouselRoot = screen.getByTestId("carousel-root");
29
+
30
+ expect(carouselRoot).toHaveAttribute("aria-label", label);
31
+ });
32
+
33
+ it("should accept ariaLabelledBy as an alternative to ariaLabel", () => {
34
+ const labelId = "carousel-heading";
35
+ render(
36
+ <>
37
+ <h2 id={labelId}>Featured products</h2>
38
+ <Carousel.Root ariaLabelledBy={labelId} data-testid="carousel-root" />
39
+ </>,
40
+ );
41
+ const carouselRoot = screen.getByTestId("carousel-root");
42
+
43
+ expect(carouselRoot).toHaveAttribute("aria-labelledby", labelId);
44
+ });
45
+
46
+ it("should render as a <section> element by default to match the WAI-ARIA carousel pattern", () => {
47
+ render(
48
+ <Carousel.Root ariaLabel="Featured products" data-testid="carousel-root" />,
49
+ );
50
+ const carouselRoot = screen.getByTestId("carousel-root");
51
+
52
+ expect(carouselRoot.tagName).toBe("SECTION");
53
+ });
54
+
55
+ it("should accept a className prop", () => {
56
+ const testClass = "custom-carousel-root";
57
+ render(
58
+ <Carousel.Root
59
+ ariaLabel="Featured products"
60
+ className={testClass}
61
+ data-testid="carousel-root"
62
+ />,
63
+ );
64
+ const carouselRoot = screen.getByTestId("carousel-root");
65
+
66
+ expect(carouselRoot).toHaveAttribute("class", testClass);
67
+ });
68
+
69
+ it("should apply a className of empty string by default when not specified", () => {
70
+ render(
71
+ <Carousel.Root ariaLabel="Featured products" data-testid="carousel-root" />,
72
+ );
73
+ const carouselRoot = screen.getByTestId("carousel-root");
74
+
75
+ expect(carouselRoot).toHaveAttribute("class", "");
76
+ });
77
+
78
+ it("should render children inside Carousel.Root", () => {
79
+ render(
80
+ <Carousel.Root ariaLabel="Featured products">
81
+ <p>Carousel body</p>
82
+ </Carousel.Root>,
83
+ );
84
+
85
+ expect(screen.getByText("Carousel body")).toBeVisible();
86
+ });
87
+ });
88
+
89
+ describe("Carousel.Viewport", () => {
90
+ it("should render the Carousel.Viewport component inside Carousel.Root", () => {
91
+ render(
92
+ <Carousel.Root ariaLabel="Featured products">
93
+ <Carousel.Viewport data-testid="carousel-viewport" />
94
+ </Carousel.Root>,
95
+ );
96
+ const viewport = screen.getByTestId("carousel-viewport");
97
+
98
+ expect(viewport).toBeVisible();
99
+ });
100
+
101
+ it("should render children inside the viewport", () => {
102
+ render(
103
+ <Carousel.Root ariaLabel="Featured products">
104
+ <Carousel.Viewport>
105
+ <p>Slide body</p>
106
+ </Carousel.Viewport>
107
+ </Carousel.Root>,
108
+ );
109
+
110
+ expect(screen.getByText("Slide body")).toBeVisible();
111
+ });
112
+
113
+ it("should set a data-carousel-viewport attribute as a CSS-targeting hook for the recommended scroll-snap rules", () => {
114
+ render(
115
+ <Carousel.Root ariaLabel="Featured products">
116
+ <Carousel.Viewport data-testid="carousel-viewport" />
117
+ </Carousel.Root>,
118
+ );
119
+ const viewport = screen.getByTestId("carousel-viewport");
120
+
121
+ expect(viewport).toHaveAttribute("data-carousel-viewport", "");
122
+ });
123
+
124
+ it("should accept a className prop", () => {
125
+ const testClass = "custom-viewport";
126
+ render(
127
+ <Carousel.Root ariaLabel="Featured products">
128
+ <Carousel.Viewport
129
+ className={testClass}
130
+ data-testid="carousel-viewport"
131
+ />
132
+ </Carousel.Root>,
133
+ );
134
+ const viewport = screen.getByTestId("carousel-viewport");
135
+
136
+ expect(viewport).toHaveAttribute("class", testClass);
137
+ });
138
+
139
+ it("should apply a className of empty string by default when not specified", () => {
140
+ render(
141
+ <Carousel.Root ariaLabel="Featured products">
142
+ <Carousel.Viewport data-testid="carousel-viewport" />
143
+ </Carousel.Root>,
144
+ );
145
+ const viewport = screen.getByTestId("carousel-viewport");
146
+
147
+ expect(viewport).toHaveAttribute("class", "");
148
+ });
149
+
150
+ it("should render slides in source order without injecting extra DOM nodes", () => {
151
+ render(
152
+ <Carousel.Root ariaLabel="Featured products">
153
+ <Carousel.Viewport data-testid="carousel-viewport">
154
+ <Carousel.Slide data-testid="slide-0" />
155
+ <Carousel.Slide data-testid="slide-1" />
156
+ <Carousel.Slide data-testid="slide-2" />
157
+ </Carousel.Viewport>
158
+ </Carousel.Root>,
159
+ );
160
+ const viewport = screen.getByTestId("carousel-viewport");
161
+
162
+ expect(viewport.querySelectorAll("[data-carousel-slide]")).toHaveLength(3);
163
+ expect(
164
+ viewport.querySelectorAll("[data-carousel-slide-clone]"),
165
+ ).toHaveLength(0);
166
+ });
167
+ });
168
+
169
+ describe("Carousel.Slide", () => {
170
+ it("should render the Carousel.Slide component inside the viewport", () => {
171
+ render(
172
+ <Carousel.Root ariaLabel="Featured products">
173
+ <Carousel.Viewport>
174
+ <Carousel.Slide data-testid="slide" />
175
+ </Carousel.Viewport>
176
+ </Carousel.Root>,
177
+ );
178
+
179
+ expect(screen.getByTestId("slide")).toBeVisible();
180
+ });
181
+
182
+ it('should render with role="group" so assistive tech treats each slide as a discrete group', () => {
183
+ render(
184
+ <Carousel.Root ariaLabel="Featured products">
185
+ <Carousel.Viewport>
186
+ <Carousel.Slide data-testid="slide" />
187
+ </Carousel.Viewport>
188
+ </Carousel.Root>,
189
+ );
190
+
191
+ expect(screen.getByTestId("slide")).toHaveAttribute("role", "group");
192
+ });
193
+
194
+ it('should render with aria-roledescription="slide" per the WAI-ARIA Carousel pattern', () => {
195
+ render(
196
+ <Carousel.Root ariaLabel="Featured products">
197
+ <Carousel.Viewport>
198
+ <Carousel.Slide data-testid="slide" />
199
+ </Carousel.Viewport>
200
+ </Carousel.Root>,
201
+ );
202
+
203
+ expect(screen.getByTestId("slide")).toHaveAttribute(
204
+ "aria-roledescription",
205
+ "slide",
206
+ );
207
+ });
208
+
209
+ it("should set a data-carousel-slide attribute as a CSS-targeting hook", () => {
210
+ render(
211
+ <Carousel.Root ariaLabel="Featured products">
212
+ <Carousel.Viewport>
213
+ <Carousel.Slide data-testid="slide" />
214
+ </Carousel.Viewport>
215
+ </Carousel.Root>,
216
+ );
217
+
218
+ expect(screen.getByTestId("slide")).toHaveAttribute(
219
+ "data-carousel-slide",
220
+ "",
221
+ );
222
+ });
223
+
224
+ it("should render children inside the slide", () => {
225
+ render(
226
+ <Carousel.Root ariaLabel="Featured products">
227
+ <Carousel.Viewport>
228
+ <Carousel.Slide>
229
+ <p>Slide content</p>
230
+ </Carousel.Slide>
231
+ </Carousel.Viewport>
232
+ </Carousel.Root>,
233
+ );
234
+
235
+ expect(screen.getByText("Slide content")).toBeVisible();
236
+ });
237
+
238
+ it("should accept a className prop", () => {
239
+ const testClass = "custom-slide";
240
+ render(
241
+ <Carousel.Root ariaLabel="Featured products">
242
+ <Carousel.Viewport>
243
+ <Carousel.Slide className={testClass} data-testid="slide" />
244
+ </Carousel.Viewport>
245
+ </Carousel.Root>,
246
+ );
247
+
248
+ expect(screen.getByTestId("slide")).toHaveAttribute("class", testClass);
249
+ });
250
+
251
+ it("should apply a className of empty string by default when not specified", () => {
252
+ render(
253
+ <Carousel.Root ariaLabel="Featured products">
254
+ <Carousel.Viewport>
255
+ <Carousel.Slide data-testid="slide" />
256
+ </Carousel.Viewport>
257
+ </Carousel.Root>,
258
+ );
259
+
260
+ expect(screen.getByTestId("slide")).toHaveAttribute("class", "");
261
+ });
262
+
263
+ it("should expose its zero-based registration position as data-index", () => {
264
+ render(
265
+ <Carousel.Root ariaLabel="Featured products">
266
+ <Carousel.Viewport>
267
+ <Carousel.Slide data-testid="slide-0" />
268
+ <Carousel.Slide data-testid="slide-1" />
269
+ <Carousel.Slide data-testid="slide-2" />
270
+ </Carousel.Viewport>
271
+ </Carousel.Root>,
272
+ );
273
+
274
+ expect(screen.getByTestId("slide-0")).toHaveAttribute("data-index", "0");
275
+ expect(screen.getByTestId("slide-1")).toHaveAttribute("data-index", "1");
276
+ expect(screen.getByTestId("slide-2")).toHaveAttribute("data-index", "2");
277
+ });
278
+
279
+ it("should expose the total registered slide count as data-total on every slide", () => {
280
+ render(
281
+ <Carousel.Root ariaLabel="Featured products">
282
+ <Carousel.Viewport>
283
+ <Carousel.Slide data-testid="slide-0" />
284
+ <Carousel.Slide data-testid="slide-1" />
285
+ <Carousel.Slide data-testid="slide-2" />
286
+ </Carousel.Viewport>
287
+ </Carousel.Root>,
288
+ );
289
+
290
+ ["slide-0", "slide-1", "slide-2"].forEach((id) => {
291
+ expect(screen.getByTestId(id)).toHaveAttribute("data-total", "3");
292
+ });
293
+ });
294
+
295
+ it("should update data-index and data-total when slides are unmounted", () => {
296
+ const { rerender } = render(
297
+ <Carousel.Root ariaLabel="Featured products">
298
+ <Carousel.Viewport>
299
+ <Carousel.Slide data-testid="slide-0" />
300
+ <Carousel.Slide data-testid="slide-1" />
301
+ <Carousel.Slide data-testid="slide-2" />
302
+ </Carousel.Viewport>
303
+ </Carousel.Root>,
304
+ );
305
+
306
+ rerender(
307
+ <Carousel.Root ariaLabel="Featured products">
308
+ <Carousel.Viewport>
309
+ <Carousel.Slide data-testid="slide-0" />
310
+ <Carousel.Slide data-testid="slide-2" />
311
+ </Carousel.Viewport>
312
+ </Carousel.Root>,
313
+ );
314
+
315
+ expect(screen.getByTestId("slide-0")).toHaveAttribute("data-index", "0");
316
+ expect(screen.getByTestId("slide-2")).toHaveAttribute("data-index", "1");
317
+ expect(screen.getByTestId("slide-0")).toHaveAttribute("data-total", "2");
318
+ expect(screen.getByTestId("slide-2")).toHaveAttribute("data-total", "2");
319
+ });
320
+ });
321
+
322
+ describe("Carousel.Slide data-state", () => {
323
+ it('should mark the slide at the active page with data-state="active" and the rest as "inactive"', () => {
324
+ render(
325
+ <Carousel.Root ariaLabel="Featured products">
326
+ <Carousel.Viewport>
327
+ <Carousel.Slide data-testid="slide-0" />
328
+ <Carousel.Slide data-testid="slide-1" />
329
+ <Carousel.Slide data-testid="slide-2" />
330
+ </Carousel.Viewport>
331
+ </Carousel.Root>,
332
+ );
333
+
334
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
335
+ "data-state",
336
+ "active",
337
+ );
338
+ expect(screen.getByTestId("slide-1")).toHaveAttribute(
339
+ "data-state",
340
+ "inactive",
341
+ );
342
+ expect(screen.getByTestId("slide-2")).toHaveAttribute(
343
+ "data-state",
344
+ "inactive",
345
+ );
346
+ });
347
+ });
348
+
349
+ describe("Carousel.NextTrigger", () => {
350
+ it("should render the Carousel.NextTrigger component inside Carousel.Root", () => {
351
+ render(
352
+ <Carousel.Root ariaLabel="Featured products">
353
+ <Carousel.NextTrigger>Next</Carousel.NextTrigger>
354
+ </Carousel.Root>,
355
+ );
356
+
357
+ expect(screen.getByRole("button", { name: "Next" })).toBeVisible();
358
+ });
359
+
360
+ it('should render with type="button"', () => {
361
+ render(
362
+ <Carousel.Root ariaLabel="Featured products">
363
+ <Carousel.NextTrigger>Next</Carousel.NextTrigger>
364
+ </Carousel.Root>,
365
+ );
366
+
367
+ expect(screen.getByRole("button", { name: "Next" })).toHaveAttribute(
368
+ "type",
369
+ "button",
370
+ );
371
+ });
372
+
373
+ it("should accept a className prop", () => {
374
+ const testClass = "custom-next";
375
+ render(
376
+ <Carousel.Root ariaLabel="Featured products">
377
+ <Carousel.NextTrigger className={testClass}>Next</Carousel.NextTrigger>
378
+ </Carousel.Root>,
379
+ );
380
+
381
+ expect(screen.getByRole("button", { name: "Next" })).toHaveAttribute(
382
+ "class",
383
+ testClass,
384
+ );
385
+ });
386
+
387
+ it("should apply a className of empty string by default when not specified", () => {
388
+ render(
389
+ <Carousel.Root ariaLabel="Featured products">
390
+ <Carousel.NextTrigger>Next</Carousel.NextTrigger>
391
+ </Carousel.Root>,
392
+ );
393
+
394
+ expect(screen.getByRole("button", { name: "Next" })).toHaveAttribute(
395
+ "class",
396
+ "",
397
+ );
398
+ });
399
+ });
400
+
401
+ describe("Carousel.PreviousTrigger", () => {
402
+ it("should render the Carousel.PreviousTrigger component inside Carousel.Root", () => {
403
+ render(
404
+ <Carousel.Root ariaLabel="Featured products">
405
+ <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
406
+ </Carousel.Root>,
407
+ );
408
+
409
+ expect(screen.getByRole("button", { name: "Previous" })).toBeVisible();
410
+ });
411
+
412
+ it('should render with type="button"', () => {
413
+ render(
414
+ <Carousel.Root ariaLabel="Featured products">
415
+ <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
416
+ </Carousel.Root>,
417
+ );
418
+
419
+ expect(screen.getByRole("button", { name: "Previous" })).toHaveAttribute(
420
+ "type",
421
+ "button",
422
+ );
423
+ });
424
+
425
+ it("should accept a className prop", () => {
426
+ const testClass = "custom-prev";
427
+ render(
428
+ <Carousel.Root ariaLabel="Featured products">
429
+ <Carousel.PreviousTrigger className={testClass}>
430
+ Previous
431
+ </Carousel.PreviousTrigger>
432
+ </Carousel.Root>,
433
+ );
434
+
435
+ expect(screen.getByRole("button", { name: "Previous" })).toHaveAttribute(
436
+ "class",
437
+ testClass,
438
+ );
439
+ });
440
+
441
+ it("should apply a className of empty string by default when not specified", () => {
442
+ render(
443
+ <Carousel.Root ariaLabel="Featured products">
444
+ <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
445
+ </Carousel.Root>,
446
+ );
447
+
448
+ expect(screen.getByRole("button", { name: "Previous" })).toHaveAttribute(
449
+ "class",
450
+ "",
451
+ );
452
+ });
453
+ });
454
+
455
+ describe("Carousel.Slide aria-label", () => {
456
+ it('should auto-generate an aria-label of "N of M" using the slide\'s index and the live total', () => {
457
+ render(
458
+ <Carousel.Root ariaLabel="Featured products">
459
+ <Carousel.Viewport>
460
+ <Carousel.Slide data-testid="slide-0" />
461
+ <Carousel.Slide data-testid="slide-1" />
462
+ <Carousel.Slide data-testid="slide-2" />
463
+ </Carousel.Viewport>
464
+ </Carousel.Root>,
465
+ );
466
+
467
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
468
+ "aria-label",
469
+ "1 of 3",
470
+ );
471
+ expect(screen.getByTestId("slide-1")).toHaveAttribute(
472
+ "aria-label",
473
+ "2 of 3",
474
+ );
475
+ expect(screen.getByTestId("slide-2")).toHaveAttribute(
476
+ "aria-label",
477
+ "3 of 3",
478
+ );
479
+ });
480
+
481
+ it("should update the auto-generated aria-label when slides are added or removed at runtime", () => {
482
+ const { rerender } = render(
483
+ <Carousel.Root ariaLabel="Featured products">
484
+ <Carousel.Viewport>
485
+ <Carousel.Slide data-testid="slide-0" />
486
+ <Carousel.Slide data-testid="slide-1" />
487
+ <Carousel.Slide data-testid="slide-2" />
488
+ </Carousel.Viewport>
489
+ </Carousel.Root>,
490
+ );
491
+
492
+ rerender(
493
+ <Carousel.Root ariaLabel="Featured products">
494
+ <Carousel.Viewport>
495
+ <Carousel.Slide data-testid="slide-0" />
496
+ <Carousel.Slide data-testid="slide-2" />
497
+ </Carousel.Viewport>
498
+ </Carousel.Root>,
499
+ );
500
+
501
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
502
+ "aria-label",
503
+ "1 of 2",
504
+ );
505
+ expect(screen.getByTestId("slide-2")).toHaveAttribute(
506
+ "aria-label",
507
+ "2 of 2",
508
+ );
509
+ });
510
+
511
+ it("should let the consumer override the auto-generated aria-label via the ariaLabel prop", () => {
512
+ const customLabel = "Hand-picked for you";
513
+ render(
514
+ <Carousel.Root ariaLabel="Featured products">
515
+ <Carousel.Viewport>
516
+ <Carousel.Slide ariaLabel={customLabel} data-testid="slide" />
517
+ </Carousel.Viewport>
518
+ </Carousel.Root>,
519
+ );
520
+
521
+ expect(screen.getByTestId("slide")).toHaveAttribute(
522
+ "aria-label",
523
+ customLabel,
524
+ );
525
+ });
526
+
527
+ it("should keep ariaLabel stable as siblings mount and unmount around it", () => {
528
+ const customLabel = "Hand-picked for you";
529
+ const { rerender } = render(
530
+ <Carousel.Root ariaLabel="Featured products">
531
+ <Carousel.Viewport>
532
+ <Carousel.Slide data-testid="slide-0" />
533
+ <Carousel.Slide ariaLabel={customLabel} data-testid="slide-1" />
534
+ <Carousel.Slide data-testid="slide-2" />
535
+ </Carousel.Viewport>
536
+ </Carousel.Root>,
537
+ );
538
+
539
+ rerender(
540
+ <Carousel.Root ariaLabel="Featured products">
541
+ <Carousel.Viewport>
542
+ <Carousel.Slide ariaLabel={customLabel} data-testid="slide-1" />
543
+ </Carousel.Viewport>
544
+ </Carousel.Root>,
545
+ );
546
+
547
+ expect(screen.getByTestId("slide-1")).toHaveAttribute(
548
+ "aria-label",
549
+ customLabel,
550
+ );
551
+ });
552
+ });
553
+
554
+ describe("context errors", () => {
555
+ it.each([
556
+ ["Carousel.Viewport", () => <Carousel.Viewport />],
557
+ ["Carousel.Slide", () => <Carousel.Slide />],
558
+ ["Carousel.NextTrigger", () => <Carousel.NextTrigger />],
559
+ ["Carousel.PreviousTrigger", () => <Carousel.PreviousTrigger />],
560
+ ] as const)(
561
+ "should throw an error when %s is used outside Carousel.Root",
562
+ (_, ComponentRenderer) => {
563
+ expect(() => {
564
+ render(ComponentRenderer());
565
+ }).toThrow("Component must be rendered as a child of Carousel.Root");
566
+ },
567
+ );
568
+ });
569
+ });
@@ -0,0 +1,137 @@
1
+ import { useState } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ import { Carousel } from "..";
6
+
7
+ describe("Carousel controlled page state", () => {
8
+ it("should derive the active slide from the controlled page prop", () => {
9
+ render(
10
+ <Carousel.Root
11
+ ariaLabel="Featured products"
12
+ page={1}
13
+ onPageChange={() => {}}
14
+ >
15
+ <Carousel.Viewport>
16
+ <Carousel.Slide data-testid="slide-0" />
17
+ <Carousel.Slide data-testid="slide-1" />
18
+ <Carousel.Slide data-testid="slide-2" />
19
+ </Carousel.Viewport>
20
+ </Carousel.Root>,
21
+ );
22
+
23
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
24
+ "data-state",
25
+ "inactive",
26
+ );
27
+ expect(screen.getByTestId("slide-1")).toHaveAttribute(
28
+ "data-state",
29
+ "active",
30
+ );
31
+ expect(screen.getByTestId("slide-2")).toHaveAttribute(
32
+ "data-state",
33
+ "inactive",
34
+ );
35
+ });
36
+
37
+ it("should call onPageChange with page+1 when NextTrigger is clicked, without updating any internal state", async () => {
38
+ const user = userEvent.setup();
39
+ const onPageChange = vi.fn();
40
+ render(
41
+ <Carousel.Root
42
+ ariaLabel="Featured products"
43
+ page={0}
44
+ onPageChange={onPageChange}
45
+ >
46
+ <Carousel.Viewport>
47
+ <Carousel.Slide data-testid="slide-0" />
48
+ <Carousel.Slide data-testid="slide-1" />
49
+ </Carousel.Viewport>
50
+ <Carousel.NextTrigger>Next</Carousel.NextTrigger>
51
+ </Carousel.Root>,
52
+ );
53
+
54
+ await user.click(screen.getByRole("button", { name: "Next" }));
55
+
56
+ expect(onPageChange).toHaveBeenCalledTimes(1);
57
+ expect(onPageChange).toHaveBeenCalledWith(1);
58
+ // Parent has not re-rendered with a new page prop, so the active
59
+ // slide must remain at the controlled value.
60
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
61
+ "data-state",
62
+ "active",
63
+ );
64
+ expect(screen.getByTestId("slide-1")).toHaveAttribute(
65
+ "data-state",
66
+ "inactive",
67
+ );
68
+ });
69
+
70
+ it("should call onPageChange with page-1 when PreviousTrigger is clicked", async () => {
71
+ const user = userEvent.setup();
72
+ const onPageChange = vi.fn();
73
+ render(
74
+ <Carousel.Root
75
+ ariaLabel="Featured products"
76
+ page={2}
77
+ onPageChange={onPageChange}
78
+ >
79
+ <Carousel.Viewport>
80
+ <Carousel.Slide data-testid="slide-0" />
81
+ <Carousel.Slide data-testid="slide-1" />
82
+ <Carousel.Slide data-testid="slide-2" />
83
+ </Carousel.Viewport>
84
+ <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
85
+ </Carousel.Root>,
86
+ );
87
+
88
+ await user.click(screen.getByRole("button", { name: "Previous" }));
89
+
90
+ expect(onPageChange).toHaveBeenCalledTimes(1);
91
+ expect(onPageChange).toHaveBeenCalledWith(1);
92
+ });
93
+
94
+ it("should reflect the parent's controlled page across both trigger clicks and external state changes", async () => {
95
+ const user = userEvent.setup();
96
+ function Parent() {
97
+ const [page, setPage] = useState(0);
98
+ return (
99
+ <>
100
+ <button type="button" onClick={() => setPage(0)}>
101
+ Reset
102
+ </button>
103
+ <Carousel.Root
104
+ ariaLabel="Featured products"
105
+ page={page}
106
+ onPageChange={setPage}
107
+ >
108
+ <Carousel.Viewport>
109
+ <Carousel.Slide data-testid="slide-0" />
110
+ <Carousel.Slide data-testid="slide-1" />
111
+ <Carousel.Slide data-testid="slide-2" />
112
+ </Carousel.Viewport>
113
+ <Carousel.NextTrigger>Next</Carousel.NextTrigger>
114
+ </Carousel.Root>
115
+ </>
116
+ );
117
+ }
118
+
119
+ render(<Parent />);
120
+
121
+ await user.click(screen.getByRole("button", { name: "Next" }));
122
+ await user.click(screen.getByRole("button", { name: "Next" }));
123
+ expect(screen.getByTestId("slide-2")).toHaveAttribute(
124
+ "data-state",
125
+ "active",
126
+ );
127
+
128
+ // External (non-trigger) state change must be reflected too — proves the
129
+ // visual is driven by the controlled prop, not by an internal counter
130
+ // that advances independently of the parent.
131
+ await user.click(screen.getByRole("button", { name: "Reset" }));
132
+ expect(screen.getByTestId("slide-0")).toHaveAttribute(
133
+ "data-state",
134
+ "active",
135
+ );
136
+ });
137
+ });