@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,329 @@
1
+ import { Ref } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ import { Slot } from "../Slot";
5
+
6
+ import {
7
+ MillerColumnsContext,
8
+ MillerColumnsColumnContext,
9
+ MillerColumnsItemContext,
10
+ } from "./MillerColumnsContext";
11
+ import {
12
+ useMillerColumnsColumn,
13
+ useMillerColumnsContext,
14
+ useMillerColumnsItem,
15
+ useMillerColumnsItemContext,
16
+ useMillerColumnsResizeHandle,
17
+ useMillerColumnsRoot,
18
+ } from "./hooks";
19
+
20
+ import { partitionItemChildren } from "./utils";
21
+
22
+ import type {
23
+ MillerColumnsRootProps,
24
+ MillerColumnsColumnProps,
25
+ MillerColumnsItemProps,
26
+ MillerColumnsItemIndicatorProps,
27
+ MillerColumnsResizeHandleProps,
28
+ MillerColumnsPreviewPanelProps,
29
+ } from "./types";
30
+
31
+ /**
32
+ * The root of a Miller Columns widget — a horizontal strip of vertical
33
+ * lists where selecting a node reveals its children in the next column.
34
+ *
35
+ * Renders the strip container (`role="tree"`) into which every
36
+ * `MillerColumns.Column` projects itself via a portal. Authoring the tree
37
+ * is recursive: an `Item` declares its child column as a *nested*
38
+ * `<MillerColumns.Column>`, and the strip flattens those nested columns
39
+ * into a single left-to-right row.
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * <MillerColumns.Root>
44
+ * <MillerColumns.Column>
45
+ * <MillerColumns.Item value="docs">Docs</MillerColumns.Item>
46
+ * </MillerColumns.Column>
47
+ * </MillerColumns.Root>
48
+ * ```
49
+ */
50
+ export function MillerColumnsRoot({
51
+ children,
52
+ defaultValue,
53
+ value,
54
+ onValueChange,
55
+ ...rest
56
+ }: MillerColumnsRootProps) {
57
+ const { contextValue, columnCount, registerSlotRef, stripRef } =
58
+ useMillerColumnsRoot(value, defaultValue, onValueChange);
59
+
60
+ return (
61
+ <MillerColumnsContext.Provider value={contextValue}>
62
+ <div
63
+ ref={stripRef}
64
+ role="tree"
65
+ data-miller-columns-strip=""
66
+ data-orientation="horizontal"
67
+ {...rest}
68
+ >
69
+ {Array.from({ length: columnCount }, (_, depth) => (
70
+ <div
71
+ key={depth}
72
+ data-miller-columns-slot=""
73
+ style={{ display: "contents" }}
74
+ ref={registerSlotRef(depth)}
75
+ />
76
+ ))}
77
+ {children}
78
+ </div>
79
+ </MillerColumnsContext.Provider>
80
+ );
81
+ }
82
+
83
+ MillerColumnsRoot.displayName = "MillerColumnsRoot";
84
+
85
+ /**
86
+ * A single vertical list of items within the strip. Renders a
87
+ * `<div role="group">` that is portal-projected into the `Root` strip, so
88
+ * a `Column` nested inside an `Item` still appears side-by-side with its
89
+ * ancestors rather than in document flow.
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * <MillerColumns.Column>
94
+ * <MillerColumns.Item value="a">A</MillerColumns.Item>
95
+ * </MillerColumns.Column>
96
+ * ```
97
+ */
98
+ export function MillerColumnsColumn({
99
+ children,
100
+ style,
101
+ ...rest
102
+ }: MillerColumnsColumnProps) {
103
+ const { slot, depth, width, columnContextValue } = useMillerColumnsColumn();
104
+
105
+ if (!slot) {
106
+ return null;
107
+ }
108
+
109
+ return createPortal(
110
+ <MillerColumnsColumnContext.Provider value={columnContextValue}>
111
+ <div
112
+ role="group"
113
+ data-miller-columns-column=""
114
+ data-depth={depth}
115
+ style={{ ...style, ...(width !== undefined ? { width } : {}) }}
116
+ {...rest}
117
+ >
118
+ {children}
119
+ </div>
120
+ </MillerColumnsColumnContext.Provider>,
121
+ slot,
122
+ );
123
+ }
124
+
125
+ MillerColumnsColumn.displayName = "MillerColumnsColumn";
126
+
127
+ /**
128
+ * A single selectable node within a `Column`. Renders a
129
+ * `<div role="treeitem">` for its cell content.
130
+ *
131
+ * The {@link MillerColumnsItemProps.value | `value`} prop is the stable
132
+ * identifier used to match this item against the active selection path.
133
+ *
134
+ * An `Item` becomes a branch by nesting a `<MillerColumns.Column>` among
135
+ * its children. That child column is mounted (and projected into the
136
+ * strip) only while the item is selected; an item with no nested column
137
+ * is a leaf.
138
+ *
139
+ * **`asChild` prop.** Pass `asChild` to render the cell as a
140
+ * consumer-supplied element (e.g. an `<a>`) instead of the default
141
+ * `<div>`. All treeitem ARIA attributes, event handlers, and the internal
142
+ * ref are merged onto that child. A nested `<MillerColumns.Column>` is
143
+ * still declared as a sibling of the cell element.
144
+ *
145
+ * **Ref forwarding.** A `ref` prop (React 19 ref-as-prop style) is
146
+ * forwarded to the rendered element and composed with the library's
147
+ * internal ref.
148
+ *
149
+ * @example Leaf
150
+ * ```tsx
151
+ * <MillerColumns.Item value="guides">Guides</MillerColumns.Item>
152
+ * ```
153
+ *
154
+ * @example Branch
155
+ * ```tsx
156
+ * <MillerColumns.Item value="docs">
157
+ * Docs
158
+ * <MillerColumns.Column>
159
+ * <MillerColumns.Item value="guides">Guides</MillerColumns.Item>
160
+ * </MillerColumns.Column>
161
+ * </MillerColumns.Item>
162
+ * ```
163
+ */
164
+ export function MillerColumnsItem<T extends HTMLElement = HTMLDivElement>({
165
+ children,
166
+ ref,
167
+ asChild = false,
168
+ ...props
169
+ }: MillerColumnsItemProps<T>) {
170
+ const { cell, column } = partitionItemChildren(children);
171
+ const { itemProps, selected, itemContextValue } = useMillerColumnsItem(
172
+ { ref: ref as Ref<HTMLDivElement>, ...props },
173
+ column !== null,
174
+ );
175
+
176
+ return (
177
+ <MillerColumnsItemContext.Provider value={itemContextValue}>
178
+ {asChild ? (
179
+ <Slot {...itemProps}>{cell[0]}</Slot>
180
+ ) : (
181
+ <div {...itemProps}>{cell}</div>
182
+ )}
183
+ {selected ? column : null}
184
+ </MillerColumnsItemContext.Provider>
185
+ );
186
+ }
187
+
188
+ MillerColumnsItem.displayName = "MillerColumnsItem";
189
+
190
+ /**
191
+ * An optional, decorative affordance for a branch item — typically a
192
+ * chevron or arrow signalling "this item reveals a child column".
193
+ *
194
+ * Renders a `<span aria-hidden="true">` (so the glyph is ignored by
195
+ * assistive technology) **only for branch items**; for a leaf item it
196
+ * renders nothing. Place it among an `Item`'s cell content.
197
+ *
198
+ * **Styling hooks.**
199
+ * - `data-state="selected" | "unselected"` — mirrors the parent item.
200
+ * - `data-has-children` — always present (the indicator only renders
201
+ * for branch items).
202
+ *
203
+ * @example
204
+ * ```tsx
205
+ * <MillerColumns.Item value="docs">
206
+ * Docs
207
+ * <MillerColumns.ItemIndicator>▸</MillerColumns.ItemIndicator>
208
+ * <MillerColumns.Column>…</MillerColumns.Column>
209
+ * </MillerColumns.Item>
210
+ * ```
211
+ */
212
+ export function MillerColumnsItemIndicator({
213
+ children,
214
+ ...rest
215
+ }: MillerColumnsItemIndicatorProps) {
216
+ const { selected, hasChildren } = useMillerColumnsItemContext();
217
+
218
+ if (!hasChildren) {
219
+ return null;
220
+ }
221
+
222
+ return (
223
+ <span
224
+ aria-hidden="true"
225
+ data-state={selected ? "selected" : "unselected"}
226
+ data-has-children=""
227
+ {...rest}
228
+ >
229
+ {children}
230
+ </span>
231
+ );
232
+ }
233
+
234
+ MillerColumnsItemIndicator.displayName = "MillerColumnsItemIndicator";
235
+
236
+ /**
237
+ * A drag-to-resize affordance for the column it is rendered in. Renders a
238
+ * `<div role="separator" aria-orientation="vertical">` that, while
239
+ * pointer-dragged, drives that column's width as state on the `Root`.
240
+ *
241
+ * Must be rendered among a `MillerColumns.Column`'s children — it reads
242
+ * the column's depth from context. Position it with CSS (typically
243
+ * absolutely, against the column's trailing edge).
244
+ *
245
+ * @example
246
+ * ```tsx
247
+ * <MillerColumns.Column>
248
+ * <MillerColumns.ResizeHandle />
249
+ * <MillerColumns.Item value="a">A</MillerColumns.Item>
250
+ * </MillerColumns.Column>
251
+ * ```
252
+ */
253
+ export function MillerColumnsResizeHandle(
254
+ props: MillerColumnsResizeHandleProps,
255
+ ) {
256
+ const { handleProps } = useMillerColumnsResizeHandle(props);
257
+
258
+ return <div {...handleProps} />;
259
+ }
260
+
261
+ MillerColumnsResizeHandle.displayName = "MillerColumnsResizeHandle";
262
+
263
+ /**
264
+ * A trailing panel for previewing the current selection — the
265
+ * macOS-Finder-style preview pane.
266
+ *
267
+ * Renders a plain `<div data-miller-columns-preview>` as the last child
268
+ * of the strip, sitting to the right of the columns. The component is
269
+ * deliberately content-agnostic: it does not know how to preview an
270
+ * item, so the consumer supplies whatever the panel should show.
271
+ *
272
+ * Pair it with {@link useMillerColumnsSelection} to render content for
273
+ * the current selection. Author it as the **last child** of `Root`, a
274
+ * sibling of the root `Column`.
275
+ *
276
+ * @example
277
+ * ```tsx
278
+ * function FilePreview() {
279
+ * const { selectedValue } = useMillerColumnsSelection();
280
+ * return selectedValue ? <Preview id={selectedValue} /> : null;
281
+ * }
282
+ *
283
+ * <MillerColumns.Root>
284
+ * <MillerColumns.Column>{items}</MillerColumns.Column>
285
+ * <MillerColumns.PreviewPanel>
286
+ * <FilePreview />
287
+ * </MillerColumns.PreviewPanel>
288
+ * </MillerColumns.Root>;
289
+ * ```
290
+ */
291
+ export function MillerColumnsPreviewPanel({
292
+ children,
293
+ ...rest
294
+ }: MillerColumnsPreviewPanelProps) {
295
+ useMillerColumnsContext();
296
+
297
+ return (
298
+ <div data-miller-columns-preview="" {...rest}>
299
+ {children}
300
+ </div>
301
+ );
302
+ }
303
+
304
+ MillerColumnsPreviewPanel.displayName = "MillerColumnsPreviewPanel";
305
+
306
+ type MillerColumnsCompound = typeof MillerColumnsRoot & {
307
+ Root: typeof MillerColumnsRoot;
308
+ Column: typeof MillerColumnsColumn;
309
+ Item: typeof MillerColumnsItem;
310
+ ItemIndicator: typeof MillerColumnsItemIndicator;
311
+ ResizeHandle: typeof MillerColumnsResizeHandle;
312
+ PreviewPanel: typeof MillerColumnsPreviewPanel;
313
+ };
314
+
315
+ const MillerColumnsCompound: MillerColumnsCompound = Object.assign(
316
+ MillerColumnsRoot,
317
+ {
318
+ Root: MillerColumnsRoot,
319
+ Column: MillerColumnsColumn,
320
+ Item: MillerColumnsItem,
321
+ ItemIndicator: MillerColumnsItemIndicator,
322
+ ResizeHandle: MillerColumnsResizeHandle,
323
+ PreviewPanel: MillerColumnsPreviewPanel,
324
+ },
325
+ );
326
+
327
+ MillerColumnsCompound.displayName = "MillerColumns";
328
+
329
+ export { MillerColumnsCompound as MillerColumns };
@@ -0,0 +1,25 @@
1
+ import { createStrictContext } from "../utils";
2
+
3
+ import type {
4
+ MillerColumnsContextValue,
5
+ MillerColumnsColumnContextValue,
6
+ MillerColumnsItemContextValue,
7
+ } from "./types";
8
+
9
+ export const [MillerColumnsContext, useMillerColumnsContext] =
10
+ createStrictContext<MillerColumnsContextValue>(
11
+ "MillerColumns sub-components must be rendered inside <MillerColumns.Root>.",
12
+ "MillerColumnsContext",
13
+ );
14
+
15
+ export const [MillerColumnsColumnContext, useMillerColumnsColumnContext] =
16
+ createStrictContext<MillerColumnsColumnContextValue>(
17
+ "MillerColumns.Item must be rendered inside <MillerColumns.Column>.",
18
+ "MillerColumnsColumnContext",
19
+ );
20
+
21
+ export const [MillerColumnsItemContext, useMillerColumnsItemContext] =
22
+ createStrictContext<MillerColumnsItemContextValue>(
23
+ "MillerColumns.ItemIndicator must be rendered inside <MillerColumns.Item>.",
24
+ "MillerColumnsItemContext",
25
+ );
@@ -0,0 +1,278 @@
1
+ # MillerColumns
2
+
3
+ A compound component for the **Miller columns** pattern (also called
4
+ cascading lists or the macOS Finder "column view"): a horizontal strip
5
+ of vertical lists where selecting a node reveals its children in the
6
+ next column to the right.
7
+
8
+ ```tsx
9
+ import { MillerColumns } from "@primitiv-ui/react";
10
+
11
+ function Node({ node }) {
12
+ return (
13
+ <MillerColumns.Item value={node.id}>
14
+ {node.label}
15
+ {node.children?.length ? (
16
+ <>
17
+ <MillerColumns.ItemIndicator>▸</MillerColumns.ItemIndicator>
18
+ <MillerColumns.Column>
19
+ {node.children.map((child) => (
20
+ <Node key={child.id} node={child} />
21
+ ))}
22
+ </MillerColumns.Column>
23
+ </>
24
+ ) : null}
25
+ </MillerColumns.Item>
26
+ );
27
+ }
28
+
29
+ <MillerColumns.Root defaultValue={["docs", "guides"]}>
30
+ <MillerColumns.Column>
31
+ {tree.map((node) => (
32
+ <Node key={node.id} node={node} />
33
+ ))}
34
+ </MillerColumns.Column>
35
+ </MillerColumns.Root>;
36
+ ```
37
+
38
+ ## Authoring model
39
+
40
+ The tree is authored by **recursive composition** — there is no `data`
41
+ prop. An `Item` becomes a *branch* by nesting a
42
+ `<MillerColumns.Column>` among its children; that nested column lists
43
+ the item's children, each of which is another `Item`. An `Item` with no
44
+ nested column is a *leaf*.
45
+
46
+ Although child columns are authored nested, every `Column` is
47
+ **portal-projected** into the `Root` strip, so the active columns sit
48
+ side-by-side in a single left-to-right row regardless of how deeply
49
+ they were declared.
50
+
51
+ A branch's nested column is only **mounted while that branch is
52
+ selected**, so a consumer's recursive `Node` component naturally stops
53
+ recursing at inactive branches — only the columns along the active path
54
+ are ever rendered.
55
+
56
+ ## Sub-components
57
+
58
+ | Export | Role | Notes |
59
+ | ----------------------------- | ------------- | ---------------------------------------------------------------------------------------------- |
60
+ | `MillerColumns.Root` | State owner | Uncontrolled (`defaultValue`) or controlled (`value` + `onValueChange`); renders the strip |
61
+ | `MillerColumns.Column` | List | A vertical list of items, projected into the strip as `role="group"` |
62
+ | `MillerColumns.Item` | Tree node | A `role="treeitem"`; branch when it nests a `Column`. Supports `disabled`, `asChild`, `ref` |
63
+ | `MillerColumns.ItemIndicator` | Icon wrapper | Decorative `aria-hidden` icon, rendered only for branch items |
64
+ | `MillerColumns.ResizeHandle` | Resize grip | A `role="separator"` drag handle that sets its column's width |
65
+ | `MillerColumns.PreviewPanel` | Preview pane | A content-agnostic trailing panel; pair with `useMillerColumnsSelection` |
66
+
67
+ ## Selection model
68
+
69
+ The selection is a single **active path** — an array of item ids from
70
+ the root column down to the deepest selected item. Selecting an item at
71
+ depth _d_ truncates the path to _d_ and appends the new id, so every
72
+ column deeper than _d_ closes.
73
+
74
+ - **Uncontrolled** — pass `defaultValue` (or omit it to start with
75
+ nothing selected).
76
+ - **Controlled** — pass `value` and `onValueChange` together. The
77
+ parent owns the path; the component defers every change back through
78
+ the callback.
79
+
80
+ The two shapes are discriminated at the type level: passing
81
+ `defaultValue` alongside `value` is a type error.
82
+
83
+ There is no multi-select — only one path is active at a time.
84
+
85
+ ## Keyboard interaction
86
+
87
+ | Key | Behaviour |
88
+ | ------------------- | ---------------------------------------------------------------------- |
89
+ | `ArrowUp` / `ArrowDown` | Move focus within the focused column (wraps, skips disabled items) |
90
+ | `Home` / `End` | Focus the first / last item of the focused column |
91
+ | `Enter` / `Space` | Select the focused item |
92
+ | `ArrowRight` | Branch: select it and move focus to its child column's first item; leaf: no-op |
93
+ | `ArrowLeft` | Move focus to the selected item of the parent column |
94
+ | `Tab` | Move into / out of the whole tree (single tabstop) |
95
+
96
+ The strip is a single roving-tabindex widget: exactly one item is
97
+ tabbable at a time. The tabstop follows the last-focused item and
98
+ defaults to the deepest selected item, falling back to the first item
99
+ of the root column.
100
+
101
+ ## Auto-scroll
102
+
103
+ When selecting a branch reveals a new column, the strip scrolls to its
104
+ trailing edge so the newly opened column is brought into view. This
105
+ fires only when a column is *added* — neither the initial render nor
106
+ closing a column scrolls the strip. Scrolling only has a visible
107
+ effect when the strip is itself an overflow container; give
108
+ `[data-miller-columns-strip]` an `overflow-x` for it to take hold.
109
+
110
+ ## ARIA
111
+
112
+ - The strip is `role="tree"`.
113
+ - Each `Column` is `role="group"`.
114
+ - Each `Item` is `role="treeitem"` with `aria-level` (1-based column
115
+ depth), `aria-selected`, and — on branch items — `aria-expanded`.
116
+
117
+ ## Disabled items
118
+
119
+ Pass `disabled` on an `Item` to render `aria-disabled="true"` and
120
+ `data-disabled`, ignore clicks and activation keys, and skip the item
121
+ during arrow-key navigation. Disabled items remain in the DOM and
122
+ focusable for discovery.
123
+
124
+ ## Resizable columns
125
+
126
+ Drop a `MillerColumns.ResizeHandle` among a `Column`'s children to make
127
+ that column drag-resizable. The handle renders a
128
+ `<div role="separator" aria-orientation="vertical">`; while it is
129
+ pointer-dragged it drives that column's width as state on the `Root`,
130
+ applied as the column's inline `width`.
131
+
132
+ ```tsx
133
+ <MillerColumns.Column className="column">
134
+ <MillerColumns.ResizeHandle className="resize-handle" />
135
+ {items}
136
+ </MillerColumns.Column>
137
+ ```
138
+
139
+ The handle ships with no styles or position — give it a width and pin
140
+ it to the column's trailing edge yourself:
141
+
142
+ ```css
143
+ .column {
144
+ position: relative;
145
+ }
146
+ .resize-handle {
147
+ position: absolute;
148
+ inset-block: 0;
149
+ inset-inline-end: 0;
150
+ width: 6px;
151
+ cursor: col-resize;
152
+ }
153
+ .resize-handle[data-dragging] {
154
+ /* feedback while a drag is in progress */
155
+ }
156
+ ```
157
+
158
+ The first drag measures the column's current rendered width and
159
+ resizes from there; later drags resume from the last resized width. A
160
+ width is clamped so it can never be dragged below zero — use CSS
161
+ `min-width` for any larger floor. Resize is pointer-only; the handle
162
+ is not keyboard-operable.
163
+
164
+ ## Preview panel
165
+
166
+ `MillerColumns.PreviewPanel` is a trailing pane — the macOS-Finder
167
+ "preview" column — rendered as the last child of the strip, to the
168
+ right of the columns. It is deliberately **content-agnostic**: the
169
+ component cannot know how to preview an item, so the consumer decides
170
+ what the panel shows. Author it as the last child of `Root`, a sibling
171
+ of the root `Column`:
172
+
173
+ ```tsx
174
+ <MillerColumns.Root>
175
+ <MillerColumns.Column>{items}</MillerColumns.Column>
176
+ <MillerColumns.PreviewPanel>
177
+ <FilePreview />
178
+ </MillerColumns.PreviewPanel>
179
+ </MillerColumns.Root>
180
+ ```
181
+
182
+ To render content for whatever is selected, read the selection with
183
+ the `useMillerColumnsSelection` hook from any component inside `Root`:
184
+
185
+ ```tsx
186
+ import { useMillerColumnsSelection } from "@primitiv-ui/react";
187
+
188
+ function FilePreview() {
189
+ const { path, selectedValue } = useMillerColumnsSelection();
190
+ if (!selectedValue) {
191
+ return <p>Nothing selected</p>;
192
+ }
193
+ return <Preview id={selectedValue} />;
194
+ }
195
+ ```
196
+
197
+ `useMillerColumnsSelection` returns the active `path` and the deepest
198
+ `selectedValue` (`undefined` when nothing is selected). It works for
199
+ both controlled and uncontrolled roots, and throws if called outside
200
+ `MillerColumns.Root`.
201
+
202
+ The panel ships with no ARIA role. The strip is a `role="tree"`, whose
203
+ conforming children are `treeitem`s and `group`s — so give the panel
204
+ content its own labelled landmark (`role`, `aria-label`, …) through
205
+ props if the preview warrants being announced.
206
+
207
+ ## `asChild` composition
208
+
209
+ `MillerColumns.Item` accepts `asChild` to render the cell as a
210
+ consumer-supplied element instead of the default `<div>`. All treeitem
211
+ ARIA attributes, event handlers, and the internal ref are merged onto
212
+ the child (child handler runs first, then the component's). A nested
213
+ `<MillerColumns.Column>` is still declared as a sibling of the cell
214
+ element:
215
+
216
+ ```tsx
217
+ <MillerColumns.Item<HTMLAnchorElement> asChild value="docs">
218
+ <a href="#docs">Docs</a>
219
+ <MillerColumns.Column>…</MillerColumns.Column>
220
+ </MillerColumns.Item>
221
+ ```
222
+
223
+ A `ref` prop (React 19 ref-as-prop style) is forwarded to the rendered
224
+ element and composed with the library's internal ref.
225
+
226
+ ## Styling hooks
227
+
228
+ Zero styles ship with the component. Style it through `data-*` hooks;
229
+ typically the strip is `display: flex` and each column scrolls
230
+ vertically.
231
+
232
+ Inside the strip, each column is wrapped in a transparent
233
+ (`display: contents`) slot element that fixes left-to-right order — so
234
+ target columns with `[data-miller-columns-column]`, not a direct-child
235
+ selector like `[data-miller-columns-strip] > *`.
236
+
237
+ ```css
238
+ [data-miller-columns-strip] {
239
+ display: flex;
240
+ }
241
+ [data-miller-columns-column] {
242
+ overflow-y: auto;
243
+ }
244
+ [data-miller-columns-column][data-depth="0"] {
245
+ /* the root column */
246
+ }
247
+ [role="treeitem"][data-state="selected"] {
248
+ /* the selected item in each column */
249
+ }
250
+ [role="treeitem"][data-has-children] {
251
+ /* branch items */
252
+ }
253
+ [role="treeitem"][data-disabled] {
254
+ opacity: 0.5;
255
+ }
256
+ ```
257
+
258
+ | Element | Attributes |
259
+ | --------------- | --------------------------------------------------------------------------- |
260
+ | Strip (`Root`) | `data-miller-columns-strip`, `data-orientation="horizontal"` |
261
+ | `Column` | `data-miller-columns-column`, `data-depth` |
262
+ | `Item` | `data-state="selected" \| "unselected"`, `data-depth`, `data-has-children`, `data-disabled` |
263
+ | `ItemIndicator` | `data-state`, `data-has-children` |
264
+ | `ResizeHandle` | `data-miller-columns-resize-handle`, `data-dragging` (present mid-drag) |
265
+ | `PreviewPanel` | `data-miller-columns-preview` |
266
+
267
+ ## Deferred / follow-up work
268
+
269
+ The following were intentionally left out of the first version and are
270
+ good candidates for later, independent cycles:
271
+
272
+ 1. **Context-menu image preview.** Once a Context Menu component
273
+ exists, a leaf image `Item` could open a context menu on
274
+ right-click whose first entry ("Preview", with an eye icon) opens
275
+ the `Modal` to show the image larger.
276
+ 2. **Keyboard-operable resize.** `MillerColumns.ResizeHandle` is
277
+ pointer-only; an arrow-key resize on the focused handle would round
278
+ out the WAI-ARIA window-splitter pattern.