@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,75 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Field } from "../Field";
4
+
5
+ describe("Field state cascade", () => {
6
+ it("sets data-field-invalid='' on the root when invalid", () => {
7
+ // Arrange & Act
8
+ render(<Field.Root data-testid="field" invalid />);
9
+
10
+ // Assert
11
+ expect(screen.getByTestId("field")).toHaveAttribute(
12
+ "data-field-invalid",
13
+ "",
14
+ );
15
+ });
16
+
17
+ it("sets data-field-disabled='' on the root when disabled", () => {
18
+ // Arrange & Act
19
+ render(<Field.Root data-testid="field" disabled />);
20
+
21
+ // Assert
22
+ expect(screen.getByTestId("field")).toHaveAttribute(
23
+ "data-field-disabled",
24
+ "",
25
+ );
26
+ });
27
+
28
+ it("sets data-field-required='' on the root when required", () => {
29
+ // Arrange & Act
30
+ render(<Field.Root data-testid="field" required />);
31
+
32
+ // Assert
33
+ expect(screen.getByTestId("field")).toHaveAttribute(
34
+ "data-field-required",
35
+ "",
36
+ );
37
+ });
38
+
39
+ it("does not set state data-* attributes when the corresponding flag is false", () => {
40
+ // Arrange & Act
41
+ render(<Field.Root data-testid="field" />);
42
+ const root = screen.getByTestId("field");
43
+
44
+ // Assert
45
+ expect(root).not.toHaveAttribute("data-field-invalid");
46
+ expect(root).not.toHaveAttribute("data-field-disabled");
47
+ expect(root).not.toHaveAttribute("data-field-required");
48
+ });
49
+
50
+ it("Field.ErrorText returns null when invalid=false", () => {
51
+ // Arrange & Act
52
+ render(
53
+ <Field.Root>
54
+ <Field.ErrorText>You should not see this.</Field.ErrorText>
55
+ </Field.Root>,
56
+ );
57
+
58
+ // Assert
59
+ expect(
60
+ screen.queryByText("You should not see this."),
61
+ ).not.toBeInTheDocument();
62
+ });
63
+
64
+ it("Field.ErrorText renders when invalid=true", () => {
65
+ // Arrange & Act
66
+ render(
67
+ <Field.Root invalid>
68
+ <Field.ErrorText>Required.</Field.ErrorText>
69
+ </Field.Root>,
70
+ );
71
+
72
+ // Assert
73
+ expect(screen.getByText("Required.")).toBeInTheDocument();
74
+ });
75
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./useFieldContext";
2
+ export * from "./useFieldProps";
@@ -0,0 +1 @@
1
+ export { useFieldContext } from "../FieldContext";
@@ -0,0 +1,57 @@
1
+ import { useContext } from "react";
2
+
3
+ import { FieldContext } from "../FieldContext";
4
+
5
+ type FieldAwareProps = {
6
+ id?: string;
7
+ "aria-describedby"?: string;
8
+ "aria-invalid"?:
9
+ | boolean
10
+ | "true"
11
+ | "false"
12
+ | "grammar"
13
+ | "spelling"
14
+ | undefined;
15
+ disabled?: boolean;
16
+ required?: boolean;
17
+ };
18
+
19
+ /**
20
+ * Merges consumer-supplied props with any {@link FieldContext} available
21
+ * higher in the tree. Use it inside form controls (Input, Textarea,
22
+ * Select, …) so they automatically inherit the field's id, aria
23
+ * wiring, and `disabled` / `required` cascade when nested inside
24
+ * `<Field.Root>`.
25
+ *
26
+ * **Merge rules**
27
+ * - `id`, `disabled`, `required`, `aria-invalid`: consumer wins; the
28
+ * field provides the fallback.
29
+ * - `aria-describedby`: composes — consumer-supplied ids come first,
30
+ * then the field's `descriptionId`, then the `errorId` when invalid.
31
+ *
32
+ * When no FieldContext is in scope the consumer props are returned
33
+ * unchanged, so existing usage outside a `<Field.Root>` is unaffected.
34
+ */
35
+ export function useFieldProps<P extends FieldAwareProps>(consumerProps: P): P {
36
+ const field = useContext(FieldContext);
37
+ if (!field) return consumerProps;
38
+
39
+ const composedDescribedBy =
40
+ [
41
+ consumerProps["aria-describedby"],
42
+ field.descriptionId,
43
+ field.invalid ? field.errorId : null,
44
+ ]
45
+ .filter(Boolean)
46
+ .join(" ") || undefined;
47
+
48
+ return {
49
+ ...consumerProps,
50
+ id: consumerProps.id ?? field.id,
51
+ disabled: consumerProps.disabled ?? field.disabled,
52
+ required: consumerProps.required ?? field.required,
53
+ "aria-invalid":
54
+ consumerProps["aria-invalid"] ?? (field.invalid || undefined),
55
+ "aria-describedby": composedDescribedBy,
56
+ };
57
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Field";
2
+ export * from "./types";
@@ -0,0 +1,33 @@
1
+ import { ComponentProps, ReactNode } from "react";
2
+
3
+ export type FieldRootProps = ComponentProps<"div"> & {
4
+ /** Stable id wired to the control via {@link FieldLabel}'s `htmlFor`. Auto-generated via `useId` when omitted. */
5
+ id?: string;
6
+ /** Marks the field as invalid; cascades to the control via context. */
7
+ invalid?: boolean;
8
+ /** Disables the field; cascades to the control via context. */
9
+ disabled?: boolean;
10
+ /** Marks the field as required; cascades to the control via context. */
11
+ required?: boolean;
12
+ /** Renders the consumer element instead of `<div>` via Slot. */
13
+ asChild?: boolean;
14
+ children?: ReactNode;
15
+ };
16
+
17
+ export type FieldLabelProps = ComponentProps<"label"> & {
18
+ /** Renders the consumer element instead of `<label>` via Slot. */
19
+ asChild?: boolean;
20
+ children?: ReactNode;
21
+ };
22
+
23
+ export type FieldDescriptionProps = ComponentProps<"div"> & {
24
+ /** Renders the consumer element instead of `<div>` via Slot. */
25
+ asChild?: boolean;
26
+ children?: ReactNode;
27
+ };
28
+
29
+ export type FieldErrorTextProps = ComponentProps<"div"> & {
30
+ /** Renders the consumer element instead of `<div>` via Slot. */
31
+ asChild?: boolean;
32
+ children?: ReactNode;
33
+ };
@@ -0,0 +1,104 @@
1
+ import { FieldsetLegendProps, FieldsetProps } from "./types";
2
+
3
+ /**
4
+ * The root of a Fieldset — renders a native `<fieldset>` element.
5
+ *
6
+ * A `<fieldset>` carries an implicit `role="group"` and groups a set of
7
+ * related form controls (radios, checkboxes, inputs). Pair it with a
8
+ * {@link FieldsetLegend} so the group is announced with an accessible
9
+ * name when the user enters any control inside it.
10
+ *
11
+ * **Disabled.** Setting `disabled` forwards the native attribute, which
12
+ * disables *every* form control nested inside the fieldset at once — the
13
+ * standard way to disable a whole section of a form. It also sets
14
+ * `data-disabled=""` so CSS can target the state without the `:disabled`
15
+ * pseudo-class:
16
+ *
17
+ * ```css
18
+ * fieldset[data-disabled] { opacity: 0.5; }
19
+ * ```
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * <Fieldset.Root>
24
+ * <Fieldset.Legend>Notifications</Fieldset.Legend>
25
+ * <label><input type="checkbox" name="email" /> Email</label>
26
+ * <label><input type="checkbox" name="sms" /> SMS</label>
27
+ * </Fieldset.Root>
28
+ * ```
29
+ */
30
+ function Fieldset({ disabled, children, ...rest }: FieldsetProps) {
31
+ return (
32
+ <fieldset
33
+ {...rest}
34
+ disabled={disabled}
35
+ data-disabled={disabled ? "" : undefined}
36
+ >
37
+ {children}
38
+ </fieldset>
39
+ );
40
+ }
41
+
42
+ Fieldset.displayName = "Fieldset";
43
+
44
+ /**
45
+ * The caption for a {@link Fieldset} — renders a native `<legend>`.
46
+ *
47
+ * The `<legend>` must be the first child of the `<fieldset>`. It supplies
48
+ * the group's accessible name, so assistive technology announces it when
49
+ * the user moves focus into any control within the group.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * <Fieldset.Legend>Shipping address</Fieldset.Legend>
54
+ * ```
55
+ */
56
+ function FieldsetLegend({ children, ...rest }: FieldsetLegendProps) {
57
+ return <legend {...rest}>{children}</legend>;
58
+ }
59
+
60
+ FieldsetLegend.displayName = "FieldsetLegend";
61
+
62
+ type FieldsetCompound = typeof Fieldset & {
63
+ Root: typeof Fieldset;
64
+ Legend: typeof FieldsetLegend;
65
+ };
66
+
67
+ const FieldsetCompound: FieldsetCompound = Object.assign(Fieldset, {
68
+ Root: Fieldset,
69
+ Legend: FieldsetLegend,
70
+ });
71
+
72
+ /**
73
+ * Headless, accessible **Fieldset** — a stateless compound component that
74
+ * groups related form controls, with zero styles.
75
+ *
76
+ * `Fieldset` is both callable (an alias of {@link Fieldset | `Fieldset.Root`})
77
+ * and carries its sub-component as a static property. Prefer the
78
+ * namespaced form in application code:
79
+ *
80
+ * - {@link Fieldset | `Fieldset.Root`} — `<fieldset>`, implicit `role="group"`.
81
+ * - {@link FieldsetLegend | `Fieldset.Legend`} — `<legend>`, the group's accessible name.
82
+ *
83
+ * @example Grouping a set of radios
84
+ * ```tsx
85
+ * import { Fieldset } from "@primitiv-ui/react";
86
+ *
87
+ * <Fieldset.Root>
88
+ * <Fieldset.Legend>Plan</Fieldset.Legend>
89
+ * <label><input type="radio" name="plan" value="free" /> Free</label>
90
+ * <label><input type="radio" name="plan" value="pro" /> Pro</label>
91
+ * </Fieldset.Root>
92
+ * ```
93
+ *
94
+ * @example Disabling a whole section
95
+ * ```tsx
96
+ * <Fieldset.Root disabled>
97
+ * <Fieldset.Legend>Billing</Fieldset.Legend>
98
+ * …every nested control is disabled…
99
+ * </Fieldset.Root>
100
+ * ```
101
+ */
102
+ FieldsetCompound.displayName = "Fieldset";
103
+
104
+ export { FieldsetCompound as Fieldset };
@@ -0,0 +1,74 @@
1
+ # Fieldset
2
+
3
+ A headless, accessible grouping for related form controls — a stateless
4
+ compound component wrapping `<fieldset>` and `<legend>`. Zero styles ship.
5
+
6
+ ```tsx
7
+ import { Fieldset } from "@primitiv-ui/react";
8
+
9
+ <Fieldset.Root>
10
+ <Fieldset.Legend>Notifications</Fieldset.Legend>
11
+ <label><input type="checkbox" name="email" /> Email</label>
12
+ <label><input type="checkbox" name="sms" /> SMS</label>
13
+ </Fieldset.Root>;
14
+ ```
15
+
16
+ `Fieldset` is both callable (an alias of `Fieldset.Root`) and carries its
17
+ sub-component as a static property.
18
+
19
+ ## Parts
20
+
21
+ | Part | Element | Notes |
22
+ | ----------------- | ------------ | ------------------------------------------------------ |
23
+ | `Fieldset.Root` | `<fieldset>` | Implicit `role="group"`; `disabled` styling hook |
24
+ | `Fieldset.Legend` | `<legend>` | Supplies the group's accessible name |
25
+
26
+ ## Props
27
+
28
+ ### `Fieldset.Root`
29
+
30
+ | Prop | Type | Default | Notes |
31
+ | ---------- | ---------------------------- | ------- | ----------------------------------------------------------- |
32
+ | `disabled` | `boolean` | — | Native `disabled` + `data-disabled=""` styling hook |
33
+ | `...rest` | `ComponentProps<"fieldset">` | — | All other `<fieldset>` props (`aria-*`, `data-*`, etc.) |
34
+
35
+ ### `Fieldset.Legend`
36
+
37
+ | Prop | Type | Default | Notes |
38
+ | --------- | -------------------------- | ------- | -------------------------------- |
39
+ | `...rest` | `ComponentProps<"legend">` | — | All `<legend>` props |
40
+
41
+ ## Accessible name
42
+
43
+ A `<fieldset>` has an implicit `role="group"`. Place a `Fieldset.Legend`
44
+ as the first child so the group is announced with a name — assistive
45
+ technology reads the legend when the user moves focus into any control
46
+ within the group.
47
+
48
+ ```tsx
49
+ <Fieldset.Root>
50
+ <Fieldset.Legend>Shipping address</Fieldset.Legend>
51
+
52
+ </Fieldset.Root>
53
+ ```
54
+
55
+ ## Disabled
56
+
57
+ Passing `disabled` to `Fieldset.Root` forwards the native `disabled`
58
+ attribute. Natively, this disables **every** form control nested inside
59
+ the fieldset at once — the standard way to disable a whole section of a
60
+ form. It also sets `data-disabled=""` so CSS can target the state
61
+ directly:
62
+
63
+ ```tsx
64
+ <Fieldset.Root disabled>
65
+ <Fieldset.Legend>Billing</Fieldset.Legend>
66
+ …every nested control is disabled…
67
+ </Fieldset.Root>
68
+ ```
69
+
70
+ ```css
71
+ fieldset[data-disabled] {
72
+ opacity: 0.5;
73
+ }
74
+ ```
@@ -0,0 +1,81 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Fieldset } from "../Fieldset";
4
+
5
+ describe("Fieldset basic rendering", () => {
6
+ it("renders a <fieldset> element with an implicit group role", () => {
7
+ // Arrange & Act
8
+ render(<Fieldset.Root />);
9
+
10
+ // Assert
11
+ expect(screen.getByRole("group").tagName).toBe("FIELDSET");
12
+ });
13
+
14
+ it("Fieldset is callable as an alias of Fieldset.Root", () => {
15
+ // Arrange & Act
16
+ render(<Fieldset />);
17
+
18
+ // Assert
19
+ expect(screen.getByRole("group").tagName).toBe("FIELDSET");
20
+ });
21
+
22
+ it("renders Fieldset.Legend as a <legend> element", () => {
23
+ // Arrange & Act
24
+ render(
25
+ <Fieldset.Root>
26
+ <Fieldset.Legend>Shipping address</Fieldset.Legend>
27
+ </Fieldset.Root>,
28
+ );
29
+
30
+ // Assert
31
+ expect(screen.getByText("Shipping address").tagName).toBe("LEGEND");
32
+ });
33
+
34
+ it("uses the legend as the group's accessible name", () => {
35
+ // Arrange & Act
36
+ render(
37
+ <Fieldset.Root>
38
+ <Fieldset.Legend>Contact details</Fieldset.Legend>
39
+ </Fieldset.Root>,
40
+ );
41
+
42
+ // Assert
43
+ expect(
44
+ screen.getByRole("group", { name: "Contact details" }),
45
+ ).toBeInTheDocument();
46
+ });
47
+
48
+ it("renders children inside the fieldset", () => {
49
+ // Arrange & Act
50
+ render(
51
+ <Fieldset.Root>
52
+ <input data-testid="field" />
53
+ </Fieldset.Root>,
54
+ );
55
+
56
+ // Assert
57
+ expect(screen.getByRole("group")).toContainElement(
58
+ screen.getByTestId("field"),
59
+ );
60
+ });
61
+
62
+ it("passes through className and data-* attributes on the root", () => {
63
+ // Arrange & Act
64
+ render(<Fieldset.Root className="group" data-testid="fs" />);
65
+
66
+ // Assert
67
+ expect(screen.getByTestId("fs")).toHaveClass("group");
68
+ });
69
+
70
+ it("passes through props on the legend", () => {
71
+ // Arrange & Act
72
+ render(
73
+ <Fieldset.Root>
74
+ <Fieldset.Legend className="group__legend">Billing</Fieldset.Legend>
75
+ </Fieldset.Root>,
76
+ );
77
+
78
+ // Assert
79
+ expect(screen.getByText("Billing")).toHaveClass("group__legend");
80
+ });
81
+ });
@@ -0,0 +1,41 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Fieldset } from "../Fieldset";
4
+
5
+ describe("Fieldset disabled state", () => {
6
+ it("sets the native disabled attribute on the fieldset", () => {
7
+ // Arrange & Act
8
+ render(<Fieldset.Root disabled />);
9
+
10
+ // Assert
11
+ expect(screen.getByRole("group")).toBeDisabled();
12
+ });
13
+
14
+ it('sets data-disabled="" as a CSS styling hook', () => {
15
+ // Arrange & Act
16
+ render(<Fieldset.Root disabled data-testid="fs" />);
17
+
18
+ // Assert
19
+ expect(screen.getByTestId("fs")).toHaveAttribute("data-disabled", "");
20
+ });
21
+
22
+ it("does not set data-disabled when not disabled", () => {
23
+ // Arrange & Act
24
+ render(<Fieldset.Root data-testid="fs" />);
25
+
26
+ // Assert
27
+ expect(screen.getByTestId("fs")).not.toHaveAttribute("data-disabled");
28
+ });
29
+
30
+ it("disables form controls nested inside the fieldset", () => {
31
+ // Arrange & Act
32
+ render(
33
+ <Fieldset.Root disabled>
34
+ <input data-testid="field" />
35
+ </Fieldset.Root>,
36
+ );
37
+
38
+ // Assert
39
+ expect(screen.getByTestId("field")).toBeDisabled();
40
+ });
41
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./Fieldset";
2
+ export * from "./types";
@@ -0,0 +1,5 @@
1
+ import { ComponentProps } from "react";
2
+
3
+ export type FieldsetProps = ComponentProps<"fieldset">;
4
+
5
+ export type FieldsetLegendProps = ComponentProps<"legend">;
@@ -0,0 +1,120 @@
1
+ import { useFieldProps } from "../Field/hooks";
2
+ import { Slot } from "../Slot";
3
+ import { InputProps } from "./types";
4
+
5
+ /**
6
+ * A headless, accessible single-line text input.
7
+ *
8
+ * Renders a native `<input>` and passes every standard input attribute
9
+ * straight through to the DOM — `value` / `defaultValue`, `placeholder`,
10
+ * `type`, `name`, `required`, `pattern`, `min`, `max`, `step`,
11
+ * `maxLength`, and so on. No styles ship with the component.
12
+ *
13
+ * **Default type.** `type="text"` is set by default. Override with the
14
+ * `type` prop for any native variant (`"email"`, `"password"`,
15
+ * `"number"`, `"search"`, `"tel"`, `"url"`, `"date"`, …). Future
16
+ * composite primitives (`PasswordInput`, `NumberInput`, `DatePicker`)
17
+ * will layer richer interaction on top of those types.
18
+ *
19
+ * **Labelling.** An `<input>` has no implicit accessible name. Pair it
20
+ * with a `<label>` (`htmlFor` → the input's `id`), or pass `aria-label`
21
+ * / `aria-labelledby` for the control to be announced correctly.
22
+ *
23
+ * **Native validation.** All HTML constraint-validation attributes
24
+ * (`required`, `pattern`, `min`, `max`, `minLength`, `maxLength`,
25
+ * `type="email"` / `"url"` / `"number"`) work as the browser intends —
26
+ * the component does not interfere. CSS can target `input:invalid`
27
+ * directly; for assistive technology, set `aria-invalid` explicitly
28
+ * based on your validation state.
29
+ *
30
+ * **Form library compatibility.** `ref`, `name`, `onChange`, and
31
+ * `onBlur` all pass through, so the spread pattern used by
32
+ * react-hook-form's `register("field")` and similar libraries works
33
+ * directly:
34
+ *
35
+ * ```tsx
36
+ * <Input {...register("email")} type="email" required />
37
+ * ```
38
+ *
39
+ * **Field integration.** When rendered inside a `<Field.Root>`, Input
40
+ * opts into `FieldContext` and inherits `id`, `aria-describedby`,
41
+ * `aria-invalid`, `disabled`, and `required` from the field. Any prop
42
+ * the consumer passes wins; `aria-describedby` is composed (consumer
43
+ * ids first, then field-supplied description / error ids). Outside a
44
+ * `<Field.Root>`, behaviour is unchanged.
45
+ *
46
+ * **Ref forwarding.** Pass a `ref` prop to access the underlying
47
+ * `HTMLInputElement`:
48
+ *
49
+ * ```tsx
50
+ * const ref = useRef<HTMLInputElement>(null);
51
+ * <Input ref={ref} aria-label="Email" />
52
+ * ```
53
+ *
54
+ * **Disabled.** Sets native `disabled` (removing the field from the tab
55
+ * order and blocking input) plus `data-disabled=""` so CSS can target
56
+ * `[data-disabled]` without relying on the `:disabled` pseudo-class:
57
+ *
58
+ * ```css
59
+ * input[data-disabled] { opacity: 0.5; cursor: not-allowed; }
60
+ * ```
61
+ *
62
+ * **`asChild` composition.** Renders the consumer's element instead of
63
+ * `<input>`, merging all props (aria-*, data-*, event handlers, ref)
64
+ * via the {@link Slot} utility. `type` is **not** forwarded in this
65
+ * mode — the child element owns its own type semantics.
66
+ *
67
+ * **Adornments** live in the separate `InputGroup` primitive — leading
68
+ * / trailing icons, currency symbols, clear buttons, password-reveal
69
+ * toggles.
70
+ *
71
+ * @example Basic usage
72
+ * ```tsx
73
+ * <label htmlFor="email">Email</label>
74
+ * <Input id="email" type="email" required />
75
+ * ```
76
+ *
77
+ * @example Inside a Field — id, aria-*, disabled, required all wired automatically
78
+ * ```tsx
79
+ * <Field.Root invalid={!!errors.email}>
80
+ * <Field.Label>Email</Field.Label>
81
+ * <Input type="email" {...register("email")} />
82
+ * <Field.ErrorText>{errors.email?.message}</Field.ErrorText>
83
+ * </Field.Root>
84
+ * ```
85
+ *
86
+ * @example Disabled
87
+ * ```tsx
88
+ * <Input aria-label="Email" disabled />
89
+ * ```
90
+ *
91
+ * @example asChild — wrap a custom input variant
92
+ * ```tsx
93
+ * <Input asChild aria-label="Email">
94
+ * <MaskedInput mask="email" />
95
+ * </Input>
96
+ * ```
97
+ */
98
+ export function Input({
99
+ asChild = false,
100
+ type = "text",
101
+ children,
102
+ ref,
103
+ ...consumer
104
+ }: InputProps) {
105
+ const merged = useFieldProps(consumer);
106
+
107
+ const rootProps = {
108
+ ...merged,
109
+ ref,
110
+ "data-disabled": merged.disabled ? "" : undefined,
111
+ };
112
+
113
+ if (asChild) {
114
+ return <Slot {...rootProps}>{children}</Slot>;
115
+ }
116
+
117
+ return <input type={type} {...rootProps} />;
118
+ }
119
+
120
+ Input.displayName = "Input";