@primitiv-ui/react 0.1.27 → 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
@@ -3,6 +3,7 @@ import type { ReactElement } from "react";
3
3
  import { Slot } from "../Slot/index.ts";
4
4
 
5
5
  import {
6
+ BreadcrumbEllipsisProps,
6
7
  BreadcrumbItemProps,
7
8
  BreadcrumbLinkProps,
8
9
  BreadcrumbListProps,
@@ -15,9 +16,18 @@ import {
15
16
  * The root of a Breadcrumb — a `<nav>` landmark that wraps the breadcrumb
16
17
  * trail.
17
18
  *
19
+ * Stateless: `Breadcrumb.Root` holds no context and derives no state — it
20
+ * is purely a semantic wrapper. All native `<nav>` attributes pass straight
21
+ * through to the DOM, so there is no controlled/uncontrolled shape to
22
+ * reason about.
23
+ *
18
24
  * The `<nav>` defaults to `aria-label="Breadcrumb"` so assistive technology
19
- * announces it as the breadcrumb navigation landmark. Override `aria-label`
20
- * if your product uses different terminology.
25
+ * announces it as the breadcrumb navigation landmark, distinguishing it
26
+ * from any other `<nav>` on the page (e.g. primary site navigation).
27
+ * Override `aria-label` if your product uses different terminology (e.g.
28
+ * `"You are here"`).
29
+ *
30
+ * @extends HTMLElement
21
31
  *
22
32
  * @example
23
33
  * ```tsx
@@ -25,6 +35,13 @@ import {
25
35
  * <Breadcrumb.List>…</Breadcrumb.List>
26
36
  * </Breadcrumb.Root>
27
37
  * ```
38
+ *
39
+ * @example Custom accessible name
40
+ * ```tsx
41
+ * <Breadcrumb.Root aria-label="You are here">
42
+ * <Breadcrumb.List>…</Breadcrumb.List>
43
+ * </Breadcrumb.Root>
44
+ * ```
28
45
  */
29
46
  export function BreadcrumbRoot({
30
47
  children,
@@ -37,16 +54,25 @@ export function BreadcrumbRoot({
37
54
  );
38
55
  }
39
56
 
40
- /** @internal */
57
+ // Runtime-dead: the compound alias below (same object via Object.assign)
58
+ // overwrites this to "Breadcrumb" at load, so the value is never observable.
59
+ // The assignment stays because it declares `displayName` on
60
+ // `typeof BreadcrumbRoot`, which TBreadcrumbCompound extends.
61
+ // Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
41
62
  BreadcrumbRoot.displayName = "BreadcrumbRoot";
42
63
 
43
64
  /**
44
65
  * The ordered list of breadcrumb entries — renders an `<ol>`.
45
66
  *
46
67
  * An ordered list is the correct semantic: breadcrumb entries have a
47
- * meaningful sequence from the site root to the current page. Contains
68
+ * meaningful sequence from the site root to the current page (unlike a
69
+ * `<ul>`, whose items are unordered). Renders as a direct child of
70
+ * {@link BreadcrumbRoot | `Breadcrumb.Root`}'s `<nav>`, and contains
48
71
  * {@link BreadcrumbItem | `Breadcrumb.Item`}s interleaved with
49
- * {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s.
72
+ * {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s. Stateless — no
73
+ * context is read or provided.
74
+ *
75
+ * @extends HTMLOListElement
50
76
  *
51
77
  * @example
52
78
  * ```tsx
@@ -71,7 +97,13 @@ BreadcrumbList.displayName = "BreadcrumbList";
71
97
  * A single entry in the breadcrumb trail — renders an `<li>`.
72
98
  *
73
99
  * Wraps either a {@link BreadcrumbLink | `Breadcrumb.Link`} (an ancestor
74
- * page) or a {@link BreadcrumbPage | `Breadcrumb.Page`} (the current page).
100
+ * page) or a {@link BreadcrumbPage | `Breadcrumb.Page`} (the current page),
101
+ * as a child of {@link BreadcrumbList | `Breadcrumb.List`}'s `<ol>`.
102
+ * {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s are siblings of
103
+ * `Breadcrumb.Item`, not nested inside it. Purely structural — no ARIA
104
+ * attributes or state of its own.
105
+ *
106
+ * @extends HTMLLIElement
75
107
  *
76
108
  * @example
77
109
  * ```tsx
@@ -93,12 +125,18 @@ BreadcrumbItem.displayName = "BreadcrumbItem";
93
125
  /**
94
126
  * A link to an ancestor page — renders an `<a>`.
95
127
  *
96
- * Use for every entry except the current page. Pass `href` (and any other
97
- * anchor attributes) directly.
128
+ * Use for every entry except the current page (the last entry should be a
129
+ * {@link BreadcrumbPage | `Breadcrumb.Page`} instead). Pass `href` (and any
130
+ * other anchor attributes) directly; nothing is inferred or defaulted.
131
+ *
132
+ * **`asChild` composition.** Pass `asChild` to render a consumer-supplied
133
+ * element — typically a routing library's `<Link>` — instead of the native
134
+ * `<a>`, with all of `Breadcrumb.Link`'s props (`href`, event handlers,
135
+ * `className`, `ref`, …) merged onto it via the {@link Slot} pattern. The
136
+ * child **must** be a single React element that accepts a `ref`; the
137
+ * native `<a>` is dropped entirely rather than wrapping the child.
98
138
  *
99
- * **`asChild` prop.** Pass `asChild` to render a consumer-supplied element —
100
- * typically a routing library's `<Link>` — with the breadcrumb link's props
101
- * merged in. The native `<a>` is dropped.
139
+ * @extends HTMLAnchorElement
102
140
  *
103
141
  * @example
104
142
  * ```tsx
@@ -129,9 +167,13 @@ BreadcrumbLink.displayName = "BreadcrumbLink";
129
167
  /**
130
168
  * The current page in the trail — renders a `<span aria-current="page">`.
131
169
  *
132
- * The last entry of a breadcrumb is the page the user is on, so it is not a
133
- * link. `aria-current="page"` tells assistive technology this entry
134
- * represents the current location.
170
+ * The last entry of a breadcrumb is the page the user is already on, so it
171
+ * is deliberately not a {@link BreadcrumbLink | `Breadcrumb.Link`} — a link
172
+ * to the current page is not a meaningful navigation target.
173
+ * `aria-current="page"` is fixed by the component (not overridable) and
174
+ * tells assistive technology this entry represents the current location.
175
+ *
176
+ * @extends HTMLSpanElement
135
177
  *
136
178
  * @example
137
179
  * ```tsx
@@ -159,12 +201,18 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
159
201
  * `<li role="presentation" aria-hidden="true">`.
160
202
  *
161
203
  * The separator sits inside the `<ol>` as a sibling of the
162
- * {@link BreadcrumbItem | `Breadcrumb.Item`}s, but `role="presentation"`
163
- * removes it from the list semantics and `aria-hidden` hides it from the
164
- * accessibility tree — it is purely decorative.
204
+ * {@link BreadcrumbItem | `Breadcrumb.Item`}s (an `<ol>`'s valid children
205
+ * are all `<li>`s, so the separator is one too), but `role="presentation"`
206
+ * removes it from the list semantics — screen readers don't announce it as
207
+ * a list item — and `aria-hidden="true"` hides it from the accessibility
208
+ * tree entirely. Both attributes are fixed by the component and not
209
+ * overridable; it is purely decorative.
165
210
  *
166
211
  * Defaults to a `"/"` glyph; pass `children` to use a custom separator (an
167
- * icon, a chevron, etc.).
212
+ * icon, a chevron, `"›"`, etc.) — whatever is passed replaces the default
213
+ * entirely rather than being appended to it.
214
+ *
215
+ * @extends HTMLLIElement
168
216
  *
169
217
  * @example Default separator
170
218
  * ```tsx
@@ -190,6 +238,69 @@ export function BreadcrumbSeparator({
190
238
  /** @internal */
191
239
  BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
192
240
 
241
+ /**
242
+ * The decorative "more" glyph for an overflow trigger — renders a
243
+ * `<span role="presentation" aria-hidden="true">`.
244
+ *
245
+ * Purely a visual glyph, not itself the interactive element. Breadcrumb
246
+ * owns no truncation or menu-open state of its own — composing an overflow
247
+ * trigger means wrapping this glyph in a real interactive element (a
248
+ * `<button>`, or any {@link Slot}-compatible component) and reaching for
249
+ * {@link https://primitiv-ui.dev/docs/components/dropdown | `Dropdown`} for
250
+ * the menu itself. `Breadcrumb.Ellipsis` sits inside that button as its
251
+ * decorative content; the button's *accessible name* must come from
252
+ * elsewhere (visible text, or a
253
+ * {@link https://primitiv-ui.dev/docs/components/visually-hidden | `VisuallyHidden`}
254
+ * label) — `aria-hidden="true"` excludes this glyph from accessible-name
255
+ * computation entirely, exactly like {@link BreadcrumbSeparator}.
256
+ *
257
+ * Defaults to a `"…"` glyph; pass `children` to use a custom one (an icon,
258
+ * `"···"`, etc.) — whatever is passed replaces the default entirely rather
259
+ * than being appended to it.
260
+ *
261
+ * @extends HTMLSpanElement
262
+ *
263
+ * @example Default glyph
264
+ * ```tsx
265
+ * <Breadcrumb.Ellipsis />
266
+ * ```
267
+ *
268
+ * @example Composed as an overflow trigger with Dropdown
269
+ * ```tsx
270
+ * <Breadcrumb.Item>
271
+ * <Dropdown.Root>
272
+ * <Dropdown.Trigger asChild>
273
+ * <button type="button">
274
+ * <Breadcrumb.Ellipsis />
275
+ * <VisuallyHidden>Show hidden pages</VisuallyHidden>
276
+ * </button>
277
+ * </Dropdown.Trigger>
278
+ * <Dropdown.Content>
279
+ * <Dropdown.Item onSelect={() => navigate("/products")}>
280
+ * Products
281
+ * </Dropdown.Item>
282
+ * </Dropdown.Content>
283
+ * </Dropdown.Root>
284
+ * </Breadcrumb.Item>
285
+ * ```
286
+ */
287
+ export function BreadcrumbEllipsis({
288
+ children = "…",
289
+ asChild = false,
290
+ ...rest
291
+ }: BreadcrumbEllipsisProps): ReactElement {
292
+ const ellipsisProps = { ...rest, role: "presentation" as const, "aria-hidden": true as const };
293
+
294
+ if (asChild) {
295
+ return <Slot {...ellipsisProps}>{children}</Slot>;
296
+ }
297
+
298
+ return <span {...ellipsisProps}>{children}</span>;
299
+ }
300
+
301
+ /** @internal */
302
+ BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";
303
+
193
304
  /** Type of the {@link Breadcrumb} compound: the root callable plus its attached sub-components. */
194
305
  export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
195
306
  Root: typeof BreadcrumbRoot;
@@ -198,6 +309,7 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
198
309
  Link: typeof BreadcrumbLink;
199
310
  Page: typeof BreadcrumbPage;
200
311
  Separator: typeof BreadcrumbSeparator;
312
+ Ellipsis: typeof BreadcrumbEllipsis;
201
313
  };
202
314
 
203
315
  /**
@@ -217,6 +329,7 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
217
329
  * - {@link BreadcrumbLink | `Breadcrumb.Link`} — `<a>`, an ancestor-page link.
218
330
  * - {@link BreadcrumbPage | `Breadcrumb.Page`} — `<span aria-current="page">`, the current page.
219
331
  * - {@link BreadcrumbSeparator | `Breadcrumb.Separator`} — decorative `<li>` divider.
332
+ * - {@link BreadcrumbEllipsis | `Breadcrumb.Ellipsis`} — decorative `<span>` overflow-trigger glyph.
220
333
  *
221
334
  * @example Minimal usage
222
335
  * ```tsx
@@ -239,7 +352,11 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
239
352
  * </Breadcrumb.Root>
240
353
  * ```
241
354
  *
355
+ * @see {@link BreadcrumbRoot} for the `aria-label` landmark default.
356
+ * @see {@link BreadcrumbLink} for the `asChild` router-link pattern.
357
+ * @see {@link BreadcrumbPage} for marking the current, non-linked entry.
242
358
  * @see {@link BreadcrumbSeparator} for customising the divider glyph.
359
+ * @see {@link BreadcrumbEllipsis} for composing an overflow trigger with `Dropdown`.
243
360
  */
244
361
  const BreadcrumbCompound: TBreadcrumbCompound = Object.assign(BreadcrumbRoot, {
245
362
  Root: BreadcrumbRoot,
@@ -248,6 +365,7 @@ const BreadcrumbCompound: TBreadcrumbCompound = Object.assign(BreadcrumbRoot, {
248
365
  Link: BreadcrumbLink,
249
366
  Page: BreadcrumbPage,
250
367
  Separator: BreadcrumbSeparator,
368
+ Ellipsis: BreadcrumbEllipsis,
251
369
  });
252
370
 
253
371
  /** @internal */
@@ -35,6 +35,7 @@ import { Breadcrumb } from "@primitiv-ui/react";
35
35
  | `Breadcrumb.Link` | `<a>` | — | yes |
36
36
  | `Breadcrumb.Page` | `<span>` | `aria-current="page"` | — |
37
37
  | `Breadcrumb.Separator` | `<li>` | `role="presentation"`, `aria-hidden="true"` | — |
38
+ | `Breadcrumb.Ellipsis` | `<span>` | `role="presentation"`, `aria-hidden="true"` | yes |
38
39
 
39
40
  Every sub-component passes all of its element's native attributes straight
40
41
  through to the DOM.
@@ -79,6 +80,52 @@ It defaults to a `"/"` glyph. Pass `children` to use a custom separator:
79
80
  <Breadcrumb.Separator><ChevronRight /></Breadcrumb.Separator>
80
81
  ```
81
82
 
83
+ ## Overflow trigger
84
+
85
+ `Breadcrumb` owns no truncation or menu-open state of its own — deciding
86
+ which crumbs collapse is a styled-surface concern (see the registry `card`
87
+ composite for the same "no data model in the primitive" philosophy). What
88
+ the headless layer provides is `Breadcrumb.Ellipsis`: a purely decorative
89
+ `"…"` glyph for the trigger's visual content.
90
+
91
+ Compose it with [`Dropdown`](../Dropdown/README.md) to build the actual
92
+ trigger — a real interactive element (a `<button>`, or anything
93
+ `asChild`-compatible) wrapping the glyph, with the accessible name coming
94
+ from visible text or a `VisuallyHidden` label rather than the glyph itself
95
+ (`Breadcrumb.Ellipsis` is `aria-hidden`, exactly like `Breadcrumb.Separator`,
96
+ so it never contributes to accessible-name computation):
97
+
98
+ ```tsx
99
+ import { Breadcrumb, Dropdown, VisuallyHidden } from "@primitiv-ui/react";
100
+
101
+ <Breadcrumb.Item>
102
+ <Dropdown.Root>
103
+ <Dropdown.Trigger asChild>
104
+ <button type="button">
105
+ <Breadcrumb.Ellipsis />
106
+ <VisuallyHidden>Show hidden pages</VisuallyHidden>
107
+ </button>
108
+ </Dropdown.Trigger>
109
+ <Dropdown.Content>
110
+ <Dropdown.Item onSelect={() => navigate("/products")}>
111
+ Products
112
+ </Dropdown.Item>
113
+ <Dropdown.Item onSelect={() => navigate("/products/electronics")}>
114
+ Electronics
115
+ </Dropdown.Item>
116
+ </Dropdown.Content>
117
+ </Dropdown.Root>
118
+ </Breadcrumb.Item>;
119
+ ```
120
+
121
+ `Breadcrumb.Ellipsis` defaults to a `"…"` glyph; pass `children` to use a
122
+ custom one:
123
+
124
+ ```tsx
125
+ <Breadcrumb.Ellipsis>···</Breadcrumb.Ellipsis>
126
+ <Breadcrumb.Ellipsis><MoreIcon /></Breadcrumb.Ellipsis>
127
+ ```
128
+
82
129
  ## `asChild` composition
83
130
 
84
131
  `Breadcrumb.Link` accepts `asChild` so it can render a routing library's link
@@ -129,4 +129,15 @@ describe("Breadcrumb basic rendering", () => {
129
129
  "page",
130
130
  );
131
131
  });
132
+
133
+ it("sets a displayName on the compound and each sub-component", () => {
134
+ // Assert — an empty displayName would render each as anonymous in DevTools.
135
+ // Root aliases the compound (Object.assign), so its name is "Breadcrumb".
136
+ expect(Breadcrumb.displayName).toBe("Breadcrumb");
137
+ expect(Breadcrumb.List.displayName).toBe("BreadcrumbList");
138
+ expect(Breadcrumb.Item.displayName).toBe("BreadcrumbItem");
139
+ expect(Breadcrumb.Link.displayName).toBe("BreadcrumbLink");
140
+ expect(Breadcrumb.Page.displayName).toBe("BreadcrumbPage");
141
+ expect(Breadcrumb.Separator.displayName).toBe("BreadcrumbSeparator");
142
+ });
132
143
  });
@@ -0,0 +1,110 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { Breadcrumb } from "../Breadcrumb";
5
+ import { Dropdown } from "../../Dropdown/Dropdown";
6
+
7
+ describe("Breadcrumb ellipsis", () => {
8
+ it("renders the Ellipsis as a decorative <span> with a default glyph", () => {
9
+ // Arrange & Act
10
+ render(<Breadcrumb.Ellipsis data-testid="ellipsis" />);
11
+ const ellipsis = screen.getByTestId("ellipsis");
12
+
13
+ // Assert — presentation + aria-hidden keep it out of the accessibility
14
+ // tree entirely, matching Breadcrumb.Separator's own convention: it is
15
+ // the interactive element composed AROUND it that must carry the
16
+ // accessible name, not this glyph.
17
+ expect(ellipsis.tagName).toBe("SPAN");
18
+ expect(ellipsis).toHaveAttribute("role", "presentation");
19
+ expect(ellipsis).toHaveAttribute("aria-hidden", "true");
20
+ expect(ellipsis).toHaveTextContent("…");
21
+ });
22
+
23
+ it("lets the consumer override the Ellipsis glyph", () => {
24
+ // Arrange & Act
25
+ render(
26
+ <Breadcrumb.Ellipsis data-testid="ellipsis">···</Breadcrumb.Ellipsis>,
27
+ );
28
+
29
+ // Assert
30
+ expect(screen.getByTestId("ellipsis")).toHaveTextContent("···");
31
+ });
32
+
33
+ it("renders Breadcrumb.Ellipsis as the consumer element via asChild, with no wrapping <span>", () => {
34
+ // Arrange & Act
35
+ render(
36
+ <Breadcrumb.Ellipsis asChild>
37
+ <i data-testid="custom-ellipsis" />
38
+ </Breadcrumb.Ellipsis>,
39
+ );
40
+ const ellipsis = screen.getByTestId("custom-ellipsis");
41
+
42
+ // Assert — the native <span> is dropped entirely
43
+ expect(ellipsis.tagName).toBe("I");
44
+ expect(ellipsis.closest("span")).toBeNull();
45
+ });
46
+
47
+ it("merges Breadcrumb.Ellipsis props onto the asChild element", () => {
48
+ // Arrange & Act
49
+ render(
50
+ <Breadcrumb.Ellipsis asChild className="crumb-ellipsis">
51
+ <i data-testid="custom-ellipsis" />
52
+ </Breadcrumb.Ellipsis>,
53
+ );
54
+
55
+ // Assert — role/aria-hidden still land on the consumer's element
56
+ const ellipsis = screen.getByTestId("custom-ellipsis");
57
+ expect(ellipsis).toHaveClass("crumb-ellipsis");
58
+ expect(ellipsis).toHaveAttribute("role", "presentation");
59
+ expect(ellipsis).toHaveAttribute("aria-hidden", "true");
60
+ });
61
+
62
+ it("sets a displayName on Ellipsis", () => {
63
+ // Assert — an empty displayName would render it as anonymous in DevTools.
64
+ expect(Breadcrumb.Ellipsis.displayName).toBe("BreadcrumbEllipsis");
65
+ });
66
+
67
+ it("composes cleanly as Dropdown.Trigger's asChild child — the trigger owns the interactive element and accessible name, the Ellipsis is purely decorative inside it", async () => {
68
+ // Arrange — the documented composition pattern: the actual <button> is
69
+ // a sibling of the aria-hidden Ellipsis glyph, not the Ellipsis itself,
70
+ // so the button's accessible name comes from the visible text rather
71
+ // than being swallowed by the glyph's own aria-hidden.
72
+ const user = userEvent.setup();
73
+ render(
74
+ <Breadcrumb.Item>
75
+ <Dropdown.Root>
76
+ <Dropdown.Trigger asChild>
77
+ <button type="button" data-testid="overflow-trigger">
78
+ <Breadcrumb.Ellipsis />
79
+ <span>More</span>
80
+ </button>
81
+ </Dropdown.Trigger>
82
+ <Dropdown.Content>
83
+ <Dropdown.Item>Products</Dropdown.Item>
84
+ </Dropdown.Content>
85
+ </Dropdown.Root>
86
+ </Breadcrumb.Item>,
87
+ );
88
+ const trigger = screen.getByTestId("overflow-trigger");
89
+
90
+ // Assert — ARIA wiring from Dropdown lands on the real button, and its
91
+ // accessible name is the visible "More" text, not swallowed by the
92
+ // aria-hidden ellipsis glyph.
93
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
94
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
95
+ expect(
96
+ screen.getByRole("button", { name: "More" }),
97
+ ).toBe(trigger);
98
+
99
+ // Act
100
+ await user.click(trigger);
101
+
102
+ // Assert — the menu opens with the hidden crumb as a real menu item.
103
+ // `hidden: true` because jsdom does not compute Popover-API visibility —
104
+ // the same convention every Dropdown test in this repo already uses.
105
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
106
+ expect(
107
+ screen.getByRole("menuitem", { name: "Products", hidden: true }),
108
+ ).toBeInTheDocument();
109
+ });
110
+ });
@@ -1,22 +1,88 @@
1
1
  import { ComponentProps } from "react";
2
2
 
3
- /** Props for {@link Breadcrumb.Root} — all `<nav>` attributes. */
3
+ /**
4
+ * Props for {@link BreadcrumbRoot | `Breadcrumb.Root`} — all native `<nav>`
5
+ * attributes. `aria-label` defaults to `"Breadcrumb"` when omitted; pass it
6
+ * explicitly to override the accessible name announced to assistive
7
+ * technology.
8
+ *
9
+ * @extends HTMLElement
10
+ */
4
11
  export type BreadcrumbRootProps = ComponentProps<"nav">;
5
12
 
6
- /** Props for {@link Breadcrumb.List} — all `<ol>` attributes. */
13
+ /**
14
+ * Props for {@link BreadcrumbList | `Breadcrumb.List`} — all native `<ol>`
15
+ * attributes. No props are added; content is
16
+ * {@link BreadcrumbItem | `Breadcrumb.Item`}s interleaved with
17
+ * {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s.
18
+ *
19
+ * @extends HTMLOListElement
20
+ */
7
21
  export type BreadcrumbListProps = ComponentProps<"ol">;
8
22
 
9
- /** Props for {@link Breadcrumb.Item} — all `<li>` attributes. */
23
+ /**
24
+ * Props for {@link BreadcrumbItem | `Breadcrumb.Item`} — all native `<li>`
25
+ * attributes. No props are added; wraps a single
26
+ * {@link BreadcrumbLink | `Breadcrumb.Link`} or
27
+ * {@link BreadcrumbPage | `Breadcrumb.Page`}.
28
+ *
29
+ * @extends HTMLLIElement
30
+ */
10
31
  export type BreadcrumbItemProps = ComponentProps<"li">;
11
32
 
12
- /** Props for {@link Breadcrumb.Link} — all `<a>` attributes plus `asChild`. */
33
+ /**
34
+ * Props for {@link BreadcrumbLink | `Breadcrumb.Link`} — all native `<a>`
35
+ * attributes plus the `asChild` escape hatch.
36
+ *
37
+ * @extends HTMLAnchorElement
38
+ */
13
39
  export type BreadcrumbLinkProps = ComponentProps<"a"> & {
14
- /** Render the consumer's own element instead of the native `<a>`. */
40
+ /**
41
+ * Renders the child element instead of a native `<a>`, merging all of
42
+ * `Breadcrumb.Link`'s props — `href`, event handlers, `className`,
43
+ * `ref` — onto it via {@link Slot}. Use for routing-library link
44
+ * components (e.g. React Router's `<Link>`) that need breadcrumb
45
+ * semantics. See the `asChild` example on {@link BreadcrumbLink}.
46
+ * @default false
47
+ */
15
48
  asChild?: boolean;
16
49
  };
17
50
 
18
- /** Props for {@link Breadcrumb.Page} — all `<span>` attributes. */
51
+ /**
52
+ * Props for {@link BreadcrumbPage | `Breadcrumb.Page`} — all native
53
+ * `<span>` attributes. `aria-current="page"` is fixed by the component and
54
+ * cannot be overridden through these props.
55
+ *
56
+ * @extends HTMLSpanElement
57
+ */
19
58
  export type BreadcrumbPageProps = ComponentProps<"span">;
20
59
 
21
- /** Props for {@link Breadcrumb.Separator} — all `<li>` attributes. */
60
+ /**
61
+ * Props for {@link BreadcrumbSeparator | `Breadcrumb.Separator`} — all
62
+ * native `<li>` attributes. `role="presentation"` and `aria-hidden="true"`
63
+ * are fixed by the component; `children` defaults to a `"/"` glyph when
64
+ * omitted (see {@link BreadcrumbSeparator}'s custom-separator example).
65
+ *
66
+ * @extends HTMLLIElement
67
+ */
22
68
  export type BreadcrumbSeparatorProps = ComponentProps<"li">;
69
+
70
+ /**
71
+ * Props for {@link BreadcrumbEllipsis | `Breadcrumb.Ellipsis`} — all native
72
+ * `<span>` attributes plus the `asChild` escape hatch. `role="presentation"`
73
+ * and `aria-hidden="true"` are fixed by the component; `children` defaults
74
+ * to a `"…"` glyph when omitted (see {@link BreadcrumbEllipsis}'s
75
+ * custom-glyph example).
76
+ *
77
+ * @extends HTMLSpanElement
78
+ */
79
+ export type BreadcrumbEllipsisProps = ComponentProps<"span"> & {
80
+ /**
81
+ * Renders the child element instead of a native `<span>`, merging all of
82
+ * `Breadcrumb.Ellipsis`'s props — `role`, `aria-hidden`, `className`,
83
+ * `ref` — onto it via {@link Slot}. See the `asChild` example on
84
+ * {@link BreadcrumbEllipsis}.
85
+ * @default false
86
+ */
87
+ asChild?: boolean;
88
+ };
@@ -46,6 +46,8 @@ import { ButtonProps } from "./types";
46
46
  * </Button>
47
47
  * ```
48
48
  *
49
+ * @extends HTMLButtonElement
50
+ *
49
51
  * @example Text button
50
52
  * ```tsx
51
53
  * <Button onClick={handleSave}>Save</Button>
@@ -123,4 +123,10 @@ describe("Button basic rendering", () => {
123
123
  // Assert
124
124
  expect(onClick).toHaveBeenCalledTimes(1);
125
125
  });
126
+
127
+ it('sets displayName to "Button" for React DevTools and stack traces', () => {
128
+ // Assert — the component's identity is observable in DevTools and error
129
+ // output; an empty displayName would render it as an anonymous component.
130
+ expect(Button.displayName).toBe("Button");
131
+ });
126
132
  });
@@ -3,10 +3,31 @@ import { ComponentProps, ReactNode, Ref } from "react";
3
3
  /**
4
4
  * Props for {@link Button} — all native `<button>` attributes (with `type`
5
5
  * defaulting to `"button"`) plus the `asChild` escape hatch and a typed `ref`.
6
+ *
7
+ * @extends HTMLButtonElement
6
8
  */
7
9
  export type ButtonProps = Omit<ComponentProps<"button">, "type"> & {
10
+ /**
11
+ * The button's native `type` attribute, restricted to the three valid
12
+ * values. Defaults to `"button"` (not the DOM's own default of
13
+ * `"submit"`), so a `Button` placed inside a `<form>` never triggers an
14
+ * accidental submit unless set explicitly.
15
+ * @default "button"
16
+ */
8
17
  type?: "button" | "submit" | "reset";
18
+ /**
19
+ * Renders the child element instead of a native `<button>`, merging all
20
+ * props — aria-*, data-*, event handlers, ref — onto it via
21
+ * {@link Slot}. `type` is not forwarded in this mode; the child owns its
22
+ * own type semantics. See the `asChild` example on {@link Button}.
23
+ * @default false
24
+ */
9
25
  asChild?: boolean;
26
+ /**
27
+ * Forwarded to the underlying `HTMLButtonElement`, or — under
28
+ * `asChild` — merged onto the rendered child via {@link Slot}.
29
+ */
10
30
  ref?: Ref<HTMLButtonElement>;
31
+ /** Button content. Under `asChild`, becomes the single child element `Slot` merges props onto. */
11
32
  children?: ReactNode;
12
33
  };