@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,228 @@
1
+ import { Slot } from "../Slot";
2
+ import { InputGroupAdornmentProps, InputGroupRootProps } from "./types";
3
+
4
+ /**
5
+ * The root of an InputGroup — a stateless `<div>` wrapper that frames a
6
+ * single form control alongside optional leading and trailing adornments.
7
+ *
8
+ * `InputGroup` is intentionally **layout-and-anatomy only**. It owns no
9
+ * state, provides no context, and does not know which control sits
10
+ * inside it. Use CSS `:focus-within` and `:has(input:disabled)` /
11
+ * `:has(input[aria-invalid="true"])` to style the frame in response to
12
+ * the inner control's state — no JS coordination required.
13
+ *
14
+ * **Styling hook.** `data-input-group=""` on the root.
15
+ *
16
+ * **`asChild` composition.** Pass `asChild` to render the consumer's
17
+ * element instead of `<div>` — e.g. a `<label>` so a click anywhere on
18
+ * the frame focuses the wrapped input.
19
+ *
20
+ * @example Basic frame with no adornments
21
+ * ```tsx
22
+ * <InputGroup>
23
+ * <Input aria-label="Search" type="search" />
24
+ * </InputGroup>
25
+ * ```
26
+ *
27
+ * @example Frame rendered as a <label> so the whole frame is clickable
28
+ * ```tsx
29
+ * <InputGroup asChild>
30
+ * <label>
31
+ * <InputGroup.LeadingAdornment><SearchIcon /></InputGroup.LeadingAdornment>
32
+ * <Input type="search" />
33
+ * </label>
34
+ * </InputGroup>
35
+ * ```
36
+ */
37
+ function InputGroupRoot({
38
+ asChild = false,
39
+ children,
40
+ ref,
41
+ ...rest
42
+ }: InputGroupRootProps) {
43
+ const rootProps = {
44
+ ...rest,
45
+ ref,
46
+ "data-input-group": "",
47
+ };
48
+
49
+ if (asChild) {
50
+ return <Slot {...rootProps}>{children}</Slot>;
51
+ }
52
+
53
+ return <div {...rootProps}>{children}</div>;
54
+ }
55
+
56
+ InputGroupRoot.displayName = "InputGroupRoot";
57
+
58
+ /**
59
+ * A leading adornment slot — a `<span>` positioned at the start of the
60
+ * frame for icons, currency symbols, prefix text, or interactive
61
+ * buttons.
62
+ *
63
+ * **Styling hook.** `data-input-group-adornment="leading"` on the
64
+ * adornment element.
65
+ *
66
+ * **Accessibility.** No `aria-hidden` is set automatically — the
67
+ * adornment is just a positioned slot. Mark decorative icons
68
+ * `aria-hidden="true"` yourself (or wrap with `AccessibleIcon`), and
69
+ * give interactive children a proper accessible name.
70
+ *
71
+ * **`asChild` composition.** Pass `asChild` to render an interactive
72
+ * element such as `<button>` — for a clickable icon that triggers a
73
+ * search, opens a colour picker, or any other action. Event handlers
74
+ * compose (child runs first); refs forward to the consumer element.
75
+ *
76
+ * @example Decorative leading icon
77
+ * ```tsx
78
+ * <InputGroup.LeadingAdornment>
79
+ * <SearchIcon aria-hidden="true" />
80
+ * </InputGroup.LeadingAdornment>
81
+ * ```
82
+ *
83
+ * @example Interactive leading button via asChild
84
+ * ```tsx
85
+ * <InputGroup.LeadingAdornment asChild>
86
+ * <button type="button" aria-label="Open colour picker" onClick={open}>
87
+ * <ColourSwatch />
88
+ * </button>
89
+ * </InputGroup.LeadingAdornment>
90
+ * ```
91
+ */
92
+ function InputGroupLeadingAdornment({
93
+ asChild = false,
94
+ children,
95
+ ref,
96
+ ...rest
97
+ }: InputGroupAdornmentProps) {
98
+ const adornmentProps = {
99
+ ...rest,
100
+ ref,
101
+ "data-input-group-adornment": "leading" as const,
102
+ };
103
+
104
+ if (asChild) {
105
+ return <Slot {...adornmentProps}>{children}</Slot>;
106
+ }
107
+
108
+ return <span {...adornmentProps}>{children}</span>;
109
+ }
110
+
111
+ InputGroupLeadingAdornment.displayName = "InputGroupLeadingAdornment";
112
+
113
+ /**
114
+ * A trailing adornment slot — a `<span>` positioned at the end of the
115
+ * frame for icons, suffix text, clear buttons, or password-reveal
116
+ * toggles.
117
+ *
118
+ * **Styling hook.** `data-input-group-adornment="trailing"` on the
119
+ * adornment element.
120
+ *
121
+ * **Accessibility.** No `aria-hidden` is set automatically — the
122
+ * adornment is just a positioned slot. Mark decorative icons
123
+ * `aria-hidden="true"` yourself (or wrap with `AccessibleIcon`), and
124
+ * give interactive children a proper accessible name.
125
+ *
126
+ * **`asChild` composition.** Pass `asChild` to render an interactive
127
+ * element such as `<button>`. Event handlers compose (child runs
128
+ * first); refs forward to the consumer element.
129
+ *
130
+ * @example Trailing clear button via asChild
131
+ * ```tsx
132
+ * <InputGroup.TrailingAdornment asChild>
133
+ * <button type="button" aria-label="Clear" onClick={clear}>
134
+ * <XIcon aria-hidden="true" />
135
+ * </button>
136
+ * </InputGroup.TrailingAdornment>
137
+ * ```
138
+ */
139
+ function InputGroupTrailingAdornment({
140
+ asChild = false,
141
+ children,
142
+ ref,
143
+ ...rest
144
+ }: InputGroupAdornmentProps) {
145
+ const adornmentProps = {
146
+ ...rest,
147
+ ref,
148
+ "data-input-group-adornment": "trailing" as const,
149
+ };
150
+
151
+ if (asChild) {
152
+ return <Slot {...adornmentProps}>{children}</Slot>;
153
+ }
154
+
155
+ return <span {...adornmentProps}>{children}</span>;
156
+ }
157
+
158
+ InputGroupTrailingAdornment.displayName = "InputGroupTrailingAdornment";
159
+
160
+ type TInputGroupCompound = typeof InputGroupRoot & {
161
+ Root: typeof InputGroupRoot;
162
+ LeadingAdornment: typeof InputGroupLeadingAdornment;
163
+ TrailingAdornment: typeof InputGroupTrailingAdornment;
164
+ };
165
+
166
+ /**
167
+ * Headless, accessible **InputGroup** — a stateless compound that frames
168
+ * a single form control alongside optional leading and trailing
169
+ * adornments. Zero styles ship.
170
+ *
171
+ * `InputGroup` is intentionally **not** input-specific. It maps directly
172
+ * to the `framed-control/*` design-token anatomy (height,
173
+ * padding-inline, gap, icon-size, radius) and works just as well around
174
+ * a `<Textarea>`, a future `NumberInput.Control`, or any other framed
175
+ * control — the only thing tying it to `<Input>` is the name.
176
+ *
177
+ * `InputGroup` is both callable (an alias of `InputGroup.Root`) and
178
+ * carries its sub-components as static properties:
179
+ *
180
+ * - {@link InputGroupRoot | `InputGroup.Root`} — the wrapping `<div>`,
181
+ * `data-input-group=""` styling hook.
182
+ * - {@link InputGroupLeadingAdornment | `InputGroup.LeadingAdornment`} —
183
+ * leading slot, `data-input-group-adornment="leading"`.
184
+ * - {@link InputGroupTrailingAdornment | `InputGroup.TrailingAdornment`} —
185
+ * trailing slot, `data-input-group-adornment="trailing"`.
186
+ *
187
+ * **State coordination.** None. CSS handles disabled / invalid /
188
+ * focus-within styling via `:has()` and `:focus-within`. When `Field`
189
+ * lands it will sit *outside* `InputGroup` and own label / error /
190
+ * description wiring without InputGroup needing to change.
191
+ *
192
+ * @example Leading search icon
193
+ * ```tsx
194
+ * import { InputGroup, Input } from "@primitiv-ui/react";
195
+ *
196
+ * <InputGroup>
197
+ * <InputGroup.LeadingAdornment>
198
+ * <SearchIcon aria-hidden="true" />
199
+ * </InputGroup.LeadingAdornment>
200
+ * <Input aria-label="Search" type="search" />
201
+ * </InputGroup>
202
+ * ```
203
+ *
204
+ * @example Trailing clear button
205
+ * ```tsx
206
+ * <InputGroup>
207
+ * <Input value={q} onChange={onChange} aria-label="Search" />
208
+ * <InputGroup.TrailingAdornment asChild>
209
+ * <button type="button" aria-label="Clear" onClick={clear}>
210
+ * <XIcon aria-hidden="true" />
211
+ * </button>
212
+ * </InputGroup.TrailingAdornment>
213
+ * </InputGroup>
214
+ * ```
215
+ *
216
+ * @see {@link InputGroupRoot} for frame anatomy and `asChild` on Root.
217
+ * @see {@link InputGroupLeadingAdornment} for leading-slot semantics.
218
+ * @see {@link InputGroupTrailingAdornment} for trailing-slot semantics.
219
+ */
220
+ const InputGroupCompound: TInputGroupCompound = Object.assign(InputGroupRoot, {
221
+ Root: InputGroupRoot,
222
+ LeadingAdornment: InputGroupLeadingAdornment,
223
+ TrailingAdornment: InputGroupTrailingAdornment,
224
+ });
225
+
226
+ InputGroupCompound.displayName = "InputGroup";
227
+
228
+ export { InputGroupCompound as InputGroup };
@@ -0,0 +1,178 @@
1
+ # InputGroup
2
+
3
+ A headless, accessible compound that frames a single form control
4
+ alongside optional leading and trailing adornments — icons, currency
5
+ symbols, clear buttons, password-reveal toggles. Zero styles ship.
6
+
7
+ ```tsx
8
+ import { InputGroup, Input } from "@primitiv-ui/react";
9
+
10
+ <InputGroup>
11
+ <InputGroup.LeadingAdornment>
12
+ <SearchIcon aria-hidden="true" />
13
+ </InputGroup.LeadingAdornment>
14
+ <Input aria-label="Search" type="search" />
15
+ </InputGroup>
16
+ ```
17
+
18
+ ## Anatomy
19
+
20
+ | Part | Element | Styling hook |
21
+ | --------------------------------- | -------- | --------------------------------------------- |
22
+ | `InputGroup` / `InputGroup.Root` | `<div>` | `data-input-group=""` |
23
+ | `InputGroup.LeadingAdornment` | `<span>` | `data-input-group-adornment="leading"` |
24
+ | `InputGroup.TrailingAdornment` | `<span>` | `data-input-group-adornment="trailing"` |
25
+
26
+ `InputGroup` is intentionally **not** input-specific. The wrapper maps
27
+ to the `framed-control/*` design-token anatomy (height, padding-inline,
28
+ gap, icon-size, radius) and works around `<Input>`, `<Textarea>`, or any
29
+ future framed control — the only thing tying it to `<Input>` is the
30
+ name.
31
+
32
+ ## State coordination — there is none
33
+
34
+ `InputGroup` owns no state, provides no context, and accepts no
35
+ `disabled` / `invalid` props. State-dependent styling on the frame
36
+ delegates to CSS:
37
+
38
+ ```css
39
+ [data-input-group] {
40
+ display: inline-flex;
41
+ align-items: center;
42
+ gap: var(--frame-gap);
43
+ padding-inline: var(--frame-padding-inline);
44
+ border: 1px solid var(--frame-border);
45
+ border-radius: var(--frame-radius);
46
+ }
47
+
48
+ /* Focus ring around the whole frame */
49
+ [data-input-group]:focus-within {
50
+ outline: 2px solid var(--focus-ring);
51
+ outline-offset: 1px;
52
+ }
53
+
54
+ /* Disabled-frame styling — driven by the inner input */
55
+ [data-input-group]:has(input:disabled) {
56
+ opacity: 0.5;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ /* Invalid-frame styling — same idea */
61
+ [data-input-group]:has(input[aria-invalid="true"]) {
62
+ border-color: var(--danger);
63
+ }
64
+ ```
65
+
66
+ When `Field` lands it will sit *outside* `InputGroup` and own label /
67
+ description / error-text wiring; `InputGroup` will not need to change.
68
+
69
+ ## Adornments
70
+
71
+ ### Decorative
72
+
73
+ A decorative icon — give the icon `aria-hidden="true"` (or wrap with
74
+ `AccessibleIcon`). The default `<span>` adornment has no automatic ARIA
75
+ semantics — that's the consumer's call.
76
+
77
+ ```tsx
78
+ <InputGroup>
79
+ <InputGroup.LeadingAdornment>
80
+ <MailIcon aria-hidden="true" />
81
+ </InputGroup.LeadingAdornment>
82
+ <Input type="email" aria-label="Email" />
83
+ </InputGroup>
84
+ ```
85
+
86
+ ### Interactive — `asChild` + `<button>`
87
+
88
+ For a clear button, password-reveal toggle, or any clickable adornment,
89
+ pass `asChild` and supply the `<button>` directly. The data attribute
90
+ and ref merge onto the button; event handlers compose (child runs
91
+ first).
92
+
93
+ ```tsx
94
+ <InputGroup>
95
+ <Input value={q} onChange={onChange} aria-label="Search" />
96
+ <InputGroup.TrailingAdornment asChild>
97
+ <button type="button" aria-label="Clear" onClick={() => onChange("")}>
98
+ <XIcon aria-hidden="true" />
99
+ </button>
100
+ </InputGroup.TrailingAdornment>
101
+ </InputGroup>
102
+ ```
103
+
104
+ ### Leading and trailing together
105
+
106
+ JSX order matches visual order — put `LeadingAdornment` before the
107
+ control, `TrailingAdornment` after.
108
+
109
+ ```tsx
110
+ <InputGroup>
111
+ <InputGroup.LeadingAdornment><MailIcon aria-hidden="true" /></InputGroup.LeadingAdornment>
112
+ <Input type="email" aria-label="Email" />
113
+ <InputGroup.TrailingAdornment asChild>
114
+ <button type="button" aria-label="Clear">
115
+ <XIcon aria-hidden="true" />
116
+ </button>
117
+ </InputGroup.TrailingAdornment>
118
+ </InputGroup>
119
+ ```
120
+
121
+ ## `asChild` on Root
122
+
123
+ Pass `asChild` on `InputGroup.Root` to render the frame as a `<label>`
124
+ so clicking anywhere on the frame focuses the wrapped input:
125
+
126
+ ```tsx
127
+ <InputGroup asChild>
128
+ <label>
129
+ <InputGroup.LeadingAdornment><SearchIcon aria-hidden="true" /></InputGroup.LeadingAdornment>
130
+ <Input type="search" />
131
+ </label>
132
+ </InputGroup>
133
+ ```
134
+
135
+ ## With react-hook-form
136
+
137
+ `InputGroup` doesn't get in the way — spread `register` onto the inner
138
+ `<Input>` as usual:
139
+
140
+ ```tsx
141
+ import { useForm } from "react-hook-form";
142
+
143
+ const { register, formState: { errors } } = useForm();
144
+
145
+ <label htmlFor="email">Email</label>
146
+ <InputGroup>
147
+ <InputGroup.LeadingAdornment><MailIcon aria-hidden="true" /></InputGroup.LeadingAdornment>
148
+ <Input
149
+ id="email"
150
+ type="email"
151
+ required
152
+ {...register("email")}
153
+ aria-invalid={!!errors.email}
154
+ aria-describedby="email-error"
155
+ />
156
+ </InputGroup>
157
+ {errors.email && <span id="email-error">{errors.email.message}</span>}
158
+ ```
159
+
160
+ ## Ref forwarding
161
+
162
+ Every part accepts a `ref` prop:
163
+
164
+ ```tsx
165
+ const groupRef = useRef<HTMLDivElement>(null);
166
+ const inputRef = useRef<HTMLInputElement>(null);
167
+
168
+ <InputGroup ref={groupRef}>
169
+ <Input ref={inputRef} aria-label="Search" />
170
+ </InputGroup>
171
+ ```
172
+
173
+ ## Coming next
174
+
175
+ - **Field** — the label / description / error-text coordinator that
176
+ wraps an `InputGroup` (or any other control) and auto-wires `id`,
177
+ `aria-describedby`, and the `invalid` cascade. Until it ships, do that
178
+ wiring manually.
@@ -0,0 +1,109 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ import { InputGroup } from "../InputGroup";
6
+
7
+ describe("InputGroup asChild composition", () => {
8
+ it("Root asChild renders the consumer element with data-input-group merged on", () => {
9
+ // Arrange & Act
10
+ render(
11
+ <InputGroup.Root asChild data-testid="group">
12
+ <label />
13
+ </InputGroup.Root>,
14
+ );
15
+ const root = screen.getByTestId("group");
16
+
17
+ // Assert
18
+ expect(root.tagName).toBe("LABEL");
19
+ expect(root).toHaveAttribute("data-input-group", "");
20
+ });
21
+
22
+ it("LeadingAdornment asChild renders the consumer element with the leading data attribute", () => {
23
+ // Arrange & Act
24
+ render(
25
+ <InputGroup.Root>
26
+ <InputGroup.LeadingAdornment asChild data-testid="lead">
27
+ <button type="button">Search</button>
28
+ </InputGroup.LeadingAdornment>
29
+ </InputGroup.Root>,
30
+ );
31
+ const lead = screen.getByTestId("lead");
32
+
33
+ // Assert
34
+ expect(lead.tagName).toBe("BUTTON");
35
+ expect(lead).toHaveAttribute("data-input-group-adornment", "leading");
36
+ });
37
+
38
+ it("TrailingAdornment asChild renders the consumer element with the trailing data attribute", () => {
39
+ // Arrange & Act
40
+ render(
41
+ <InputGroup.Root>
42
+ <InputGroup.TrailingAdornment asChild data-testid="trail">
43
+ <button type="button">Clear</button>
44
+ </InputGroup.TrailingAdornment>
45
+ </InputGroup.Root>,
46
+ );
47
+ const trail = screen.getByTestId("trail");
48
+
49
+ // Assert
50
+ expect(trail.tagName).toBe("BUTTON");
51
+ expect(trail).toHaveAttribute("data-input-group-adornment", "trailing");
52
+ });
53
+
54
+ it("composes onClick when an adornment wraps a button via asChild", async () => {
55
+ // Arrange
56
+ const user = userEvent.setup();
57
+ const order: string[] = [];
58
+ render(
59
+ <InputGroup.Root>
60
+ <InputGroup.TrailingAdornment
61
+ asChild
62
+ onClick={() => order.push("adornment")}
63
+ >
64
+ <button type="button" onClick={() => order.push("child")}>
65
+ Clear
66
+ </button>
67
+ </InputGroup.TrailingAdornment>
68
+ </InputGroup.Root>,
69
+ );
70
+
71
+ // Act
72
+ await user.click(screen.getByRole("button", { name: "Clear" }));
73
+
74
+ // Assert
75
+ expect(order).toEqual(["child", "adornment"]);
76
+ });
77
+
78
+ it("forwards a ref through Slot on the Root", () => {
79
+ // Arrange
80
+ const ref = createRef<HTMLLabelElement>();
81
+
82
+ // Act
83
+ render(
84
+ <InputGroup.Root asChild data-testid="group" ref={ref}>
85
+ <label />
86
+ </InputGroup.Root>,
87
+ );
88
+
89
+ // Assert
90
+ expect(ref.current).toBe(screen.getByTestId("group"));
91
+ });
92
+
93
+ it("forwards a ref through Slot on an Adornment", () => {
94
+ // Arrange
95
+ const ref = createRef<HTMLButtonElement>();
96
+
97
+ // Act
98
+ render(
99
+ <InputGroup.Root>
100
+ <InputGroup.TrailingAdornment asChild ref={ref}>
101
+ <button type="button">Clear</button>
102
+ </InputGroup.TrailingAdornment>
103
+ </InputGroup.Root>,
104
+ );
105
+
106
+ // Assert
107
+ expect(ref.current).toBe(screen.getByRole("button", { name: "Clear" }));
108
+ });
109
+ });
@@ -0,0 +1,106 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+
4
+ import { InputGroup } from "../InputGroup";
5
+
6
+ describe("InputGroup basic rendering", () => {
7
+ it("renders a <div> wrapper element", () => {
8
+ // Arrange & Act
9
+ render(<InputGroup.Root data-testid="group" />);
10
+
11
+ // Assert
12
+ expect(screen.getByTestId("group").tagName).toBe("DIV");
13
+ });
14
+
15
+ it("sets data-input-group on the root as a CSS styling hook", () => {
16
+ // Arrange & Act
17
+ render(<InputGroup.Root data-testid="group" />);
18
+
19
+ // Assert
20
+ expect(screen.getByTestId("group")).toHaveAttribute(
21
+ "data-input-group",
22
+ "",
23
+ );
24
+ });
25
+
26
+ it("InputGroup is callable as an alias of InputGroup.Root", () => {
27
+ // Arrange & Act
28
+ render(<InputGroup data-testid="group" />);
29
+
30
+ // Assert
31
+ expect(screen.getByTestId("group").tagName).toBe("DIV");
32
+ });
33
+
34
+ it("forwards a ref to the root element", () => {
35
+ // Arrange
36
+ const ref = createRef<HTMLDivElement>();
37
+
38
+ // Act
39
+ render(<InputGroup.Root data-testid="group" ref={ref} />);
40
+
41
+ // Assert
42
+ expect(ref.current).toBe(screen.getByTestId("group"));
43
+ });
44
+
45
+ it("renders LeadingAdornment as a <span> with data-input-group-adornment='leading'", () => {
46
+ // Arrange & Act
47
+ render(
48
+ <InputGroup.Root>
49
+ <InputGroup.LeadingAdornment data-testid="lead" />
50
+ </InputGroup.Root>,
51
+ );
52
+ const span = screen.getByTestId("lead");
53
+
54
+ // Assert
55
+ expect(span.tagName).toBe("SPAN");
56
+ expect(span).toHaveAttribute("data-input-group-adornment", "leading");
57
+ });
58
+
59
+ it("renders TrailingAdornment as a <span> with data-input-group-adornment='trailing'", () => {
60
+ // Arrange & Act
61
+ render(
62
+ <InputGroup.Root>
63
+ <InputGroup.TrailingAdornment data-testid="trail" />
64
+ </InputGroup.Root>,
65
+ );
66
+ const span = screen.getByTestId("trail");
67
+
68
+ // Assert
69
+ expect(span.tagName).toBe("SPAN");
70
+ expect(span).toHaveAttribute("data-input-group-adornment", "trailing");
71
+ });
72
+
73
+ it("renders children inside the wrapper", () => {
74
+ // Arrange & Act
75
+ render(
76
+ <InputGroup.Root>
77
+ <input aria-label="Search" />
78
+ </InputGroup.Root>,
79
+ );
80
+
81
+ // Assert
82
+ expect(screen.getByRole("textbox", { name: "Search" })).toBeInTheDocument();
83
+ });
84
+
85
+ it("passes through className on the root", () => {
86
+ // Arrange & Act
87
+ render(<InputGroup.Root className="frame" data-testid="g" />);
88
+
89
+ // Assert
90
+ expect(screen.getByTestId("g")).toHaveClass("frame");
91
+ });
92
+
93
+ it("passes through className on adornments", () => {
94
+ // Arrange & Act
95
+ render(
96
+ <InputGroup.Root>
97
+ <InputGroup.LeadingAdornment className="lead" data-testid="lead" />
98
+ <InputGroup.TrailingAdornment className="trail" data-testid="trail" />
99
+ </InputGroup.Root>,
100
+ );
101
+
102
+ // Assert
103
+ expect(screen.getByTestId("lead")).toHaveClass("lead");
104
+ expect(screen.getByTestId("trail")).toHaveClass("trail");
105
+ });
106
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./InputGroup";
2
+ export * from "./types";
@@ -0,0 +1,13 @@
1
+ import { ComponentProps, ReactNode, Ref } from "react";
2
+
3
+ export type InputGroupRootProps = ComponentProps<"div"> & {
4
+ asChild?: boolean;
5
+ ref?: Ref<HTMLDivElement>;
6
+ children?: ReactNode;
7
+ };
8
+
9
+ export type InputGroupAdornmentProps = ComponentProps<"span"> & {
10
+ asChild?: boolean;
11
+ ref?: Ref<HTMLSpanElement>;
12
+ children?: ReactNode;
13
+ };