@primitiv-ui/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (585) hide show
  1. package/README.md +79 -0
  2. package/package.json +59 -0
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
  4. package/src/AccessibleIcon/README.md +42 -0
  5. package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
  6. package/src/AccessibleIcon/index.ts +2 -0
  7. package/src/AccessibleIcon/types.ts +8 -0
  8. package/src/Accordion/Accordion.tsx +412 -0
  9. package/src/Accordion/AccordionContext.ts +12 -0
  10. package/src/Accordion/README.md +202 -0
  11. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
  12. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
  13. package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
  14. package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
  15. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
  16. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
  17. package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
  18. package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
  19. package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
  20. package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
  21. package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
  22. package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
  23. package/src/Accordion/hooks/index.ts +6 -0
  24. package/src/Accordion/hooks/useAccordionContext.ts +1 -0
  25. package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
  26. package/src/Accordion/hooks/useAccordionItem.ts +22 -0
  27. package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
  28. package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
  29. package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
  30. package/src/Accordion/index.ts +1 -0
  31. package/src/Accordion/types.ts +81 -0
  32. package/src/Alert/Alert.tsx +43 -0
  33. package/src/Alert/README.md +54 -0
  34. package/src/Alert/__tests__/Alert.test.tsx +28 -0
  35. package/src/Alert/index.ts +2 -0
  36. package/src/Alert/types.ts +5 -0
  37. package/src/Avatar/Avatar.tsx +149 -0
  38. package/src/Avatar/AvatarContext.ts +20 -0
  39. package/src/Avatar/README.md +116 -0
  40. package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
  41. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
  42. package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
  43. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
  44. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
  45. package/src/Avatar/hooks/index.ts +2 -0
  46. package/src/Avatar/hooks/useAvatarContext.ts +1 -0
  47. package/src/Avatar/hooks/useAvatarImage.ts +40 -0
  48. package/src/Avatar/index.ts +3 -0
  49. package/src/Avatar/types.ts +44 -0
  50. package/src/Breadcrumb/Breadcrumb.tsx +234 -0
  51. package/src/Breadcrumb/README.md +111 -0
  52. package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
  53. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
  54. package/src/Breadcrumb/index.ts +2 -0
  55. package/src/Breadcrumb/types.ts +22 -0
  56. package/src/Button/Button.tsx +95 -0
  57. package/src/Button/README.md +112 -0
  58. package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
  59. package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
  60. package/src/Button/__tests__/Button.contract.test.tsx +72 -0
  61. package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
  62. package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
  63. package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
  64. package/src/Button/index.ts +2 -0
  65. package/src/Button/types.ts +8 -0
  66. package/src/Carousel/Carousel.tsx +708 -0
  67. package/src/Carousel/CarouselContext.ts +11 -0
  68. package/src/Carousel/README.md +848 -0
  69. package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
  70. package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
  71. package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
  72. package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
  73. package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
  74. package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
  75. package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
  76. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
  77. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
  78. package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
  79. package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
  80. package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
  81. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
  82. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
  83. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
  84. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
  85. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
  86. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
  87. package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
  88. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
  89. package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
  90. package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
  91. package/src/Carousel/hooks/index.ts +4 -0
  92. package/src/Carousel/hooks/useCarouselContext.ts +13 -0
  93. package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
  94. package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
  95. package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
  96. package/src/Carousel/index.ts +3 -0
  97. package/src/Carousel/types.ts +400 -0
  98. package/src/Checkbox/Checkbox.tsx +228 -0
  99. package/src/Checkbox/CheckboxContext.ts +12 -0
  100. package/src/Checkbox/README.md +156 -0
  101. package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
  102. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
  103. package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
  104. package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
  105. package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
  106. package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
  107. package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
  108. package/src/Checkbox/hooks/index.ts +2 -0
  109. package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
  110. package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
  111. package/src/Checkbox/index.ts +1 -0
  112. package/src/Checkbox/types.ts +33 -0
  113. package/src/CheckboxCard/CheckboxCard.tsx +208 -0
  114. package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
  115. package/src/CheckboxCard/README.md +114 -0
  116. package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
  117. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
  118. package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
  119. package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
  120. package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
  121. package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
  122. package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
  123. package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
  124. package/src/CheckboxCard/hooks/index.ts +2 -0
  125. package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
  126. package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
  127. package/src/CheckboxCard/index.ts +3 -0
  128. package/src/CheckboxCard/types.ts +33 -0
  129. package/src/Collapsible/Collapsible.tsx +316 -0
  130. package/src/Collapsible/CollapsibleContext.ts +7 -0
  131. package/src/Collapsible/README.md +174 -0
  132. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
  133. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
  134. package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
  135. package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
  136. package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
  137. package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
  138. package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
  139. package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
  140. package/src/Collapsible/hooks/index.ts +2 -0
  141. package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
  142. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
  143. package/src/Collapsible/index.ts +1 -0
  144. package/src/Collapsible/types.ts +48 -0
  145. package/src/ContextMenu/ContextMenu.tsx +1004 -0
  146. package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
  147. package/src/ContextMenu/ContextMenuContext.ts +21 -0
  148. package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
  149. package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
  150. package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
  151. package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
  152. package/src/ContextMenu/README.md +275 -0
  153. package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
  154. package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
  155. package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
  156. package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
  157. package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
  158. package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
  159. package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
  160. package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
  161. package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
  162. package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
  163. package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
  164. package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
  165. package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
  166. package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
  167. package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
  168. package/src/ContextMenu/constants.ts +4 -0
  169. package/src/ContextMenu/index.ts +1 -0
  170. package/src/ContextMenu/types.ts +199 -0
  171. package/src/DirectionProvider/DirectionContext.ts +21 -0
  172. package/src/DirectionProvider/DirectionProvider.tsx +31 -0
  173. package/src/DirectionProvider/README.md +62 -0
  174. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
  175. package/src/DirectionProvider/index.ts +3 -0
  176. package/src/DirectionProvider/types.ts +10 -0
  177. package/src/Divider/Divider.tsx +57 -0
  178. package/src/Divider/README.md +57 -0
  179. package/src/Divider/__tests__/Divider.test.tsx +41 -0
  180. package/src/Divider/index.ts +1 -0
  181. package/src/Divider/types.ts +5 -0
  182. package/src/Dropdown/Dropdown.tsx +842 -0
  183. package/src/Dropdown/DropdownContentContext.ts +15 -0
  184. package/src/Dropdown/DropdownContext.ts +17 -0
  185. package/src/Dropdown/DropdownGroupContext.ts +8 -0
  186. package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
  187. package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
  188. package/src/Dropdown/DropdownSubContext.ts +15 -0
  189. package/src/Dropdown/README.md +284 -0
  190. package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
  191. package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
  192. package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
  193. package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
  194. package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
  195. package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
  196. package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
  197. package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
  198. package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
  199. package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
  200. package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
  201. package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
  202. package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
  203. package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
  204. package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
  205. package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
  206. package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
  207. package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
  208. package/src/Dropdown/constants.ts +4 -0
  209. package/src/Dropdown/hooks/index.ts +9 -0
  210. package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
  211. package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
  212. package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
  213. package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
  214. package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
  215. package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
  216. package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
  217. package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
  218. package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
  219. package/src/Dropdown/index.ts +1 -0
  220. package/src/Dropdown/types.ts +200 -0
  221. package/src/EmptyState/EmptyState.tsx +245 -0
  222. package/src/EmptyState/README.md +129 -0
  223. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
  224. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
  225. package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
  226. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
  227. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
  228. package/src/EmptyState/index.ts +2 -0
  229. package/src/EmptyState/types.ts +21 -0
  230. package/src/Field/Field.tsx +239 -0
  231. package/src/Field/FieldContext.ts +22 -0
  232. package/src/Field/README.md +167 -0
  233. package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
  234. package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
  235. package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
  236. package/src/Field/hooks/index.ts +2 -0
  237. package/src/Field/hooks/useFieldContext.ts +1 -0
  238. package/src/Field/hooks/useFieldProps.ts +57 -0
  239. package/src/Field/index.ts +2 -0
  240. package/src/Field/types.ts +33 -0
  241. package/src/Fieldset/Fieldset.tsx +104 -0
  242. package/src/Fieldset/README.md +74 -0
  243. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
  244. package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
  245. package/src/Fieldset/index.ts +2 -0
  246. package/src/Fieldset/types.ts +5 -0
  247. package/src/Input/Input.tsx +120 -0
  248. package/src/Input/README.md +180 -0
  249. package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
  250. package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
  251. package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
  252. package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
  253. package/src/Input/index.ts +2 -0
  254. package/src/Input/types.ts +7 -0
  255. package/src/InputGroup/InputGroup.tsx +228 -0
  256. package/src/InputGroup/README.md +178 -0
  257. package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
  258. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
  259. package/src/InputGroup/index.ts +2 -0
  260. package/src/InputGroup/types.ts +13 -0
  261. package/src/MillerColumns/MillerColumns.tsx +329 -0
  262. package/src/MillerColumns/MillerColumnsContext.ts +25 -0
  263. package/src/MillerColumns/README.md +278 -0
  264. package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
  265. package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
  266. package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
  267. package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
  268. package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
  269. package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
  270. package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
  271. package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
  272. package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
  273. package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
  274. package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
  275. package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
  276. package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
  277. package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
  278. package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
  279. package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
  280. package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
  281. package/src/MillerColumns/hooks/index.ts +7 -0
  282. package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
  283. package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
  284. package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
  285. package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
  286. package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
  287. package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
  288. package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
  289. package/src/MillerColumns/index.ts +3 -0
  290. package/src/MillerColumns/types.ts +93 -0
  291. package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
  292. package/src/MillerColumns/utils.ts +75 -0
  293. package/src/Modal/Modal.tsx +474 -0
  294. package/src/Modal/ModalContext.ts +13 -0
  295. package/src/Modal/README.md +207 -0
  296. package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
  297. package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
  298. package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
  299. package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
  300. package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
  301. package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
  302. package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
  303. package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
  304. package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
  305. package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
  306. package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
  307. package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
  308. package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
  309. package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
  310. package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
  311. package/src/Modal/hooks/index.ts +4 -0
  312. package/src/Modal/hooks/useModalContent.ts +62 -0
  313. package/src/Modal/hooks/useModalContext.ts +1 -0
  314. package/src/Modal/hooks/useModalRoot.ts +81 -0
  315. package/src/Modal/hooks/useModalTrigger.ts +25 -0
  316. package/src/Modal/index.ts +3 -0
  317. package/src/Modal/types.ts +76 -0
  318. package/src/Portal/Portal.tsx +28 -0
  319. package/src/Portal/README.md +70 -0
  320. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
  321. package/src/Portal/index.ts +2 -0
  322. package/src/Portal/types.ts +6 -0
  323. package/src/Progress/Progress.tsx +178 -0
  324. package/src/Progress/ProgressContext.ts +15 -0
  325. package/src/Progress/README.md +112 -0
  326. package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
  327. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
  328. package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
  329. package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
  330. package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
  331. package/src/Progress/hooks/index.ts +2 -0
  332. package/src/Progress/hooks/useProgressContext.ts +1 -0
  333. package/src/Progress/hooks/useProgressRoot.ts +45 -0
  334. package/src/Progress/index.ts +3 -0
  335. package/src/Progress/types.ts +43 -0
  336. package/src/RadioCard/README.md +133 -0
  337. package/src/RadioCard/RadioCard.tsx +334 -0
  338. package/src/RadioCard/RadioCardContext.ts +23 -0
  339. package/src/RadioCard/RadioCardItemContext.ts +10 -0
  340. package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
  341. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
  342. package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
  343. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
  344. package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
  345. package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
  346. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
  347. package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
  348. package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
  349. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
  350. package/src/RadioCard/hooks/index.ts +3 -0
  351. package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
  352. package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
  353. package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
  354. package/src/RadioCard/index.ts +4 -0
  355. package/src/RadioCard/types.ts +51 -0
  356. package/src/RadioGroup/README.md +185 -0
  357. package/src/RadioGroup/RadioGroup.tsx +353 -0
  358. package/src/RadioGroup/RadioGroupContext.ts +23 -0
  359. package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
  360. package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
  361. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
  362. package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
  363. package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
  364. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
  365. package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
  366. package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
  367. package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
  368. package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
  369. package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
  370. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
  371. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
  372. package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
  373. package/src/RadioGroup/hooks/index.ts +3 -0
  374. package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
  375. package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
  376. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
  377. package/src/RadioGroup/index.ts +1 -0
  378. package/src/RadioGroup/types.ts +51 -0
  379. package/src/Select/README.md +203 -0
  380. package/src/Select/Select.tsx +204 -0
  381. package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
  382. package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
  383. package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
  384. package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
  385. package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
  386. package/src/Select/__tests__/Select.group.test.tsx +42 -0
  387. package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
  388. package/src/Select/index.ts +2 -0
  389. package/src/Select/types.ts +89 -0
  390. package/src/SkipNav/README.md +87 -0
  391. package/src/SkipNav/SkipNav.tsx +116 -0
  392. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
  393. package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
  394. package/src/SkipNav/index.ts +1 -0
  395. package/src/SkipNav/types.ts +26 -0
  396. package/src/Slider/README.md +215 -0
  397. package/src/Slider/Slider.tsx +308 -0
  398. package/src/Slider/SliderContext.ts +24 -0
  399. package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
  400. package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
  401. package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
  402. package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
  403. package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
  404. package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
  405. package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
  406. package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
  407. package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
  408. package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
  409. package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
  410. package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
  411. package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
  412. package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
  413. package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
  414. package/src/Slider/hooks/index.ts +3 -0
  415. package/src/Slider/hooks/useSliderContext.ts +1 -0
  416. package/src/Slider/hooks/useSliderRoot.ts +197 -0
  417. package/src/Slider/hooks/useSliderThumb.ts +77 -0
  418. package/src/Slider/index.ts +3 -0
  419. package/src/Slider/types.ts +48 -0
  420. package/src/Slider/utils.ts +155 -0
  421. package/src/Slot/Slot.tsx +158 -0
  422. package/src/Slot/__tests__/Slot.test.tsx +163 -0
  423. package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
  424. package/src/Slot/composeEventHandlers.ts +38 -0
  425. package/src/Slot/index.ts +3 -0
  426. package/src/Slot/types.ts +5 -0
  427. package/src/Status/README.md +50 -0
  428. package/src/Status/Status.tsx +44 -0
  429. package/src/Status/__tests__/Status.test.tsx +28 -0
  430. package/src/Status/index.ts +2 -0
  431. package/src/Status/types.ts +5 -0
  432. package/src/Switch/README.md +121 -0
  433. package/src/Switch/Switch.tsx +167 -0
  434. package/src/Switch/SwitchContext.ts +10 -0
  435. package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
  436. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
  437. package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
  438. package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
  439. package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
  440. package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
  441. package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
  442. package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
  443. package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
  444. package/src/Switch/hooks/index.ts +2 -0
  445. package/src/Switch/hooks/useSwitchContext.ts +1 -0
  446. package/src/Switch/hooks/useSwitchRoot.ts +28 -0
  447. package/src/Switch/index.ts +3 -0
  448. package/src/Switch/types.ts +37 -0
  449. package/src/Table/README.md +205 -0
  450. package/src/Table/Table.tsx +380 -0
  451. package/src/Table/__tests__/Table.Body.test.tsx +61 -0
  452. package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
  453. package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
  454. package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
  455. package/src/Table/__tests__/Table.Head.test.tsx +61 -0
  456. package/src/Table/__tests__/Table.Header.test.tsx +73 -0
  457. package/src/Table/__tests__/Table.Root.test.tsx +49 -0
  458. package/src/Table/__tests__/Table.Row.test.tsx +67 -0
  459. package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
  460. package/src/Table/index.ts +1 -0
  461. package/src/Table/types.ts +63 -0
  462. package/src/Tabs/README.md +110 -0
  463. package/src/Tabs/Tabs.tsx +434 -0
  464. package/src/Tabs/TabsContext.ts +13 -0
  465. package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
  466. package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
  467. package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
  468. package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
  469. package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
  470. package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
  471. package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
  472. package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
  473. package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
  474. package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
  475. package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
  476. package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
  477. package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
  478. package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
  479. package/src/Tabs/hooks/index.ts +4 -0
  480. package/src/Tabs/hooks/useTabsContent.ts +27 -0
  481. package/src/Tabs/hooks/useTabsContext.ts +1 -0
  482. package/src/Tabs/hooks/useTabsRoot.ts +148 -0
  483. package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
  484. package/src/Tabs/index.ts +3 -0
  485. package/src/Tabs/types.ts +99 -0
  486. package/src/Tabs/utils.ts +8 -0
  487. package/src/Textarea/README.md +98 -0
  488. package/src/Textarea/Textarea.tsx +93 -0
  489. package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
  490. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
  491. package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
  492. package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
  493. package/src/Textarea/index.ts +2 -0
  494. package/src/Textarea/types.ts +7 -0
  495. package/src/Toggle/README.md +97 -0
  496. package/src/Toggle/Toggle.tsx +81 -0
  497. package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
  498. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
  499. package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
  500. package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
  501. package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
  502. package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
  503. package/src/Toggle/index.ts +2 -0
  504. package/src/Toggle/types.ts +23 -0
  505. package/src/ToggleGroup/README.md +137 -0
  506. package/src/ToggleGroup/ToggleGroup.tsx +298 -0
  507. package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
  508. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
  509. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
  510. package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
  511. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
  512. package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
  513. package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
  514. package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
  515. package/src/ToggleGroup/hooks/index.ts +2 -0
  516. package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
  517. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
  518. package/src/ToggleGroup/index.ts +2 -0
  519. package/src/ToggleGroup/types.ts +72 -0
  520. package/src/Tooltip/README.md +214 -0
  521. package/src/Tooltip/Tooltip.tsx +260 -0
  522. package/src/Tooltip/TooltipContext.ts +20 -0
  523. package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
  524. package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
  525. package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
  526. package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
  527. package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
  528. package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
  529. package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
  530. package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
  531. package/src/Tooltip/hooks/index.ts +4 -0
  532. package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
  533. package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
  534. package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
  535. package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
  536. package/src/Tooltip/index.ts +1 -0
  537. package/src/Tooltip/types.ts +64 -0
  538. package/src/Tree/README.md +339 -0
  539. package/src/Tree/Tree.tsx +571 -0
  540. package/src/Tree/TreeContext.ts +24 -0
  541. package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
  542. package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
  543. package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
  544. package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
  545. package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
  546. package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
  547. package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
  548. package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
  549. package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
  550. package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
  551. package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
  552. package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
  553. package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
  554. package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
  555. package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
  556. package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
  557. package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
  558. package/src/Tree/hooks/index.ts +3 -0
  559. package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
  560. package/src/Tree/hooks/useTreePath.ts +68 -0
  561. package/src/Tree/hooks/useTreeRoot.ts +279 -0
  562. package/src/Tree/index.ts +3 -0
  563. package/src/Tree/types.ts +224 -0
  564. package/src/Tree/utils.ts +59 -0
  565. package/src/VisuallyHidden/README.md +58 -0
  566. package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
  567. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
  568. package/src/VisuallyHidden/index.ts +2 -0
  569. package/src/VisuallyHidden/types.ts +5 -0
  570. package/src/hooks/index.ts +3 -0
  571. package/src/hooks/useCollection.ts +74 -0
  572. package/src/hooks/useControllableState.ts +81 -0
  573. package/src/hooks/useRovingTabindex.ts +178 -0
  574. package/src/index.ts +38 -0
  575. package/src/test/intersectionObserverPolyfill.ts +83 -0
  576. package/src/test/popoverPolyfill.ts +86 -0
  577. package/src/test/scrollPolyfill.ts +23 -0
  578. package/src/types.ts +13 -0
  579. package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
  580. package/src/utils/__tests__/deriveId.test.ts +28 -0
  581. package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
  582. package/src/utils/createStrictContext.ts +49 -0
  583. package/src/utils/deriveId.ts +31 -0
  584. package/src/utils/getKeyToActionMap.ts +95 -0
  585. package/src/utils/index.ts +3 -0
