@primitiv-ui/react 0.1.28 → 0.1.29

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 (307) hide show
  1. package/README.md +4 -0
  2. package/package.json +6 -1
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
  4. package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
  5. package/src/AccessibleIcon/types.ts +15 -2
  6. package/src/Accordion/Accordion.tsx +25 -4
  7. package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
  8. package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
  9. package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
  10. package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
  11. package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
  12. package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
  13. package/src/Accordion/types.ts +72 -21
  14. package/src/Alert/Alert.tsx +36 -7
  15. package/src/Alert/__tests__/Alert.test.tsx +5 -0
  16. package/src/Alert/types.ts +18 -2
  17. package/src/Avatar/Avatar.tsx +99 -15
  18. package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
  19. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
  20. package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
  21. package/src/Avatar/hooks/useAvatarImage.ts +16 -0
  22. package/src/Avatar/types.ts +15 -3
  23. package/src/Breadcrumb/Breadcrumb.tsx +136 -18
  24. package/src/Breadcrumb/README.md +47 -0
  25. package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
  26. package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
  27. package/src/Breadcrumb/types.ts +73 -7
  28. package/src/Button/Button.tsx +2 -0
  29. package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
  30. package/src/Button/types.ts +21 -0
  31. package/src/Carousel/Carousel.tsx +263 -15
  32. package/src/Carousel/README.md +749 -72
  33. package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
  34. package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
  35. package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
  36. package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
  37. package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
  38. package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
  39. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +88 -0
  40. package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
  41. package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
  42. package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
  43. package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
  44. package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
  45. package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
  46. package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
  47. package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
  48. package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
  49. package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
  50. package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
  51. package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
  52. package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
  53. package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
  54. package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
  55. package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
  56. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
  57. package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
  58. package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
  59. package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
  60. package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
  61. package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
  62. package/src/Carousel/hooks/index.ts +1 -0
  63. package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
  64. package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
  65. package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
  66. package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
  67. package/src/Carousel/loopEngine.ts +54 -0
  68. package/src/Carousel/types.ts +457 -35
  69. package/src/Checkbox/Checkbox.tsx +9 -1
  70. package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
  71. package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
  72. package/src/Checkbox/types.ts +19 -0
  73. package/src/CheckboxCard/CheckboxCard.tsx +13 -4
  74. package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
  75. package/src/CheckboxCard/types.ts +65 -17
  76. package/src/Collapsible/Collapsible.tsx +49 -3
  77. package/src/Collapsible/README.md +40 -0
  78. package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
  79. package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
  80. package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
  81. package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
  82. package/src/Collapsible/types.ts +89 -22
  83. package/src/ContextMenu/ContextMenu.tsx +270 -4
  84. package/src/ContextMenu/types.ts +148 -6
  85. package/src/DirectionProvider/DirectionProvider.tsx +41 -9
  86. package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
  87. package/src/DirectionProvider/types.ts +28 -3
  88. package/src/Divider/Divider.tsx +24 -10
  89. package/src/Divider/__tests__/Divider.test.tsx +5 -0
  90. package/src/Divider/types.ts +10 -3
  91. package/src/Drawer/Drawer.tsx +315 -0
  92. package/src/Drawer/README.md +124 -0
  93. package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
  94. package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
  95. package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
  96. package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
  97. package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
  98. package/src/Drawer/index.ts +2 -0
  99. package/src/Drawer/types.ts +66 -0
  100. package/src/Dropdown/Dropdown.tsx +117 -0
  101. package/src/Dropdown/types.ts +241 -11
  102. package/src/EmptyState/EmptyState.tsx +74 -4
  103. package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
  104. package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
  105. package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
  106. package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
  107. package/src/EmptyState/types.ts +49 -6
  108. package/src/Field/Field.tsx +23 -0
  109. package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
  110. package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
  111. package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
  112. package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
  113. package/src/Field/types.ts +60 -12
  114. package/src/Fieldset/Fieldset.tsx +54 -18
  115. package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
  116. package/src/Fieldset/types.ts +20 -2
  117. package/src/Input/Input.tsx +14 -9
  118. package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
  119. package/src/Input/types.ts +19 -2
  120. package/src/InputGroup/InputGroup.tsx +11 -3
  121. package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
  122. package/src/InputGroup/types.ts +26 -5
  123. package/src/MillerColumns/MillerColumns.tsx +86 -19
  124. package/src/MillerColumns/types.ts +83 -27
  125. package/src/Modal/Modal.tsx +31 -4
  126. package/src/Modal/README.md +19 -10
  127. package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
  128. package/src/Modal/hooks/useModalContent.ts +32 -0
  129. package/src/Modal/types.ts +110 -29
  130. package/src/NavigationMenu/NavigationMenu.tsx +890 -0
  131. package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
  132. package/src/NavigationMenu/README.md +355 -0
  133. package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
  134. package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
  135. package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
  136. package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
  137. package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
  138. package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
  139. package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
  140. package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
  141. package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
  142. package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
  143. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
  144. package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
  145. package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
  146. package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
  147. package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
  148. package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
  149. package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
  150. package/src/NavigationMenu/constants.ts +5 -0
  151. package/src/NavigationMenu/hooks/index.ts +7 -0
  152. package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
  153. package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
  154. package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
  155. package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
  156. package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
  157. package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
  158. package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
  159. package/src/NavigationMenu/index.ts +3 -0
  160. package/src/NavigationMenu/types.ts +259 -0
  161. package/src/NavigationMenu/utils.ts +89 -0
  162. package/src/Popover/Popover.tsx +427 -0
  163. package/src/Popover/PopoverContext.ts +13 -0
  164. package/src/Popover/README.md +106 -0
  165. package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
  166. package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
  167. package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
  168. package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
  169. package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
  170. package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
  171. package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
  172. package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
  173. package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
  174. package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
  175. package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
  176. package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
  177. package/src/Popover/hooks/index.ts +3 -0
  178. package/src/Popover/hooks/usePopoverContent.ts +98 -0
  179. package/src/Popover/hooks/usePopoverRoot.ts +84 -0
  180. package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
  181. package/src/Popover/index.ts +2 -0
  182. package/src/Popover/types.ts +197 -0
  183. package/src/Portal/Portal.tsx +50 -10
  184. package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
  185. package/src/Portal/types.ts +5 -0
  186. package/src/Progress/Progress.tsx +33 -1
  187. package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
  188. package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
  189. package/src/Progress/types.ts +20 -4
  190. package/src/Radio/Radio.tsx +9 -1
  191. package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
  192. package/src/Radio/hooks/useRadioRoot.ts +3 -0
  193. package/src/Radio/types.ts +20 -0
  194. package/src/RadioCard/RadioCard.tsx +11 -0
  195. package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
  196. package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
  197. package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
  198. package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
  199. package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
  200. package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
  201. package/src/RadioCard/types.ts +95 -22
  202. package/src/RadioGroup/RadioGroup.tsx +11 -0
  203. package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
  204. package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
  205. package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
  206. package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
  207. package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
  208. package/src/RadioGroup/types.ts +73 -9
  209. package/src/SegmentedControl/README.md +154 -0
  210. package/src/SegmentedControl/SegmentedControl.tsx +309 -0
  211. package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
  212. package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
  213. package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
  214. package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
  215. package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
  216. package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
  217. package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
  218. package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
  219. package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
  220. package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
  221. package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
  222. package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
  223. package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
  224. package/src/SegmentedControl/hooks/index.ts +2 -0
  225. package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
  226. package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +86 -0
  227. package/src/SegmentedControl/index.ts +2 -0
  228. package/src/SegmentedControl/types.ts +138 -0
  229. package/src/Select/README.md +122 -157
  230. package/src/Select/Select.tsx +595 -68
  231. package/src/Select/SelectContext.ts +38 -0
  232. package/src/Select/SelectItemIndicatorContext.ts +29 -0
  233. package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
  234. package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
  235. package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
  236. package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
  237. package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
  238. package/src/Select/__tests__/Select.form.test.tsx +38 -0
  239. package/src/Select/__tests__/Select.group.test.tsx +6 -6
  240. package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
  241. package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
  242. package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
  243. package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
  244. package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
  245. package/src/Select/__tests__/Select.placeholder.test.tsx +4 -4
  246. package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
  247. package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
  248. package/src/Select/__tests__/Select.selection.test.tsx +104 -0
  249. package/src/Select/__tests__/Select.separator.test.tsx +71 -0
  250. package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
  251. package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
  252. package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
  253. package/src/Select/constants.ts +8 -0
  254. package/src/Select/hooks/index.ts +2 -0
  255. package/src/Select/hooks/useSelectContent.ts +149 -0
  256. package/src/Select/hooks/useSelectRoot.ts +149 -0
  257. package/src/Select/types.ts +199 -31
  258. package/src/SkipNav/SkipNav.tsx +69 -29
  259. package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
  260. package/src/SkipNav/types.ts +17 -9
  261. package/src/Slider/Slider.tsx +33 -1
  262. package/src/Slider/SliderContext.ts +28 -1
  263. package/src/Slider/types.ts +157 -9
  264. package/src/Slot/Slot.tsx +94 -19
  265. package/src/Slot/__tests__/Slot.test.tsx +110 -0
  266. package/src/Slot/composeEventHandlers.ts +26 -2
  267. package/src/Slot/types.ts +16 -0
  268. package/src/Status/Status.tsx +36 -15
  269. package/src/Status/__tests__/Status.test.tsx +5 -0
  270. package/src/Status/types.ts +12 -1
  271. package/src/Switch/Switch.tsx +15 -2
  272. package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
  273. package/src/Switch/hooks/useSwitchRoot.ts +3 -0
  274. package/src/Switch/types.ts +49 -22
  275. package/src/Table/Table.tsx +44 -10
  276. package/src/Table/types.ts +66 -17
  277. package/src/Tabs/Tabs.tsx +8 -0
  278. package/src/Tabs/types.ts +29 -4
  279. package/src/Textarea/Textarea.tsx +9 -1
  280. package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
  281. package/src/Textarea/types.ts +20 -3
  282. package/src/Toggle/Toggle.tsx +51 -13
  283. package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
  284. package/src/Toggle/types.ts +54 -4
  285. package/src/ToggleGroup/ToggleGroup.tsx +14 -1
  286. package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
  287. package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
  288. package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
  289. package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
  290. package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
  291. package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
  292. package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
  293. package/src/ToggleGroup/types.ts +55 -2
  294. package/src/Tooltip/Tooltip.tsx +98 -20
  295. package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
  296. package/src/Tooltip/hooks/useTooltipProvider.ts +13 -1
  297. package/src/Tooltip/types.ts +89 -8
  298. package/src/Tree/Tree.tsx +187 -33
  299. package/src/Tree/types.ts +54 -12
  300. package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
  301. package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
  302. package/src/VisuallyHidden/types.ts +11 -1
  303. package/src/index.ts +4 -0
  304. package/src/test/intersectionObserverPolyfill.ts +7 -0
  305. package/src/test/resizeObserverPolyfill.ts +80 -0
  306. package/src/utils/focusable.ts +15 -0
  307. package/src/utils/index.ts +1 -0
