@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,178 @@
1
+ import { useMemo } from "react";
2
+
3
+ import { Slot } from "../Slot";
4
+
5
+ import { ProgressContext } from "./ProgressContext";
6
+ import { useProgressContext, useProgressRoot } from "./hooks";
7
+ import { ProgressIndicatorProps, ProgressRootProps } from "./types";
8
+
9
+ function defaultGetValueLabel(value: number, max: number): string {
10
+ return `${Math.round((value / max) * 100)}%`;
11
+ }
12
+
13
+ /**
14
+ * The root of a Progress bar — a `<div role="progressbar">` that resolves
15
+ * `value` / `max` into a {@link ProgressState} and provides
16
+ * {@link ProgressContext} to a descendant
17
+ * {@link ProgressIndicator | `Progress.Indicator`}.
18
+ *
19
+ * Progress is a **display-only** component: it has no internal state and is
20
+ * never interactive. The `value` prop is always consumer-driven — there is
21
+ * no controlled/uncontrolled split.
22
+ *
23
+ * **Indeterminate.** Omit `value` (or pass `null`) when the completion ratio
24
+ * is unknown. `aria-valuenow` / `aria-valuetext` are dropped and
25
+ * `data-state="indeterminate"`.
26
+ *
27
+ * **ARIA.** `role="progressbar"`, `aria-valuemin="0"`, `aria-valuemax`, and —
28
+ * when determinate — `aria-valuenow` and `aria-valuetext` are set
29
+ * automatically. Provide an accessible name via `aria-label` or
30
+ * `aria-labelledby`.
31
+ *
32
+ * **Styling hooks.** `data-state="indeterminate" | "loading" | "complete"`,
33
+ * plus `data-value` (determinate only) and `data-max` on the root.
34
+ *
35
+ * @example Determinate
36
+ * ```tsx
37
+ * <Progress.Root value={60} aria-label="Upload progress">
38
+ * <Progress.Indicator />
39
+ * </Progress.Root>
40
+ * ```
41
+ *
42
+ * @example Indeterminate
43
+ * ```tsx
44
+ * <Progress.Root aria-label="Loading">
45
+ * <Progress.Indicator />
46
+ * </Progress.Root>
47
+ * ```
48
+ */
49
+ function ProgressRoot({
50
+ value,
51
+ max,
52
+ getValueLabel = defaultGetValueLabel,
53
+ asChild = false,
54
+ children,
55
+ ...rest
56
+ }: ProgressRootProps) {
57
+ const resolved = useProgressRoot({ value, max });
58
+ const { state } = resolved;
59
+
60
+ const contextValue = useMemo(
61
+ () => ({ value: resolved.value, max: resolved.max, state }),
62
+ [resolved.value, resolved.max, state],
63
+ );
64
+
65
+ const valueProps =
66
+ resolved.value === null
67
+ ? {}
68
+ : {
69
+ "aria-valuenow": resolved.value,
70
+ "aria-valuetext": getValueLabel(resolved.value, resolved.max),
71
+ "data-value": resolved.value,
72
+ };
73
+
74
+ const rootProps = {
75
+ ...rest,
76
+ role: "progressbar" as const,
77
+ "aria-valuemin": 0,
78
+ "aria-valuemax": resolved.max,
79
+ "data-state": state,
80
+ "data-max": resolved.max,
81
+ ...valueProps,
82
+ };
83
+
84
+ return (
85
+ <ProgressContext.Provider value={contextValue}>
86
+ {asChild ? (
87
+ <Slot {...rootProps}>{children}</Slot>
88
+ ) : (
89
+ <div {...rootProps}>{children}</div>
90
+ )}
91
+ </ProgressContext.Provider>
92
+ );
93
+ }
94
+
95
+ ProgressRoot.displayName = "ProgressRoot";
96
+
97
+ /**
98
+ * The visual fill of a Progress bar — a decorative `<div>` that mirrors the
99
+ * parent {@link ProgressRoot}'s state via `data-*` attributes so consumers
100
+ * can drive its width with pure CSS.
101
+ *
102
+ * **Styling hooks.** `data-state="indeterminate" | "loading" | "complete"`,
103
+ * plus `data-value` (determinate only) and `data-max`. Compute the fill
104
+ * width from these — e.g. `width: calc(var(--value) / var(--max) * 100%)` via
105
+ * a CSS custom property, or an inline `style` set by the consumer.
106
+ *
107
+ * **`asChild` prop.** Pass `asChild` to render the consumer's own element as
108
+ * the indicator, with the `data-*` hooks merged in.
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * <Progress.Root value={75} aria-label="Upload progress">
113
+ * <Progress.Indicator />
114
+ * </Progress.Root>
115
+ * ```
116
+ *
117
+ * @throws if rendered outside a `Progress.Root`.
118
+ */
119
+ function ProgressIndicator({
120
+ asChild = false,
121
+ children,
122
+ ...rest
123
+ }: ProgressIndicatorProps) {
124
+ const { value, max, state } = useProgressContext();
125
+ const indicatorProps = {
126
+ ...rest,
127
+ "data-state": state,
128
+ "data-value": value === null ? undefined : value,
129
+ "data-max": max,
130
+ };
131
+ if (asChild) {
132
+ return <Slot {...indicatorProps}>{children}</Slot>;
133
+ }
134
+ return <div {...indicatorProps}>{children}</div>;
135
+ }
136
+
137
+ ProgressIndicator.displayName = "ProgressIndicator";
138
+
139
+ type TProgressCompound = typeof ProgressRoot & {
140
+ Root: typeof ProgressRoot;
141
+ Indicator: typeof ProgressIndicator;
142
+ };
143
+
144
+ /**
145
+ * Headless, accessible **Progress** — a compound component implementing the
146
+ * [WAI-ARIA progressbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/)
147
+ * on a `<div role="progressbar">`. Display-only and non-interactive: it
148
+ * reflects a consumer-supplied `value`. Zero styles ship.
149
+ *
150
+ * `Progress` is both callable (an alias of
151
+ * {@link ProgressRoot | `Progress.Root`}) and carries its sub-components as
152
+ * static properties.
153
+ *
154
+ * - {@link ProgressRoot | `Progress.Root`} — `role="progressbar"`, value
155
+ * resolution, context provider.
156
+ * - {@link ProgressIndicator | `Progress.Indicator`} — decorative fill;
157
+ * width driven by `data-*` via CSS.
158
+ *
159
+ * @example Minimal usage
160
+ * ```tsx
161
+ * import { Progress } from "@primitiv-ui/react";
162
+ *
163
+ * <Progress.Root value={40} aria-label="Upload progress">
164
+ * <Progress.Indicator />
165
+ * </Progress.Root>
166
+ * ```
167
+ *
168
+ * @see {@link ProgressRoot} for value resolution and ARIA details.
169
+ * @see {@link ProgressIndicator} for styling the fill.
170
+ */
171
+ const ProgressCompound: TProgressCompound = Object.assign(ProgressRoot, {
172
+ Root: ProgressRoot,
173
+ Indicator: ProgressIndicator,
174
+ });
175
+
176
+ ProgressCompound.displayName = "Progress";
177
+
178
+ export { ProgressCompound as Progress };
@@ -0,0 +1,15 @@
1
+ import { createStrictContext } from "../utils";
2
+
3
+ import { ProgressState } from "./types";
4
+
5
+ export type ProgressContextValue = {
6
+ value: number | null;
7
+ max: number;
8
+ state: ProgressState;
9
+ };
10
+
11
+ export const [ProgressContext, useProgressContext] =
12
+ createStrictContext<ProgressContextValue>(
13
+ "Progress.Indicator must be rendered inside a <Progress.Root>.",
14
+ "ProgressContext",
15
+ );
@@ -0,0 +1,112 @@
1
+ # Progress
2
+
3
+ Headless, accessible **Progress** — a compound component implementing the
4
+ [WAI-ARIA progressbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/)
5
+ on a `<div role="progressbar">`. Display-only and non-interactive: it
6
+ reflects a consumer-supplied `value`. Zero styles ship.
7
+
8
+ ```tsx
9
+ import { Progress } from "@primitiv-ui/react";
10
+
11
+ <Progress.Root value={60} aria-label="Upload progress">
12
+ <Progress.Indicator />
13
+ </Progress.Root>;
14
+ ```
15
+
16
+ ## Sub-components
17
+
18
+ | Export | Element | ARIA / data hooks | `asChild` |
19
+ |--------|---------|-------------------|-----------|
20
+ | `Progress.Root` | `<div>` | `role="progressbar"`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext`, `data-state`, `data-value`, `data-max` | yes |
21
+ | `Progress.Indicator` | `<div>` | `data-state`, `data-value`, `data-max` | yes |
22
+
23
+ ## Props
24
+
25
+ ### `Progress.Root`
26
+
27
+ | Prop | Type | Default | Notes |
28
+ |------|------|---------|-------|
29
+ | `value` | `number \| null` | `null` | Current progress between `0` and `max`. `null` (or omitted) renders an indeterminate bar. |
30
+ | `max` | `number` | `100` | Upper bound of `value`. Must be a positive, finite number. |
31
+ | `getValueLabel` | `(value: number, max: number) => string` | rounded percentage | Produces `aria-valuetext`. Not called while indeterminate. |
32
+ | `asChild` | `boolean` | `false` | Render the consumer's own element instead of `<div>`. |
33
+ | `...rest` | `ComponentProps<"div">` | — | All other `<div>` props (`aria-label`, `className`, etc.). |
34
+
35
+ `Progress.Indicator` accepts all `<div>` props plus `asChild`.
36
+
37
+ ## Determinate vs indeterminate
38
+
39
+ Pass a numeric `value` for a determinate bar — `aria-valuenow` and
40
+ `aria-valuetext` are set and `data-state` is `"loading"` or `"complete"`.
41
+
42
+ ```tsx
43
+ <Progress.Root value={75} aria-label="Upload progress">
44
+ <Progress.Indicator />
45
+ </Progress.Root>
46
+ ```
47
+
48
+ Omit `value` (or pass `null`) when the completion ratio is unknown.
49
+ `aria-valuenow` / `aria-valuetext` / `data-value` are dropped and
50
+ `data-state="indeterminate"`.
51
+
52
+ ```tsx
53
+ <Progress.Root aria-label="Loading">
54
+ <Progress.Indicator />
55
+ </Progress.Root>
56
+ ```
57
+
58
+ ## Value label
59
+
60
+ `aria-valuetext` defaults to a rounded percentage (e.g. `"42%"`). Override it
61
+ with `getValueLabel` when a percentage isn't the most useful description:
62
+
63
+ ```tsx
64
+ <Progress.Root
65
+ value={3}
66
+ max={10}
67
+ getValueLabel={(value, max) => `${value} of ${max} files uploaded`}
68
+ aria-label="Upload progress"
69
+ >
70
+ <Progress.Indicator />
71
+ </Progress.Root>
72
+ ```
73
+
74
+ ## Accessibility
75
+
76
+ `Progress` is display-only — it is never focusable or interactive. Always give
77
+ the Root an accessible name with `aria-label` or `aria-labelledby`.
78
+
79
+ `max` must be a positive, finite number, and a numeric `value` must fall
80
+ between `0` and `max`. Both are validated — an out-of-range prop throws a
81
+ clear error rather than rendering a misleading bar.
82
+
83
+ ## The Indicator
84
+
85
+ `Progress.Indicator` is the visual fill. It mirrors the Root's `data-state`,
86
+ `data-value`, and `data-max`, so its width can be driven with pure CSS — for
87
+ example via a custom property the consumer sets, or an inline `style`:
88
+
89
+ ```tsx
90
+ <Progress.Root value={value} max={max} aria-label="Upload progress">
91
+ <Progress.Indicator style={{ inlineSize: `${(value / max) * 100}%` }} />
92
+ </Progress.Root>
93
+ ```
94
+
95
+ ## `asChild` composition
96
+
97
+ Both sub-components accept `asChild`. The library's ARIA attributes and
98
+ `data-*` hooks are merged onto the consumer's element.
99
+
100
+ ```tsx
101
+ <Progress.Root asChild value={50} aria-label="Upload progress">
102
+ <section>…</section>
103
+ </Progress.Root>
104
+ ```
105
+
106
+ ## Styling hooks
107
+
108
+ | Attribute | Values | Set on |
109
+ |-----------|--------|--------|
110
+ | `data-state` | `"indeterminate"` \| `"loading"` \| `"complete"` | `Progress.Root`, `Progress.Indicator` |
111
+ | `data-value` | the resolved `value` (determinate only) | `Progress.Root`, `Progress.Indicator` |
112
+ | `data-max` | the resolved `max` | `Progress.Root`, `Progress.Indicator` |
@@ -0,0 +1,37 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Progress } from "../Progress";
4
+
5
+ describe("Progress asChild", () => {
6
+ it("renders the Root as the consumer element when asChild is set", () => {
7
+ // Arrange & Act
8
+ render(
9
+ <Progress.Root asChild value={50} aria-label="Upload">
10
+ <section>fill</section>
11
+ </Progress.Root>,
12
+ );
13
+ const bar = screen.getByRole("progressbar", { name: "Upload" });
14
+
15
+ // Assert — section rendered, not div; ARIA merged onto it
16
+ expect(bar.tagName).toBe("SECTION");
17
+ expect(bar).toHaveAttribute("aria-valuenow", "50");
18
+ expect(bar).toHaveAttribute("data-state", "loading");
19
+ });
20
+
21
+ it("renders the Indicator as the consumer element when asChild is set", () => {
22
+ // Arrange & Act
23
+ render(
24
+ <Progress.Root value={50} aria-label="Upload">
25
+ <Progress.Indicator asChild>
26
+ <span data-testid="custom-fill" />
27
+ </Progress.Indicator>
28
+ </Progress.Root>,
29
+ );
30
+ const fill = screen.getByTestId("custom-fill");
31
+
32
+ // Assert — our span rendered; data-* hooks merged onto it
33
+ expect(fill.tagName).toBe("SPAN");
34
+ expect(fill).toHaveAttribute("data-state", "loading");
35
+ expect(fill).toHaveAttribute("data-value", "50");
36
+ });
37
+ });
@@ -0,0 +1,65 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Progress } from "../Progress";
4
+
5
+ describe("Progress basic rendering", () => {
6
+ it('renders a <div role="progressbar">', () => {
7
+ // Arrange & Act
8
+ render(<Progress.Root aria-label="Loading" />);
9
+ const bar = screen.getByRole("progressbar", { name: "Loading" });
10
+
11
+ // Assert
12
+ expect(bar.tagName).toBe("DIV");
13
+ });
14
+
15
+ it('defaults aria-valuemin="0" and aria-valuemax="100"', () => {
16
+ // Arrange & Act
17
+ render(<Progress.Root aria-label="Loading" />);
18
+ const bar = screen.getByRole("progressbar", { name: "Loading" });
19
+
20
+ // Assert
21
+ expect(bar).toHaveAttribute("aria-valuemin", "0");
22
+ expect(bar).toHaveAttribute("aria-valuemax", "100");
23
+ });
24
+
25
+ it('is indeterminate when value is omitted: data-state="indeterminate", no aria-valuenow', () => {
26
+ // Arrange & Act
27
+ render(<Progress.Root aria-label="Loading" />);
28
+ const bar = screen.getByRole("progressbar", { name: "Loading" });
29
+
30
+ // Assert
31
+ expect(bar).toHaveAttribute("data-state", "indeterminate");
32
+ expect(bar).not.toHaveAttribute("aria-valuenow");
33
+ expect(bar).not.toHaveAttribute("data-value");
34
+ });
35
+
36
+ it("renders Progress.Indicator as a <div> inside the root", () => {
37
+ // Arrange & Act
38
+ render(
39
+ <Progress.Root aria-label="Loading">
40
+ <Progress.Indicator data-testid="indicator" />
41
+ </Progress.Root>,
42
+ );
43
+ const indicator = screen.getByTestId("indicator");
44
+
45
+ // Assert
46
+ expect(indicator.tagName).toBe("DIV");
47
+ expect(indicator).toBeInTheDocument();
48
+ });
49
+
50
+ it("mirrors data-state and data-max from the root onto the Indicator", () => {
51
+ // Arrange & Act
52
+ render(
53
+ <Progress.Root aria-label="Loading">
54
+ <Progress.Indicator data-testid="indicator" />
55
+ </Progress.Root>,
56
+ );
57
+
58
+ // Assert
59
+ expect(screen.getByTestId("indicator")).toHaveAttribute(
60
+ "data-state",
61
+ "indeterminate",
62
+ );
63
+ expect(screen.getByTestId("indicator")).toHaveAttribute("data-max", "100");
64
+ });
65
+ });
@@ -0,0 +1,40 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { Progress } from "../Progress";
4
+
5
+ describe("Progress error handling", () => {
6
+ beforeEach(() => {
7
+ vi.spyOn(console, "error").mockImplementation(() => {});
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ it("throws when Progress.Indicator is rendered outside a Progress.Root", () => {
15
+ // Arrange & Act & Assert
16
+ expect(() => render(<Progress.Indicator />)).toThrow(
17
+ "Progress.Indicator must be rendered inside a <Progress.Root>.",
18
+ );
19
+ });
20
+
21
+ it.each([0, -10, NaN, Infinity])(
22
+ "throws when max is not a positive finite number (%s)",
23
+ (max) => {
24
+ // Arrange & Act & Assert
25
+ expect(() =>
26
+ render(<Progress.Root max={max} aria-label="Upload" />),
27
+ ).toThrow(/`max` must be a positive, finite number/);
28
+ },
29
+ );
30
+
31
+ it.each([-1, 101, NaN, Infinity])(
32
+ "throws when value falls outside 0..max (%s)",
33
+ (value) => {
34
+ // Arrange & Act & Assert
35
+ expect(() =>
36
+ render(<Progress.Root value={value} aria-label="Upload" />),
37
+ ).toThrow(/`value` must be a finite number between 0 and/);
38
+ },
39
+ );
40
+ });
@@ -0,0 +1,7 @@
1
+ export const determinateStateCases = [
2
+ { value: 0, max: 100, expectedState: "loading" },
3
+ { value: 60, max: 100, expectedState: "loading" },
4
+ { value: 100, max: 100, expectedState: "complete" },
5
+ { value: 30, max: 60, expectedState: "loading" },
6
+ { value: 60, max: 60, expectedState: "complete" },
7
+ ] as const;
@@ -0,0 +1,83 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Progress } from "../Progress";
4
+
5
+ import { determinateStateCases } from "./Progress.fixtures";
6
+
7
+ describe("Progress value", () => {
8
+ it("sets aria-valuenow and data-value from value", () => {
9
+ // Arrange & Act
10
+ render(<Progress.Root value={42} aria-label="Upload" />);
11
+ const bar = screen.getByRole("progressbar", { name: "Upload" });
12
+
13
+ // Assert
14
+ expect(bar).toHaveAttribute("aria-valuenow", "42");
15
+ expect(bar).toHaveAttribute("data-value", "42");
16
+ });
17
+
18
+ it("defaults aria-valuetext to a rounded percentage", () => {
19
+ // Arrange & Act
20
+ render(<Progress.Root value={1} max={3} aria-label="Upload" />);
21
+
22
+ // Assert
23
+ expect(screen.getByRole("progressbar", { name: "Upload" })).toHaveAttribute(
24
+ "aria-valuetext",
25
+ "33%",
26
+ );
27
+ });
28
+
29
+ it("honours a custom max for aria-valuemax and the percentage label", () => {
30
+ // Arrange & Act
31
+ render(<Progress.Root value={25} max={50} aria-label="Upload" />);
32
+ const bar = screen.getByRole("progressbar", { name: "Upload" });
33
+
34
+ // Assert
35
+ expect(bar).toHaveAttribute("aria-valuemax", "50");
36
+ expect(bar).toHaveAttribute("aria-valuetext", "50%");
37
+ });
38
+
39
+ it("uses a custom getValueLabel for aria-valuetext", () => {
40
+ // Arrange & Act
41
+ render(
42
+ <Progress.Root
43
+ value={3}
44
+ max={10}
45
+ getValueLabel={(v, m) => `${v} of ${m} files`}
46
+ aria-label="Upload"
47
+ />,
48
+ );
49
+
50
+ // Assert
51
+ expect(screen.getByRole("progressbar", { name: "Upload" })).toHaveAttribute(
52
+ "aria-valuetext",
53
+ "3 of 10 files",
54
+ );
55
+ });
56
+
57
+ it("mirrors data-value onto the Indicator when determinate", () => {
58
+ // Arrange & Act
59
+ render(
60
+ <Progress.Root value={70} aria-label="Upload">
61
+ <Progress.Indicator data-testid="indicator" />
62
+ </Progress.Root>,
63
+ );
64
+
65
+ // Assert
66
+ expect(screen.getByTestId("indicator")).toHaveAttribute("data-value", "70");
67
+ });
68
+
69
+ describe.each(determinateStateCases)(
70
+ "value $value of $max",
71
+ ({ value, max, expectedState }) => {
72
+ it(`derives data-state="${expectedState}"`, () => {
73
+ // Arrange & Act
74
+ render(<Progress.Root value={value} max={max} aria-label="Upload" />);
75
+
76
+ // Assert
77
+ expect(
78
+ screen.getByRole("progressbar", { name: "Upload" }),
79
+ ).toHaveAttribute("data-state", expectedState);
80
+ });
81
+ },
82
+ );
83
+ });
@@ -0,0 +1,2 @@
1
+ export { useProgressContext } from "./useProgressContext";
2
+ export { useProgressRoot } from "./useProgressRoot";
@@ -0,0 +1 @@
1
+ export { useProgressContext } from "../ProgressContext";
@@ -0,0 +1,45 @@
1
+ import { ProgressState } from "../types";
2
+
3
+ const DEFAULT_MAX = 100;
4
+
5
+ type UseProgressRootArgs = {
6
+ value?: number | null;
7
+ max?: number;
8
+ };
9
+
10
+ type UseProgressRootResult = {
11
+ value: number | null;
12
+ max: number;
13
+ state: ProgressState;
14
+ };
15
+
16
+ /**
17
+ * Resolves a Progress root's `value` / `max` props into the canonical
18
+ * `{ value, max, state }` triple consumed by the Root element and shared
19
+ * through {@link ProgressContext}.
20
+ */
21
+ export function useProgressRoot({
22
+ value = null,
23
+ max = DEFAULT_MAX,
24
+ }: UseProgressRootArgs): UseProgressRootResult {
25
+ if (!Number.isFinite(max) || max <= 0) {
26
+ throw new Error(
27
+ `Progress.Root: \`max\` must be a positive, finite number. Received: ${max}.`,
28
+ );
29
+ }
30
+
31
+ if (value != null && (!Number.isFinite(value) || value < 0 || value > max)) {
32
+ throw new Error(
33
+ `Progress.Root: \`value\` must be a finite number between 0 and ${max}, or null for an indeterminate bar. Received: ${value}.`,
34
+ );
35
+ }
36
+
37
+ const isIndeterminate = value == null;
38
+ const state: ProgressState = isIndeterminate
39
+ ? "indeterminate"
40
+ : value >= max
41
+ ? "complete"
42
+ : "loading";
43
+
44
+ return { value: isIndeterminate ? null : value, max, state };
45
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./Progress";
2
+ export * from "./ProgressContext";
3
+ export * from "./types";
@@ -0,0 +1,43 @@
1
+ import { ComponentProps } from "react";
2
+
3
+ /**
4
+ * The lifecycle state of a progress bar, derived from `value` and `max`.
5
+ *
6
+ * - `"indeterminate"` — `value` is `null` / omitted; the operation's
7
+ * completion ratio is unknown.
8
+ * - `"loading"` — `value` is a number below `max`.
9
+ * - `"complete"` — `value` has reached `max`.
10
+ */
11
+ export type ProgressState = "indeterminate" | "loading" | "complete";
12
+
13
+ /**
14
+ * Props for {@link Progress.Root} — all `<div>` attributes except the ones
15
+ * the component derives itself (`role` and the `aria-value*` family), plus
16
+ * the progress-specific props.
17
+ */
18
+ export type ProgressRootProps = Omit<
19
+ ComponentProps<"div">,
20
+ "role" | "aria-valuemin" | "aria-valuemax" | "aria-valuenow" | "aria-valuetext"
21
+ > & {
22
+ /**
23
+ * Current progress, between `0` and `max`. Pass `null` (or omit) for an
24
+ * indeterminate progress bar whose completion ratio is unknown.
25
+ */
26
+ value?: number | null;
27
+ /** Upper bound of `value`. Must be a positive finite number. Defaults to `100`. */
28
+ max?: number;
29
+ /**
30
+ * Produces the human-readable `aria-valuetext`. Receives the resolved
31
+ * `value` and `max`. Defaults to a rounded percentage (e.g. `"42%"`).
32
+ * Not called while indeterminate.
33
+ */
34
+ getValueLabel?: (value: number, max: number) => string;
35
+ /** Render the consumer's own element instead of the native `<div>`. */
36
+ asChild?: boolean;
37
+ };
38
+
39
+ /** Props for {@link Progress.Indicator} — all `<div>` attributes plus `asChild`. */
40
+ export type ProgressIndicatorProps = ComponentProps<"div"> & {
41
+ /** Render the consumer's own element instead of the native `<div>`. */
42
+ asChild?: boolean;
43
+ };