@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,412 @@
1
+ import { Ref, useEffect } from "react";
2
+
3
+ import { useDirection } from "../DirectionProvider";
4
+ import { Slot } from "../Slot";
5
+
6
+ import type {
7
+ AccordionRootProps,
8
+ AccordionItemProps,
9
+ AccordionTriggerProps,
10
+ AccordionHeaderProps,
11
+ AccordionContentProps,
12
+ AccordionTriggerIconProps,
13
+ } from "./types";
14
+
15
+ import type { HeadingTag } from "../types";
16
+
17
+ import { AccordionContext, AccordionItemContext } from "./AccordionContext";
18
+ import {
19
+ useAccordionContext,
20
+ useAccordionHeaderContext,
21
+ useAccordionItem,
22
+ useAccordionItemContext,
23
+ useAccordionRoot,
24
+ } from "./hooks";
25
+ import { useAccordionTrigger } from "./hooks/useAccordionTrigger";
26
+
27
+ /**
28
+ * The root of an Accordion widget — owns the expanded-items state, provides
29
+ * context to descendants, and renders a plain `<div>`.
30
+ *
31
+ * Supports two state modes, statically discriminated at the type level:
32
+ *
33
+ * - **Uncontrolled** — pass {@link AccordionRootUncontrolledProps.defaultValue | `defaultValue`}
34
+ * (or omit it to start with all items collapsed). The component owns and
35
+ * updates the expanded set internally.
36
+ * - **Controlled** — pass {@link AccordionRootControlledProps.value | `value`} *and*
37
+ * {@link AccordionRootControlledProps.onValueChange | `onValueChange`} together.
38
+ * The parent owns the expanded set; the component defers every state change
39
+ * back through the callback.
40
+ *
41
+ * In both modes, `multiple` controls whether more than one item can be open
42
+ * simultaneously. By default only one item can be expanded at a time — opening
43
+ * a new item collapses the previous one.
44
+ *
45
+ * **Styling hooks.** `data-orientation="vertical" | "horizontal"` is set on
46
+ * the rendered container. No `aria-orientation` is emitted because it is not
47
+ * valid on a `<div>` according to the WAI-ARIA spec.
48
+ *
49
+ * **Reading direction.** `dir` (`"ltr"` / `"rtl"`) sets the arrow-key
50
+ * direction for horizontal accordions and the container's `dir` attribute.
51
+ * When omitted, it is inherited from the nearest {@link DirectionProvider},
52
+ * falling back to `"ltr"` when there is no provider.
53
+ *
54
+ * @example Uncontrolled — single open item
55
+ * ```tsx
56
+ * <Accordion.Root defaultValue="item-1">
57
+ * <Accordion.Item value="item-1">…</Accordion.Item>
58
+ * <Accordion.Item value="item-2">…</Accordion.Item>
59
+ * </Accordion.Root>
60
+ * ```
61
+ *
62
+ * @example Controlled — multiple open items
63
+ * ```tsx
64
+ * const [expanded, setExpanded] = useState<string[]>([]);
65
+ *
66
+ * <Accordion.Root multiple value={expanded} onValueChange={setExpanded}>
67
+ * <Accordion.Item value="shipping">…</Accordion.Item>
68
+ * <Accordion.Item value="returns">…</Accordion.Item>
69
+ * </Accordion.Root>
70
+ * ```
71
+ */
72
+ export function AccordionRoot({
73
+ children,
74
+ multiple = false,
75
+ defaultValue,
76
+ value: controlledValue,
77
+ onValueChange,
78
+ orientation = "vertical",
79
+ dir,
80
+ ...rest
81
+ }: AccordionRootProps) {
82
+ const resolvedDir = dir ?? useDirection();
83
+ const { contextValue } = useAccordionRoot(
84
+ controlledValue,
85
+ defaultValue,
86
+ multiple,
87
+ onValueChange,
88
+ orientation,
89
+ resolvedDir,
90
+ );
91
+
92
+ return (
93
+ <AccordionContext.Provider value={contextValue}>
94
+ <div data-orientation={orientation} dir={resolvedDir} {...rest}>
95
+ {children}
96
+ </div>
97
+ </AccordionContext.Provider>
98
+ );
99
+ }
100
+
101
+ AccordionRoot.displayName = "AccordionRoot";
102
+
103
+ /**
104
+ * A single collapsible section within an accordion. Wraps one
105
+ * `Accordion.Header` + `Accordion.Content` pair and tracks their shared
106
+ * expanded / collapsed state via context.
107
+ *
108
+ * The {@link AccordionItemProps.value | `value`} prop is the stable identifier
109
+ * used to match this item against the root's expanded set. If omitted, a
110
+ * stable auto-generated ID is used via `useId()` — useful for fully anonymous
111
+ * items where the consumer doesn't need to drive state from outside.
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * <Accordion.Item value="shipping">
116
+ * <Accordion.Header>
117
+ * <Accordion.Trigger>Shipping</Accordion.Trigger>
118
+ * </Accordion.Header>
119
+ * <Accordion.Content>Free on orders over £50.</Accordion.Content>
120
+ * </Accordion.Item>
121
+ * ```
122
+ */
123
+ export function AccordionItem({
124
+ children,
125
+ value,
126
+ ...rest
127
+ }: AccordionItemProps) {
128
+ const { contextValue } = useAccordionItem(value);
129
+
130
+ return (
131
+ <AccordionItemContext.Provider value={contextValue}>
132
+ <div {...rest}>{children}</div>
133
+ </AccordionItemContext.Provider>
134
+ );
135
+ }
136
+
137
+ AccordionItem.displayName = "AccordionItem";
138
+
139
+ /**
140
+ * Renders the heading element that wraps an `Accordion.Trigger`. Defaults to
141
+ * `<h3>` but the level is configurable via the
142
+ * {@link AccordionHeaderProps.level | `level`} prop so the heading hierarchy
143
+ * of the surrounding page is preserved.
144
+ *
145
+ * The WAI-ARIA Accordion pattern requires each trigger to be wrapped in a
146
+ * heading at the appropriate level for its position in the document outline.
147
+ *
148
+ * @example
149
+ * ```tsx
150
+ * <Accordion.Header level={2}>
151
+ * <Accordion.Trigger>Shipping policy</Accordion.Trigger>
152
+ * </Accordion.Header>
153
+ * ```
154
+ */
155
+ export function AccordionHeader({
156
+ children,
157
+ level = 3,
158
+ ...rest
159
+ }: AccordionHeaderProps) {
160
+ useAccordionHeaderContext();
161
+ const HeadingTag: HeadingTag = `h${level}`;
162
+
163
+ return <HeadingTag {...rest}>{children}</HeadingTag>;
164
+ }
165
+
166
+ AccordionHeader.displayName = "AccordionHeader";
167
+
168
+ /**
169
+ * The button that toggles an accordion item open or closed. Renders
170
+ * `<button type="button">` by default and wires up all required ARIA
171
+ * attributes, click handling, and keyboard navigation automatically.
172
+ *
173
+ * **Disabled behaviour.** When `disabled` is `true` the trigger is rendered
174
+ * with `aria-disabled="true"` and `data-disabled="true"` instead of the
175
+ * native HTML `disabled` attribute. This keeps the button focusable so
176
+ * keyboard users can discover it, while preventing activation. Disabled
177
+ * triggers are excluded from arrow-key navigation.
178
+ *
179
+ * **`asChild` prop.** Pass `asChild` to render an arbitrary child element
180
+ * instead of the default `<button>`. All accordion ARIA attributes, event
181
+ * handlers, and the internal ref are merged onto the child following the
182
+ * Composition pattern:
183
+ * - Event handlers compose — the child's handler runs first, then the trigger's.
184
+ * - `style` is shallow-merged (child wins on collisions).
185
+ * - `className` strings are concatenated.
186
+ * - Refs from both sides are composed via `composeRefs`.
187
+ *
188
+ * When `asChild` is `true` and `disabled` is `true`, `role="button"` is
189
+ * automatically injected so that `aria-disabled` is semantically valid on
190
+ * non-button elements (e.g. `<a>`, `<div>`). Without a button role the
191
+ * `aria-disabled` attribute has no defined meaning in the ARIA spec.
192
+ *
193
+ * **Keyboard support** (WAI-ARIA Accordion pattern):
194
+ *
195
+ * | Key | Behaviour |
196
+ * | ----------------- | -------------------------------------------- |
197
+ * | `Enter` / `Space` | Toggle the focused item |
198
+ * | `ArrowDown` | Move focus to next trigger (vertical) |
199
+ * | `ArrowUp` | Move focus to previous trigger (vertical) |
200
+ * | `ArrowRight` | Move focus to next trigger (horizontal) |
201
+ * | `ArrowLeft` | Move focus to previous trigger (horizontal) |
202
+ * | `Home` | Move focus to first enabled trigger |
203
+ * | `End` | Move focus to last enabled trigger |
204
+ *
205
+ * Movement **wraps** at the ends. For horizontal orientation with
206
+ * `dir="rtl"`, `ArrowLeft` moves forward and `ArrowRight` moves backward.
207
+ *
208
+ * **Ref forwarding.** A `ref` prop (React 19 ref-as-prop style) is forwarded
209
+ * to the underlying DOM element — useful for imperative focus management.
210
+ *
211
+ * **Styling hooks.**
212
+ * - `data-state="open" | "closed"` on the rendered element.
213
+ * - `data-disabled="true" | "false"`.
214
+ *
215
+ * @example Basic
216
+ * ```tsx
217
+ * <Accordion.Trigger>Shipping policy</Accordion.Trigger>
218
+ * ```
219
+ *
220
+ * @example Disabled
221
+ * ```tsx
222
+ * <Accordion.Trigger disabled>Unavailable section</Accordion.Trigger>
223
+ * ```
224
+ *
225
+ * @example asChild — render a link with accordion semantics
226
+ * ```tsx
227
+ * <Accordion.Trigger asChild>
228
+ * <a href="#shipping">Shipping policy</a>
229
+ * </Accordion.Trigger>
230
+ * ```
231
+ */
232
+ export function AccordionTrigger<
233
+ T extends HTMLElement = HTMLButtonElement,
234
+ >({
235
+ ref,
236
+ children,
237
+ onClick,
238
+ disabled = false,
239
+ asChild = false,
240
+ ...rest
241
+ }: AccordionTriggerProps<T>) {
242
+ // Cast the external ref to match the internal button ref's element type —
243
+ // RefObject<T> is invariant in React's types, but at runtime the callback
244
+ // receives whatever DOM element is actually rendered (button or asChild).
245
+ const { triggerProps } = useAccordionTrigger({
246
+ ref: ref as Ref<HTMLButtonElement>,
247
+ onClick,
248
+ disabled,
249
+ asChild,
250
+ ...rest,
251
+ });
252
+
253
+ if (asChild) {
254
+ return <Slot {...triggerProps}>{children}</Slot>;
255
+ }
256
+
257
+ return (
258
+ <button type="button" {...triggerProps}>
259
+ {children}
260
+ </button>
261
+ );
262
+ }
263
+
264
+ AccordionTrigger.displayName = "AccordionTrigger";
265
+
266
+ /**
267
+ * The panel that is revealed when the associated `Accordion.Trigger` is
268
+ * activated. Renders a `<div role="region" aria-labelledby="…">` whose
269
+ * visibility is controlled by the `hidden` attribute.
270
+ *
271
+ * **`forceMount` prop.** By default the panel is removed from visibility with
272
+ * `hidden` when closed. Pass `forceMount` to keep the panel in the DOM at all
273
+ * times. In `forceMount` mode the `hidden` attribute is never set, so
274
+ * CSS transitions on open / close work correctly — consumers can use
275
+ * `[data-state="closed"] { display: none; }` (or equivalent animation
276
+ * classes) to control visibility themselves.
277
+ *
278
+ * When `forceMount` is `true` and the panel is closed, `aria-hidden="true"` is
279
+ * set automatically so assistive technology ignores the off-screen content.
280
+ * It is removed when the panel opens. Consumers can override this by passing
281
+ * `aria-hidden` explicitly (it appears after the automatic value in the spread).
282
+ *
283
+ * **`role="region"` escape hatch.** Each panel renders with `role="region"` by
284
+ * default, creating an ARIA landmark. Accordions with many items can produce
285
+ * landmark overload in screen readers. Opt out by passing `role={undefined}`:
286
+ * ```tsx
287
+ * <Accordion.Content role={undefined}>…</Accordion.Content>
288
+ * ```
289
+ *
290
+ * **Styling hooks.**
291
+ * - `data-state="open" | "closed"` on the rendered element.
292
+ *
293
+ * @example Default (hidden attribute)
294
+ * ```tsx
295
+ * <Accordion.Content>Free shipping on orders over £50.</Accordion.Content>
296
+ * ```
297
+ *
298
+ * @example With forceMount for CSS animations
299
+ * ```tsx
300
+ * <Accordion.Content forceMount className="panel">
301
+ * Content that animates in and out.
302
+ * </Accordion.Content>
303
+ * ```
304
+ */
305
+ export function AccordionContent({
306
+ children,
307
+ forceMount = false,
308
+ ...rest
309
+ }: AccordionContentProps) {
310
+ const { panelId, buttonId, itemId, isExpanded } = useAccordionItemContext();
311
+ const { registerPanel, unregisterPanel } = useAccordionContext();
312
+
313
+ useEffect(() => {
314
+ registerPanel(itemId);
315
+ return () => unregisterPanel(itemId);
316
+ }, [itemId, registerPanel, unregisterPanel]);
317
+
318
+ return (
319
+ <div
320
+ id={panelId}
321
+ aria-labelledby={buttonId}
322
+ role="region"
323
+ hidden={forceMount ? undefined : !isExpanded}
324
+ aria-hidden={forceMount && !isExpanded ? true : undefined}
325
+ data-state={isExpanded ? "open" : "closed"}
326
+ {...rest}
327
+ >
328
+ {children}
329
+ </div>
330
+ );
331
+ }
332
+
333
+ AccordionContent.displayName = "AccordionContent";
334
+
335
+ /**
336
+ * A wrapper that hides its icon child from the accessibility tree and
337
+ * provides a `data-state` hook for open/close animations. Accepts any
338
+ * renderable React content as a child — an inline `<svg>`, a component
339
+ * from a third-party icon library (lucide-react, react-icons, etc.), or
340
+ * any custom icon component.
341
+ *
342
+ * Renders a `<span>` with `aria-hidden="true"` around the child so the
343
+ * icon is hidden from assistive technology regardless of whether the child
344
+ * component forwards unknown props. Any additional props (`className`,
345
+ * `data-*`, `ref`, etc.) are forwarded to that `<span>`.
346
+ *
347
+ * **Styling hooks.**
348
+ * - `data-state="open" | "closed"` on the rendered `<span>`.
349
+ * - `aria-hidden="true"` on the rendered `<span>`.
350
+ *
351
+ * @example Inline SVG
352
+ * ```tsx
353
+ * <Accordion.Trigger>
354
+ * Shipping policy
355
+ * <Accordion.TriggerIcon>
356
+ * <svg …><path d="…" /></svg>
357
+ * </Accordion.TriggerIcon>
358
+ * </Accordion.Trigger>
359
+ * ```
360
+ *
361
+ * @example Third-party icon component
362
+ * ```tsx
363
+ * import { ChevronDown } from "lucide-react";
364
+ *
365
+ * <Accordion.Trigger>
366
+ * Shipping policy
367
+ * <Accordion.TriggerIcon>
368
+ * <ChevronDown />
369
+ * </Accordion.TriggerIcon>
370
+ * </Accordion.Trigger>
371
+ * ```
372
+ */
373
+ export function AccordionTriggerIcon({
374
+ children,
375
+ ...rest
376
+ }: AccordionTriggerIconProps) {
377
+ const { isExpanded } = useAccordionItemContext();
378
+
379
+ return (
380
+ <span
381
+ {...rest}
382
+ aria-hidden="true"
383
+ data-state={isExpanded ? "open" : "closed"}
384
+ >
385
+ {children}
386
+ </span>
387
+ );
388
+ }
389
+
390
+ AccordionTriggerIcon.displayName = "AccordionTriggerIcon";
391
+
392
+ type AccordionCompound = typeof AccordionRoot & {
393
+ Root: typeof AccordionRoot;
394
+ Item: typeof AccordionItem;
395
+ Header: typeof AccordionHeader;
396
+ Trigger: typeof AccordionTrigger;
397
+ Content: typeof AccordionContent;
398
+ TriggerIcon: typeof AccordionTriggerIcon;
399
+ };
400
+
401
+ const AccordionCompound: AccordionCompound = Object.assign(AccordionRoot, {
402
+ Root: AccordionRoot,
403
+ Item: AccordionItem,
404
+ Header: AccordionHeader,
405
+ Trigger: AccordionTrigger,
406
+ Content: AccordionContent,
407
+ TriggerIcon: AccordionTriggerIcon,
408
+ });
409
+
410
+ AccordionCompound.displayName = "Accordion";
411
+
412
+ export { AccordionCompound as Accordion };
@@ -0,0 +1,12 @@
1
+ import { createStrictContext } from "../utils";
2
+ import { AccordionContextValue, AccordionItemContextValue } from "./types";
3
+
4
+ export const [AccordionContext, useAccordionContext] =
5
+ createStrictContext<AccordionContextValue>(
6
+ "AccordionItem must be used within AccordionRoot",
7
+ );
8
+
9
+ export const [AccordionItemContext, useAccordionItemContext] =
10
+ createStrictContext<AccordionItemContextValue>(
11
+ "Component must be used within AccordionItem",
12
+ );
@@ -0,0 +1,202 @@
1
+ # Accordion
2
+
3
+ A compound component implementing the
4
+ [WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).
5
+
6
+ ```tsx
7
+ import { Accordion } from "@primitiv-ui/react";
8
+
9
+ <Accordion.Root defaultValue="shipping">
10
+ <Accordion.Item value="shipping">
11
+ <Accordion.Header>
12
+ <Accordion.Trigger>Shipping policy</Accordion.Trigger>
13
+ </Accordion.Header>
14
+ <Accordion.Content>Free on orders over £50.</Accordion.Content>
15
+ </Accordion.Item>
16
+ <Accordion.Item value="returns">
17
+ <Accordion.Header>
18
+ <Accordion.Trigger>Returns</Accordion.Trigger>
19
+ </Accordion.Header>
20
+ <Accordion.Content>30-day returns accepted.</Accordion.Content>
21
+ </Accordion.Item>
22
+ </Accordion.Root>;
23
+ ```
24
+
25
+ ## Sub-components
26
+
27
+ | Export | Role | Notes |
28
+ | ----------------------- | -------------- | ----------------------------------------------------------------------- |
29
+ | `Accordion.Root` | State owner | Uncontrolled (`defaultValue`) or controlled (`value` + `onValueChange`) |
30
+ | `Accordion.Item` | Item wrapper | Owns the trigger–panel pair; optional `value` prop |
31
+ | `Accordion.Header` | Heading | Configurable level (`h1`–`h6`); defaults to `h3` |
32
+ | `Accordion.Trigger` | Toggle button | Supports `asChild`, ref forwarding, and `disabled` |
33
+ | `Accordion.Content` | `region` panel | Supports `forceMount` for CSS animation |
34
+ | `Accordion.TriggerIcon` | Icon wrapper | Injects `aria-hidden` and `data-state` onto a decorative icon |
35
+
36
+ ## Keyboard interaction
37
+
38
+ | Key | Behaviour |
39
+ | ----------------- | ------------------------------------------------- |
40
+ | `Enter` / `Space` | Toggle the focused item |
41
+ | `ArrowDown` | Move focus to next trigger (vertical orientation) |
42
+ | `ArrowUp` | Move focus to previous trigger (vertical) |
43
+ | `ArrowRight` | Move focus to next trigger (horizontal) |
44
+ | `ArrowLeft` | Move focus to previous trigger (horizontal) |
45
+ | `Home` | Jump to first enabled trigger |
46
+ | `End` | Jump to last enabled trigger |
47
+
48
+ Focus movement **wraps** at the ends. Disabled triggers are excluded from
49
+ arrow-key navigation but remain focusable via `Tab`.
50
+
51
+ ## State modes
52
+
53
+ - **Uncontrolled** — pass `defaultValue` (or omit to start with all items collapsed).
54
+ - **Controlled** — pass `value` (a `string[]`) and `onValueChange` together.
55
+
56
+ ## Multiple mode
57
+
58
+ By default only one item can be open at a time — opening a second item
59
+ collapses the first. Pass `multiple` to allow any number of items open
60
+ simultaneously:
61
+
62
+ ```tsx
63
+ <Accordion.Root multiple defaultValue="shipping">
64
+
65
+ </Accordion.Root>
66
+ ```
67
+
68
+ ## Disabled items
69
+
70
+ Disabled triggers are rendered with `aria-disabled="true"` (not the native
71
+ `disabled` attribute) so they remain focusable for keyboard discovery:
72
+
73
+ ```tsx
74
+ <Accordion.Trigger disabled>Unavailable section</Accordion.Trigger>
75
+ ```
76
+
77
+ ## `forceMount` for animations
78
+
79
+ By default the content panel is hidden with the `hidden` attribute. Pass
80
+ `forceMount` to keep the panel in the DOM and control visibility via CSS:
81
+
82
+ ```tsx
83
+ <Accordion.Content forceMount className="panel">
84
+ Content that animates in and out.
85
+ </Accordion.Content>
86
+ ```
87
+
88
+ ```css
89
+ .panel[data-state="closed"] {
90
+ display: none; /* or use an animation */
91
+ }
92
+ ```
93
+
94
+ When `forceMount` is active and the panel is closed, `aria-hidden="true"` is
95
+ applied automatically so assistive technology skips the off-screen content.
96
+ It is removed when the panel opens. Consumers can override this by passing
97
+ `aria-hidden` explicitly.
98
+
99
+ ## `role="region"` escape hatch
100
+
101
+ Each `Accordion.Content` renders with `role="region"`, creating an ARIA
102
+ landmark. This is appropriate for panels whose content benefits from
103
+ landmark navigation, but accordions with many items can produce landmark
104
+ overload in screen readers (JAWS/NVDA expose all landmarks in their landmark
105
+ list).
106
+
107
+ Opt out on individual panels by passing `role={undefined}`:
108
+
109
+ ```tsx
110
+ <Accordion.Content role={undefined}>
111
+ Simple prose that doesn't need landmark navigation.
112
+ </Accordion.Content>
113
+ ```
114
+
115
+ ## `asChild` composition
116
+
117
+ `Accordion.Trigger` accepts an `asChild` prop to render any child element
118
+ with full accordion semantics. All ARIA attributes, event handlers, and
119
+ the internal ref are merged onto the child (child handler runs first, then
120
+ the trigger's):
121
+
122
+ ```tsx
123
+ <Accordion.Trigger asChild>
124
+ <a href="#shipping">Shipping policy</a>
125
+ </Accordion.Trigger>
126
+ ```
127
+
128
+ `Enter` and `Space` are handled in `onKeyDown` so non-button elements (e.g.
129
+ `<a>`) toggle correctly without relying on native click behaviour.
130
+
131
+ When `asChild` is combined with `disabled`, `role="button"` is injected
132
+ automatically so that `aria-disabled` is semantically valid on non-button
133
+ elements. Without a button role, `aria-disabled` has no defined meaning:
134
+
135
+ ```tsx
136
+ <Accordion.Trigger asChild disabled>
137
+ <a href="#shipping">Temporarily unavailable</a>
138
+ {/* rendered with role="button" aria-disabled="true" */}
139
+ </Accordion.Trigger>
140
+ ```
141
+
142
+ ## Trigger icon
143
+
144
+ Wrap a decorative icon in `Accordion.TriggerIcon` to hide it from assistive
145
+ technology and expose a `data-state` hook for rotation animations. The child
146
+ can be any renderable React content — an inline `<svg>`, a component from
147
+ a third-party icon library (lucide-react, react-icons, etc.), or a custom
148
+ icon component. `aria-hidden` and `data-state` are placed on a wrapping
149
+ `<span>`, so they work regardless of whether the icon component forwards
150
+ unknown props.
151
+
152
+ ```tsx
153
+ import { ChevronDown } from "lucide-react";
154
+
155
+ <Accordion.Trigger>
156
+ Shipping policy
157
+ <Accordion.TriggerIcon>
158
+ <ChevronDown />
159
+ </Accordion.TriggerIcon>
160
+ </Accordion.Trigger>;
161
+ ```
162
+
163
+ ## Reading direction (RTL)
164
+
165
+ Pass `dir="rtl"` on `Accordion.Root` combined with `orientation="horizontal"`
166
+ to invert the arrow-key direction so `ArrowLeft` moves forward and
167
+ `ArrowRight` moves backward, matching right-to-left reading order:
168
+
169
+ ```tsx
170
+ <Accordion.Root orientation="horizontal" dir="rtl">
171
+
172
+ </Accordion.Root>
173
+ ```
174
+
175
+ When `dir` is omitted, it is inherited from the nearest
176
+ [`DirectionProvider`](../DirectionProvider/README.md), falling back to `"ltr"`
177
+ when there is no provider. An explicit `dir` prop always wins over the
178
+ inherited value.
179
+
180
+ ## Styling hooks
181
+
182
+ ```css
183
+ /* Trigger open/closed */
184
+ [data-state="open"] .chevron {
185
+ transform: rotate(180deg);
186
+ }
187
+
188
+ /* Content panel */
189
+ [role="region"][data-state="closed"] {
190
+ display: none;
191
+ }
192
+
193
+ /* Disabled trigger */
194
+ [aria-disabled="true"] {
195
+ opacity: 0.5;
196
+ cursor: not-allowed;
197
+ }
198
+ ```
199
+
200
+ `data-state` (`"open"` | `"closed"`), `data-disabled` (`"true"` | `"false"`),
201
+ and `data-orientation` (`"vertical"` | `"horizontal"`) are available on
202
+ relevant rendered elements.