@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,214 @@
1
+ # Tooltip
2
+
3
+ A headless, accessible compound component implementing the
4
+ [WAI-ARIA Tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).
5
+
6
+ Tooltip is built as a pure React state machine with no floating-UI or
7
+ measurement dependency. Positioning is fully the consumer's responsibility
8
+ via CSS Anchor Positioning — the component emits no inline styles.
9
+
10
+ - Hover and focus open/close with configurable delay.
11
+ - Provider-level skip-delay: a second tooltip opens instantly when one is
12
+ already open and the cursor moves between triggers.
13
+ - Grace period: 100 ms after leaving the trigger lets users move the cursor
14
+ into the tooltip content without it closing.
15
+ - `onEscapeKeyDown` and `onPointerDownOutside` escape hatches.
16
+ - Portal support for breaking out of stacking contexts.
17
+ - `forceMount` on Portal and Content for CSS exit animations.
18
+ - `asChild` composition on Trigger and Arrow.
19
+
20
+ ```tsx
21
+ import { Tooltip } from "@primitiv-ui/react";
22
+
23
+ <Tooltip.Provider>
24
+ <Tooltip.Root>
25
+ <Tooltip.Trigger className="save-btn">Save</Tooltip.Trigger>
26
+ <Tooltip.Portal>
27
+ <Tooltip.Content className="tooltip">
28
+ Save your changes
29
+ </Tooltip.Content>
30
+ </Tooltip.Portal>
31
+ </Tooltip.Root>
32
+ </Tooltip.Provider>
33
+ ```
34
+
35
+ ## Sub-components
36
+
37
+ | Export | Element | Notes |
38
+ | ------------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
39
+ | `Tooltip.Provider` | context provider | Sets `delayDuration` (default 700 ms) and `skipDelayDuration` (default 300 ms) for all descendants |
40
+ | `Tooltip.Root` | context provider | Owns open state per tooltip. `open`, `defaultOpen`, `onOpenChange`, `delayDuration`, `disableHoverableContent` |
41
+ | `Tooltip.Trigger` | `<button>` | Wires `aria-describedby`, hover/focus handlers. `asChild` |
42
+ | `Tooltip.Portal` | `createPortal` | `container?: HTMLElement` (default `document.body`). `forceMount` |
43
+ | `Tooltip.Content` | `<div role="tooltip">` | `data-state`. Escape hatches for Esc / pointer-down-outside. `forceMount` |
44
+ | `Tooltip.Arrow` | `<span>` | Optional visual pointer. All positioning via consumer CSS. `asChild` |
45
+
46
+ ## Keyboard interaction
47
+
48
+ | Key | Behaviour |
49
+ | ----- | ------------------------------------------------------------------------ |
50
+ | `Tab` | Moves focus to the trigger; opens the tooltip immediately (no delay) |
51
+ | `Tab` (away) | Closes the tooltip immediately |
52
+ | `Esc` | Closes the tooltip. Preventable via `onEscapeKeyDown` |
53
+
54
+ ## State modes
55
+
56
+ - **Uncontrolled** — pass `defaultOpen` (or omit for closed on mount).
57
+ - **Controlled** — pass `open` and `onOpenChange` together.
58
+
59
+ The two shapes are statically discriminated at the type level.
60
+
61
+ ```tsx
62
+ // Uncontrolled
63
+ <Tooltip.Root defaultOpen>…</Tooltip.Root>
64
+
65
+ // Controlled
66
+ const [open, setOpen] = useState(false);
67
+ <Tooltip.Root open={open} onOpenChange={setOpen}>
68
+
69
+ </Tooltip.Root>
70
+ ```
71
+
72
+ ## Delay and skip-delay
73
+
74
+ `delayDuration` (default 700 ms) controls how long the pointer must rest on
75
+ the trigger before the tooltip opens. Set it on `Tooltip.Provider` to apply
76
+ globally, or on an individual `Tooltip.Root` to override.
77
+
78
+ `skipDelayDuration` (default 300 ms) controls the "skip window". Once one
79
+ tooltip is open, moving to any other trigger opens it instantly. After the
80
+ last tooltip closes, the skip window is active for `skipDelayDuration` ms
81
+ before the normal delay resumes.
82
+
83
+ ```tsx
84
+ <Tooltip.Provider delayDuration={400} skipDelayDuration={150}>
85
+
86
+ </Tooltip.Provider>
87
+ ```
88
+
89
+ ## Hoverable content
90
+
91
+ By default, users can move the cursor from the trigger into the tooltip
92
+ content without it closing (a 100 ms grace period bridges the gap). Set
93
+ `disableHoverableContent` on `Tooltip.Root` to close the tooltip immediately
94
+ when the cursor leaves the trigger:
95
+
96
+ ```tsx
97
+ <Tooltip.Root disableHoverableContent>
98
+
99
+ </Tooltip.Root>
100
+ ```
101
+
102
+ ## Escape hatches
103
+
104
+ Both close paths — Escape and pointer-down-outside — fire observable
105
+ callbacks on `Tooltip.Content`. Call `event.preventDefault()` to keep the
106
+ tooltip open:
107
+
108
+ ```tsx
109
+ <Tooltip.Content
110
+ onEscapeKeyDown={(event) => {
111
+ event.preventDefault(); // keep open when Escape is pressed
112
+ }}
113
+ onPointerDownOutside={(event) => {
114
+ event.preventDefault(); // keep open when clicking outside
115
+ }}
116
+ >
117
+
118
+ </Tooltip.Content>
119
+ ```
120
+
121
+ ## CSS anchor positioning
122
+
123
+ This component is headless — it applies **no inline styles** and no
124
+ positioning logic. Position the tooltip with CSS Anchor Positioning:
125
+
126
+ ```css
127
+ /* Trigger declares an anchor name */
128
+ .my-trigger {
129
+ anchor-name: --my-tooltip;
130
+ }
131
+
132
+ /* Content attaches to that anchor */
133
+ .my-tooltip {
134
+ position: absolute;
135
+ position-anchor: --my-tooltip;
136
+ position-area: top;
137
+ position-try-fallbacks: bottom, left, right;
138
+ margin-bottom: 8px;
139
+ }
140
+ ```
141
+
142
+ For multiple tooltips on a page, assign each trigger/content pair a
143
+ distinct `anchor-name`. A data attribute or CSS `:nth-child` selector
144
+ can automate this without per-element class names.
145
+
146
+ ## Arrow
147
+
148
+ `Tooltip.Arrow` renders a `<span>` (or a custom element via `asChild`).
149
+ Arrow position is entirely consumer CSS. A common pattern places it with
150
+ `position: absolute` relative to the content and uses `position-area` or
151
+ a CSS pseudo-element triangle:
152
+
153
+ ```tsx
154
+ <Tooltip.Content className="tooltip">
155
+ Save your changes
156
+ <Tooltip.Arrow className="tooltip__arrow" />
157
+ </Tooltip.Content>
158
+ ```
159
+
160
+ ```css
161
+ .tooltip__arrow {
162
+ position: absolute;
163
+ bottom: -4px;
164
+ left: 50%;
165
+ translate: -50% 0;
166
+ width: 8px;
167
+ height: 8px;
168
+ background: inherit;
169
+ rotate: 45deg;
170
+ }
171
+ ```
172
+
173
+ ## Animation hooks
174
+
175
+ `Tooltip.Portal` and `Tooltip.Content` accept `forceMount`. When set, the
176
+ subtree stays in the DOM while closed so a CSS animation can play on
177
+ `data-state="closed"`:
178
+
179
+ ```tsx
180
+ <Tooltip.Portal forceMount>
181
+ <Tooltip.Content forceMount>…</Tooltip.Content>
182
+ </Tooltip.Portal>
183
+ ```
184
+
185
+ ```css
186
+ [data-state="open"] { animation: fade-in 120ms ease-out; }
187
+ [data-state="closed"] { animation: fade-out 80ms ease-in forwards; }
188
+ ```
189
+
190
+ ## `asChild` composition
191
+
192
+ `Tooltip.Trigger` and `Tooltip.Arrow` accept `asChild`. The component
193
+ merges its own props (event handlers, ARIA, `data-state`) onto the
194
+ child element; the child's own handlers run first.
195
+
196
+ ```tsx
197
+ <Tooltip.Trigger asChild>
198
+ <a href="/help">Help</a>
199
+ </Tooltip.Trigger>
200
+
201
+ <Tooltip.Arrow asChild>
202
+ <svg viewBox="0 0 10 5">…</svg>
203
+ </Tooltip.Arrow>
204
+ ```
205
+
206
+ ## Styling hooks
207
+
208
+ `data-state="open" | "closed"` is set on both `Tooltip.Trigger` and
209
+ `Tooltip.Content`:
210
+
211
+ ```css
212
+ .tooltip[data-state="open"] { opacity: 1; }
213
+ .tooltip[data-state="closed"] { opacity: 0; }
214
+ ```
@@ -0,0 +1,260 @@
1
+ import { Portal } from "../Portal";
2
+ import { Slot, composeRefs } from "../Slot";
3
+
4
+ import {
5
+ TooltipProvider,
6
+ TooltipProviderProvider,
7
+ useTooltipContext,
8
+ } from "./TooltipContext";
9
+ import {
10
+ useTooltipContent,
11
+ useTooltipProvider,
12
+ useTooltipRoot,
13
+ useTooltipTrigger,
14
+ } from "./hooks";
15
+ import type {
16
+ TooltipArrowProps,
17
+ TooltipContentProps,
18
+ TooltipPortalProps,
19
+ TooltipProviderProps,
20
+ TooltipRootProps,
21
+ TooltipTriggerProps,
22
+ } from "./types";
23
+
24
+ /**
25
+ * Wraps one or more `Tooltip.Root` instances and provides shared
26
+ * delay configuration. Required ancestor — rendering `Tooltip.Root`
27
+ * without a Provider throws a context error.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * <Tooltip.Provider delayDuration={400}>
32
+ * <App />
33
+ * </Tooltip.Provider>
34
+ * ```
35
+ */
36
+ function TooltipProviderComponent({
37
+ children,
38
+ delayDuration = 700,
39
+ skipDelayDuration = 300,
40
+ }: TooltipProviderProps) {
41
+ const { contextValue } = useTooltipProvider({ delayDuration, skipDelayDuration });
42
+ return (
43
+ <TooltipProviderProvider value={contextValue}>
44
+ {children}
45
+ </TooltipProviderProvider>
46
+ );
47
+ }
48
+
49
+ TooltipProviderComponent.displayName = "TooltipProvider";
50
+
51
+ /**
52
+ * The state boundary for a single tooltip. Owns the open/closed
53
+ * state and wires it down to all sub-components via context.
54
+ *
55
+ * Supports two state modes, statically discriminated at the type level:
56
+ *
57
+ * - **Uncontrolled** — pass `defaultOpen` or omit for closed-on-mount.
58
+ * - **Controlled** — pass `open` (and optionally `onOpenChange`).
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * <Tooltip.Root>
63
+ * <Tooltip.Trigger>Save</Tooltip.Trigger>
64
+ * <Tooltip.Content>Save your changes</Tooltip.Content>
65
+ * </Tooltip.Root>
66
+ * ```
67
+ */
68
+ function TooltipRoot({
69
+ children,
70
+ defaultOpen,
71
+ open,
72
+ onOpenChange,
73
+ delayDuration,
74
+ disableHoverableContent,
75
+ }: TooltipRootProps) {
76
+ const { contextValue } = useTooltipRoot({
77
+ defaultOpen,
78
+ open,
79
+ onOpenChange,
80
+ delayDuration,
81
+ disableHoverableContent,
82
+ });
83
+
84
+ return <TooltipProvider value={contextValue}>{children}</TooltipProvider>;
85
+ }
86
+
87
+ TooltipRoot.displayName = "TooltipRoot";
88
+
89
+ /**
90
+ * The element that triggers the tooltip on hover and focus. Renders a
91
+ * `<button type="button">` by default; use `asChild` to wrap a custom
92
+ * element.
93
+ *
94
+ * ARIA wiring:
95
+ * - `aria-describedby` points at `Tooltip.Content`'s id.
96
+ * - `data-state="open" | "closed"` mirrors the tooltip state.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * <Tooltip.Trigger>Save</Tooltip.Trigger>
101
+ *
102
+ * <Tooltip.Trigger asChild>
103
+ * <a href="/help">Help</a>
104
+ * </Tooltip.Trigger>
105
+ * ```
106
+ */
107
+ function TooltipTrigger({
108
+ ref,
109
+ asChild = false,
110
+ onPointerEnter,
111
+ onPointerLeave,
112
+ onFocus,
113
+ onBlur,
114
+ onKeyDown,
115
+ type,
116
+ ...rest
117
+ }: TooltipTriggerProps) {
118
+ const { getTriggerProps } = useTooltipTrigger({
119
+ onPointerEnter,
120
+ onPointerLeave,
121
+ onFocus,
122
+ onBlur,
123
+ onKeyDown,
124
+ ...rest,
125
+ });
126
+
127
+ if (asChild) {
128
+ return <Slot ref={ref} {...getTriggerProps()} />;
129
+ }
130
+
131
+ return <button ref={ref} type={type ?? "button"} {...getTriggerProps()} />;
132
+ }
133
+
134
+ TooltipTrigger.displayName = "TooltipTrigger";
135
+
136
+ /**
137
+ * Renders its children through `React.createPortal` so the tooltip
138
+ * content is detached from wherever `Tooltip.Root` lives in the tree
139
+ * and becomes a direct child of `container` (default `document.body`).
140
+ *
141
+ * By default the portal only renders while the tooltip is open. Pass
142
+ * `forceMount` to keep the subtree mounted when closed — useful for
143
+ * CSS exit animations driven by `data-state="closed"`.
144
+ *
145
+ * @example
146
+ * ```tsx
147
+ * <Tooltip.Portal>
148
+ * <Tooltip.Content>…</Tooltip.Content>
149
+ * </Tooltip.Portal>
150
+ * ```
151
+ */
152
+ function TooltipPortal({ children, container, forceMount }: TooltipPortalProps) {
153
+ const { open } = useTooltipContext();
154
+
155
+ if (!open && !forceMount) return null;
156
+
157
+ return <Portal container={container}>{children}</Portal>;
158
+ }
159
+
160
+ TooltipPortal.displayName = "TooltipPortal";
161
+
162
+ /**
163
+ * The tooltip panel. Renders a `<div role="tooltip">` with the shared
164
+ * `contentId` so `Tooltip.Trigger`'s `aria-describedby` resolves correctly.
165
+ *
166
+ * - Unmounts when the tooltip closes unless `forceMount` is set.
167
+ * - `data-state="open" | "closed"` is always present for CSS animation hooks.
168
+ * - Pointer events on the content cancel the grace-period close timer,
169
+ * letting users move the cursor from the trigger into the content without
170
+ * the tooltip dismissing (unless `disableHoverableContent` is set on Root).
171
+ *
172
+ * **Escape hatches:**
173
+ * - `onEscapeKeyDown` — fires when Escape is pressed while the tooltip is
174
+ * open; call `event.preventDefault()` to keep it open.
175
+ * - `onPointerDownOutside` — fires on a pointer-down outside the content;
176
+ * call `event.preventDefault()` to keep it open.
177
+ *
178
+ * CSS anchor positioning is the consumer's responsibility. Apply
179
+ * `anchor-name` to the trigger and `position-anchor` / `position-area`
180
+ * to the content in your own stylesheet.
181
+ *
182
+ * @example
183
+ * ```tsx
184
+ * <Tooltip.Content className="tooltip__content">
185
+ * Save your changes
186
+ * <Tooltip.Arrow className="tooltip__arrow" />
187
+ * </Tooltip.Content>
188
+ * ```
189
+ */
190
+ function TooltipContent({
191
+ ref,
192
+ forceMount,
193
+ onEscapeKeyDown,
194
+ onPointerDownOutside,
195
+ onPointerEnter,
196
+ onPointerLeave,
197
+ ...rest
198
+ }: TooltipContentProps) {
199
+ const { open } = useTooltipContext();
200
+ const { getContentProps, internalRef } = useTooltipContent({
201
+ onEscapeKeyDown,
202
+ onPointerDownOutside,
203
+ onPointerEnter,
204
+ onPointerLeave,
205
+ ...rest,
206
+ });
207
+
208
+ if (!open && !forceMount) return null;
209
+
210
+ return <div ref={composeRefs(internalRef, ref)} {...getContentProps()} />;
211
+ }
212
+
213
+ TooltipContent.displayName = "TooltipContent";
214
+
215
+ /**
216
+ * An optional visual pointer that connects the tooltip content to its
217
+ * trigger. Renders a `<span>` by default; use `asChild` for a custom
218
+ * element such as an SVG.
219
+ *
220
+ * All positioning and styling is the consumer's responsibility via CSS.
221
+ *
222
+ * @example
223
+ * ```tsx
224
+ * <Tooltip.Content>
225
+ * Save your changes
226
+ * <Tooltip.Arrow className="tooltip__arrow" />
227
+ * </Tooltip.Content>
228
+ * ```
229
+ */
230
+ function TooltipArrow({ ref, asChild = false, ...rest }: TooltipArrowProps) {
231
+ if (asChild) {
232
+ return <Slot ref={ref} {...rest} />;
233
+ }
234
+
235
+ return <span ref={ref} {...rest} />;
236
+ }
237
+
238
+ TooltipArrow.displayName = "TooltipArrow";
239
+
240
+ type TooltipCompound = typeof TooltipRoot & {
241
+ Provider: typeof TooltipProviderComponent;
242
+ Root: typeof TooltipRoot;
243
+ Trigger: typeof TooltipTrigger;
244
+ Portal: typeof TooltipPortal;
245
+ Content: typeof TooltipContent;
246
+ Arrow: typeof TooltipArrow;
247
+ };
248
+
249
+ const TooltipCompound: TooltipCompound = Object.assign(TooltipRoot, {
250
+ Provider: TooltipProviderComponent,
251
+ Root: TooltipRoot,
252
+ Trigger: TooltipTrigger,
253
+ Portal: TooltipPortal,
254
+ Content: TooltipContent,
255
+ Arrow: TooltipArrow,
256
+ });
257
+
258
+ TooltipCompound.displayName = "Tooltip";
259
+
260
+ export { TooltipCompound as Tooltip };
@@ -0,0 +1,20 @@
1
+ import { createStrictContext } from "../utils";
2
+
3
+ import type { TooltipContextValue, TooltipProviderContextValue } from "./types";
4
+
5
+ export const [TooltipProviderContext, useTooltipProviderContext] =
6
+ createStrictContext<TooltipProviderContextValue>(
7
+ "Tooltip sub-components must be rendered inside a <Tooltip.Provider>.",
8
+ "TooltipProviderContext",
9
+ );
10
+
11
+ export const [TooltipContext, useTooltipContext] =
12
+ createStrictContext<TooltipContextValue>(
13
+ "Tooltip sub-components must be rendered inside a <Tooltip.Root>.",
14
+ "TooltipContext",
15
+ );
16
+
17
+ const TooltipProviderProvider = TooltipProviderContext.Provider;
18
+ const TooltipProvider = TooltipContext.Provider;
19
+
20
+ export { TooltipProviderProvider, TooltipProvider };
@@ -0,0 +1,77 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { Tooltip } from "../Tooltip";
5
+
6
+ describe("Tooltip — asChild", () => {
7
+ it("Trigger with asChild renders the consumer element instead of a button", () => {
8
+ render(
9
+ <Tooltip.Provider>
10
+ <Tooltip.Root defaultOpen>
11
+ <Tooltip.Trigger asChild>
12
+ <a href="/help">Help</a>
13
+ </Tooltip.Trigger>
14
+ <Tooltip.Content>Helpful tooltip</Tooltip.Content>
15
+ </Tooltip.Root>
16
+ </Tooltip.Provider>,
17
+ );
18
+
19
+ const link = screen.getByRole("link", { name: "Help" });
20
+ expect(link).toBeInTheDocument();
21
+ expect(screen.queryByRole("button")).toBeNull();
22
+ });
23
+
24
+ it("Trigger with asChild merges aria-describedby onto the consumer element", () => {
25
+ render(
26
+ <Tooltip.Provider>
27
+ <Tooltip.Root defaultOpen>
28
+ <Tooltip.Trigger asChild>
29
+ <a href="/help">Help</a>
30
+ </Tooltip.Trigger>
31
+ <Tooltip.Content>Helpful tooltip</Tooltip.Content>
32
+ </Tooltip.Root>
33
+ </Tooltip.Provider>,
34
+ );
35
+
36
+ const link = screen.getByRole("link", { name: "Help" });
37
+ const tooltip = screen.getByRole("tooltip");
38
+ expect(link).toHaveAttribute("aria-describedby", tooltip.id);
39
+ });
40
+
41
+ it("Trigger with asChild still opens the tooltip on focus", async () => {
42
+ const user = userEvent.setup();
43
+ render(
44
+ <Tooltip.Provider>
45
+ <Tooltip.Root>
46
+ <Tooltip.Trigger asChild>
47
+ <a href="/help">Help</a>
48
+ </Tooltip.Trigger>
49
+ <Tooltip.Content>Helpful tooltip</Tooltip.Content>
50
+ </Tooltip.Root>
51
+ </Tooltip.Provider>,
52
+ );
53
+
54
+ await user.tab();
55
+
56
+ expect(screen.getByRole("tooltip")).toBeInTheDocument();
57
+ });
58
+
59
+ it("Arrow with asChild renders the consumer element", () => {
60
+ render(
61
+ <Tooltip.Provider>
62
+ <Tooltip.Root defaultOpen>
63
+ <Tooltip.Trigger>Hover me</Tooltip.Trigger>
64
+ <Tooltip.Content>
65
+ Tooltip text
66
+ <Tooltip.Arrow asChild>
67
+ <svg data-testid="arrow-svg" />
68
+ </Tooltip.Arrow>
69
+ </Tooltip.Content>
70
+ </Tooltip.Root>
71
+ </Tooltip.Provider>,
72
+ );
73
+
74
+ expect(screen.getByTestId("arrow-svg")).toBeInTheDocument();
75
+ expect(screen.queryByRole("tooltip")?.querySelector("span")).toBeNull();
76
+ });
77
+ });