@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
@@ -0,0 +1,151 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl disabled — per item", () => {
7
+ it("forwards the native disabled attribute and sets data-disabled on the segment", () => {
8
+ // Arrange & Act
9
+ render(
10
+ <SegmentedControl.Root aria-label="Mode">
11
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
12
+ <SegmentedControl.Item value="b" disabled>
13
+ B
14
+ </SegmentedControl.Item>
15
+ </SegmentedControl.Root>,
16
+ );
17
+ const b = screen.getByRole("radio", { name: "B" });
18
+
19
+ // Assert
20
+ expect(b).toBeDisabled();
21
+ expect(b).toHaveAttribute("data-disabled", "");
22
+ expect(screen.getByRole("radio", { name: "A" })).not.toHaveAttribute(
23
+ "data-disabled",
24
+ );
25
+ });
26
+
27
+ it("skips a disabled segment when arrowing forwards", async () => {
28
+ // Arrange
29
+ const user = userEvent.setup();
30
+ render(
31
+ <SegmentedControl.Root aria-label="Mode" defaultValue="a">
32
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
33
+ <SegmentedControl.Item value="b" disabled>
34
+ B
35
+ </SegmentedControl.Item>
36
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
37
+ </SegmentedControl.Root>,
38
+ );
39
+ const a = screen.getByRole("radio", { name: "A" });
40
+ const c = screen.getByRole("radio", { name: "C" });
41
+ a.focus();
42
+
43
+ // Act
44
+ await user.keyboard("{ArrowRight}");
45
+
46
+ // Assert
47
+ expect(c).toHaveFocus();
48
+ expect(c).toHaveAttribute("aria-checked", "true");
49
+ });
50
+
51
+ it("skips a disabled segment when computing the home-base tab stop", () => {
52
+ // Arrange & Act
53
+ render(
54
+ <SegmentedControl.Root aria-label="Mode">
55
+ <SegmentedControl.Item value="a" disabled>
56
+ A
57
+ </SegmentedControl.Item>
58
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
59
+ </SegmentedControl.Root>,
60
+ );
61
+
62
+ // Assert: first enabled segment is the tab stop, not the disabled one
63
+ expect(screen.getByRole("radio", { name: "A" })).toHaveAttribute(
64
+ "tabindex",
65
+ "-1",
66
+ );
67
+ expect(screen.getByRole("radio", { name: "B" })).toHaveAttribute(
68
+ "tabindex",
69
+ "0",
70
+ );
71
+ });
72
+ });
73
+
74
+ describe("SegmentedControl disabled — whole control", () => {
75
+ it("sets data-disabled on the root and disables every segment", () => {
76
+ // Arrange & Act
77
+ render(
78
+ <SegmentedControl.Root aria-label="Mode" disabled defaultValue="a">
79
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
80
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
81
+ </SegmentedControl.Root>,
82
+ );
83
+
84
+ // Assert
85
+ expect(screen.getByRole("radiogroup")).toHaveAttribute("data-disabled", "");
86
+ const a = screen.getByRole("radio", { name: "A" });
87
+ const b = screen.getByRole("radio", { name: "B" });
88
+ expect(a).toBeDisabled();
89
+ expect(a).toHaveAttribute("data-disabled", "");
90
+ expect(b).toBeDisabled();
91
+ expect(b).toHaveAttribute("data-disabled", "");
92
+ });
93
+
94
+ it("still reflects the selected segment while the control is disabled", () => {
95
+ // Arrange & Act
96
+ render(
97
+ <SegmentedControl.Root aria-label="Mode" disabled defaultValue="a">
98
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
99
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
100
+ </SegmentedControl.Root>,
101
+ );
102
+
103
+ // Assert
104
+ expect(screen.getByRole("radio", { name: "A" })).toHaveAttribute(
105
+ "aria-checked",
106
+ "true",
107
+ );
108
+ });
109
+
110
+ it("does not fire onValueChange when a disabled control's segment is clicked", async () => {
111
+ // Arrange
112
+ const user = userEvent.setup();
113
+ const onValueChange = vi.fn();
114
+ render(
115
+ <SegmentedControl.Root
116
+ aria-label="Mode"
117
+ disabled
118
+ onValueChange={onValueChange}
119
+ >
120
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
121
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
122
+ </SegmentedControl.Root>,
123
+ );
124
+
125
+ // Act
126
+ await user.click(screen.getByRole("radio", { name: "B" }));
127
+
128
+ // Assert
129
+ expect(onValueChange).not.toHaveBeenCalled();
130
+ });
131
+
132
+ it("takes no segment into the tab sequence when disabled and nothing is selected", () => {
133
+ // Arrange & Act
134
+ render(
135
+ <SegmentedControl.Root aria-label="Mode" disabled>
136
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
137
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
138
+ </SegmentedControl.Root>,
139
+ );
140
+
141
+ // Assert
142
+ expect(screen.getByRole("radio", { name: "A" })).toHaveAttribute(
143
+ "tabindex",
144
+ "-1",
145
+ );
146
+ expect(screen.getByRole("radio", { name: "B" })).toHaveAttribute(
147
+ "tabindex",
148
+ "-1",
149
+ );
150
+ });
151
+ });
@@ -0,0 +1,17 @@
1
+ import { render } from "@testing-library/react";
2
+
3
+ import { SegmentedControl } from "../SegmentedControl";
4
+
5
+ describe("SegmentedControl error handling", () => {
6
+ it("throws a helpful error when SegmentedControl.Item is rendered outside Root", () => {
7
+ // Arrange
8
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
9
+
10
+ // Assert
11
+ expect(() =>
12
+ render(<SegmentedControl.Item value="a">A</SegmentedControl.Item>),
13
+ ).toThrow(/SegmentedControl sub-components must be rendered inside/);
14
+
15
+ error.mockRestore();
16
+ });
17
+ });
@@ -0,0 +1,117 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl keyboard interaction", () => {
7
+ it("selects and focuses the next segment on ArrowRight (horizontal default)", async () => {
8
+ // Arrange
9
+ const user = userEvent.setup();
10
+ render(
11
+ <SegmentedControl.Root aria-label="Mode" defaultValue="a">
12
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
13
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
14
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
15
+ </SegmentedControl.Root>,
16
+ );
17
+ const a = screen.getByRole("radio", { name: "A" });
18
+ const b = screen.getByRole("radio", { name: "B" });
19
+ a.focus();
20
+
21
+ // Act
22
+ await user.keyboard("{ArrowRight}");
23
+
24
+ // Assert
25
+ expect(b).toHaveFocus();
26
+ expect(b).toHaveAttribute("aria-checked", "true");
27
+ expect(a).toHaveAttribute("aria-checked", "false");
28
+ });
29
+
30
+ it("selects and focuses the previous segment on ArrowLeft", async () => {
31
+ // Arrange
32
+ const user = userEvent.setup();
33
+ render(
34
+ <SegmentedControl.Root aria-label="Mode" defaultValue="b">
35
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
36
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
37
+ </SegmentedControl.Root>,
38
+ );
39
+ const a = screen.getByRole("radio", { name: "A" });
40
+ const b = screen.getByRole("radio", { name: "B" });
41
+ b.focus();
42
+
43
+ // Act
44
+ await user.keyboard("{ArrowLeft}");
45
+
46
+ // Assert
47
+ expect(a).toHaveFocus();
48
+ expect(a).toHaveAttribute("aria-checked", "true");
49
+ });
50
+
51
+ it("wraps from last to first on ArrowRight", async () => {
52
+ // Arrange
53
+ const user = userEvent.setup();
54
+ render(
55
+ <SegmentedControl.Root aria-label="Mode" defaultValue="b">
56
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
57
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
58
+ </SegmentedControl.Root>,
59
+ );
60
+ const a = screen.getByRole("radio", { name: "A" });
61
+ const b = screen.getByRole("radio", { name: "B" });
62
+ b.focus();
63
+
64
+ // Act
65
+ await user.keyboard("{ArrowRight}");
66
+
67
+ // Assert
68
+ expect(a).toHaveFocus();
69
+ expect(a).toHaveAttribute("aria-checked", "true");
70
+ });
71
+
72
+ it("wraps from first to last on ArrowLeft", async () => {
73
+ // Arrange
74
+ const user = userEvent.setup();
75
+ render(
76
+ <SegmentedControl.Root aria-label="Mode" defaultValue="a">
77
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
78
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
79
+ </SegmentedControl.Root>,
80
+ );
81
+ const a = screen.getByRole("radio", { name: "A" });
82
+ const b = screen.getByRole("radio", { name: "B" });
83
+ a.focus();
84
+
85
+ // Act
86
+ await user.keyboard("{ArrowLeft}");
87
+
88
+ // Assert
89
+ expect(b).toHaveFocus();
90
+ expect(b).toHaveAttribute("aria-checked", "true");
91
+ });
92
+
93
+ it("selects and focuses with vertical arrows when orientation is vertical", async () => {
94
+ // Arrange
95
+ const user = userEvent.setup();
96
+ render(
97
+ <SegmentedControl.Root
98
+ aria-label="Mode"
99
+ orientation="vertical"
100
+ defaultValue="a"
101
+ >
102
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
103
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
104
+ </SegmentedControl.Root>,
105
+ );
106
+ const a = screen.getByRole("radio", { name: "A" });
107
+ const b = screen.getByRole("radio", { name: "B" });
108
+ a.focus();
109
+
110
+ // Act
111
+ await user.keyboard("{ArrowDown}");
112
+
113
+ // Assert
114
+ expect(b).toHaveFocus();
115
+ expect(b).toHaveAttribute("aria-checked", "true");
116
+ });
117
+ });
@@ -0,0 +1,54 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl orientation", () => {
7
+ it("ignores vertical arrows when orientation is horizontal (default)", async () => {
8
+ // Arrange
9
+ const user = userEvent.setup();
10
+ render(
11
+ <SegmentedControl.Root aria-label="Mode" defaultValue="a">
12
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
13
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
14
+ </SegmentedControl.Root>,
15
+ );
16
+ const a = screen.getByRole("radio", { name: "A" });
17
+ const b = screen.getByRole("radio", { name: "B" });
18
+ a.focus();
19
+
20
+ // Act
21
+ await user.keyboard("{ArrowDown}");
22
+
23
+ // Assert — vertical arrows are inert on a horizontal control
24
+ expect(a).toHaveFocus();
25
+ expect(a).toHaveAttribute("aria-checked", "true");
26
+ expect(b).toHaveAttribute("aria-checked", "false");
27
+ });
28
+
29
+ it("ignores horizontal arrows when orientation is vertical", async () => {
30
+ // Arrange
31
+ const user = userEvent.setup();
32
+ render(
33
+ <SegmentedControl.Root
34
+ aria-label="Mode"
35
+ orientation="vertical"
36
+ defaultValue="a"
37
+ >
38
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
39
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
40
+ </SegmentedControl.Root>,
41
+ );
42
+ const a = screen.getByRole("radio", { name: "A" });
43
+ const b = screen.getByRole("radio", { name: "B" });
44
+ a.focus();
45
+
46
+ // Act
47
+ await user.keyboard("{ArrowRight}");
48
+
49
+ // Assert — horizontal arrows are inert on a vertical control
50
+ expect(a).toHaveFocus();
51
+ expect(a).toHaveAttribute("aria-checked", "true");
52
+ expect(b).toHaveAttribute("aria-checked", "false");
53
+ });
54
+ });
@@ -0,0 +1,56 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { DirectionProvider } from "../../DirectionProvider/index.ts";
5
+ import { SegmentedControl } from "../SegmentedControl";
6
+
7
+ describe("SegmentedControl reading direction", () => {
8
+ it("inverts horizontal arrows when dir is rtl", async () => {
9
+ // Arrange
10
+ const user = userEvent.setup();
11
+ render(
12
+ <SegmentedControl.Root aria-label="Mode" dir="rtl" defaultValue="a">
13
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
14
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
15
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
16
+ </SegmentedControl.Root>,
17
+ );
18
+ const b = screen.getByRole("radio", { name: "B" });
19
+ screen.getByRole("radio", { name: "A" }).focus();
20
+
21
+ // Assert — dir reaches the DOM
22
+ expect(screen.getByRole("radiogroup")).toHaveAttribute("dir", "rtl");
23
+
24
+ // Act — in RTL, Arrow Left moves forward
25
+ await user.keyboard("{ArrowLeft}");
26
+
27
+ // Assert
28
+ expect(b).toHaveFocus();
29
+ expect(b).toHaveAttribute("aria-checked", "true");
30
+ });
31
+
32
+ it("inherits reading direction from a DirectionProvider", async () => {
33
+ // Arrange
34
+ const user = userEvent.setup();
35
+ render(
36
+ <DirectionProvider dir="rtl">
37
+ <SegmentedControl.Root aria-label="Mode" defaultValue="a">
38
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
39
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
40
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
41
+ </SegmentedControl.Root>
42
+ </DirectionProvider>,
43
+ );
44
+ const b = screen.getByRole("radio", { name: "B" });
45
+ screen.getByRole("radio", { name: "A" }).focus();
46
+
47
+ // Assert — provider direction reaches the DOM
48
+ expect(screen.getByRole("radiogroup")).toHaveAttribute("dir", "rtl");
49
+
50
+ // Act — in RTL, Arrow Left moves forward
51
+ await user.keyboard("{ArrowLeft}");
52
+
53
+ // Assert
54
+ expect(b).toHaveFocus();
55
+ });
56
+ });
@@ -0,0 +1,45 @@
1
+ import { createRef } from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl.Item ref forwarding", () => {
7
+ it("forwards the underlying button element to a consumer function ref", () => {
8
+ // Arrange
9
+ const received: (HTMLButtonElement | null)[] = [];
10
+ const functionRef = (node: HTMLButtonElement | null) => {
11
+ received.push(node);
12
+ };
13
+
14
+ // Act
15
+ render(
16
+ <SegmentedControl.Root aria-label="Mode">
17
+ <SegmentedControl.Item value="a" ref={functionRef}>
18
+ A
19
+ </SegmentedControl.Item>
20
+ </SegmentedControl.Root>,
21
+ );
22
+
23
+ // Assert
24
+ const button = screen.getByRole("radio", { name: "A" });
25
+ expect(received).toContain(button);
26
+ });
27
+
28
+ it("forwards the underlying button element to a consumer object ref", () => {
29
+ // Arrange
30
+ const objectRef = createRef<HTMLButtonElement>();
31
+
32
+ // Act
33
+ render(
34
+ <SegmentedControl.Root aria-label="Mode">
35
+ <SegmentedControl.Item value="a" ref={objectRef}>
36
+ A
37
+ </SegmentedControl.Item>
38
+ </SegmentedControl.Root>,
39
+ );
40
+
41
+ // Assert
42
+ const button = screen.getByRole("radio", { name: "A" });
43
+ expect(objectRef.current).toBe(button);
44
+ });
45
+ });
@@ -0,0 +1,104 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl roving tabindex", () => {
7
+ it("puts only the first segment in the tab sequence when nothing is selected", () => {
8
+ // Arrange & Act
9
+ render(
10
+ <SegmentedControl.Root aria-label="Mode">
11
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
12
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
13
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
14
+ </SegmentedControl.Root>,
15
+ );
16
+
17
+ // Assert
18
+ expect(screen.getByRole("radio", { name: "A" })).toHaveAttribute(
19
+ "tabindex",
20
+ "0",
21
+ );
22
+ expect(screen.getByRole("radio", { name: "B" })).toHaveAttribute(
23
+ "tabindex",
24
+ "-1",
25
+ );
26
+ expect(screen.getByRole("radio", { name: "C" })).toHaveAttribute(
27
+ "tabindex",
28
+ "-1",
29
+ );
30
+ });
31
+
32
+ it("puts only the selected segment in the tab sequence when one is selected", () => {
33
+ // Arrange & Act
34
+ render(
35
+ <SegmentedControl.Root aria-label="Mode" defaultValue="b">
36
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
37
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
38
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
39
+ </SegmentedControl.Root>,
40
+ );
41
+
42
+ // Assert
43
+ expect(screen.getByRole("radio", { name: "A" })).toHaveAttribute(
44
+ "tabindex",
45
+ "-1",
46
+ );
47
+ expect(screen.getByRole("radio", { name: "B" })).toHaveAttribute(
48
+ "tabindex",
49
+ "0",
50
+ );
51
+ expect(screen.getByRole("radio", { name: "C" })).toHaveAttribute(
52
+ "tabindex",
53
+ "-1",
54
+ );
55
+ });
56
+
57
+ it("moves the tab stop to a newly selected segment", async () => {
58
+ // Arrange
59
+ const user = userEvent.setup();
60
+ render(
61
+ <SegmentedControl.Root aria-label="Mode">
62
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
63
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
64
+ </SegmentedControl.Root>,
65
+ );
66
+ const a = screen.getByRole("radio", { name: "A" });
67
+ const b = screen.getByRole("radio", { name: "B" });
68
+ expect(a).toHaveAttribute("tabindex", "0");
69
+
70
+ // Act
71
+ await user.click(b);
72
+
73
+ // Assert
74
+ expect(a).toHaveAttribute("tabindex", "-1");
75
+ expect(b).toHaveAttribute("tabindex", "0");
76
+ });
77
+
78
+ it("tabs into the single home-base segment and not the others", async () => {
79
+ // Arrange
80
+ const user = userEvent.setup();
81
+ render(
82
+ <>
83
+ <button type="button">Before</button>
84
+ <SegmentedControl.Root aria-label="Mode">
85
+ <SegmentedControl.Item value="a">A</SegmentedControl.Item>
86
+ <SegmentedControl.Item value="b">B</SegmentedControl.Item>
87
+ <SegmentedControl.Item value="c">C</SegmentedControl.Item>
88
+ </SegmentedControl.Root>
89
+ <button type="button">After</button>
90
+ </>,
91
+ );
92
+ screen.getByRole("button", { name: "Before" }).focus();
93
+
94
+ // Act
95
+ await user.tab();
96
+
97
+ // Assert: focus lands on the first segment (the home base)
98
+ expect(screen.getByRole("radio", { name: "A" })).toHaveFocus();
99
+
100
+ // Tabbing again escapes the group entirely — the others are tabindex -1.
101
+ await user.tab();
102
+ expect(screen.getByRole("button", { name: "After" })).toHaveFocus();
103
+ });
104
+ });
@@ -0,0 +1,94 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+
4
+ import { SegmentedControl } from "../SegmentedControl";
5
+
6
+ describe("SegmentedControl uncontrolled state", () => {
7
+ it("reflects defaultValue on mount", () => {
8
+ // Arrange & Act
9
+ render(
10
+ <SegmentedControl.Root aria-label="Mode" defaultValue="styled">
11
+ <SegmentedControl.Item value="headless">Headless</SegmentedControl.Item>
12
+ <SegmentedControl.Item value="styled">Styled</SegmentedControl.Item>
13
+ </SegmentedControl.Root>,
14
+ );
15
+
16
+ // Assert
17
+ expect(screen.getByRole("radio", { name: "Headless" })).toHaveAttribute(
18
+ "aria-checked",
19
+ "false",
20
+ );
21
+ expect(screen.getByRole("radio", { name: "Styled" })).toHaveAttribute(
22
+ "aria-checked",
23
+ "true",
24
+ );
25
+ expect(screen.getByRole("radio", { name: "Styled" })).toHaveAttribute(
26
+ "data-state",
27
+ "checked",
28
+ );
29
+ });
30
+
31
+ it("selects an item on click and un-selects the previous one", async () => {
32
+ // Arrange
33
+ const user = userEvent.setup();
34
+ render(
35
+ <SegmentedControl.Root aria-label="Mode" defaultValue="headless">
36
+ <SegmentedControl.Item value="headless">Headless</SegmentedControl.Item>
37
+ <SegmentedControl.Item value="styled">Styled</SegmentedControl.Item>
38
+ </SegmentedControl.Root>,
39
+ );
40
+ const headless = screen.getByRole("radio", { name: "Headless" });
41
+ const styled = screen.getByRole("radio", { name: "Styled" });
42
+ expect(headless).toHaveAttribute("aria-checked", "true");
43
+
44
+ // Act
45
+ await user.click(styled);
46
+
47
+ // Assert
48
+ expect(headless).toHaveAttribute("aria-checked", "false");
49
+ expect(styled).toHaveAttribute("aria-checked", "true");
50
+ });
51
+
52
+ it("fires onValueChange with the new value on every distinct selection", async () => {
53
+ // Arrange
54
+ const user = userEvent.setup();
55
+ const onValueChange = vi.fn();
56
+ render(
57
+ <SegmentedControl.Root aria-label="Mode" onValueChange={onValueChange}>
58
+ <SegmentedControl.Item value="headless">Headless</SegmentedControl.Item>
59
+ <SegmentedControl.Item value="styled">Styled</SegmentedControl.Item>
60
+ </SegmentedControl.Root>,
61
+ );
62
+
63
+ // Act
64
+ await user.click(screen.getByRole("radio", { name: "Headless" }));
65
+ await user.click(screen.getByRole("radio", { name: "Styled" }));
66
+
67
+ // Assert
68
+ expect(onValueChange).toHaveBeenNthCalledWith(1, "headless");
69
+ expect(onValueChange).toHaveBeenNthCalledWith(2, "styled");
70
+ });
71
+
72
+ it("does not re-fire onValueChange when the already-selected item is clicked again", async () => {
73
+ // Arrange
74
+ const user = userEvent.setup();
75
+ const onValueChange = vi.fn();
76
+ render(
77
+ <SegmentedControl.Root
78
+ aria-label="Mode"
79
+ defaultValue="headless"
80
+ onValueChange={onValueChange}
81
+ >
82
+ <SegmentedControl.Item value="headless">Headless</SegmentedControl.Item>
83
+ <SegmentedControl.Item value="styled">Styled</SegmentedControl.Item>
84
+ </SegmentedControl.Root>,
85
+ );
86
+
87
+ // Act
88
+ await user.click(screen.getByRole("radio", { name: "Headless" }));
89
+ await user.click(screen.getByRole("radio", { name: "Headless" }));
90
+
91
+ // Assert
92
+ expect(onValueChange).not.toHaveBeenCalled();
93
+ });
94
+ });
@@ -0,0 +1,2 @@
1
+ export { useSegmentedControlContext } from "./useSegmentedControlContext";
2
+ export { useSegmentedControlRoot } from "./useSegmentedControlRoot";
@@ -0,0 +1 @@
1
+ export { useSegmentedControlContext } from "../SegmentedControlContext";