@@ -0,0 +1,228 @@
1
+ import { useMemo } from "react";
2
+
3
+ import { Slot, composeEventHandlers } from "../Slot";
4
+
5
+ import { CheckboxContext } from "./CheckboxContext";
6
+ import { useCheckboxContext, useCheckboxRoot } from "./hooks";
7
+ import {
8
+ CheckboxIndicatorProps,
9
+ CheckboxRootProps,
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 Checkbox — a native `<button role="checkbox">` that
20
+ * owns the tri-state checked value and provides
21
+ * {@link CheckboxContext | `CheckboxContext`} to descendant
22
+ * {@link CheckboxIndicator | `Checkbox.Indicator`}s.
23
+ *
24
+ * Supports two state modes, statically discriminated at the type level:
25
+ *
26
+ * - **Uncontrolled** — pass
27
+ * {@link CheckboxRootProps.defaultChecked | `defaultChecked`} (or omit
28
+ * for unchecked-on-mount). The component owns the value internally.
29
+ * - **Controlled** — pass
30
+ * {@link CheckboxRootProps.checked | `checked`} *and*
31
+ * {@link CheckboxRootProps.onCheckedChange | `onCheckedChange`}
32
+ * together. The parent owns the value; the component defers every
33
+ * change back through the callback.
34
+ *
35
+ * Both `checked` and `defaultChecked` accept `boolean | "indeterminate"`.
36
+ * Clicking an indeterminate checkbox resolves it to `true` per the
37
+ * WAI-ARIA tri-state convention, then flips boolean on subsequent clicks.
38
+ *
39
+ * **ARIA.** `role="checkbox"` and `aria-checked` are set automatically;
40
+ * `aria-checked="mixed"` represents the indeterminate state.
41
+ *
42
+ * **Styling hooks.** `data-state="checked" | "unchecked" | "indeterminate"`
43
+ * on the root, plus `data-disabled=""` when disabled.
44
+ *
45
+ * **`asChild` prop.** Pass `asChild` to render any consumer-supplied
46
+ * element (e.g. `<li role="menuitemcheckbox">` for menu composition)
47
+ * with the checkbox's ARIA attributes, data-state, composed onClick, and
48
+ * ref merged in. The native `<button>` is dropped; consumers who want
49
+ * keyboard activation on a non-button element are responsible for
50
+ * providing it.
51
+ *
52
+ * @example Uncontrolled
53
+ * ```tsx
54
+ * <Checkbox.Root defaultChecked aria-label="Accept terms">
55
+ * <Checkbox.Indicator>
56
+ * <CheckIcon />
57
+ * </Checkbox.Indicator>
58
+ * </Checkbox.Root>
59
+ * ```
60
+ *
61
+ * @example Controlled
62
+ * ```tsx
63
+ * const [checked, setChecked] = useState<CheckedState>(false);
64
+ *
65
+ * <Checkbox.Root checked={checked} onCheckedChange={setChecked} aria-label="…">
66
+ * <Checkbox.Indicator>
67
+ * <CheckIcon />
68
+ * </Checkbox.Indicator>
69
+ * </Checkbox.Root>
70
+ * ```
71
+ *
72
+ * @example Composed into a menu item via `asChild`
73
+ * ```tsx
74
+ * <Checkbox.Root asChild aria-label="Show hidden files">
75
+ * <li role="menuitemcheckbox">Show hidden files</li>
76
+ * </Checkbox.Root>
77
+ * ```
78
+ */
79
+ function CheckboxRoot(props: CheckboxRootProps) {
80
+ const {
81
+ defaultChecked,
82
+ checked,
83
+ onCheckedChange,
84
+ onClick,
85
+ disabled,
86
+ asChild = false,
87
+ children,
88
+ ...rest
89
+ } = props;
90
+ const { checked: isChecked, toggle } = useCheckboxRoot({
91
+ defaultChecked,
92
+ checked,
93
+ onCheckedChange,
94
+ });
95
+ const contextValue = useMemo(() => ({ checked: isChecked }), [isChecked]);
96
+ const rootProps = {
97
+ ...rest,
98
+ role: "checkbox" as const,
99
+ "aria-checked":
100
+ isChecked === "indeterminate"
101
+ ? ("mixed" as const)
102
+ : (isChecked as boolean),
103
+ "data-state": dataStateOf(isChecked),
104
+ "data-disabled": disabled ? "" : undefined,
105
+ disabled,
106
+ onClick: composeEventHandlers(onClick, toggle),
107
+ };
108
+ return (
109
+ <CheckboxContext.Provider value={contextValue}>
110
+ {asChild ? (
111
+ <Slot {...rootProps}>{children}</Slot>
112
+ ) : (
113
+ <button type="button" {...rootProps}>
114
+ {children}
115
+ </button>
116
+ )}
117
+ </CheckboxContext.Provider>
118
+ );
119
+ }
120
+
121
+ CheckboxRoot.displayName = "CheckboxRoot";
122
+
123
+ /**
124
+ * A decorative `<span aria-hidden="true">` that renders its children
125
+ * only while the parent {@link CheckboxRoot | `Checkbox.Root`} is
126
+ * **checked** or **indeterminate** — never when unchecked. The
127
+ * checkbox's accessible state is already conveyed by `aria-checked`
128
+ * on the root, so the indicator is purely visual.
129
+ *
130
+ * **Styling hook.** Mirrors the root's
131
+ * `data-state="checked" | "unchecked" | "indeterminate"` so the same
132
+ * CSS rules can target both.
133
+ *
134
+ * **`asChild` prop.** Pass `asChild` to render the consumer's own
135
+ * element (typically an `<svg>` tick icon) as the indicator itself,
136
+ * with `aria-hidden` and `data-state` merged onto that element rather
137
+ * than a wrapper.
138
+ *
139
+ * **`forceMount` prop.** Pass `forceMount` to keep the indicator in
140
+ * the DOM while unchecked so a CSS exit animation can play against
141
+ * `data-state="unchecked"`. Consumers who use `forceMount` own the
142
+ * exit lifecycle themselves.
143
+ *
144
+ * @example Default span wrapper
145
+ * ```tsx
146
+ * <Checkbox.Indicator>
147
+ * <CheckIcon />
148
+ * </Checkbox.Indicator>
149
+ * ```
150
+ *
151
+ * @example Icon as the indicator via `asChild`
152
+ * ```tsx
153
+ * <Checkbox.Indicator asChild>
154
+ * <svg viewBox="0 0 10 10"><path d="M1 5l3 3 5-7" /></svg>
155
+ * </Checkbox.Indicator>
156
+ * ```
157
+ *
158
+ * @example Force-mounted for exit animation
159
+ * ```tsx
160
+ * <Checkbox.Indicator forceMount>
161
+ * <CheckIcon />
162
+ * </Checkbox.Indicator>
163
+ * ```
164
+ *
165
+ * @throws if rendered outside a `Checkbox.Root`.
166
+ */
167
+ function CheckboxIndicator({
168
+ children,
169
+ forceMount,
170
+ asChild = false,
171
+ ...rest
172
+ }: CheckboxIndicatorProps) {
173
+ const { checked } = useCheckboxContext();
174
+ const isVisible = checked !== false;
175
+ if (!isVisible && !forceMount) return null;
176
+ const indicatorProps = {
177
+ ...rest,
178
+ "aria-hidden": "true" as const,
179
+ "data-state": dataStateOf(checked),
180
+ };
181
+ if (asChild) {
182
+ return <Slot {...indicatorProps}>{children}</Slot>;
183
+ }
184
+ return <span {...indicatorProps}>{children}</span>;
185
+ }
186
+
187
+ CheckboxIndicator.displayName = "CheckboxIndicator";
188
+
189
+ type TCheckboxCompound = typeof CheckboxRoot & {
190
+ Root: typeof CheckboxRoot;
191
+ Indicator: typeof CheckboxIndicator;
192
+ };
193
+
194
+ /**
195
+ * Headless, accessible **Checkbox** — a compound component built on a
196
+ * native `<button role="checkbox">` that implements the
197
+ * [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
198
+ * including the tri-state ("mixed") variant. Zero styles ship.
199
+ *
200
+ * `Checkbox` is both callable (an alias of {@link CheckboxRoot | `Checkbox.Root`})
201
+ * and carries its sub-components as static properties. Prefer the
202
+ * namespaced form in application code for readability and grep-ability.
203
+ *
204
+ * - {@link CheckboxRoot | `Checkbox.Root`} — state owner, context provider, toggle button.
205
+ * - {@link CheckboxIndicator | `Checkbox.Indicator`} — decorative tick, conditional on checked state.
206
+ *
207
+ * @example Minimal usage
208
+ * ```tsx
209
+ * import { Checkbox } from "@primitiv-ui/react";
210
+ *
211
+ * <Checkbox.Root aria-label="Accept terms">
212
+ * <Checkbox.Indicator>
213
+ * <CheckIcon />
214
+ * </Checkbox.Indicator>
215
+ * </Checkbox.Root>;
216
+ * ```
217
+ *
218
+ * @see {@link CheckboxRoot} for state modes and tri-state semantics.
219
+ * @see {@link CheckboxIndicator} for the mount gate and animation hooks.
220
+ */
221
+ const CheckboxCompound: TCheckboxCompound = Object.assign(CheckboxRoot, {
222
+ Root: CheckboxRoot,
223
+ Indicator: CheckboxIndicator,
224
+ });
225
+
226
+ CheckboxCompound.displayName = "Checkbox";
227
+
228
+ export { CheckboxCompound as Checkbox };
@@ -0,0 +1,12 @@
1
+ import { createStrictContext } from "../utils";
2
+
3
+ import { CheckedState } from "./types";
4
+
5
+ export type CheckboxContextValue = {
6
+ checked: CheckedState;
7
+ };
8
+
9
+ export const [CheckboxContext, useCheckboxContext] =
10
+ createStrictContext<CheckboxContextValue>(
11
+ "Checkbox sub-components must be rendered inside a <Checkbox.Root>.",
12
+ );
@@ -0,0 +1,156 @@
1
+ # Checkbox
2
+
3
+ A headless, accessible compound component implementing the
4
+ [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/),
5
+ including the tri-state ("mixed") variant.
6
+
7
+ Checkbox renders a native `<button role="checkbox">` so it gets
8
+ keyboard activation (`Space` / `Enter`), focus ring, and disabled
9
+ semantics for free from the browser. The React layer adds three-state
10
+ support, `Indicator` mounting driven by the checked state, and the
11
+ `asChild` composition every primitive in this package supports.
12
+
13
+ ```tsx
14
+ import { Checkbox } from "@primitiv-ui/react";
15
+
16
+ <Checkbox.Root defaultChecked aria-label="Accept terms">
17
+ <Checkbox.Indicator>
18
+ <CheckIcon />
19
+ </Checkbox.Indicator>
20
+ </Checkbox.Root>;
21
+ ```
22
+
23
+ ## Sub-components
24
+
25
+ | Export | Element | Notes |
26
+ | -------------------- | ---------- | ------------------------------------------------------------------------------------------ |
27
+ | `Checkbox.Root` | `<button>` | `role="checkbox"`, `aria-checked`, `data-state`, `data-disabled`. `asChild` |
28
+ | `Checkbox.Indicator` | `<span>` | `aria-hidden="true"`. Renders only while checked or indeterminate. `asChild`, `forceMount` |
29
+
30
+ ## Checked state
31
+
32
+ Checkbox is tri-state. The `checked` / `defaultChecked` value is
33
+ `boolean | "indeterminate"`:
34
+
35
+ | Value | `aria-checked` | `data-state` | Indicator renders? |
36
+ | ----------------- | -------------- | ----------------- | ------------------------ |
37
+ | `true` | `"true"` | `"checked"` | Yes |
38
+ | `false` | `"false"` | `"unchecked"` | No (unless `forceMount`) |
39
+ | `"indeterminate"` | `"mixed"` | `"indeterminate"` | Yes |
40
+
41
+ Clicking an indeterminate checkbox resolves it to `true` per the
42
+ WAI-ARIA tri-state convention; subsequent clicks flip between `true`
43
+ and `false`.
44
+
45
+ ## State modes
46
+
47
+ - **Uncontrolled** — pass `defaultChecked` (or omit for unchecked on mount).
48
+ - **Controlled** — pass `checked` **and** `onCheckedChange` together.
49
+
50
+ The two shapes are statically discriminated at the type level;
51
+ TypeScript rejects mixing them.
52
+
53
+ ```tsx
54
+ // Uncontrolled
55
+ <Checkbox.Root defaultChecked>…</Checkbox.Root>;
56
+
57
+ // Controlled
58
+ const [checked, setChecked] = useState<CheckedState>(false);
59
+ <Checkbox.Root checked={checked} onCheckedChange={setChecked}>
60
+
61
+ </Checkbox.Root>;
62
+ ```
63
+
64
+ ## Keyboard interaction
65
+
66
+ | Key | Behaviour |
67
+ | ------- | ---------------------------------------- |
68
+ | `Space` | Toggles the checkbox (native `<button>`) |
69
+ | `Enter` | Toggles the checkbox (native `<button>`) |
70
+
71
+ Keyboard handling comes from the underlying `<button>` element — no
72
+ custom `keydown` listeners are needed.
73
+
74
+ ## Disabled
75
+
76
+ Passing `disabled` forwards the native `disabled` attribute to the
77
+ button (removing it from the tab order and suppressing clicks) **and**
78
+ sets `data-disabled=""` on the root so CSS can target
79
+ `[data-disabled]` without reaching for `:disabled`.
80
+
81
+ ```tsx
82
+ <Checkbox.Root disabled aria-label="Locked setting">
83
+ <Checkbox.Indicator>
84
+ <CheckIcon />
85
+ </Checkbox.Indicator>
86
+ </Checkbox.Root>
87
+ ```
88
+
89
+ ## `asChild` composition
90
+
91
+ Both `Checkbox.Root` and `Checkbox.Indicator` accept an `asChild`
92
+ boolean. When set, the component delegates rendering to its single
93
+ child element and merges its own ARIA attributes, data-state, composed
94
+ event handlers, and ref onto the child (the asChild contract: the
95
+ child's handler runs first, the library's runs second unless the child
96
+ calls `preventDefault`).
97
+
98
+ ```tsx
99
+ // Menu-item checkbox — the same state machinery, different element + role.
100
+ <Checkbox.Root asChild aria-label="Show hidden files">
101
+ <li role="menuitemcheckbox">Show hidden files</li>
102
+ </Checkbox.Root>
103
+
104
+ // Icon-only indicator — the svg itself becomes the indicator.
105
+ <Checkbox.Indicator asChild>
106
+ <svg viewBox="0 0 10 10">
107
+ <path d="M1 5l3 3 5-7" />
108
+ </svg>
109
+ </Checkbox.Indicator>
110
+ ```
111
+
112
+ `Root`'s `asChild` is what lets `Dropdown.CheckboxItem` (in a later
113
+ phase) wrap an `<li role="menuitemcheckbox">` around the Checkbox
114
+ state / toggle behaviour without re-implementing any of it.
115
+
116
+ ## Animation hooks
117
+
118
+ `Checkbox.Indicator` accepts a `forceMount` boolean. When set, the
119
+ indicator stays in the DOM regardless of checked state so a CSS
120
+ animation can play against `data-state="unchecked"`:
121
+
122
+ ```tsx
123
+ <Checkbox.Indicator forceMount>
124
+ <CheckIcon />
125
+ </Checkbox.Indicator>
126
+ ```
127
+
128
+ ```css
129
+ [data-state="checked"] {
130
+ animation: tick-in 120ms ease-out;
131
+ }
132
+ [data-state="unchecked"] {
133
+ animation: tick-out 100ms ease-in forwards;
134
+ }
135
+ ```
136
+
137
+ Consumers using `forceMount` own the exit timing themselves.
138
+
139
+ ## Styling hooks
140
+
141
+ `data-state="checked" | "unchecked" | "indeterminate"` is set on both
142
+ `Checkbox.Root` and `Checkbox.Indicator`, letting any CSS system target
143
+ the three phases.
144
+
145
+ ```css
146
+ button[data-state="checked"] {
147
+ background: oklch(65% 0.18 145);
148
+ }
149
+ button[data-state="indeterminate"] {
150
+ background: oklch(70% 0.1 250);
151
+ }
152
+ button[data-disabled] {
153
+ opacity: 0.5;
154
+ cursor: not-allowed;
155
+ }
156
+ ```
@@ -0,0 +1,69 @@
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 asChild composition", () => {
7
+ it("Root asChild delegates to the child element while keeping ARIA and toggle wiring", async () => {
8
+ // Arrange
9
+ const user = userEvent.setup();
10
+ const onCheckedChange = vi.fn();
11
+ render(
12
+ <Checkbox.Root
13
+ asChild
14
+ onCheckedChange={onCheckedChange}
15
+ aria-label="Accept terms"
16
+ >
17
+ <li>Accept</li>
18
+ </Checkbox.Root>,
19
+ );
20
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
21
+
22
+ // Assert element is the consumer's <li>
23
+ expect(checkbox.tagName).toBe("LI");
24
+ // ARIA + data-state merged onto the child
25
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
26
+ expect(checkbox).toHaveAttribute("data-state", "unchecked");
27
+
28
+ // Act
29
+ await user.click(checkbox);
30
+
31
+ // Assert toggle still fires through composed onClick
32
+ expect(onCheckedChange).toHaveBeenCalledWith(true);
33
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
34
+ });
35
+
36
+ it("Root asChild lets the consumer override the role for menu-item contexts", () => {
37
+ // Arrange & Act
38
+ render(
39
+ <Checkbox.Root asChild aria-label="Accept terms">
40
+ <li role="menuitemcheckbox">Accept</li>
41
+ </Checkbox.Root>,
42
+ );
43
+
44
+ // Assert
45
+ const item = screen.getByRole("menuitemcheckbox", { name: "Accept terms" });
46
+ expect(item).toHaveAttribute("aria-checked", "false");
47
+ expect(item).toHaveAttribute("data-state", "unchecked");
48
+ });
49
+
50
+ it("Indicator asChild delegates rendering to the consumer's element", () => {
51
+ // Arrange & Act
52
+ render(
53
+ <Checkbox.Root defaultChecked aria-label="Accept terms">
54
+ <Checkbox.Indicator asChild>
55
+ <svg data-testid="tick" viewBox="0 0 10 10">
56
+ <path d="M1 5l3 3 5-7" />
57
+ </svg>
58
+ </Checkbox.Indicator>
59
+ </Checkbox.Root>,
60
+ );
61
+
62
+ // Assert
63
+ const tick = screen.getByTestId("tick");
64
+ expect(tick.tagName.toLowerCase()).toBe("svg");
65
+ expect(tick).toHaveAttribute("aria-hidden", "true");
66
+ expect(tick).toHaveAttribute("data-state", "checked");
67
+ });
68
+
69
+ });
@@ -0,0 +1,41 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Checkbox } from "../Checkbox";
4
+
5
+ describe("Checkbox basic rendering", () => {
6
+ it('renders a <button> with role="checkbox"', () => {
7
+ // Arrange & Act
8
+ render(<Checkbox.Root aria-label="Accept terms" />);
9
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
10
+
11
+ // Assert
12
+ expect(checkbox.tagName).toBe("BUTTON");
13
+ });
14
+
15
+ it('defaults aria-checked to "false"', () => {
16
+ // Arrange & Act
17
+ render(<Checkbox.Root aria-label="Accept terms" />);
18
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
19
+
20
+ // Assert
21
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
22
+ });
23
+
24
+ it('defaults type="button" so the checkbox never submits an enclosing form', () => {
25
+ // Arrange & Act
26
+ render(<Checkbox.Root aria-label="Accept terms" />);
27
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
28
+
29
+ // Assert
30
+ expect(checkbox).toHaveAttribute("type", "button");
31
+ });
32
+
33
+ it('sets data-state="unchecked" on the root when unchecked', () => {
34
+ // Arrange & Act
35
+ render(<Checkbox.Root aria-label="Accept terms" />);
36
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
37
+
38
+ // Assert
39
+ expect(checkbox).toHaveAttribute("data-state", "unchecked");
40
+ });
41
+ });
@@ -0,0 +1,82 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { useState } from "react";
4
+
5
+ import { Checkbox } from "../Checkbox";
6
+
7
+ describe("Checkbox controlled state", () => {
8
+ it("reflects the controlled `checked` prop", () => {
9
+ // Arrange & Act
10
+ const { rerender } = render(
11
+ <Checkbox.Root
12
+ checked={false}
13
+ onCheckedChange={() => {}}
14
+ aria-label="Accept terms"
15
+ />,
16
+ );
17
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
18
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
19
+
20
+ rerender(
21
+ <Checkbox.Root
22
+ checked
23
+ onCheckedChange={() => {}}
24
+ aria-label="Accept terms"
25
+ />,
26
+ );
27
+
28
+ // Assert
29
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
30
+ expect(checkbox).toHaveAttribute("data-state", "checked");
31
+ });
32
+
33
+ it("does not update its rendered state when the parent refuses to update `checked`", async () => {
34
+ // Arrange
35
+ const user = userEvent.setup();
36
+ const onCheckedChange = vi.fn();
37
+ render(
38
+ <Checkbox.Root
39
+ checked={false}
40
+ onCheckedChange={onCheckedChange}
41
+ aria-label="Accept terms"
42
+ />,
43
+ );
44
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
45
+
46
+ // Act
47
+ await user.click(checkbox);
48
+
49
+ // Assert: callback fired but the rendered state stays false because the
50
+ // parent did not flip the controlled prop.
51
+ expect(onCheckedChange).toHaveBeenCalledWith(true);
52
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
53
+ expect(checkbox).toHaveAttribute("data-state", "unchecked");
54
+ });
55
+
56
+ it("lets a parent drive the value end to end", async () => {
57
+ // Arrange
58
+ const user = userEvent.setup();
59
+ function Harness() {
60
+ // Start true so the pre-click state can only be correct if the
61
+ // controlled prop is honoured (a broken impl would fall back to
62
+ // defaultChecked=false and render the opposite).
63
+ const [checked, setChecked] = useState(true);
64
+ return (
65
+ <Checkbox.Root
66
+ checked={checked}
67
+ onCheckedChange={setChecked}
68
+ aria-label="Accept terms"
69
+ />
70
+ );
71
+ }
72
+ render(<Harness />);
73
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
74
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
75
+
76
+ // Act & Assert
77
+ await user.click(checkbox);
78
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
79
+ await user.click(checkbox);
80
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
81
+ });
82
+ });
@@ -0,0 +1,15 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { Checkbox } from "../Checkbox";
4
+
5
+ describe("Checkbox disabled state", () => {
6
+ it('sets data-disabled="" on the root so CSS can target the disabled state', () => {
7
+ // Arrange & Act
8
+ render(<Checkbox.Root disabled aria-label="Accept terms" />);
9
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
10
+
11
+ // Assert
12
+ expect(checkbox).toHaveAttribute("data-disabled", "");
13
+ });
14
+
15
+ });
@@ -0,0 +1,82 @@
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 indeterminate state", () => {
7
+ it('exposes aria-checked="mixed" when defaultChecked is "indeterminate"', () => {
8
+ // Arrange & Act
9
+ render(
10
+ <Checkbox.Root
11
+ defaultChecked="indeterminate"
12
+ aria-label="Accept terms"
13
+ />,
14
+ );
15
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
16
+
17
+ // Assert
18
+ expect(checkbox).toHaveAttribute("aria-checked", "mixed");
19
+ });
20
+
21
+ it('sets data-state="indeterminate" on the root in indeterminate mode', () => {
22
+ // Arrange & Act
23
+ render(
24
+ <Checkbox.Root
25
+ defaultChecked="indeterminate"
26
+ aria-label="Accept terms"
27
+ />,
28
+ );
29
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
30
+
31
+ // Assert
32
+ expect(checkbox).toHaveAttribute("data-state", "indeterminate");
33
+ });
34
+
35
+ it("resolves to checked=true on the first click (WAI-ARIA tri-state convention)", async () => {
36
+ // Arrange
37
+ const user = userEvent.setup();
38
+ const onCheckedChange = vi.fn();
39
+ render(
40
+ <Checkbox.Root
41
+ defaultChecked="indeterminate"
42
+ onCheckedChange={onCheckedChange}
43
+ aria-label="Accept terms"
44
+ />,
45
+ );
46
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
47
+
48
+ // Act
49
+ await user.click(checkbox);
50
+
51
+ // Assert
52
+ expect(onCheckedChange).toHaveBeenCalledWith(true);
53
+ expect(checkbox).toHaveAttribute("aria-checked", "true");
54
+ expect(checkbox).toHaveAttribute("data-state", "checked");
55
+ });
56
+
57
+ it('honours controlled checked="indeterminate" across re-renders', () => {
58
+ // Arrange
59
+ const { rerender } = render(
60
+ <Checkbox.Root
61
+ checked={false}
62
+ onCheckedChange={() => {}}
63
+ aria-label="Accept terms"
64
+ />,
65
+ );
66
+ const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
67
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
68
+
69
+ // Act
70
+ rerender(
71
+ <Checkbox.Root
72
+ checked="indeterminate"
73
+ onCheckedChange={() => {}}
74
+ aria-label="Accept terms"
75
+ />,
76
+ );
77
+
78
+ // Assert
79
+ expect(checkbox).toHaveAttribute("aria-checked", "mixed");
80
+ expect(checkbox).toHaveAttribute("data-state", "indeterminate");
81
+ });
82
+ });