@@ -8,6 +8,12 @@ import { Direction } from "../DirectionProvider/index.ts";
8
8
  * the reading-direction control.
9
9
  */
10
10
  export type DropdownRootBaseProps = {
11
+ /**
12
+ * The menu's sub-components — typically a single {@link DropdownTriggerProps |
13
+ * `Dropdown.Trigger`} followed by a {@link DropdownContentProps |
14
+ * `Dropdown.Content`}. Root renders no DOM of its own; it only provides
15
+ * context to these descendants.
16
+ */
11
17
  children?: ReactNode;
12
18
  /**
13
19
  * Reading direction for the menu. Affects which arrow key opens / closes
@@ -24,8 +30,19 @@ export type DropdownRootBaseProps = {
24
30
  * and `open` is forbidden.
25
31
  */
26
32
  export type DropdownRootUncontrolledProps = DropdownRootBaseProps & {
33
+ /**
34
+ * Open state on first render. Omit to start closed. The component owns
35
+ * the state thereafter.
36
+ * @default false
37
+ */
27
38
  defaultOpen?: boolean;
39
+ /** Forbidden in uncontrolled mode — use `defaultOpen` instead. */
28
40
  open?: never;
41
+ /**
42
+ * Called whenever a user-driven transition opens or closes the menu
43
+ * (trigger click, Escape, outside click, selection). Optional in
44
+ * uncontrolled mode.
45
+ */
29
46
  onOpenChange?: (open: boolean) => void;
30
47
  };
31
48
 
@@ -35,8 +52,18 @@ export type DropdownRootUncontrolledProps = DropdownRootBaseProps & {
35
52
  * forbidden.
36
53
  */
37
54
  export type DropdownRootControlledProps = DropdownRootBaseProps & {
55
+ /** Forbidden in controlled mode — use `open` instead. */
38
56
  defaultOpen?: never;
57
+ /**
58
+ * The current open state. Must be kept in sync by the parent via
59
+ * `onOpenChange`; the component never mutates it internally.
60
+ */
39
61
  open: boolean;
62
+ /**
63
+ * Called whenever a user-driven transition would open or close the menu.
64
+ * The parent is responsible for reflecting the new value back into
65
+ * `open`. Required in controlled mode.
66
+ */
40
67
  onOpenChange: (open: boolean) => void;
41
68
  };
42
69
 
@@ -58,8 +85,17 @@ export type DropdownTriggerProps = Omit<
58
85
  ComponentProps<"button">,
59
86
  "aria-haspopup" | "aria-expanded" | "aria-controls"
60
87
  > & {
88
+ /** The trigger's visible label (or an element, with `asChild`). */
61
89
  children?: ReactNode;
90
+ /** Forwarded to the underlying `HTMLButtonElement`. */
62
91
  ref?: Ref<HTMLButtonElement>;
92
+ /**
93
+ * Render the composed child element instead of the default
94
+ * `<button type="button">`. The trigger's ARIA contract
95
+ * (`aria-haspopup`, `aria-expanded`, `aria-controls`) and its click
96
+ * handler are merged onto the child via the {@link Slot} pattern.
97
+ * @default false
98
+ */
63
99
  asChild?: boolean;
64
100
  };
65
101
 
@@ -70,10 +106,26 @@ export type DropdownTriggerProps = Omit<
70
106
  */
71
107
  export type DropdownContentProps = Omit<
72
108
  ComponentProps<"menu">,
73
- "role" | "popover" | "id"
109
+ "role" | "popover" | "id" | "ref"
74
110
  > & {
111
+ /**
112
+ * The menu items — any mix of {@link DropdownItemProps | `Dropdown.Item`},
113
+ * {@link DropdownCheckboxItemProps | `Dropdown.CheckboxItem`},
114
+ * {@link DropdownRadioGroupProps | `Dropdown.RadioGroup`},
115
+ * {@link DropdownGroupProps | `Dropdown.Group`},
116
+ * {@link DropdownSeparatorProps | `Dropdown.Separator`}, and
117
+ * {@link DropdownSubProps | `Dropdown.Sub`}.
118
+ */
75
119
  children?: ReactNode;
120
+ /** Forwarded to the underlying `HTMLMenuElement`. */
76
121
  ref?: Ref<HTMLMenuElement>;
122
+ /**
123
+ * Render the composed child element (with menu semantics) instead of the
124
+ * default `<menu role="menu" popover="auto">`. The managed `role`,
125
+ * `popover`, and `id` attributes and the keyboard handler are merged onto
126
+ * the child via the {@link Slot} pattern.
127
+ * @default false
128
+ */
77
129
  asChild?: boolean;
78
130
  };
79
131
 
@@ -86,14 +138,26 @@ export type DropdownItemProps = Omit<
86
138
  ComponentProps<"li">,
87
139
  "role" | "tabIndex" | "onSelect"
88
140
  > & {
141
+ /** The item's visible content (or an element, with `asChild`). */
89
142
  children?: ReactNode;
143
+ /** Forwarded to the underlying `HTMLLIElement`. */
90
144
  ref?: Ref<HTMLLIElement>;
145
+ /**
146
+ * Render the composed child element (with menuitem semantics) instead of
147
+ * the default `<li role="menuitem">`.
148
+ * @default false
149
+ */
91
150
  asChild?: boolean;
151
+ /**
152
+ * Mark the item non-interactive. Sets `aria-disabled="true"` and skips
153
+ * the item during arrow navigation, typeahead, and activation.
154
+ * @default false
155
+ */
92
156
  disabled?: boolean;
93
157
  /**
94
158
  * Fires when the item is activated (click, Enter, or Space). Called
95
- * with an event whose `preventDefault()` skips the auto-close that
96
- * Dropdown performs after selection.
159
+ * with a cancellable event whose `preventDefault()` skips the auto-close
160
+ * that Dropdown performs after selection.
97
161
  */
98
162
  onSelect?: (event: Event) => void;
99
163
  };
@@ -103,8 +167,15 @@ export type DropdownItemProps = Omit<
103
167
  * groups of items. Extends the native `<li>` props and adds `asChild`.
104
168
  */
105
169
  export type DropdownSeparatorProps = Omit<ComponentProps<"li">, "role"> & {
170
+ /** Optional content; separators are usually empty. */
106
171
  children?: ReactNode;
172
+ /** Forwarded to the underlying `HTMLLIElement`. */
107
173
  ref?: Ref<HTMLLIElement>;
174
+ /**
175
+ * Render the composed child element (with separator semantics) instead of
176
+ * the default `<li role="separator">`.
177
+ * @default false
178
+ */
108
179
  asChild?: boolean;
109
180
  };
110
181
 
@@ -114,14 +185,21 @@ export type DropdownSeparatorProps = Omit<ComponentProps<"li">, "role"> & {
114
185
  * `forceMount` to keep it mounted while unchecked for enter/exit animation.
115
186
  */
116
187
  export type DropdownItemIndicatorProps = ComponentProps<"span"> & {
188
+ /** The mark to render — commonly an SVG check or bullet icon. */
117
189
  children?: ReactNode;
190
+ /** Forwarded to the underlying `HTMLSpanElement`. */
118
191
  ref?: Ref<HTMLSpanElement>;
192
+ /**
193
+ * Render the composed child element instead of the default `<span>`.
194
+ * @default false
195
+ */
119
196
  asChild?: boolean;
120
197
  /**
121
198
  * Render the indicator even when its parent item is unchecked. The
122
199
  * `data-state` attribute still reflects the live state (`"checked"` /
123
200
  * `"unchecked"` / `"indeterminate"`), so consumers can animate the
124
201
  * indicator in and out instead of mounting / unmounting it.
202
+ * @default false
125
203
  */
126
204
  forceMount?: boolean;
127
205
  };
@@ -131,8 +209,18 @@ export type DropdownItemIndicatorProps = ComponentProps<"span"> & {
131
209
  * Extends the native `<li>` props and adds `asChild`.
132
210
  */
133
211
  export type DropdownGroupProps = Omit<ComponentProps<"li">, "role"> & {
212
+ /**
213
+ * The grouped items, optionally led by a {@link DropdownLabelProps |
214
+ * `Dropdown.Label`} that names the group.
215
+ */
134
216
  children?: ReactNode;
217
+ /** Forwarded to the underlying `HTMLLIElement`. */
135
218
  ref?: Ref<HTMLLIElement>;
219
+ /**
220
+ * Render the composed child element (with `role="group"`) instead of the
221
+ * default `<li role="group">` wrapping `<ul role="none">`.
222
+ * @default false
223
+ */
136
224
  asChild?: boolean;
137
225
  };
138
226
 
@@ -141,8 +229,14 @@ export type DropdownGroupProps = Omit<ComponentProps<"li">, "role"> & {
141
229
  * {@link Dropdown.Group}. Extends the native `<li>` props and adds `asChild`.
142
230
  */
143
231
  export type DropdownLabelProps = ComponentProps<"li"> & {
232
+ /** The label text. */
144
233
  children?: ReactNode;
234
+ /** Forwarded to the underlying `HTMLLIElement`. */
145
235
  ref?: Ref<HTMLLIElement>;
236
+ /**
237
+ * Render the composed child element instead of the default `<li>`.
238
+ * @default false
239
+ */
146
240
  asChild?: boolean;
147
241
  };
148
242
 
@@ -154,13 +248,29 @@ export type DropdownCheckboxItemBaseProps = Omit<
154
248
  ComponentProps<"li">,
155
249
  "role" | "tabIndex" | "aria-checked" | "defaultChecked" | "onSelect"
156
250
  > & {
251
+ /**
252
+ * The item's content — typically a {@link DropdownItemIndicatorProps |
253
+ * `Dropdown.ItemIndicator`} plus a text label.
254
+ */
157
255
  children?: ReactNode;
256
+ /** Forwarded to the underlying `HTMLLIElement`. */
158
257
  ref?: Ref<HTMLLIElement>;
258
+ /**
259
+ * Render the composed child element (with `role="menuitemcheckbox"`)
260
+ * instead of the default `<li role="menuitemcheckbox">`.
261
+ * @default false
262
+ */
159
263
  asChild?: boolean;
264
+ /**
265
+ * Mark the item non-interactive. Sets `aria-disabled="true"`; activation
266
+ * is a no-op.
267
+ * @default false
268
+ */
160
269
  disabled?: boolean;
161
270
  /**
162
- * Fires when activation completes and the close auto-fires. Call
163
- * `event.preventDefault()` to keep the menu open after toggling.
271
+ * Fires after activation toggles the checked state, with a cancellable
272
+ * event. Call `event.preventDefault()` to keep the menu open after
273
+ * toggling — useful for flipping several checkboxes in a row.
164
274
  */
165
275
  onSelect?: (event: Event) => void;
166
276
  };
@@ -172,8 +282,18 @@ export type DropdownCheckboxItemBaseProps = Omit<
172
282
  */
173
283
  export type DropdownCheckboxItemUncontrolledProps =
174
284
  DropdownCheckboxItemBaseProps & {
285
+ /**
286
+ * Checked state on first render — `true`, `false`, or `"indeterminate"`
287
+ * (rendered as `aria-checked="mixed"`). Omit to start unchecked.
288
+ * @default false
289
+ */
175
290
  defaultChecked?: CheckedState;
291
+ /** Forbidden in uncontrolled mode — use `defaultChecked` instead. */
176
292
  checked?: never;
293
+ /**
294
+ * Called with the new boolean checked state whenever the user toggles
295
+ * the item. Optional in uncontrolled mode.
296
+ */
177
297
  onCheckedChange?: (checked: boolean) => void;
178
298
  };
179
299
 
@@ -184,8 +304,18 @@ export type DropdownCheckboxItemUncontrolledProps =
184
304
  */
185
305
  export type DropdownCheckboxItemControlledProps =
186
306
  DropdownCheckboxItemBaseProps & {
307
+ /** Forbidden in controlled mode — use `checked` instead. */
187
308
  defaultChecked?: never;
309
+ /**
310
+ * The current checked state — `true`, `false`, or `"indeterminate"`.
311
+ * Must be kept in sync by the parent via `onCheckedChange`. An
312
+ * indeterminate value resolves to `true` on the next activation.
313
+ */
188
314
  checked: CheckedState;
315
+ /**
316
+ * Called with the new boolean checked state whenever the user toggles
317
+ * the item. Required in controlled mode.
318
+ */
189
319
  onCheckedChange: (checked: boolean) => void;
190
320
  };
191
321
 
@@ -201,11 +331,25 @@ export type DropdownCheckboxItemProps =
201
331
 
202
332
  /**
203
333
  * Shared base for both {@link DropdownRadioGroupProps} variants — the native
204
- * `<li>` props plus `asChild`.
334
+ * `<li>` props (with the native ordinal `value` omitted so the controlled
335
+ * variant can narrow it to a `string`) plus `asChild`.
205
336
  */
206
- export type DropdownRadioGroupBaseProps = Omit<ComponentProps<"li">, "role"> & {
337
+ export type DropdownRadioGroupBaseProps = Omit<
338
+ ComponentProps<"li">,
339
+ "role" | "value" | "defaultValue"
340
+ > & {
341
+ /**
342
+ * The radio choices — a set of {@link DropdownRadioItemProps |
343
+ * `Dropdown.RadioItem`} elements.
344
+ */
207
345
  children?: ReactNode;
346
+ /** Forwarded to the underlying `HTMLLIElement`. */
208
347
  ref?: Ref<HTMLLIElement>;
348
+ /**
349
+ * Render the composed child element (with `role="group"`) instead of the
350
+ * default `<li role="group">` wrapping `<ul role="none">`.
351
+ * @default false
352
+ */
209
353
  asChild?: boolean;
210
354
  };
211
355
 
@@ -216,8 +360,17 @@ export type DropdownRadioGroupBaseProps = Omit<ComponentProps<"li">, "role"> & {
216
360
  */
217
361
  export type DropdownRadioGroupUncontrolledProps =
218
362
  DropdownRadioGroupBaseProps & {
363
+ /**
364
+ * The item `value` selected on first render. Omit for no initial
365
+ * selection.
366
+ */
219
367
  defaultValue?: string;
368
+ /** Forbidden in uncontrolled mode — use `defaultValue` instead. */
220
369
  value?: never;
370
+ /**
371
+ * Called with the newly selected item `value` whenever the user picks a
372
+ * different radio item. Optional in uncontrolled mode.
373
+ */
221
374
  onValueChange?: (value: string) => void;
222
375
  };
223
376
 
@@ -227,8 +380,18 @@ export type DropdownRadioGroupUncontrolledProps =
227
380
  * forbidden.
228
381
  */
229
382
  export type DropdownRadioGroupControlledProps = DropdownRadioGroupBaseProps & {
383
+ /** Forbidden in controlled mode — use `value` instead. */
230
384
  defaultValue?: never;
385
+ /**
386
+ * The `value` of the currently selected {@link DropdownRadioItemProps |
387
+ * `Dropdown.RadioItem`}. Must be kept in sync by the parent via
388
+ * `onValueChange`.
389
+ */
231
390
  value: string;
391
+ /**
392
+ * Called with the newly selected item `value` whenever the user picks a
393
+ * different radio item. Required in controlled mode.
394
+ */
232
395
  onValueChange: (value: string) => void;
233
396
  };
234
397
 
@@ -244,18 +407,43 @@ export type DropdownRadioGroupProps =
244
407
 
245
408
  /**
246
409
  * Props for {@link Dropdown.RadioItem} — one option within a
247
- * {@link Dropdown.RadioGroup}. Extends the native `<li>` props and adds the
248
- * required `value`, plus `disabled`, `asChild`, and `onSelect`.
410
+ * {@link Dropdown.RadioGroup}. Extends the native `<li>` props (with the
411
+ * native ordinal `value` omitted so it can be narrowed to a required `string`)
412
+ * and adds `value`, plus `disabled`, `asChild`, and `onSelect`.
249
413
  */
250
414
  export type DropdownRadioItemProps = Omit<
251
415
  ComponentProps<"li">,
252
- "role" | "tabIndex" | "aria-checked" | "onSelect"
416
+ "role" | "tabIndex" | "aria-checked" | "onSelect" | "value"
253
417
  > & {
418
+ /**
419
+ * The item's content — typically a {@link DropdownItemIndicatorProps |
420
+ * `Dropdown.ItemIndicator`} plus a text label.
421
+ */
254
422
  children?: ReactNode;
423
+ /** Forwarded to the underlying `HTMLLIElement`. */
255
424
  ref?: Ref<HTMLLIElement>;
425
+ /**
426
+ * Render the composed child element (with `role="menuitemradio"`) instead
427
+ * of the default `<li role="menuitemradio">`.
428
+ * @default false
429
+ */
256
430
  asChild?: boolean;
431
+ /**
432
+ * Mark the item non-interactive. Sets `aria-disabled="true"`; activation
433
+ * is a no-op.
434
+ * @default false
435
+ */
257
436
  disabled?: boolean;
437
+ /**
438
+ * This item's identifier within the enclosing
439
+ * {@link DropdownRadioGroupProps | `Dropdown.RadioGroup`}. When it matches
440
+ * the group's active value the item is checked. Required.
441
+ */
258
442
  value: string;
443
+ /**
444
+ * Fires after activation selects this item, with a cancellable event.
445
+ * Call `event.preventDefault()` to keep the menu open after selecting.
446
+ */
259
447
  onSelect?: (event: Event) => void;
260
448
  };
261
449
 
@@ -264,6 +452,11 @@ export type DropdownRadioItemProps = Omit<
264
452
  * `children`.
265
453
  */
266
454
  export type DropdownSubBaseProps = {
455
+ /**
456
+ * The submenu boundary's content — a {@link DropdownSubTriggerProps |
457
+ * `Dropdown.SubTrigger`} and its sibling {@link DropdownSubContentProps |
458
+ * `Dropdown.SubContent`}.
459
+ */
267
460
  children?: ReactNode;
268
461
  };
269
462
 
@@ -273,8 +466,17 @@ export type DropdownSubBaseProps = {
273
466
  * optional and the controlled `open` prop is forbidden.
274
467
  */
275
468
  export type DropdownSubUncontrolledProps = DropdownSubBaseProps & {
469
+ /**
470
+ * Submenu open state on first render. Omit to start closed.
471
+ * @default false
472
+ */
276
473
  defaultOpen?: boolean;
474
+ /** Forbidden in uncontrolled mode — use `defaultOpen` instead. */
277
475
  open?: never;
476
+ /**
477
+ * Called whenever the submenu opens or closes. Optional in uncontrolled
478
+ * mode.
479
+ */
278
480
  onOpenChange?: (open: boolean) => void;
279
481
  };
280
482
 
@@ -284,8 +486,17 @@ export type DropdownSubUncontrolledProps = DropdownSubBaseProps & {
284
486
  * forbidden.
285
487
  */
286
488
  export type DropdownSubControlledProps = DropdownSubBaseProps & {
489
+ /** Forbidden in controlled mode — use `open` instead. */
287
490
  defaultOpen?: never;
491
+ /**
492
+ * The current submenu open state. Must be kept in sync by the parent via
493
+ * `onOpenChange`.
494
+ */
288
495
  open: boolean;
496
+ /**
497
+ * Called whenever the submenu would open or close. The parent reflects
498
+ * the new value back into `open`. Required in controlled mode.
499
+ */
289
500
  onOpenChange: (open: boolean) => void;
290
501
  };
291
502
 
@@ -307,9 +518,21 @@ export type DropdownSubTriggerProps = Omit<
307
518
  ComponentProps<"li">,
308
519
  "role" | "tabIndex" | "aria-haspopup" | "aria-expanded" | "aria-controls"
309
520
  > & {
521
+ /** The trigger's visible label (or an element, with `asChild`). */
310
522
  children?: ReactNode;
523
+ /** Forwarded to the underlying `HTMLLIElement`. */
311
524
  ref?: Ref<HTMLLIElement>;
525
+ /**
526
+ * Render the composed child element (with menuitem + submenu-trigger
527
+ * semantics) instead of the default `<li role="menuitem">`.
528
+ * @default false
529
+ */
312
530
  asChild?: boolean;
531
+ /**
532
+ * Mark the trigger non-interactive. Sets `aria-disabled="true"` and
533
+ * ignores both click and the open arrow key.
534
+ * @default false
535
+ */
313
536
  disabled?: boolean;
314
537
  };
315
538
 
@@ -320,9 +543,16 @@ export type DropdownSubTriggerProps = Omit<
320
543
  */
321
544
  export type DropdownSubContentProps = Omit<
322
545
  ComponentProps<"menu">,
323
- "role" | "popover" | "id"
546
+ "role" | "popover" | "id" | "ref"
324
547
  > & {
548
+ /** The submenu's items. */
325
549
  children?: ReactNode;
550
+ /** Forwarded to the underlying `HTMLMenuElement`. */
326
551
  ref?: Ref<HTMLMenuElement>;
552
+ /**
553
+ * Render the composed child element (with menu semantics) instead of the
554
+ * default `<menu role="menu" popover="auto">`.
555
+ * @default false
556
+ */
327
557
  asChild?: boolean;
328
558
  };
@@ -22,11 +22,16 @@ import {
22
22
  * Opt out of the live region by passing `role={undefined}` for an empty
23
23
  * state that is part of the initial, static page.
24
24
  *
25
+ * **Styling hooks.** `EmptyState.Root` emits no `data-*` attributes — it is a
26
+ * static layout component. Target it via `className` or element selectors.
27
+ *
25
28
  * **`asChild` composition.** Renders the consumer's element instead of a
26
29
  * `<div>`, merging `role="status"` and all other props in via the
27
30
  * {@link Slot} utility.
28
31
  *
29
- * @example
32
+ * @extends HTMLDivElement
33
+ *
34
+ * @example Conditional empty state (live region)
30
35
  * ```tsx
31
36
  * {results.length === 0 && (
32
37
  * <EmptyState.Root>
@@ -35,6 +40,22 @@ import {
35
40
  * </EmptyState.Root>
36
41
  * )}
37
42
  * ```
43
+ *
44
+ * @example Static empty state (opt out of live region)
45
+ * ```tsx
46
+ * <EmptyState.Root role={undefined}>
47
+ * <EmptyState.Title>No projects yet</EmptyState.Title>
48
+ * </EmptyState.Root>
49
+ * ```
50
+ *
51
+ * @example asChild — render a `<section>` instead of a `<div>`
52
+ * ```tsx
53
+ * <EmptyState.Root asChild>
54
+ * <section aria-label="Empty inbox">
55
+ * <EmptyState.Title>Your inbox is empty</EmptyState.Title>
56
+ * </section>
57
+ * </EmptyState.Root>
58
+ * ```
38
59
  */
39
60
  export function EmptyStateRoot({
40
61
  asChild = false,
@@ -50,7 +71,11 @@ export function EmptyStateRoot({
50
71
  return <div {...rootProps}>{children}</div>;
51
72
  }
52
73
 
53
- /** @internal */
74
+ // Runtime-dead: the compound alias below (same object via Object.assign)
75
+ // overwrites this to "EmptyState" at load, so the value is never observable.
76
+ // The assignment stays because it declares `displayName` on
77
+ // `typeof EmptyStateRoot`, which EmptyStateCompound extends.
78
+ // Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
54
79
  EmptyStateRoot.displayName = "EmptyStateRoot";
55
80
 
56
81
  /**
@@ -64,16 +89,28 @@ EmptyStateRoot.displayName = "EmptyStateRoot";
64
89
  * genuinely informative, pass `aria-hidden={false}` and give it an accessible
65
90
  * name yourself.
66
91
  *
92
+ * **Styling hooks.** `EmptyState.Media` emits no `data-*` attributes. Target it
93
+ * via `className` or the `[aria-hidden]` attribute selector.
94
+ *
67
95
  * **`asChild` composition.** Renders the consumer's element instead of a
68
96
  * `<div>`, merging `aria-hidden="true"` and all other props in via the
69
97
  * {@link Slot} utility.
70
98
  *
71
- * @example
99
+ * @extends HTMLDivElement
100
+ *
101
+ * @example Decorative icon (default)
72
102
  * ```tsx
73
103
  * <EmptyState.Media>
74
104
  * <InboxIcon />
75
105
  * </EmptyState.Media>
76
106
  * ```
107
+ *
108
+ * @example Informative artwork — opt back in to accessibility tree
109
+ * ```tsx
110
+ * <EmptyState.Media aria-hidden={false}>
111
+ * <img src="/chart.svg" alt="Sales trending to zero" />
112
+ * </EmptyState.Media>
113
+ * ```
77
114
  */
78
115
  export function EmptyStateMedia({
79
116
  asChild = false,
@@ -101,9 +138,19 @@ EmptyStateMedia.displayName = "EmptyStateMedia";
101
138
  * stands in for a titled section, promote the title to a real heading with
102
139
  * `asChild` so it joins the page's heading hierarchy.
103
140
  *
141
+ * **Styling hooks.** `EmptyState.Title` emits no `data-*` attributes. Target
142
+ * it via `className` or the `p` element selector.
143
+ *
104
144
  * **`asChild` composition.** Renders the consumer's element instead of a
105
145
  * `<p>`, merging all props in via the {@link Slot} utility.
106
146
  *
147
+ * @extends HTMLParagraphElement
148
+ *
149
+ * @example Default paragraph headline
150
+ * ```tsx
151
+ * <EmptyState.Title>No results found</EmptyState.Title>
152
+ * ```
153
+ *
107
154
  * @example Promote to a heading
108
155
  * ```tsx
109
156
  * <EmptyState.Title asChild>
@@ -134,9 +181,14 @@ EmptyStateTitle.displayName = "EmptyStateTitle";
134
181
  * Keep it to guidance the user can act on; the actionable controls themselves
135
182
  * belong in {@link EmptyStateActions | `Actions`}.
136
183
  *
184
+ * **Styling hooks.** `EmptyState.Description` emits no `data-*` attributes.
185
+ * Target it via `className` or the `p` element selector.
186
+ *
137
187
  * **`asChild` composition.** Renders the consumer's element instead of a
138
188
  * `<p>`, merging all props in via the {@link Slot} utility.
139
189
  *
190
+ * @extends HTMLParagraphElement
191
+ *
140
192
  * @example
141
193
  * ```tsx
142
194
  * <EmptyState.Description>Try adjusting your filters.</EmptyState.Description>
@@ -167,9 +219,14 @@ EmptyStateDescription.displayName = "EmptyStateDescription";
167
219
  * {@link EmptyStateRoot | `Root`}'s live region, the control labels are
168
220
  * included when the empty state is announced.
169
221
  *
222
+ * **Styling hooks.** `EmptyState.Actions` emits no `data-*` attributes. Target
223
+ * it via `className` or the `div` element selector.
224
+ *
170
225
  * **`asChild` composition.** Renders the consumer's element instead of a
171
226
  * `<div>`, merging all props in via the {@link Slot} utility.
172
227
  *
228
+ * @extends HTMLDivElement
229
+ *
173
230
  * @example
174
231
  * ```tsx
175
232
  * <EmptyState.Actions>
@@ -227,7 +284,7 @@ export type EmptyStateCompound = typeof EmptyStateRoot & {
227
284
  * All sub-components are stateless and optional — compose only the parts a
228
285
  * given empty state needs.
229
286
  *
230
- * @example
287
+ * @example Full composition
231
288
  * ```tsx
232
289
  * import { EmptyState } from "@primitiv-ui/react";
233
290
  *
@@ -244,6 +301,19 @@ export type EmptyStateCompound = typeof EmptyStateRoot & {
244
301
  * </EmptyState.Root>
245
302
  * )}
246
303
  * ```
304
+ *
305
+ * @example Minimal — title only
306
+ * ```tsx
307
+ * <EmptyState.Root>
308
+ * <EmptyState.Title>No messages</EmptyState.Title>
309
+ * </EmptyState.Root>
310
+ * ```
311
+ *
312
+ * @see {@link EmptyStateRoot} for the live-region opt-out and `asChild` details.
313
+ * @see {@link EmptyStateMedia} for the `aria-hidden` default and opt-back-in pattern.
314
+ * @see {@link EmptyStateTitle} for the `<p>` default and heading promotion via `asChild`.
315
+ * @see {@link EmptyStateDescription} for secondary copy placement guidance.
316
+ * @see {@link EmptyStateActions} for grouping recovery controls.
247
317
  */
248
318
  const EmptyState: EmptyStateCompound = Object.assign(EmptyStateRoot, {
249
319
  Root: EmptyStateRoot,
@@ -4,7 +4,7 @@ import { render, screen } from "@testing-library/react";
4
4
  describe("EmptyState.Actions component", () => {
5
5
  it("should render a div containing its children", () => {
6
6
  // Arrange
7
- render(
7
+ const { container } = render(
8
8
  <EmptyState.Actions>
9
9
  <button>Clear filters</button>
10
10
  </EmptyState.Actions>,
@@ -13,11 +13,14 @@ describe("EmptyState.Actions component", () => {
13
13
  // Assert
14
14
  const button = screen.getByRole("button", { name: "Clear filters" });
15
15
  expect(button.parentElement?.tagName).toBe("DIV");
16
+ // The wrapping <div> is Actions' own render root (default is not asChild),
17
+ // not the test container.
18
+ expect((container.firstChild as HTMLElement).tagName).toBe("DIV");
16
19
  });
17
20
 
18
21
  it("should render the consumer element with asChild", () => {
19
22
  // Arrange
20
- render(
23
+ const { container } = render(
21
24
  <EmptyState.Actions asChild>
22
25
  <nav>
23
26
  <button>Clear filters</button>
@@ -28,5 +31,7 @@ describe("EmptyState.Actions component", () => {
28
31
  // Assert
29
32
  const button = screen.getByRole("button", { name: "Clear filters" });
30
33
  expect(button.parentElement?.tagName).toBe("NAV");
34
+ // Slot renders the <nav> in place — no default <div> wrapper around it.
35
+ expect(container.querySelector("div")).toBeNull();
31
36
  });
32
37
  });
@@ -17,7 +17,7 @@ describe("EmptyState.Description component", () => {
17
17
 
18
18
  it("should render the consumer element with asChild", () => {
19
19
  // Arrange
20
- render(
20
+ const { container } = render(
21
21
  <EmptyState.Description asChild>
22
22
  <span>Try adjusting your filters.</span>
23
23
  </EmptyState.Description>,
@@ -26,5 +26,7 @@ describe("EmptyState.Description component", () => {
26
26
  // Assert
27
27
  const description = screen.getByText("Try adjusting your filters.");
28
28
  expect(description.tagName).toBe("SPAN");
29
+ // Slot renders the child in place — no default <p> wrapper around it.
30
+ expect(container.querySelector("p")).toBeNull();
29
31
  });
30
32
  });
@@ -25,4 +25,14 @@ describe("EmptyState.Root component", () => {
25
25
  expect(root.tagName).toBe("SECTION");
26
26
  expect(root).toHaveTextContent("No projects yet");
27
27
  });
28
+
29
+ it("sets a displayName on the compound and each sub-component", () => {
30
+ // Assert — empty displayNames would render each as anonymous in DevTools.
31
+ // Root aliases the compound (Object.assign), so its name is "EmptyState".
32
+ expect(EmptyState.displayName).toBe("EmptyState");
33
+ expect(EmptyState.Media.displayName).toBe("EmptyStateMedia");
34
+ expect(EmptyState.Title.displayName).toBe("EmptyStateTitle");
35
+ expect(EmptyState.Description.displayName).toBe("EmptyStateDescription");
36
+ expect(EmptyState.Actions.displayName).toBe("EmptyStateActions");
37
+ });
28
38
  });