@primitiv-ui/react 0.1.28 → 0.1.30
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.
- package/README.md +19 -2
- package/package.json +6 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
- package/src/AccessibleIcon/types.ts +15 -2
- package/src/Accordion/Accordion.tsx +32 -11
- package/src/Accordion/README.md +2 -2
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
- package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
- package/src/Accordion/types.ts +72 -21
- package/src/Alert/Alert.tsx +36 -7
- package/src/Alert/__tests__/Alert.test.tsx +5 -0
- package/src/Alert/types.ts +18 -2
- package/src/Avatar/Avatar.tsx +99 -15
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
- package/src/Avatar/hooks/useAvatarImage.ts +16 -0
- package/src/Avatar/types.ts +15 -3
- package/src/Breadcrumb/Breadcrumb.tsx +139 -21
- package/src/Breadcrumb/README.md +47 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
- package/src/Breadcrumb/types.ts +73 -7
- package/src/Button/Button.tsx +2 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
- package/src/Button/__tests__/Button.contract.test.tsx +1 -1
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +268 -20
- package/src/Carousel/README.md +764 -87
- package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
- package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
- package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
- package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +90 -2
- package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
- package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
- package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
- package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
- package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
- package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
- package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
- package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
- package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
- package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
- package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
- package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
- package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
- package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
- package/src/Carousel/hooks/index.ts +1 -0
- package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
- package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
- package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
- package/src/Carousel/loopEngine.ts +54 -0
- package/src/Carousel/types.ts +458 -36
- package/src/Checkbox/Checkbox.tsx +11 -3
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
- package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
- package/src/Checkbox/types.ts +19 -0
- package/src/CheckboxCard/CheckboxCard.tsx +14 -5
- package/src/CheckboxCard/README.md +1 -1
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
- package/src/CheckboxCard/types.ts +65 -17
- package/src/Collapsible/Collapsible.tsx +49 -3
- package/src/Collapsible/README.md +41 -1
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
- package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
- package/src/Collapsible/types.ts +89 -22
- package/src/Combobox/Combobox.tsx +508 -0
- package/src/Combobox/ComboboxContext.ts +23 -0
- package/src/Combobox/README.md +170 -0
- package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
- package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
- package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
- package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
- package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
- package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
- package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
- package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
- package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
- package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
- package/src/Combobox/hooks/index.ts +1 -0
- package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
- package/src/Combobox/index.ts +3 -0
- package/src/Combobox/types.ts +133 -0
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/README.md +1 -1
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/README.md +4 -4
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
- package/src/DirectionProvider/types.ts +28 -3
- package/src/Divider/Divider.tsx +24 -10
- package/src/Divider/__tests__/Divider.contract.test.tsx +1 -1
- package/src/Divider/__tests__/Divider.test.tsx +5 -0
- package/src/Divider/types.ts +10 -3
- package/src/Drawer/Drawer.tsx +315 -0
- package/src/Drawer/README.md +124 -0
- package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
- package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
- package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
- package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
- package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
- package/src/Drawer/index.ts +2 -0
- package/src/Drawer/types.ts +66 -0
- package/src/Dropdown/Dropdown.tsx +119 -2
- package/src/Dropdown/README.md +7 -5
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/README.md +2 -2
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
- package/src/EmptyState/types.ts +49 -6
- package/src/Field/Field.tsx +23 -0
- package/src/Field/README.md +1 -1
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
- package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
- package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
- package/src/Field/hooks/useFieldProps.ts +1 -1
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +55 -19
- package/src/Fieldset/README.md +2 -2
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +15 -10
- package/src/Input/README.md +2 -2
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
- package/src/Input/types.ts +19 -2
- package/src/InputGroup/InputGroup.tsx +11 -3
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
- package/src/InputGroup/types.ts +26 -5
- package/src/Listbox/Listbox.tsx +475 -0
- package/src/Listbox/ListboxContext.ts +48 -0
- package/src/Listbox/README.md +248 -0
- package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
- package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
- package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
- package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
- package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
- package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
- package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
- package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
- package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
- package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
- package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
- package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
- package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
- package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
- package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
- package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
- package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
- package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
- package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
- package/src/Listbox/constants.ts +2 -0
- package/src/Listbox/hooks/index.ts +1 -0
- package/src/Listbox/hooks/useListboxRoot.ts +407 -0
- package/src/Listbox/index.ts +3 -0
- package/src/Listbox/types.ts +238 -0
- package/src/MillerColumns/MillerColumns.tsx +186 -45
- package/src/MillerColumns/README.md +90 -20
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
- package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
- package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
- package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
- package/src/MillerColumns/constants.ts +2 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +4 -0
- package/src/MillerColumns/types.ts +136 -30
- package/src/MillerColumns/utils.ts +69 -3
- package/src/Modal/Modal.tsx +35 -8
- package/src/Modal/README.md +24 -15
- package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
- package/src/Modal/hooks/useModalContent.ts +32 -0
- package/src/Modal/types.ts +110 -29
- package/src/NavigationMenu/NavigationMenu.tsx +890 -0
- package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
- package/src/NavigationMenu/README.md +355 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
- package/src/NavigationMenu/constants.ts +5 -0
- package/src/NavigationMenu/hooks/index.ts +7 -0
- package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
- package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
- package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
- package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
- package/src/NavigationMenu/index.ts +3 -0
- package/src/NavigationMenu/types.ts +259 -0
- package/src/NavigationMenu/utils.ts +89 -0
- package/src/Popover/Popover.tsx +427 -0
- package/src/Popover/PopoverContext.ts +13 -0
- package/src/Popover/README.md +106 -0
- package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
- package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
- package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
- package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
- package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
- package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
- package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
- package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
- package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
- package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
- package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
- package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
- package/src/Popover/hooks/index.ts +3 -0
- package/src/Popover/hooks/usePopoverContent.ts +98 -0
- package/src/Popover/hooks/usePopoverRoot.ts +84 -0
- package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
- package/src/Popover/index.ts +2 -0
- package/src/Popover/types.ts +197 -0
- package/src/Portal/Portal.tsx +50 -10
- package/src/Portal/README.md +2 -2
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
- package/src/Portal/types.ts +5 -0
- package/src/Progress/Progress.tsx +33 -1
- package/src/Progress/README.md +1 -1
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
- package/src/Progress/types.ts +20 -4
- package/src/Radio/Radio.tsx +10 -2
- package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
- package/src/Radio/hooks/useRadioRoot.ts +3 -0
- package/src/Radio/types.ts +20 -0
- package/src/RadioCard/RadioCard.tsx +11 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
- package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
- package/src/RadioCard/types.ts +95 -22
- package/src/RadioGroup/README.md +2 -2
- package/src/RadioGroup/RadioGroup.tsx +12 -1
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
- package/src/RadioGroup/types.ts +73 -9
- package/src/SegmentedControl/README.md +154 -0
- package/src/SegmentedControl/SegmentedControl.tsx +309 -0
- package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
- package/src/SegmentedControl/hooks/index.ts +2 -0
- package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +87 -0
- package/src/SegmentedControl/index.ts +2 -0
- package/src/SegmentedControl/types.ts +138 -0
- package/src/Select/README.md +122 -157
- package/src/Select/Select.tsx +596 -69
- package/src/Select/SelectContext.ts +38 -0
- package/src/Select/SelectItemIndicatorContext.ts +29 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
- package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
- package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
- package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
- package/src/Select/__tests__/Select.form.test.tsx +38 -0
- package/src/Select/__tests__/Select.group.test.tsx +6 -6
- package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
- package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
- package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
- package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
- package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +7 -7
- package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
- package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
- package/src/Select/__tests__/Select.selection.test.tsx +104 -0
- package/src/Select/__tests__/Select.separator.test.tsx +71 -0
- package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
- package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
- package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
- package/src/Select/constants.ts +8 -0
- package/src/Select/hooks/index.ts +2 -0
- package/src/Select/hooks/useSelectContent.ts +149 -0
- package/src/Select/hooks/useSelectRoot.ts +149 -0
- package/src/Select/types.ts +199 -31
- package/src/SkipNav/README.md +3 -3
- package/src/SkipNav/SkipNav.tsx +72 -32
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/README.md +37 -7
- package/src/Slider/Slider.tsx +42 -4
- package/src/Slider/SliderContext.ts +28 -1
- package/src/Slider/types.ts +157 -9
- package/src/Slot/Slot.tsx +94 -19
- package/src/Slot/__tests__/Slot.test.tsx +112 -2
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/SplitButton/README.md +225 -0
- package/src/SplitButton/SplitButton.tsx +457 -0
- package/src/SplitButton/SplitButtonContext.ts +26 -0
- package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
- package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
- package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
- package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
- package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
- package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
- package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
- package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
- package/src/SplitButton/index.ts +2 -0
- package/src/SplitButton/types.ts +186 -0
- package/src/Status/Status.tsx +36 -15
- package/src/Status/__tests__/Status.test.tsx +5 -0
- package/src/Status/types.ts +12 -1
- package/src/Switch/README.md +1 -1
- package/src/Switch/Switch.tsx +17 -4
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
- package/src/Switch/hooks/useSwitchRoot.ts +3 -0
- package/src/Switch/types.ts +49 -22
- package/src/Table/README.md +110 -8
- package/src/Table/Table.tsx +246 -16
- package/src/Table/TableExpandableContext.ts +7 -0
- package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
- package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
- package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
- package/src/Table/types.ts +191 -17
- package/src/Tabs/README.md +4 -4
- package/src/Tabs/Tabs.tsx +13 -4
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
- package/src/Tabs/hooks/useTabsContent.ts +5 -0
- package/src/Tabs/hooks/useTabsRoot.ts +29 -5
- package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
- package/src/Tabs/types.ts +29 -4
- package/src/Textarea/Textarea.tsx +9 -1
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
- package/src/Textarea/types.ts +20 -3
- package/src/Toggle/Toggle.tsx +51 -13
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
- package/src/Toggle/types.ts +54 -4
- package/src/ToggleGroup/README.md +4 -4
- package/src/ToggleGroup/ToggleGroup.tsx +14 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
- package/src/ToggleGroup/types.ts +55 -2
- package/src/Tooltip/README.md +7 -7
- package/src/Tooltip/Tooltip.tsx +100 -22
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
- package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
- package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
- package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +46 -8
- package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/README.md +69 -5
- package/src/Tree/Tree.tsx +188 -34
- package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
- package/src/Tree/hooks/useTreePath.ts +33 -1
- package/src/Tree/index.ts +6 -1
- package/src/Tree/types.ts +54 -12
- package/src/Tree/utils.ts +46 -4
- package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
- package/src/VisuallyHidden/types.ts +11 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useTypeahead.ts +95 -0
- package/src/index.ts +10 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
- package/src/useLocalStorage/index.ts +1 -0
- package/src/useLocalStorage/useLocalStorage.ts +140 -0
- package/src/useMediaQuery/README.md +66 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
- package/src/useMediaQuery/index.ts +1 -0
- package/src/useMediaQuery/useMediaQuery.ts +49 -0
- package/src/usePagination/README.md +170 -0
- package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
- package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
- package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
- package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
- package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
- package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
- package/src/usePagination/index.ts +1 -0
- package/src/usePagination/usePagination.ts +292 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/getKeyToActionMap.ts +1 -1
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { SplitButton } from "../SplitButton";
|
|
5
|
+
|
|
6
|
+
describe("SplitButton — disabled", () => {
|
|
7
|
+
it("disables both halves when the group is disabled", () => {
|
|
8
|
+
render(
|
|
9
|
+
<SplitButton disabled>
|
|
10
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
11
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
12
|
+
<SplitButton.Menu />
|
|
13
|
+
</SplitButton>,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(screen.getByRole("group")).toHaveAttribute("data-disabled", "");
|
|
17
|
+
expect(
|
|
18
|
+
screen.getByRole("button", { name: "Squash and merge" }),
|
|
19
|
+
).toBeDisabled();
|
|
20
|
+
expect(
|
|
21
|
+
screen.getByRole("button", { name: "More merge options" }),
|
|
22
|
+
).toBeDisabled();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("disables one half independently, leaving the other interactive", () => {
|
|
26
|
+
render(
|
|
27
|
+
<SplitButton>
|
|
28
|
+
<SplitButton.Action disabled>Squash and merge</SplitButton.Action>
|
|
29
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
30
|
+
<SplitButton.Menu />
|
|
31
|
+
</SplitButton>,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(screen.getByRole("group")).not.toHaveAttribute("data-disabled");
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByRole("button", { name: "Squash and merge" }),
|
|
37
|
+
).toHaveAttribute("data-disabled", "");
|
|
38
|
+
expect(
|
|
39
|
+
screen.getByRole("button", { name: "More merge options" }),
|
|
40
|
+
).toBeEnabled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("disables only the menu half when the trigger is disabled", () => {
|
|
44
|
+
render(
|
|
45
|
+
<SplitButton>
|
|
46
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
47
|
+
<SplitButton.Trigger disabled aria-label="More merge options" />
|
|
48
|
+
<SplitButton.Menu />
|
|
49
|
+
</SplitButton>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(
|
|
53
|
+
screen.getByRole("button", { name: "Squash and merge" }),
|
|
54
|
+
).toBeEnabled();
|
|
55
|
+
expect(
|
|
56
|
+
screen.getByRole("button", { name: "More merge options" }),
|
|
57
|
+
).toHaveAttribute("data-disabled", "");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("re-propagates when the group's disabled flips after mount", () => {
|
|
61
|
+
const markup = (disabled: boolean) => (
|
|
62
|
+
<SplitButton disabled={disabled}>
|
|
63
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
64
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
65
|
+
<SplitButton.Menu />
|
|
66
|
+
</SplitButton>
|
|
67
|
+
);
|
|
68
|
+
const { rerender } = render(markup(false));
|
|
69
|
+
|
|
70
|
+
rerender(markup(true));
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
screen.getByRole("button", { name: "Squash and merge" }),
|
|
74
|
+
).toBeDisabled();
|
|
75
|
+
expect(
|
|
76
|
+
screen.getByRole("button", { name: "More merge options" }),
|
|
77
|
+
).toBeDisabled();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { SplitButton } from "../SplitButton";
|
|
5
|
+
|
|
6
|
+
const outsideRoot =
|
|
7
|
+
"SplitButton sub-components must be rendered inside a <SplitButton.Root>.";
|
|
8
|
+
|
|
9
|
+
describe("SplitButton — error handling", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("throws when SplitButton.Action is used outside SplitButton.Root", () => {
|
|
19
|
+
expect(() =>
|
|
20
|
+
render(<SplitButton.Action>Orphan</SplitButton.Action>),
|
|
21
|
+
).toThrowError(outsideRoot);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("throws when SplitButton.Trigger is used outside SplitButton.Root", () => {
|
|
25
|
+
expect(() => render(<SplitButton.Trigger aria-label="Orphan" />)).toThrowError(
|
|
26
|
+
outsideRoot,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { SplitButton } from "../SplitButton";
|
|
6
|
+
|
|
7
|
+
describe("SplitButton — keyboard interaction", () => {
|
|
8
|
+
it("opens the menu from the action with ArrowDown and focuses the first item", async () => {
|
|
9
|
+
const user = userEvent.setup();
|
|
10
|
+
render(
|
|
11
|
+
<SplitButton>
|
|
12
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
13
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
14
|
+
<SplitButton.Menu>
|
|
15
|
+
<SplitButton.Item>Create a merge commit</SplitButton.Item>
|
|
16
|
+
<SplitButton.Item>Rebase and merge</SplitButton.Item>
|
|
17
|
+
</SplitButton.Menu>
|
|
18
|
+
</SplitButton>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
await user.tab();
|
|
22
|
+
await user.keyboard("{ArrowDown}");
|
|
23
|
+
|
|
24
|
+
expect(
|
|
25
|
+
screen.getByRole("button", { name: "More merge options" }),
|
|
26
|
+
).toHaveAttribute("aria-expanded", "true");
|
|
27
|
+
expect(
|
|
28
|
+
screen.getByRole("menuitem", {
|
|
29
|
+
name: "Create a merge commit",
|
|
30
|
+
hidden: true,
|
|
31
|
+
}),
|
|
32
|
+
).toHaveFocus();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("leaves the menu closed for other keys, and still runs the action's own handler", async () => {
|
|
36
|
+
const user = userEvent.setup();
|
|
37
|
+
const onKeyDown = vi.fn();
|
|
38
|
+
render(
|
|
39
|
+
<SplitButton>
|
|
40
|
+
<SplitButton.Action onKeyDown={onKeyDown}>
|
|
41
|
+
Squash and merge
|
|
42
|
+
</SplitButton.Action>
|
|
43
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
44
|
+
<SplitButton.Menu>
|
|
45
|
+
<SplitButton.Item>Create a merge commit</SplitButton.Item>
|
|
46
|
+
</SplitButton.Menu>
|
|
47
|
+
</SplitButton>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await user.tab();
|
|
51
|
+
await user.keyboard("{ArrowUp}");
|
|
52
|
+
|
|
53
|
+
expect(onKeyDown).toHaveBeenCalledOnce();
|
|
54
|
+
expect(screen.getByRole("group")).toHaveAttribute("data-state", "closed");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { Dropdown } from "../../Dropdown/index.ts";
|
|
6
|
+
import { SplitButton } from "../SplitButton";
|
|
7
|
+
|
|
8
|
+
describe("SplitButton — state modes", () => {
|
|
9
|
+
it("starts open in uncontrolled mode when defaultOpen is set", () => {
|
|
10
|
+
render(
|
|
11
|
+
<SplitButton defaultOpen>
|
|
12
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
13
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
14
|
+
<SplitButton.Menu>
|
|
15
|
+
<SplitButton.Item>Rebase and merge</SplitButton.Item>
|
|
16
|
+
</SplitButton.Menu>
|
|
17
|
+
</SplitButton>,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(screen.getByRole("group")).toHaveAttribute("data-state", "open");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("defers to the parent in controlled mode", async () => {
|
|
24
|
+
const user = userEvent.setup();
|
|
25
|
+
const onOpenChange = vi.fn();
|
|
26
|
+
render(
|
|
27
|
+
<SplitButton open={false} onOpenChange={onOpenChange}>
|
|
28
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
29
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
30
|
+
<SplitButton.Menu>
|
|
31
|
+
<SplitButton.Item>Rebase and merge</SplitButton.Item>
|
|
32
|
+
</SplitButton.Menu>
|
|
33
|
+
</SplitButton>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await user.click(screen.getByRole("button", { name: "More merge options" }));
|
|
37
|
+
|
|
38
|
+
expect(onOpenChange).toHaveBeenCalledWith(true);
|
|
39
|
+
expect(screen.getByRole("group")).toHaveAttribute("data-state", "closed");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("passes the reading direction down to menus composed from Dropdown", async () => {
|
|
43
|
+
const user = userEvent.setup();
|
|
44
|
+
render(
|
|
45
|
+
<SplitButton dir="rtl" defaultOpen>
|
|
46
|
+
<SplitButton.Action>Squash and merge</SplitButton.Action>
|
|
47
|
+
<SplitButton.Trigger aria-label="More merge options" />
|
|
48
|
+
<SplitButton.Menu>
|
|
49
|
+
<Dropdown.Sub>
|
|
50
|
+
<Dropdown.SubTrigger>Merge strategies</Dropdown.SubTrigger>
|
|
51
|
+
<Dropdown.SubContent>
|
|
52
|
+
<SplitButton.Item>Rebase and merge</SplitButton.Item>
|
|
53
|
+
</Dropdown.SubContent>
|
|
54
|
+
</Dropdown.Sub>
|
|
55
|
+
</SplitButton.Menu>
|
|
56
|
+
</SplitButton>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
await user.keyboard("{ArrowLeft}");
|
|
60
|
+
|
|
61
|
+
expect(
|
|
62
|
+
screen.getByRole("menuitem", {
|
|
63
|
+
name: "Merge strategies",
|
|
64
|
+
hidden: true,
|
|
65
|
+
}),
|
|
66
|
+
).toHaveAttribute("aria-expanded", "true");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
|
+
|
|
3
|
+
import type { Direction } from "../DirectionProvider/index.ts";
|
|
4
|
+
import type {
|
|
5
|
+
DropdownContentProps,
|
|
6
|
+
DropdownItemProps,
|
|
7
|
+
DropdownSeparatorProps,
|
|
8
|
+
DropdownTriggerProps,
|
|
9
|
+
} from "../Dropdown/types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Shared base for both {@link SplitButtonRootProps} variants — the native
|
|
13
|
+
* `<div>` attributes (minus the component-owned `role` and the narrowed
|
|
14
|
+
* `dir`) plus the `asChild` escape hatch, reading direction, and the
|
|
15
|
+
* group-level `disabled`.
|
|
16
|
+
*/
|
|
17
|
+
export type SplitButtonRootBaseProps = Omit<
|
|
18
|
+
ComponentProps<"div">,
|
|
19
|
+
"role" | "dir"
|
|
20
|
+
> & {
|
|
21
|
+
/**
|
|
22
|
+
* The widget's parts — typically one
|
|
23
|
+
* {@link SplitButtonActionProps | `SplitButton.Action`}, one
|
|
24
|
+
* {@link SplitButtonTriggerProps | `SplitButton.Trigger`}, and one
|
|
25
|
+
* {@link SplitButtonMenuProps | `SplitButton.Menu`}.
|
|
26
|
+
*/
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
/** Forwarded to the underlying `HTMLDivElement` (or to the `asChild`
|
|
29
|
+
* element). */
|
|
30
|
+
ref?: Ref<HTMLDivElement>;
|
|
31
|
+
/**
|
|
32
|
+
* When `true`, Root delegates rendering to a single consumer-supplied
|
|
33
|
+
* element via the {@link Slot} pattern instead of the default `<div>`;
|
|
34
|
+
* `role="group"`, `data-state`, `data-disabled` and the remaining props are
|
|
35
|
+
* merged onto it.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Reading direction for the menu. Only affects which arrow key opens or
|
|
41
|
+
* closes a submenu composed from `Dropdown.Sub` — `ArrowRight` opens in
|
|
42
|
+
* `"ltr"`, `ArrowLeft` in `"rtl"`. When omitted it is inherited from the
|
|
43
|
+
* nearest {@link DirectionProvider}, falling back to `"ltr"`.
|
|
44
|
+
*/
|
|
45
|
+
dir?: Direction;
|
|
46
|
+
/**
|
|
47
|
+
* Disables the whole widget — both the primary action and the menu trigger
|
|
48
|
+
* become non-interactive, and `data-disabled=""` is set on the group. OR-ed
|
|
49
|
+
* with each half's own `disabled`, so a disabled group can never be
|
|
50
|
+
* overridden back to enabled by a part.
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Uncontrolled variant of {@link SplitButtonRootProps}: the component owns the
|
|
58
|
+
* menu's open state. Pass `defaultOpen` (or omit it); `onOpenChange` is
|
|
59
|
+
* optional and `open` is forbidden.
|
|
60
|
+
*/
|
|
61
|
+
export type SplitButtonRootUncontrolledProps = SplitButtonRootBaseProps & {
|
|
62
|
+
/**
|
|
63
|
+
* Whether the menu is open on first render. Omit to start closed. The
|
|
64
|
+
* component owns the state thereafter.
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
defaultOpen?: boolean;
|
|
68
|
+
/** Forbidden in uncontrolled mode — use `defaultOpen` instead. */
|
|
69
|
+
open?: never;
|
|
70
|
+
/**
|
|
71
|
+
* Called whenever a user-driven transition opens or closes the menu
|
|
72
|
+
* (trigger click, `ArrowDown` on the action, Escape, outside click,
|
|
73
|
+
* selection). Optional in uncontrolled mode.
|
|
74
|
+
*/
|
|
75
|
+
onOpenChange?: (open: boolean) => void;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Controlled variant of {@link SplitButtonRootProps}: the parent owns the
|
|
80
|
+
* menu's open state. Pass `open` and `onOpenChange` together; `defaultOpen` is
|
|
81
|
+
* forbidden.
|
|
82
|
+
*/
|
|
83
|
+
export type SplitButtonRootControlledProps = SplitButtonRootBaseProps & {
|
|
84
|
+
/** Forbidden in controlled mode — use `open` instead. */
|
|
85
|
+
defaultOpen?: never;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the menu is currently open. Must be kept in sync by the parent via
|
|
88
|
+
* `onOpenChange`; the component never mutates it internally.
|
|
89
|
+
*/
|
|
90
|
+
open: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Called whenever a user-driven transition would open or close the menu. The
|
|
93
|
+
* parent is responsible for reflecting the new value back into `open`.
|
|
94
|
+
* Required in controlled mode.
|
|
95
|
+
*/
|
|
96
|
+
onOpenChange: (open: boolean) => void;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Props for {@link SplitButtonRoot | `SplitButton.Root`} — the
|
|
101
|
+
* controlled/uncontrolled discriminated union pairing
|
|
102
|
+
* {@link SplitButtonRootUncontrolledProps} with
|
|
103
|
+
* {@link SplitButtonRootControlledProps}, plus the shared group props.
|
|
104
|
+
*/
|
|
105
|
+
export type SplitButtonRootProps =
|
|
106
|
+
| SplitButtonRootUncontrolledProps
|
|
107
|
+
| SplitButtonRootControlledProps;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Props for the internal frame component that renders the group element. Root
|
|
111
|
+
* splits it out so the frame sits *inside* the `Dropdown.Root` it renders and
|
|
112
|
+
* can therefore read the menu's open state.
|
|
113
|
+
*
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
export type SplitButtonFrameProps = Omit<
|
|
117
|
+
SplitButtonRootBaseProps,
|
|
118
|
+
"dir" | "disabled"
|
|
119
|
+
> & {
|
|
120
|
+
/** Already defaulted by Root, so the frame receives a definite boolean. */
|
|
121
|
+
disabled: boolean;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Value published by `SplitButtonContext` — the derived ids that wire the
|
|
126
|
+
* trigger's accessible name to the action, plus the group's disabled flag.
|
|
127
|
+
*
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
export type SplitButtonContextValue = {
|
|
131
|
+
/** DOM id applied to {@link SplitButtonActionProps | `SplitButton.Action`};
|
|
132
|
+
* referenced by the trigger's derived `aria-labelledby`. */
|
|
133
|
+
actionId: string;
|
|
134
|
+
/** DOM id applied to {@link SplitButtonTriggerProps | `SplitButton.Trigger`};
|
|
135
|
+
* the self-reference half of its derived `aria-labelledby`. */
|
|
136
|
+
triggerId: string;
|
|
137
|
+
/** Whether the whole widget is disabled (Root's `disabled` prop). */
|
|
138
|
+
disabled: boolean;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Props for {@link SplitButtonAction | `SplitButton.Action`} — the native
|
|
143
|
+
* `<button>` attributes minus `id`, which the component owns because the menu
|
|
144
|
+
* trigger's accessible name references it.
|
|
145
|
+
*/
|
|
146
|
+
export type SplitButtonActionProps = Omit<ComponentProps<"button">, "id"> & {
|
|
147
|
+
/** The action's visible label. Keep it real text — the menu trigger borrows
|
|
148
|
+
* it for its own accessible name. */
|
|
149
|
+
children?: ReactNode;
|
|
150
|
+
/** Forwarded to the underlying `HTMLButtonElement` (or to the `asChild`
|
|
151
|
+
* element). */
|
|
152
|
+
ref?: Ref<HTMLButtonElement>;
|
|
153
|
+
/**
|
|
154
|
+
* When `true`, renders the consumer's element instead of `<button>` via the
|
|
155
|
+
* {@link Slot} pattern. `type="button"` is **not** forwarded in this mode —
|
|
156
|
+
* the child owns its own type semantics.
|
|
157
|
+
* @default false
|
|
158
|
+
*/
|
|
159
|
+
asChild?: boolean;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Props for {@link SplitButtonTrigger | `SplitButton.Trigger`} —
|
|
164
|
+
* {@link DropdownTriggerProps} minus `id`, which the component owns because
|
|
165
|
+
* its derived `aria-labelledby` self-references it. Supply `aria-label` or
|
|
166
|
+
* `aria-labelledby` to opt out of the derived name.
|
|
167
|
+
*/
|
|
168
|
+
export type SplitButtonTriggerProps = Omit<DropdownTriggerProps, "id">;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Props for {@link SplitButtonMenu | `SplitButton.Menu`} — identical to
|
|
172
|
+
* {@link DropdownContentProps}; the menu *is* a `Dropdown.Content`.
|
|
173
|
+
*/
|
|
174
|
+
export type SplitButtonMenuProps = DropdownContentProps;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Props for {@link SplitButtonItem | `SplitButton.Item`} — identical to
|
|
178
|
+
* {@link DropdownItemProps}.
|
|
179
|
+
*/
|
|
180
|
+
export type SplitButtonItemProps = DropdownItemProps;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Props for {@link SplitButtonSeparator | `SplitButton.Separator`} — identical
|
|
184
|
+
* to {@link DropdownSeparatorProps}.
|
|
185
|
+
*/
|
|
186
|
+
export type SplitButtonSeparatorProps = DropdownSeparatorProps;
|
package/src/Status/Status.tsx
CHANGED
|
@@ -4,29 +4,50 @@ import { Slot } from "../Slot/index.ts";
|
|
|
4
4
|
import { StatusProps } from "./types";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* A polite live region for low-priority, non-urgent status messages
|
|
7
|
+
* A polite live region for low-priority, non-urgent status messages,
|
|
8
|
+
* implementing the WAI-ARIA `status` role.
|
|
8
9
|
*
|
|
9
|
-
* Renders a `<div role="status">`. The `status` role carries an
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `Alert` instead.
|
|
10
|
+
* Renders a `<div role="status">`. The `status` role carries an implicit
|
|
11
|
+
* `aria-live="polite"` and `aria-atomic="true"`, so assistive technology
|
|
12
|
+
* announces the message once the user is idle, without interrupting
|
|
13
|
+
* them — use it for confirmations, counts, and background progress. For
|
|
14
|
+
* errors the user must see immediately, reach for `Alert` instead.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* **Live region timing.** A live region only announces content that
|
|
17
|
+
* changes *after* the region is already in the DOM. Either render
|
|
18
|
+
* `Status` conditionally (mount when a message is ready), or keep it
|
|
19
|
+
* mounted and update its text:
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* {
|
|
21
|
+
* ```tsx
|
|
22
|
+
* // Conditional mount — announces on first appearance
|
|
23
|
+
* {saved && <Status>All changes saved.</Status>}
|
|
24
|
+
*
|
|
25
|
+
* // Persistent — announces on every content change
|
|
26
|
+
* <Status>{count} items in your cart</Status>
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* **`asChild` composition.** Pass `asChild` to render the consumer's own
|
|
30
|
+
* element instead of a `<div>`, merging `role="status"` and all other
|
|
31
|
+
* props onto it via the {@link Slot} utility. Event handlers compose with
|
|
32
|
+
* the child's own handlers (child runs first).
|
|
33
|
+
*
|
|
34
|
+
* @extends HTMLDivElement
|
|
23
35
|
*
|
|
24
36
|
* @example Confirmation message
|
|
25
37
|
* ```tsx
|
|
26
|
-
* {
|
|
38
|
+
* import { Status } from "@primitiv-ui/react";
|
|
39
|
+
*
|
|
40
|
+
* function SaveIndicator({ saved }: { saved: boolean }) {
|
|
41
|
+
* return saved ? <Status>All changes saved.</Status> : null;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Cart count — persistent live region
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Status>{count} items in your cart</Status>
|
|
27
48
|
* ```
|
|
28
49
|
*
|
|
29
|
-
* @example asChild —
|
|
50
|
+
* @example asChild — apply the status role to a semantic element
|
|
30
51
|
* ```tsx
|
|
31
52
|
* <Status asChild>
|
|
32
53
|
* <output>{count} items in your cart</output>
|
|
@@ -25,4 +25,9 @@ describe("Status component", () => {
|
|
|
25
25
|
expect(status.tagName).toBe("SECTION");
|
|
26
26
|
expect(status).toHaveTextContent("Saved");
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
it('sets displayName to "Status" for React DevTools and stack traces', () => {
|
|
30
|
+
// Assert — an empty displayName would render it as an anonymous component.
|
|
31
|
+
expect(Status.displayName).toBe("Status");
|
|
32
|
+
});
|
|
28
33
|
});
|
package/src/Status/types.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { ComponentProps } from "react";
|
|
1
|
+
import { ComponentProps, ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for {@link Status} — all native `<div>` attributes plus the
|
|
5
5
|
* `asChild` escape hatch.
|
|
6
6
|
*/
|
|
7
7
|
export type StatusProps = ComponentProps<"div"> & {
|
|
8
|
+
/**
|
|
9
|
+
* Renders the child element instead of a native `<div>`, merging
|
|
10
|
+
* `role="status"` and all other props onto it via {@link Slot}.
|
|
11
|
+
* Event handlers compose with the child's own handlers (child runs
|
|
12
|
+
* first). Use this to apply the live-region role to a semantically
|
|
13
|
+
* appropriate element (e.g. `<output>`).
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
8
16
|
asChild?: boolean;
|
|
17
|
+
/** Content to announce. A live region announces changes that occur
|
|
18
|
+
* after the region is already present in the DOM. */
|
|
19
|
+
children?: ReactNode;
|
|
9
20
|
};
|
package/src/Switch/README.md
CHANGED
|
@@ -45,7 +45,7 @@ Pass `checked` and `onCheckedChange` together. The parent owns the value.
|
|
|
45
45
|
```tsx
|
|
46
46
|
const [enabled, setEnabled] = useState(false);
|
|
47
47
|
|
|
48
|
-
<Switch.Root checked={enabled} onCheckedChange={setEnabled} aria-label="
|
|
48
|
+
<Switch.Root checked={enabled} onCheckedChange={setEnabled} aria-label="...">
|
|
49
49
|
<Switch.Thumb />
|
|
50
50
|
</Switch.Root>
|
|
51
51
|
```
|
package/src/Switch/Switch.tsx
CHANGED
|
@@ -25,7 +25,7 @@ function dataStateOf(checked: boolean) {
|
|
|
25
25
|
*
|
|
26
26
|
* Props routing: `className` / `style` style the **track** (the `<label>` you
|
|
27
27
|
* see — the input is hidden); every other prop (`name`, `value`, `id`,
|
|
28
|
-
* `aria-*`, `required`, `disabled`, `ref`,
|
|
28
|
+
* `aria-*`, `required`, `disabled`, `ref`, ...) spreads onto the input, because
|
|
29
29
|
* semantically the Root *is* the switch.
|
|
30
30
|
*
|
|
31
31
|
* Supports two state modes, statically discriminated at the type level:
|
|
@@ -42,6 +42,12 @@ function dataStateOf(checked: boolean) {
|
|
|
42
42
|
* **Keyboard.** Toggles on `Space` (the native checkbox behaviour); `Enter`
|
|
43
43
|
* does not toggle a checkbox-based control.
|
|
44
44
|
*
|
|
45
|
+
* **`onChange` composition.** The consumer's raw `onChange` fires first; if it
|
|
46
|
+
* calls `event.preventDefault()`, the internal mirror update and
|
|
47
|
+
* `onCheckedChange` are skipped for that event. See {@link SwitchRootBaseProps}.
|
|
48
|
+
*
|
|
49
|
+
* @extends HTMLInputElement
|
|
50
|
+
*
|
|
45
51
|
* @example Uncontrolled
|
|
46
52
|
* ```tsx
|
|
47
53
|
* <Switch.Root name="notify" value="on" defaultChecked aria-label="Enable notifications">
|
|
@@ -53,7 +59,7 @@ function dataStateOf(checked: boolean) {
|
|
|
53
59
|
* ```tsx
|
|
54
60
|
* const [enabled, setEnabled] = useState(false);
|
|
55
61
|
*
|
|
56
|
-
* <Switch.Root checked={enabled} onCheckedChange={setEnabled} aria-label="
|
|
62
|
+
* <Switch.Root checked={enabled} onCheckedChange={setEnabled} aria-label="...">
|
|
57
63
|
* <Switch.Thumb />
|
|
58
64
|
* </Switch.Root>
|
|
59
65
|
* ```
|
|
@@ -102,7 +108,11 @@ export function SwitchRoot({
|
|
|
102
108
|
);
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
|
|
111
|
+
// Runtime-dead: the compound alias below (same object via Object.assign)
|
|
112
|
+
// overwrites this to "Switch" at load, so the value is never observable. The
|
|
113
|
+
// assignment stays because it declares `displayName` on `typeof SwitchRoot`,
|
|
114
|
+
// which TSwitchCompound extends.
|
|
115
|
+
// Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
|
|
106
116
|
SwitchRoot.displayName = "SwitchRoot";
|
|
107
117
|
|
|
108
118
|
/**
|
|
@@ -114,7 +124,10 @@ SwitchRoot.displayName = "SwitchRoot";
|
|
|
114
124
|
* **Styling hook.** Mirrors the root's `data-state="checked" | "unchecked"`.
|
|
115
125
|
*
|
|
116
126
|
* **`asChild` prop.** Pass `asChild` to render the consumer's own element as
|
|
117
|
-
* the thumb, with `aria-hidden` and `data-state` merged in
|
|
127
|
+
* the thumb, with `aria-hidden` and `data-state` merged in via the
|
|
128
|
+
* {@link Slot} pattern. See {@link SwitchThumbProps}.
|
|
129
|
+
*
|
|
130
|
+
* @extends HTMLSpanElement
|
|
118
131
|
*
|
|
119
132
|
* @example
|
|
120
133
|
* ```tsx
|
|
@@ -92,4 +92,11 @@ describe("Switch basic rendering", () => {
|
|
|
92
92
|
expect(thumb).toHaveAttribute("aria-hidden", "true");
|
|
93
93
|
expect(thumb).toHaveAttribute("data-state", "unchecked");
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
it("sets a displayName on the compound and the Thumb", () => {
|
|
97
|
+
// Assert — empty displayNames would render each as anonymous in DevTools.
|
|
98
|
+
// Root aliases the compound (Object.assign), so its name is "Switch".
|
|
99
|
+
expect(Switch.displayName).toBe("Switch");
|
|
100
|
+
expect(Switch.Thumb.displayName).toBe("SwitchThumb");
|
|
101
|
+
});
|
|
95
102
|
});
|
|
@@ -34,6 +34,9 @@ export function useSwitchRoot({
|
|
|
34
34
|
onChange?.(event);
|
|
35
35
|
if (event.defaultPrevented) return;
|
|
36
36
|
const next = event.target.checked;
|
|
37
|
+
// Stryker disable next-line ConditionalExpression: in controlled mode
|
|
38
|
+
// `checked` reads controlledChecked, never `mirror`, so setting mirror here
|
|
39
|
+
// (the mutant's always-run form) has no observable effect — equivalent.
|
|
37
40
|
if (!isControlled) setMirror(next);
|
|
38
41
|
onCheckedChange?.(next);
|
|
39
42
|
};
|