@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,15 @@
1
+ import { createContext, RefObject } from "react";
2
+
3
+ /**
4
+ * Content-scoped context provided by {@link ContextMenuContent} and
5
+ * {@link ContextMenuSubContent}. Holds a ref to a single "currently open
6
+ * direct-child sub" close callback so that sibling items can dismiss the
7
+ * open sub on hover, mirroring the keyboard contract (focus returning to
8
+ * the parent menu closes the sub).
9
+ */
10
+ export type ContextMenuContentContextValue = {
11
+ closeOpenSubRef: RefObject<(() => void) | null>;
12
+ };
13
+
14
+ export const ContextMenuContentContext =
15
+ createContext<ContextMenuContentContextValue | null>(null);
@@ -0,0 +1,21 @@
1
+ import { RefObject } from "react";
2
+
3
+ import { Direction } from "../DirectionProvider";
4
+ import { createStrictContext } from "../utils";
5
+
6
+ export type ContextMenuPosition = { x: number; y: number };
7
+
8
+ export type ContextMenuContextValue = {
9
+ open: boolean;
10
+ setOpen: (open: boolean) => void;
11
+ position: ContextMenuPosition | null;
12
+ setPosition: (position: ContextMenuPosition | null) => void;
13
+ contentId: string;
14
+ triggerRef: RefObject<HTMLElement | null>;
15
+ dir: Direction;
16
+ };
17
+
18
+ export const [ContextMenuContext, useContextMenuContext] =
19
+ createStrictContext<ContextMenuContextValue>(
20
+ "ContextMenu sub-components must be rendered inside a <ContextMenu.Root>.",
21
+ );
@@ -0,0 +1,8 @@
1
+ import { createContext } from "react";
2
+
3
+ export type ContextMenuGroupContextValue = {
4
+ labelId: string;
5
+ };
6
+
7
+ export const ContextMenuGroupContext =
8
+ createContext<ContextMenuGroupContextValue | null>(null);
@@ -0,0 +1,8 @@
1
+ import { createContext } from "react";
2
+
3
+ export type ContextMenuItemIndicatorContextValue = {
4
+ checked: boolean | "indeterminate";
5
+ };
6
+
7
+ export const ContextMenuItemIndicatorContext =
8
+ createContext<ContextMenuItemIndicatorContextValue | null>(null);
@@ -0,0 +1,9 @@
1
+ import { createContext } from "react";
2
+
3
+ export type ContextMenuRadioGroupContextValue = {
4
+ value: string | undefined;
5
+ select: (value: string) => void;
6
+ };
7
+
8
+ export const ContextMenuRadioGroupContext =
9
+ createContext<ContextMenuRadioGroupContextValue | null>(null);
@@ -0,0 +1,15 @@
1
+ import { RefObject } from "react";
2
+
3
+ import { createStrictContext } from "../utils";
4
+
5
+ export type ContextMenuSubContextValue = {
6
+ open: boolean;
7
+ setOpen: (open: boolean) => void;
8
+ contentId: string;
9
+ triggerRef: RefObject<HTMLLIElement | null>;
10
+ };
11
+
12
+ export const [ContextMenuSubContext, useContextMenuSubContext] =
13
+ createStrictContext<ContextMenuSubContextValue>(
14
+ "ContextMenu.SubTrigger and ContextMenu.SubContent must be rendered inside a <ContextMenu.Sub>.",
15
+ );
@@ -0,0 +1,275 @@
1
+ # ContextMenu
2
+
3
+ A compound component implementing the
4
+ [WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/)
5
+ for right-click / long-press / context-key menus, layered on top of the
6
+ native HTML
7
+ [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API).
8
+ No portal, no floating-ui — the browser handles layering via the top
9
+ layer, and the component handles close on outside click / Escape.
10
+
11
+ ```tsx
12
+ import { ContextMenu } from "@primitiv-ui/react";
13
+
14
+ <ContextMenu.Root>
15
+ <ContextMenu.Trigger>
16
+ <div className="canvas">Right-click anywhere on this canvas</div>
17
+ </ContextMenu.Trigger>
18
+ <ContextMenu.Content>
19
+ <ContextMenu.Item onSelect={() => paste()}>Paste</ContextMenu.Item>
20
+ <ContextMenu.Item onSelect={() => duplicate()}>Duplicate</ContextMenu.Item>
21
+ <ContextMenu.Separator />
22
+ <ContextMenu.Item disabled>Archive</ContextMenu.Item>
23
+ </ContextMenu.Content>
24
+ </ContextMenu.Root>;
25
+ ```
26
+
27
+ ## Sub-components
28
+
29
+ | Export | Role | Notes |
30
+ | --------------------------- | -------------------- | ------------------------------------------------------------------------------------ |
31
+ | `ContextMenu.Root` | State owner | Uncontrolled (`defaultOpen`) or controlled (`open` + `onOpenChange`) |
32
+ | `ContextMenu.Trigger` | Right-click target | Renders `<span>`; intercepts `contextmenu`, suppresses the native menu |
33
+ | `ContextMenu.Content` | `menu` | Native `popover="manual"`, positioned at the cursor; arrow keys, typeahead, Escape |
34
+ | `ContextMenu.Item` | `menuitem` | Activatable row with `onSelect` escape hatch |
35
+ | `ContextMenu.CheckboxItem` | `menuitemcheckbox` | Tri-state toggle (`true` / `false` / `"indeterminate"`) |
36
+ | `ContextMenu.RadioGroup` | `group` | Single-selection container for `RadioItem`s |
37
+ | `ContextMenu.RadioItem` | `menuitemradio` | Must live inside a `RadioGroup` |
38
+ | `ContextMenu.ItemIndicator` | — | Icon slot inside a `CheckboxItem` / `RadioItem`; exposes `data-state` + `forceMount` |
39
+ | `ContextMenu.Label` | — | Non-interactive label; auto-wired to the enclosing `Group` via `aria-labelledby` |
40
+ | `ContextMenu.Group` | `group` | Semantic grouping for related items |
41
+ | `ContextMenu.Separator` | `separator` | Visual divider; skipped by focus and typeahead |
42
+ | `ContextMenu.Sub` | State owner | Submenu boundary; same state modes as `Root` |
43
+ | `ContextMenu.SubTrigger` | `menuitem` | Opens the submenu on click, hover, or the inline-forward arrow (`ArrowRight` LTR / `ArrowLeft` RTL) |
44
+ | `ContextMenu.SubContent` | `menu` | Submenu panel; the inline-backward arrow (`ArrowLeft` LTR / `ArrowRight` RTL) closes it and returns focus to the trigger |
45
+
46
+ All sub-components that render an element accept `asChild` to compose
47
+ their ARIA and behaviour onto a caller-supplied child.
48
+
49
+ ## Opening
50
+
51
+ The Trigger listens for the DOM `contextmenu` event on its rendered
52
+ element, calls `preventDefault()` to suppress the platform menu, and
53
+ opens the Content positioned at the pointer coordinates via
54
+ `position: fixed`.
55
+
56
+ By default, Trigger renders a `<span>` wrapping its children — a
57
+ neutral host that doesn't disturb the wrapped content's semantics. Pass
58
+ `asChild` to attach the right-click behaviour to any element (a `<div>`
59
+ canvas, a list row, an image, etc.).
60
+
61
+ ```tsx
62
+ <ContextMenu.Trigger asChild>
63
+ <ImageCard src="…" alt="…" />
64
+ </ContextMenu.Trigger>
65
+ ```
66
+
67
+ A disabled Trigger ignores `contextmenu` entirely, letting the native
68
+ browser menu through.
69
+
70
+ ## Keyboard interaction
71
+
72
+ | Key | Behaviour |
73
+ | ----------------------- | ------------------------------------------------- |
74
+ | `ArrowDown` / `ArrowUp` | Move focus to next / previous item (wraps) |
75
+ | `Home` / `End` | Jump to first / last enabled item |
76
+ | `Enter` / `Space` | Activate the focused item |
77
+ | `Escape` | Close the menu and return focus to the Trigger |
78
+ | any printable character | Typeahead — focuses the next item matching prefix |
79
+ | `ArrowRight` | Open a focused submenu (`SubTrigger`, LTR) / close the submenu (inside `SubContent`, RTL) |
80
+ | `ArrowLeft` | Close the current submenu (inside `SubContent`, LTR) / open a focused submenu (`SubTrigger`, RTL) |
81
+
82
+ Typeahead accumulates keystrokes within a 500 ms window; pressing the
83
+ same character repeatedly cycles through items sharing that first letter.
84
+ Disabled items are skipped by arrow navigation, typeahead, and activation.
85
+
86
+ ## Reading direction
87
+
88
+ Pass `dir="ltr"` or `dir="rtl"` on `ContextMenu.Root` to invert the
89
+ submenu open / close arrow keys. When omitted, the component reads the
90
+ inherited `DirectionProvider` value, falling back to `"ltr"`:
91
+
92
+ ```tsx
93
+ <DirectionProvider dir="rtl">
94
+ <ContextMenu.Root>{/* submenus now open on ArrowLeft */}</ContextMenu.Root>
95
+ </DirectionProvider>
96
+ ```
97
+
98
+ An explicit `dir` prop on `ContextMenu.Root` always wins over the
99
+ inherited value. The reading direction only affects keyboard handling —
100
+ submenu visual placement is the consumer's CSS concern (the workbench
101
+ example uses logical positioning + `position-try-fallbacks: flip-inline`
102
+ so subs reflow automatically in RTL).
103
+
104
+ ## State modes
105
+
106
+ - **Uncontrolled** — pass `defaultOpen` (or omit to start closed). Optional
107
+ `onOpenChange` observes user-driven transitions.
108
+ - **Controlled** — pass `open` and `onOpenChange` together.
109
+
110
+ `ContextMenu.Sub` follows the same contract for its own open state.
111
+
112
+ External flips of the controlled `open` prop do **not** invoke
113
+ `onOpenChange` — only user-driven transitions (right-click, Escape,
114
+ selection, outside click) do.
115
+
116
+ `ContextMenu.CheckboxItem` and `ContextMenu.RadioGroup` each expose the
117
+ same uncontrolled / controlled split for their `checked` / `value` state.
118
+
119
+ ## The `onSelect` escape hatch
120
+
121
+ Every activatable item (`Item`, `CheckboxItem`, `RadioItem`) fires
122
+ `onSelect` with a cancellable `Event` on activation. By default the menu
123
+ auto-closes; call `event.preventDefault()` to keep it open — useful for
124
+ rapidly toggling multiple checkboxes, or running an action whose outcome
125
+ is shown inline in the menu.
126
+
127
+ ```tsx
128
+ <ContextMenu.CheckboxItem
129
+ onSelect={(event) => event.preventDefault()}
130
+ onCheckedChange={setGridVisible}
131
+ >
132
+ Show grid
133
+ </ContextMenu.CheckboxItem>
134
+ ```
135
+
136
+ ## Disabled items
137
+
138
+ Disabled items receive `aria-disabled="true"` rather than the native
139
+ `disabled` attribute, so they remain visible to assistive technology but
140
+ no-op on activation. Arrow navigation and typeahead skip them.
141
+
142
+ ```tsx
143
+ <ContextMenu.Item disabled>Archive (coming soon)</ContextMenu.Item>
144
+ ```
145
+
146
+ A disabled `SubTrigger` refuses to open on both click and the
147
+ inline-forward arrow key.
148
+
149
+ ## Checkbox and radio items
150
+
151
+ ```tsx
152
+ <ContextMenu.Content>
153
+ <ContextMenu.Label>View</ContextMenu.Label>
154
+ <ContextMenu.CheckboxItem defaultChecked>Show grid</ContextMenu.CheckboxItem>
155
+ <ContextMenu.CheckboxItem>Show ruler</ContextMenu.CheckboxItem>
156
+ <ContextMenu.Separator />
157
+ <ContextMenu.RadioGroup defaultValue="system">
158
+ <ContextMenu.RadioItem value="light">Light</ContextMenu.RadioItem>
159
+ <ContextMenu.RadioItem value="dark">Dark</ContextMenu.RadioItem>
160
+ <ContextMenu.RadioItem value="system">Match system</ContextMenu.RadioItem>
161
+ </ContextMenu.RadioGroup>
162
+ </ContextMenu.Content>
163
+ ```
164
+
165
+ `CheckboxItem` supports a tri-state: `true`, `false`, or `"indeterminate"`
166
+ (which renders as `aria-checked="mixed"`). An indeterminate item resolves
167
+ to `true` on the next activation.
168
+
169
+ ### `ItemIndicator`
170
+
171
+ Render the visible mark inside the item via `ContextMenu.ItemIndicator`.
172
+ It defaults to a `<span>`, supports `asChild` so consumers can compose
173
+ onto an SVG icon, and exposes `data-state` for styling:
174
+
175
+ ```tsx
176
+ <ContextMenu.CheckboxItem
177
+ checked={showBookmarks}
178
+ onCheckedChange={setShowBookmarks}
179
+ >
180
+ <ContextMenu.ItemIndicator>
181
+ <CheckIcon />
182
+ </ContextMenu.ItemIndicator>
183
+ Show bookmarks
184
+ </ContextMenu.CheckboxItem>
185
+ ```
186
+
187
+ | `data-state` | When |
188
+ | ----------------- | ----------------------------------------------------------------------------------- |
189
+ | `"checked"` | Parent `CheckboxItem` is `true`, or parent `RadioItem` is the group's current value |
190
+ | `"unchecked"` | Parent is `false` (only reachable when `forceMount` is set — see below) |
191
+ | `"indeterminate"` | Parent `CheckboxItem` is `"indeterminate"` |
192
+
193
+ By default the indicator **unmounts** when its parent is unchecked. Pass
194
+ `forceMount` to keep the DOM node in both states so CSS transitions or a
195
+ React animation library can drive the visual state off `data-state`.
196
+
197
+ Rendering `ContextMenu.ItemIndicator` outside a `CheckboxItem` or
198
+ `RadioItem` throws a descriptive error.
199
+
200
+ ## Submenus
201
+
202
+ ```tsx
203
+ <ContextMenu.Content>
204
+ <ContextMenu.Item>Cut</ContextMenu.Item>
205
+ <ContextMenu.Sub>
206
+ <ContextMenu.SubTrigger>Share</ContextMenu.SubTrigger>
207
+ <ContextMenu.SubContent>
208
+ <ContextMenu.Item>Email</ContextMenu.Item>
209
+ <ContextMenu.Item>Copy link</ContextMenu.Item>
210
+ </ContextMenu.SubContent>
211
+ </ContextMenu.Sub>
212
+ </ContextMenu.Content>
213
+ ```
214
+
215
+ Open a submenu with the inline-forward arrow key (`ArrowRight` in LTR,
216
+ `ArrowLeft` in RTL), a click on the trigger, or pointer hover; close it
217
+ with the inline-backward arrow or by selecting an item. Focus returns to
218
+ the `SubTrigger` when the submenu closes.
219
+
220
+ ## Groups and labels
221
+
222
+ ```tsx
223
+ <ContextMenu.Content>
224
+ <ContextMenu.Group>
225
+ <ContextMenu.Label>Edit</ContextMenu.Label>
226
+ <ContextMenu.Item>Undo</ContextMenu.Item>
227
+ <ContextMenu.Item>Redo</ContextMenu.Item>
228
+ </ContextMenu.Group>
229
+ </ContextMenu.Content>
230
+ ```
231
+
232
+ Nesting a `ContextMenu.Label` inside a `ContextMenu.Group` wires the
233
+ group's `aria-labelledby` to the label automatically.
234
+
235
+ ## `asChild` composition
236
+
237
+ Every rendering sub-component accepts `asChild` to compose its
238
+ ARIA attributes, event handlers, and ref onto a caller-supplied child:
239
+
240
+ ```tsx
241
+ <ContextMenu.Trigger asChild>
242
+ <div className="canvas">Right-click here</div>
243
+ </ContextMenu.Trigger>
244
+
245
+ <ContextMenu.Item asChild>
246
+ <a href="/rename">Rename</a>
247
+ </ContextMenu.Item>
248
+ ```
249
+
250
+ ## Styling hooks
251
+
252
+ ```css
253
+ /* Open state on the menu panel */
254
+ [role="menu"][data-state="open"] {
255
+ animation: fade-in 120ms ease-out;
256
+ }
257
+
258
+ /* Highlighted item — pointer focus */
259
+ [role="menuitem"][data-highlighted],
260
+ [role="menuitemcheckbox"][data-highlighted],
261
+ [role="menuitemradio"][data-highlighted] {
262
+ background: rgba(0 0 0 / 0.06);
263
+ outline: none;
264
+ }
265
+
266
+ /* Disabled items */
267
+ [aria-disabled="true"] {
268
+ opacity: 0.5;
269
+ pointer-events: none;
270
+ }
271
+ ```
272
+
273
+ `data-state="open" | "closed"` is present on the Content for state-driven
274
+ styling. `data-highlighted` is present on `Item`, `CheckboxItem`,
275
+ `RadioItem`, and `SubTrigger` while the item has pointer focus.
@@ -0,0 +1,186 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+
3
+ import { ContextMenu } from "../ContextMenu";
4
+
5
+ describe("ContextMenu asChild", () => {
6
+ it("delegates Trigger to the child element via asChild", () => {
7
+ // Arrange & Act
8
+ render(
9
+ <ContextMenu.Root>
10
+ <ContextMenu.Trigger asChild>
11
+ <div data-testid="custom-trigger">Area</div>
12
+ </ContextMenu.Trigger>
13
+ <ContextMenu.Content>
14
+ <ContextMenu.Item>Rename</ContextMenu.Item>
15
+ </ContextMenu.Content>
16
+ </ContextMenu.Root>,
17
+ );
18
+
19
+ // Assert
20
+ const trigger = screen.getByTestId("custom-trigger");
21
+ expect(trigger.tagName).toBe("DIV");
22
+ });
23
+
24
+ it("delegates Content to the child element via asChild", () => {
25
+ // Arrange & Act
26
+ render(
27
+ <ContextMenu.Root defaultOpen>
28
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
29
+ <ContextMenu.Content asChild>
30
+ <div data-testid="custom-content">
31
+ <ContextMenu.Item>Rename</ContextMenu.Item>
32
+ </div>
33
+ </ContextMenu.Content>
34
+ </ContextMenu.Root>,
35
+ );
36
+
37
+ // Assert
38
+ const content = screen.getByTestId("custom-content");
39
+ expect(content.tagName).toBe("DIV");
40
+ expect(content).toHaveAttribute("role", "menu");
41
+ expect(content).toHaveAttribute("popover", "manual");
42
+ });
43
+
44
+ it("delegates Item to the child element and auto-closes on click", () => {
45
+ // Arrange
46
+ const onSelect = vi.fn();
47
+ const onOpenChange = vi.fn();
48
+ render(
49
+ <ContextMenu.Root defaultOpen onOpenChange={onOpenChange}>
50
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
51
+ <ContextMenu.Content>
52
+ <ContextMenu.Item asChild onSelect={onSelect}>
53
+ <a href="#rename" data-testid="custom-item">
54
+ Rename
55
+ </a>
56
+ </ContextMenu.Item>
57
+ </ContextMenu.Content>
58
+ </ContextMenu.Root>,
59
+ );
60
+ const item = screen.getByTestId("custom-item");
61
+ expect(item.tagName).toBe("A");
62
+ expect(item).toHaveAttribute("role", "menuitem");
63
+
64
+ // Act
65
+ fireEvent.click(item);
66
+
67
+ // Assert
68
+ expect(onSelect).toHaveBeenCalledOnce();
69
+ expect(onOpenChange).toHaveBeenCalledWith(false);
70
+ });
71
+
72
+ it("delegates Separator to the child element via asChild", () => {
73
+ // Arrange & Act
74
+ render(
75
+ <ContextMenu.Root defaultOpen>
76
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
77
+ <ContextMenu.Content>
78
+ <ContextMenu.Item>Rename</ContextMenu.Item>
79
+ <ContextMenu.Separator asChild>
80
+ <hr data-testid="custom-separator" />
81
+ </ContextMenu.Separator>
82
+ <ContextMenu.Item>Delete</ContextMenu.Item>
83
+ </ContextMenu.Content>
84
+ </ContextMenu.Root>,
85
+ );
86
+
87
+ // Assert
88
+ const separator = screen.getByTestId("custom-separator");
89
+ expect(separator.tagName).toBe("HR");
90
+ expect(separator).toHaveAttribute("role", "separator");
91
+ });
92
+
93
+ it("delegates Group and Label to the child elements via asChild", () => {
94
+ // Arrange & Act
95
+ render(
96
+ <ContextMenu.Root defaultOpen>
97
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
98
+ <ContextMenu.Content>
99
+ <ContextMenu.Group asChild>
100
+ <section data-testid="custom-group">
101
+ <ContextMenu.Label asChild>
102
+ <h2 data-testid="custom-label">Actions</h2>
103
+ </ContextMenu.Label>
104
+ <ContextMenu.Item>Rename</ContextMenu.Item>
105
+ </section>
106
+ </ContextMenu.Group>
107
+ </ContextMenu.Content>
108
+ </ContextMenu.Root>,
109
+ );
110
+
111
+ // Assert
112
+ expect(screen.getByTestId("custom-group").tagName).toBe("SECTION");
113
+ expect(screen.getByTestId("custom-label").tagName).toBe("H2");
114
+ });
115
+
116
+ it("delegates CheckboxItem and ItemIndicator to the child elements via asChild", () => {
117
+ // Arrange & Act
118
+ render(
119
+ <ContextMenu.Root defaultOpen>
120
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
121
+ <ContextMenu.Content>
122
+ <ContextMenu.CheckboxItem asChild defaultChecked>
123
+ <a href="#grid" data-testid="custom-check">
124
+ <ContextMenu.ItemIndicator asChild>
125
+ <svg data-testid="custom-indicator" />
126
+ </ContextMenu.ItemIndicator>
127
+ Show grid
128
+ </a>
129
+ </ContextMenu.CheckboxItem>
130
+ </ContextMenu.Content>
131
+ </ContextMenu.Root>,
132
+ );
133
+
134
+ // Assert
135
+ const check = screen.getByTestId("custom-check");
136
+ expect(check.tagName).toBe("A");
137
+ expect(check).toHaveAttribute("role", "menuitemcheckbox");
138
+ const indicator = screen.getByTestId("custom-indicator");
139
+ expect(indicator.tagName).toBe("svg");
140
+ expect(indicator).toHaveAttribute("data-state", "checked");
141
+ });
142
+
143
+ it("delegates RadioGroup, RadioItem, Sub, SubTrigger, and SubContent via asChild", () => {
144
+ // Arrange & Act
145
+ render(
146
+ <ContextMenu.Root defaultOpen>
147
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
148
+ <ContextMenu.Content>
149
+ <ContextMenu.RadioGroup asChild defaultValue="a">
150
+ <section data-testid="custom-rg">
151
+ <ContextMenu.RadioItem asChild value="a">
152
+ <a href="#a" data-testid="custom-ri">
153
+ Alpha
154
+ </a>
155
+ </ContextMenu.RadioItem>
156
+ </section>
157
+ </ContextMenu.RadioGroup>
158
+ <ContextMenu.Sub defaultOpen>
159
+ <ContextMenu.SubTrigger asChild>
160
+ <a href="#more" data-testid="custom-st">
161
+ More
162
+ </a>
163
+ </ContextMenu.SubTrigger>
164
+ <ContextMenu.SubContent asChild>
165
+ <div data-testid="custom-sc">
166
+ <ContextMenu.Item>Nested</ContextMenu.Item>
167
+ </div>
168
+ </ContextMenu.SubContent>
169
+ </ContextMenu.Sub>
170
+ </ContextMenu.Content>
171
+ </ContextMenu.Root>,
172
+ );
173
+
174
+ // Assert
175
+ expect(screen.getByTestId("custom-rg").tagName).toBe("SECTION");
176
+ expect(screen.getByTestId("custom-ri")).toHaveAttribute(
177
+ "role",
178
+ "menuitemradio",
179
+ );
180
+ expect(screen.getByTestId("custom-st")).toHaveAttribute(
181
+ "aria-haspopup",
182
+ "menu",
183
+ );
184
+ expect(screen.getByTestId("custom-sc")).toHaveAttribute("role", "menu");
185
+ });
186
+ });
@@ -0,0 +1,39 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { ContextMenu } from "../ContextMenu";
4
+
5
+ describe("ContextMenu basic rendering", () => {
6
+ it("renders the Trigger's children so the area that should respond to right-click is in the DOM", () => {
7
+ // Arrange & Act
8
+ render(
9
+ <ContextMenu.Root>
10
+ <ContextMenu.Trigger>
11
+ <div>Right-click area</div>
12
+ </ContextMenu.Trigger>
13
+ </ContextMenu.Root>,
14
+ );
15
+
16
+ // Assert
17
+ expect(screen.getByText("Right-click area")).toBeInTheDocument();
18
+ });
19
+
20
+ it("renders Content as a native popover with popover=manual so the browser does not light-dismiss the menu on the right-click's mouseup", () => {
21
+ // Arrange & Act
22
+ render(
23
+ <ContextMenu.Root defaultOpen>
24
+ <ContextMenu.Trigger>Area</ContextMenu.Trigger>
25
+ <ContextMenu.Content>Items go here</ContextMenu.Content>
26
+ </ContextMenu.Root>,
27
+ );
28
+
29
+ // Assert — popover=manual is critical: with "auto" the browser treats
30
+ // the right-click's pointerdown (which fires before the popover opened)
31
+ // and the subsequent pointerup as an outside-dismiss gesture, closing
32
+ // the menu the instant the user releases the button. We close on
33
+ // outside click and Escape ourselves instead.
34
+ const menu = screen.getByRole("menu", { hidden: true });
35
+ expect(menu.tagName).toBe("MENU");
36
+ expect(menu).toHaveAttribute("popover", "manual");
37
+ expect(menu.id).toBeTruthy();
38
+ });
39
+ });