@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,117 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { Checkbox } from "../Checkbox";
5
+
6
+ describe("Checkbox.Indicator", () => {
7
+ it("does not render its children when the checkbox is unchecked", () => {
8
+ // Arrange & Act
9
+ render(
10
+ <Checkbox.Root aria-label="Accept terms">
11
+ <Checkbox.Indicator>tick</Checkbox.Indicator>
12
+ </Checkbox.Root>,
13
+ );
14
+
15
+ // Assert
16
+ expect(screen.queryByText("tick")).not.toBeInTheDocument();
17
+ });
18
+
19
+ it("renders its children when the checkbox is checked", () => {
20
+ // Arrange & Act
21
+ render(
22
+ <Checkbox.Root defaultChecked aria-label="Accept terms">
23
+ <Checkbox.Indicator>tick</Checkbox.Indicator>
24
+ </Checkbox.Root>,
25
+ );
26
+
27
+ // Assert
28
+ expect(screen.getByText("tick")).toBeInTheDocument();
29
+ });
30
+
31
+ it("renders its children when the checkbox is indeterminate", () => {
32
+ // Arrange & Act
33
+ render(
34
+ <Checkbox.Root defaultChecked="indeterminate" aria-label="Accept terms">
35
+ <Checkbox.Indicator>tick</Checkbox.Indicator>
36
+ </Checkbox.Root>,
37
+ );
38
+
39
+ // Assert
40
+ expect(screen.getByText("tick")).toBeInTheDocument();
41
+ });
42
+
43
+ it("mounts and unmounts in response to toggling", async () => {
44
+ // Arrange
45
+ const user = userEvent.setup();
46
+ render(
47
+ <Checkbox.Root aria-label="Accept terms">
48
+ <Checkbox.Indicator>tick</Checkbox.Indicator>
49
+ </Checkbox.Root>,
50
+ );
51
+ expect(screen.queryByText("tick")).not.toBeInTheDocument();
52
+
53
+ // Act
54
+ await user.click(screen.getByRole("checkbox", { name: "Accept terms" }));
55
+
56
+ // Assert
57
+ expect(screen.getByText("tick")).toBeInTheDocument();
58
+ });
59
+
60
+ it('carries aria-hidden="true" since it is decorative', () => {
61
+ // Arrange & Act
62
+ render(
63
+ <Checkbox.Root defaultChecked aria-label="Accept terms">
64
+ <Checkbox.Indicator data-testid="indicator">tick</Checkbox.Indicator>
65
+ </Checkbox.Root>,
66
+ );
67
+
68
+ // Assert
69
+ expect(screen.getByTestId("indicator")).toHaveAttribute(
70
+ "aria-hidden",
71
+ "true",
72
+ );
73
+ });
74
+
75
+ it("mirrors the checkbox's data-state on the indicator", () => {
76
+ // Arrange & Act
77
+ render(
78
+ <Checkbox.Root defaultChecked="indeterminate" aria-label="Accept terms">
79
+ <Checkbox.Indicator data-testid="indicator">tick</Checkbox.Indicator>
80
+ </Checkbox.Root>,
81
+ );
82
+
83
+ // Assert
84
+ expect(screen.getByTestId("indicator")).toHaveAttribute(
85
+ "data-state",
86
+ "indeterminate",
87
+ );
88
+ });
89
+
90
+ it("stays in the DOM while unchecked when forceMount is set", () => {
91
+ // Arrange & Act
92
+ render(
93
+ <Checkbox.Root aria-label="Accept terms">
94
+ <Checkbox.Indicator forceMount data-testid="indicator">
95
+ tick
96
+ </Checkbox.Indicator>
97
+ </Checkbox.Root>,
98
+ );
99
+ const indicator = screen.getByTestId("indicator");
100
+
101
+ // Assert
102
+ expect(indicator).toBeInTheDocument();
103
+ expect(indicator).toHaveAttribute("data-state", "unchecked");
104
+ });
105
+
106
+ it("throws when rendered outside Checkbox.Root", () => {
107
+ // Arrange
108
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
109
+
110
+ // Assert
111
+ expect(() =>
112
+ render(<Checkbox.Indicator>tick</Checkbox.Indicator>),
113
+ ).toThrow();
114
+
115
+ error.mockRestore();
116
+ });
117
+ });
@@ -0,0 +1,89 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { Checkbox } from "../Checkbox";
5
+
6
+ describe("Checkbox uncontrolled state", () => {
7
+ it("starts checked when defaultChecked is true", () => {
8
+ // Arrange & Act
9
+ render(<Checkbox.Root defaultChecked aria-label="Accept terms" />);
10
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
11
+
12
+ // Assert
13
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
14
+ expect(checkbox).toHaveAttribute("data-state", "checked");
15
+ });
16
+
17
+ it("toggles on click from unchecked to checked", async () => {
18
+ // Arrange
19
+ const user = userEvent.setup();
20
+ render(<Checkbox.Root aria-label="Accept terms" />);
21
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
22
+
23
+ // Act
24
+ await user.click(checkbox);
25
+
26
+ // Assert
27
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
28
+ expect(checkbox).toHaveAttribute("data-state", "checked");
29
+ });
30
+
31
+ it("toggles on click from checked to unchecked", async () => {
32
+ // Arrange
33
+ const user = userEvent.setup();
34
+ render(<Checkbox.Root defaultChecked aria-label="Accept terms" />);
35
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
36
+ // Precondition: defaultChecked really did initialise as checked.
37
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
38
+
39
+ // Act
40
+ await user.click(checkbox);
41
+
42
+ // Assert
43
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
44
+ expect(checkbox).toHaveAttribute("data-state", "unchecked");
45
+ });
46
+
47
+ it("calls onCheckedChange with the new boolean value", async () => {
48
+ // Arrange
49
+ const user = userEvent.setup();
50
+ const onCheckedChange = vi.fn();
51
+ render(
52
+ <Checkbox.Root
53
+ onCheckedChange={onCheckedChange}
54
+ aria-label="Accept terms"
55
+ />,
56
+ );
57
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
58
+
59
+ // Act
60
+ await user.click(checkbox);
61
+ await user.click(checkbox);
62
+
63
+ // Assert
64
+ expect(onCheckedChange).toHaveBeenNthCalledWith(1, true);
65
+ expect(onCheckedChange).toHaveBeenNthCalledWith(2, false);
66
+ });
67
+
68
+ it("composes the consumer's onClick with the internal toggle (consumer runs first)", async () => {
69
+ // Arrange
70
+ const user = userEvent.setup();
71
+ const order: string[] = [];
72
+ const onClick = vi.fn(() => order.push("consumer"));
73
+ const onCheckedChange = vi.fn(() => order.push("internal"));
74
+ render(
75
+ <Checkbox.Root
76
+ onClick={onClick}
77
+ onCheckedChange={onCheckedChange}
78
+ aria-label="Accept terms"
79
+ />,
80
+ );
81
+
82
+ // Act
83
+ await user.click(screen.getByRole("checkbox", { name: "Accept terms" }));
84
+
85
+ // Assert
86
+ expect(order).toEqual(["consumer", "internal"]);
87
+ });
88
+
89
+ });
@@ -0,0 +1,2 @@
1
+ export { useCheckboxRoot } from "./useCheckboxRoot";
2
+ export { useCheckboxContext } from "./useCheckboxContext";
@@ -0,0 +1 @@
1
+ export { useCheckboxContext } from "../CheckboxContext";
@@ -0,0 +1,32 @@
1
+ import { useCallback } from "react";
2
+
3
+ import { useControllableState } from "../../hooks";
4
+
5
+ import { CheckedState } from "../types";
6
+
7
+ type UseCheckboxRootArgs = {
8
+ defaultChecked?: CheckedState;
9
+ checked?: CheckedState;
10
+ onCheckedChange?: (checked: boolean) => void;
11
+ };
12
+
13
+ export function useCheckboxRoot({
14
+ defaultChecked,
15
+ checked: controlledChecked,
16
+ onCheckedChange,
17
+ }: UseCheckboxRootArgs) {
18
+ const [checked, setChecked] = useControllableState<CheckedState>(
19
+ controlledChecked,
20
+ defaultChecked ?? false,
21
+ );
22
+
23
+ const toggle = useCallback(() => {
24
+ // Indeterminate resolves to checked per the WAI-ARIA tri-state
25
+ // convention; boolean flips.
26
+ const next = checked === "indeterminate" ? true : !checked;
27
+ setChecked(next);
28
+ onCheckedChange?.(next);
29
+ }, [checked, setChecked, onCheckedChange]);
30
+
31
+ return { checked, toggle };
32
+ }
@@ -0,0 +1 @@
1
+ export * from "./Checkbox";
@@ -0,0 +1,33 @@
1
+ import { ComponentProps, ReactNode, Ref } from "react";
2
+
3
+ export type CheckedState = boolean | "indeterminate";
4
+
5
+ export type CheckboxIndicatorProps = ComponentProps<"span"> & {
6
+ children?: ReactNode;
7
+ forceMount?: boolean;
8
+ asChild?: boolean;
9
+ };
10
+
11
+ type CheckboxRootBaseProps = Omit<
12
+ ComponentProps<"button">,
13
+ "type" | "role" | "aria-checked" | "defaultChecked"
14
+ > & {
15
+ asChild?: boolean;
16
+ ref?: Ref<HTMLButtonElement>;
17
+ };
18
+
19
+ type CheckboxRootUncontrolledProps = CheckboxRootBaseProps & {
20
+ defaultChecked?: CheckedState;
21
+ checked?: never;
22
+ onCheckedChange?: (checked: boolean) => void;
23
+ };
24
+
25
+ type CheckboxRootControlledProps = CheckboxRootBaseProps & {
26
+ defaultChecked?: never;
27
+ checked: CheckedState;
28
+ onCheckedChange: (checked: boolean) => void;
29
+ };
30
+
31
+ export type CheckboxRootProps =
32
+ | CheckboxRootUncontrolledProps
33
+ | CheckboxRootControlledProps;
@@ -0,0 +1,208 @@
1
+ import { useMemo } from "react";
2
+
3
+ import { Slot, composeEventHandlers } from "../Slot";
4
+
5
+ import { CheckboxCardContext } from "./CheckboxCardContext";
6
+ import { useCheckboxCardContext, useCheckboxCardRoot } from "./hooks";
7
+ import {
8
+ CheckboxCardIndicatorProps,
9
+ CheckboxCardRootProps,
10
+ CheckedState,
11
+ } from "./types";
12
+
13
+ function dataStateOf(checked: CheckedState) {
14
+ if (checked === "indeterminate") return "indeterminate" as const;
15
+ return checked ? ("checked" as const) : ("unchecked" as const);
16
+ }
17
+
18
+ /**
19
+ * The root of a CheckboxCard — a native `<button role="checkbox">` whose
20
+ * entire card surface is the interactive area. Owns the tri-state checked
21
+ * value and provides {@link CheckboxCardContext | `CheckboxCardContext`} to
22
+ * descendant {@link CheckboxCardIndicator | `CheckboxCard.Indicator`}s.
23
+ *
24
+ * Supports two state modes, statically discriminated at the type level:
25
+ *
26
+ * - **Uncontrolled** — pass
27
+ * {@link CheckboxCardRootProps.defaultChecked | `defaultChecked`} (or omit
28
+ * for unchecked-on-mount). The component owns the value internally.
29
+ * - **Controlled** — pass
30
+ * {@link CheckboxCardRootProps.checked | `checked`} *and*
31
+ * {@link CheckboxCardRootProps.onCheckedChange | `onCheckedChange`}
32
+ * together. The parent owns the value.
33
+ *
34
+ * Both `checked` and `defaultChecked` accept `boolean | "indeterminate"`.
35
+ * Clicking an indeterminate card resolves it to `true` per the WAI-ARIA
36
+ * tri-state convention, then flips boolean on subsequent clicks.
37
+ *
38
+ * **ARIA.** `role="checkbox"` and `aria-checked` are set automatically;
39
+ * `aria-checked="mixed"` represents the indeterminate state.
40
+ *
41
+ * **Styling hooks.** `data-state="checked" | "unchecked" | "indeterminate"`
42
+ * on the root, plus `data-disabled=""` when disabled.
43
+ *
44
+ * **`asChild` prop.** Pass `asChild` to render any consumer-supplied element
45
+ * with the checkbox's ARIA attributes, data-state, composed onClick, and ref
46
+ * merged in.
47
+ *
48
+ * @example Uncontrolled
49
+ * ```tsx
50
+ * <CheckboxCard.Root defaultChecked aria-label="Enable feature">
51
+ * <CheckboxCard.Indicator>
52
+ * <CheckIcon />
53
+ * </CheckboxCard.Indicator>
54
+ * <h3>Feature name</h3>
55
+ * <p>Feature description</p>
56
+ * </CheckboxCard.Root>
57
+ * ```
58
+ *
59
+ * @example Controlled
60
+ * ```tsx
61
+ * const [enabled, setEnabled] = useState<CheckedState>(false);
62
+ *
63
+ * <CheckboxCard.Root checked={enabled} onCheckedChange={setEnabled} aria-label="…">
64
+ * <CheckboxCard.Indicator />
65
+ * Feature name
66
+ * </CheckboxCard.Root>
67
+ * ```
68
+ */
69
+ function CheckboxCardRoot(props: CheckboxCardRootProps) {
70
+ const {
71
+ defaultChecked,
72
+ checked,
73
+ onCheckedChange,
74
+ onClick,
75
+ disabled,
76
+ asChild = false,
77
+ children,
78
+ ...rest
79
+ } = props;
80
+ const { checked: isChecked, toggle } = useCheckboxCardRoot({
81
+ defaultChecked,
82
+ checked,
83
+ onCheckedChange,
84
+ });
85
+ const contextValue = useMemo(() => ({ checked: isChecked }), [isChecked]);
86
+ const rootProps = {
87
+ ...rest,
88
+ role: "checkbox" as const,
89
+ "aria-checked":
90
+ isChecked === "indeterminate"
91
+ ? ("mixed" as const)
92
+ : (isChecked as boolean),
93
+ "data-state": dataStateOf(isChecked),
94
+ "data-disabled": disabled ? "" : undefined,
95
+ disabled,
96
+ onClick: composeEventHandlers(onClick, toggle),
97
+ };
98
+ return (
99
+ <CheckboxCardContext.Provider value={contextValue}>
100
+ {asChild ? (
101
+ <Slot {...rootProps}>{children}</Slot>
102
+ ) : (
103
+ <button type="button" {...rootProps}>
104
+ {children}
105
+ </button>
106
+ )}
107
+ </CheckboxCardContext.Provider>
108
+ );
109
+ }
110
+
111
+ CheckboxCardRoot.displayName = "CheckboxCardRoot";
112
+
113
+ /**
114
+ * A decorative `<span aria-hidden="true">` that renders its children only
115
+ * while the parent {@link CheckboxCardRoot | `CheckboxCard.Root`} is
116
+ * **checked** or **indeterminate** — never when unchecked. The checkbox's
117
+ * accessible state is already conveyed by `aria-checked` on the root, so
118
+ * the indicator is purely visual.
119
+ *
120
+ * **Styling hook.** Mirrors the root's
121
+ * `data-state="checked" | "unchecked" | "indeterminate"`.
122
+ *
123
+ * **`asChild` prop.** Pass `asChild` to render the consumer's own element
124
+ * (typically an `<svg>` tick icon) as the indicator itself.
125
+ *
126
+ * **`forceMount` prop.** Pass `forceMount` to keep the indicator in the DOM
127
+ * while unchecked so a CSS exit animation can play against
128
+ * `data-state="unchecked"`.
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * <CheckboxCard.Indicator>
133
+ * <CheckIcon />
134
+ * </CheckboxCard.Indicator>
135
+ * ```
136
+ *
137
+ * @throws if rendered outside a `CheckboxCard.Root`.
138
+ */
139
+ function CheckboxCardIndicator({
140
+ children,
141
+ forceMount,
142
+ asChild = false,
143
+ ...rest
144
+ }: CheckboxCardIndicatorProps) {
145
+ const { checked } = useCheckboxCardContext();
146
+ const isVisible = checked !== false;
147
+ if (!isVisible && !forceMount) return null;
148
+ const indicatorProps = {
149
+ ...rest,
150
+ "aria-hidden": "true" as const,
151
+ "data-state": dataStateOf(checked),
152
+ };
153
+ if (asChild) {
154
+ return <Slot {...indicatorProps}>{children}</Slot>;
155
+ }
156
+ return <span {...indicatorProps}>{children}</span>;
157
+ }
158
+
159
+ CheckboxCardIndicator.displayName = "CheckboxCardIndicator";
160
+
161
+ type TCheckboxCardCompound = typeof CheckboxCardRoot & {
162
+ Root: typeof CheckboxCardRoot;
163
+ Indicator: typeof CheckboxCardIndicator;
164
+ };
165
+
166
+ /**
167
+ * Headless, accessible **CheckboxCard** — a card/tile-shaped checkbox
168
+ * implementing the
169
+ * [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
170
+ * including the tri-state ("mixed") variant. The entire card surface is the
171
+ * interactive element. Zero styles ship.
172
+ *
173
+ * `CheckboxCard` is both callable (an alias of
174
+ * {@link CheckboxCardRoot | `CheckboxCard.Root`}) and carries its
175
+ * sub-components as static properties.
176
+ *
177
+ * - {@link CheckboxCardRoot | `CheckboxCard.Root`} — state owner, context
178
+ * provider, toggle button.
179
+ * - {@link CheckboxCardIndicator | `CheckboxCard.Indicator`} — decorative
180
+ * tick, conditional on checked state.
181
+ *
182
+ * @example Minimal usage
183
+ * ```tsx
184
+ * import { CheckboxCard } from "@primitiv-ui/react";
185
+ *
186
+ * <CheckboxCard.Root aria-label="Enable feature">
187
+ * <CheckboxCard.Indicator>
188
+ * <CheckIcon />
189
+ * </CheckboxCard.Indicator>
190
+ * <h3>Feature name</h3>
191
+ * <p>Enable this to unlock advanced capabilities.</p>
192
+ * </CheckboxCard.Root>
193
+ * ```
194
+ *
195
+ * @see {@link CheckboxCardRoot} for state modes and tri-state semantics.
196
+ * @see {@link CheckboxCardIndicator} for the mount gate and animation hooks.
197
+ */
198
+ const CheckboxCardCompound: TCheckboxCardCompound = Object.assign(
199
+ CheckboxCardRoot,
200
+ {
201
+ Root: CheckboxCardRoot,
202
+ Indicator: CheckboxCardIndicator,
203
+ },
204
+ );
205
+
206
+ CheckboxCardCompound.displayName = "CheckboxCard";
207
+
208
+ export { CheckboxCardCompound as CheckboxCard };
@@ -0,0 +1,12 @@
1
+ import { createStrictContext } from "../utils";
2
+
3
+ import { CheckedState } from "./types";
4
+
5
+ export type CheckboxCardContextValue = {
6
+ checked: CheckedState;
7
+ };
8
+
9
+ export const [CheckboxCardContext, useCheckboxCardContext] =
10
+ createStrictContext<CheckboxCardContextValue>(
11
+ "CheckboxCard sub-components must be rendered inside a <CheckboxCard.Root>.",
12
+ );
@@ -0,0 +1,114 @@
1
+ # CheckboxCard
2
+
3
+ Headless, accessible **CheckboxCard** — a card/tile-shaped checkbox implementing
4
+ the
5
+ [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
6
+ including the tri-state ("mixed") variant. The entire card surface is the
7
+ interactive element. Zero styles ship.
8
+
9
+ ```tsx
10
+ import { CheckboxCard } from "@primitiv-ui/react";
11
+
12
+ <CheckboxCard.Root aria-label="Enable dark mode">
13
+ <CheckboxCard.Indicator>
14
+ <CheckIcon />
15
+ </CheckboxCard.Indicator>
16
+ <h3>Dark mode</h3>
17
+ <p>Switch the interface to a dark colour scheme.</p>
18
+ </CheckboxCard.Root>
19
+ ```
20
+
21
+ ## Sub-components
22
+
23
+ | Export | Element | ARIA / data hooks | `asChild` |
24
+ |--------|---------|------------------|-----------|
25
+ | `CheckboxCard.Root` | `<button>` | `role="checkbox"`, `aria-checked`, `data-state`, `data-disabled` | yes |
26
+ | `CheckboxCard.Indicator` | `<span>` | `aria-hidden="true"`, `data-state` | yes |
27
+
28
+ ## State modes
29
+
30
+ ### Uncontrolled
31
+
32
+ Pass `defaultChecked` (or omit for unchecked on mount). The component owns
33
+ the value internally.
34
+
35
+ ```tsx
36
+ <CheckboxCard.Root defaultChecked aria-label="Enable dark mode">
37
+ <CheckboxCard.Indicator />
38
+ Dark mode
39
+ </CheckboxCard.Root>
40
+ ```
41
+
42
+ ### Controlled
43
+
44
+ Pass `checked` and `onCheckedChange` together. The parent owns the value.
45
+
46
+ ```tsx
47
+ const [enabled, setEnabled] = useState<CheckedState>(false);
48
+
49
+ <CheckboxCard.Root checked={enabled} onCheckedChange={setEnabled} aria-label="…">
50
+ <CheckboxCard.Indicator />
51
+ Dark mode
52
+ </CheckboxCard.Root>
53
+ ```
54
+
55
+ ## Tri-state (indeterminate)
56
+
57
+ Both `checked` and `defaultChecked` accept `boolean | "indeterminate"`.
58
+ `aria-checked="mixed"` and `data-state="indeterminate"` are set automatically.
59
+ Clicking an indeterminate card resolves it to `true` per the WAI-ARIA tri-state
60
+ convention, then flips boolean on subsequent clicks.
61
+
62
+ ```tsx
63
+ <CheckboxCard.Root defaultChecked="indeterminate" aria-label="Select all">
64
+ <CheckboxCard.Indicator />
65
+ Select all items
66
+ </CheckboxCard.Root>
67
+ ```
68
+
69
+ ## Keyboard interaction
70
+
71
+ Clicking or pressing `Space` / `Enter` on the focused card toggles its state
72
+ (native `<button>` behaviour).
73
+
74
+ ## Disabled
75
+
76
+ Pass `disabled` on the Root. The native attribute suppresses clicks, removes
77
+ the card from the focus ring, and `data-disabled=""` is set for CSS targeting.
78
+
79
+ ```tsx
80
+ <CheckboxCard.Root aria-label="Premium feature" disabled>
81
+ <CheckboxCard.Indicator />
82
+ Premium feature
83
+ </CheckboxCard.Root>
84
+ ```
85
+
86
+ ## `asChild` composition
87
+
88
+ Both sub-components accept `asChild`. The library's ARIA attributes,
89
+ `data-state`, event handlers, and ref are merged onto the consumer's element.
90
+
91
+ ```tsx
92
+ <CheckboxCard.Root asChild aria-label="Enable feature">
93
+ <li>Feature name</li>
94
+ </CheckboxCard.Root>
95
+ ```
96
+
97
+ ## Indicator animation hooks
98
+
99
+ `CheckboxCard.Indicator` mounts only while the card is checked or indeterminate.
100
+ Pass `forceMount` to keep it in the DOM — `data-state="unchecked"` lets a CSS
101
+ exit animation play.
102
+
103
+ ```tsx
104
+ <CheckboxCard.Indicator forceMount>
105
+ <CheckIcon />
106
+ </CheckboxCard.Indicator>
107
+ ```
108
+
109
+ ## Styling hooks
110
+
111
+ | Attribute | Values | Set on |
112
+ |-----------|--------|--------|
113
+ | `data-state` | `"checked"` \| `"unchecked"` \| `"indeterminate"` | `CheckboxCard.Root`, `CheckboxCard.Indicator` |
114
+ | `data-disabled` | `""` (present when disabled) | `CheckboxCard.Root` |
@@ -0,0 +1,54 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { CheckboxCard } from "../CheckboxCard";
5
+
6
+ describe("CheckboxCard asChild", () => {
7
+ it("renders the Root as the consumer element when asChild is set", () => {
8
+ // Arrange & Act
9
+ render(
10
+ <CheckboxCard.Root asChild aria-label="Enable feature">
11
+ <div>Feature card</div>
12
+ </CheckboxCard.Root>,
13
+ );
14
+
15
+ // Assert — div rendered, not button; role and aria-checked merged
16
+ const card = screen.getByRole("checkbox", { name: "Enable feature" });
17
+ expect(card.tagName).toBe("DIV");
18
+ expect(card).toHaveAttribute("aria-checked", "false");
19
+ });
20
+
21
+ it("merges onClick onto the asChild Root element", async () => {
22
+ // Arrange
23
+ const user = userEvent.setup();
24
+ const onClick = vi.fn();
25
+ render(
26
+ <CheckboxCard.Root asChild aria-label="Enable feature" onClick={onClick}>
27
+ <div>Feature card</div>
28
+ </CheckboxCard.Root>,
29
+ );
30
+
31
+ // Act
32
+ await user.click(screen.getByRole("checkbox", { name: "Enable feature" }));
33
+
34
+ // Assert
35
+ expect(onClick).toHaveBeenCalledOnce();
36
+ });
37
+
38
+ it("renders the Indicator as the consumer element when asChild is set", () => {
39
+ // Arrange & Act
40
+ render(
41
+ <CheckboxCard.Root aria-label="Enable feature" defaultChecked>
42
+ <CheckboxCard.Indicator asChild>
43
+ <svg data-testid="icon" viewBox="0 0 10 10" />
44
+ </CheckboxCard.Indicator>
45
+ </CheckboxCard.Root>,
46
+ );
47
+
48
+ // Assert — svg rendered, not span; aria-hidden and data-state merged
49
+ const icon = screen.getByTestId("icon");
50
+ expect(icon.tagName).toBe("svg");
51
+ expect(icon).toHaveAttribute("aria-hidden", "true");
52
+ expect(icon).toHaveAttribute("data-state", "checked");
53
+ });
54
+ });