@primitiv-ui/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/package.json +59 -0
- package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
- package/src/AccessibleIcon/README.md +42 -0
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
- package/src/AccessibleIcon/index.ts +2 -0
- package/src/AccessibleIcon/types.ts +8 -0
- package/src/Accordion/Accordion.tsx +412 -0
- package/src/Accordion/AccordionContext.ts +12 -0
- package/src/Accordion/README.md +202 -0
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
- package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
- package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
- package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
- package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
- package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
- package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
- package/src/Accordion/hooks/index.ts +6 -0
- package/src/Accordion/hooks/useAccordionContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
- package/src/Accordion/hooks/useAccordionItem.ts +22 -0
- package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
- package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
- package/src/Accordion/index.ts +1 -0
- package/src/Accordion/types.ts +81 -0
- package/src/Alert/Alert.tsx +43 -0
- package/src/Alert/README.md +54 -0
- package/src/Alert/__tests__/Alert.test.tsx +28 -0
- package/src/Alert/index.ts +2 -0
- package/src/Alert/types.ts +5 -0
- package/src/Avatar/Avatar.tsx +149 -0
- package/src/Avatar/AvatarContext.ts +20 -0
- package/src/Avatar/README.md +116 -0
- package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
- package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
- package/src/Avatar/hooks/index.ts +2 -0
- package/src/Avatar/hooks/useAvatarContext.ts +1 -0
- package/src/Avatar/hooks/useAvatarImage.ts +40 -0
- package/src/Avatar/index.ts +3 -0
- package/src/Avatar/types.ts +44 -0
- package/src/Breadcrumb/Breadcrumb.tsx +234 -0
- package/src/Breadcrumb/README.md +111 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
- package/src/Breadcrumb/index.ts +2 -0
- package/src/Breadcrumb/types.ts +22 -0
- package/src/Button/Button.tsx +95 -0
- package/src/Button/README.md +112 -0
- package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
- package/src/Button/__tests__/Button.contract.test.tsx +72 -0
- package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
- package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
- package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +8 -0
- package/src/Carousel/Carousel.tsx +708 -0
- package/src/Carousel/CarouselContext.ts +11 -0
- package/src/Carousel/README.md +848 -0
- package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
- package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
- package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
- package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
- package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
- package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
- package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
- package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
- package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
- package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
- package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
- package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
- package/src/Carousel/hooks/index.ts +4 -0
- package/src/Carousel/hooks/useCarouselContext.ts +13 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
- package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
- package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
- package/src/Carousel/index.ts +3 -0
- package/src/Carousel/types.ts +400 -0
- package/src/Checkbox/Checkbox.tsx +228 -0
- package/src/Checkbox/CheckboxContext.ts +12 -0
- package/src/Checkbox/README.md +156 -0
- package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
- package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
- package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
- package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
- package/src/Checkbox/hooks/index.ts +2 -0
- package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
- package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
- package/src/Checkbox/index.ts +1 -0
- package/src/Checkbox/types.ts +33 -0
- package/src/CheckboxCard/CheckboxCard.tsx +208 -0
- package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
- package/src/CheckboxCard/README.md +114 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
- package/src/CheckboxCard/hooks/index.ts +2 -0
- package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
- package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
- package/src/CheckboxCard/index.ts +3 -0
- package/src/CheckboxCard/types.ts +33 -0
- package/src/Collapsible/Collapsible.tsx +316 -0
- package/src/Collapsible/CollapsibleContext.ts +7 -0
- package/src/Collapsible/README.md +174 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
- package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
- package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
- package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
- package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
- package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
- package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
- package/src/Collapsible/hooks/index.ts +2 -0
- package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/types.ts +48 -0
- package/src/ContextMenu/ContextMenu.tsx +1004 -0
- package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
- package/src/ContextMenu/ContextMenuContext.ts +21 -0
- package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
- package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
- package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
- package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
- package/src/ContextMenu/README.md +275 -0
- package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
- package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
- package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
- package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
- package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
- package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
- package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
- package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
- package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
- package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
- package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
- package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
- package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
- package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
- package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
- package/src/ContextMenu/constants.ts +4 -0
- package/src/ContextMenu/index.ts +1 -0
- package/src/ContextMenu/types.ts +199 -0
- package/src/DirectionProvider/DirectionContext.ts +21 -0
- package/src/DirectionProvider/DirectionProvider.tsx +31 -0
- package/src/DirectionProvider/README.md +62 -0
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
- package/src/DirectionProvider/index.ts +3 -0
- package/src/DirectionProvider/types.ts +10 -0
- package/src/Divider/Divider.tsx +57 -0
- package/src/Divider/README.md +57 -0
- package/src/Divider/__tests__/Divider.test.tsx +41 -0
- package/src/Divider/index.ts +1 -0
- package/src/Divider/types.ts +5 -0
- package/src/Dropdown/Dropdown.tsx +842 -0
- package/src/Dropdown/DropdownContentContext.ts +15 -0
- package/src/Dropdown/DropdownContext.ts +17 -0
- package/src/Dropdown/DropdownGroupContext.ts +8 -0
- package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
- package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
- package/src/Dropdown/DropdownSubContext.ts +15 -0
- package/src/Dropdown/README.md +284 -0
- package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
- package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
- package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
- package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
- package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
- package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
- package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
- package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
- package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
- package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
- package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
- package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
- package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
- package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
- package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
- package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
- package/src/Dropdown/constants.ts +4 -0
- package/src/Dropdown/hooks/index.ts +9 -0
- package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
- package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
- package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
- package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
- package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
- package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
- package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Dropdown/types.ts +200 -0
- package/src/EmptyState/EmptyState.tsx +245 -0
- package/src/EmptyState/README.md +129 -0
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
- package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
- package/src/EmptyState/index.ts +2 -0
- package/src/EmptyState/types.ts +21 -0
- package/src/Field/Field.tsx +239 -0
- package/src/Field/FieldContext.ts +22 -0
- package/src/Field/README.md +167 -0
- package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
- package/src/Field/hooks/index.ts +2 -0
- package/src/Field/hooks/useFieldContext.ts +1 -0
- package/src/Field/hooks/useFieldProps.ts +57 -0
- package/src/Field/index.ts +2 -0
- package/src/Field/types.ts +33 -0
- package/src/Fieldset/Fieldset.tsx +104 -0
- package/src/Fieldset/README.md +74 -0
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
- package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
- package/src/Fieldset/index.ts +2 -0
- package/src/Fieldset/types.ts +5 -0
- package/src/Input/Input.tsx +120 -0
- package/src/Input/README.md +180 -0
- package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
- package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
- package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
- package/src/Input/index.ts +2 -0
- package/src/Input/types.ts +7 -0
- package/src/InputGroup/InputGroup.tsx +228 -0
- package/src/InputGroup/README.md +178 -0
- package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
- package/src/InputGroup/index.ts +2 -0
- package/src/InputGroup/types.ts +13 -0
- package/src/MillerColumns/MillerColumns.tsx +329 -0
- package/src/MillerColumns/MillerColumnsContext.ts +25 -0
- package/src/MillerColumns/README.md +278 -0
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
- package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
- package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
- package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
- package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
- package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
- package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
- package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
- package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
- package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
- package/src/MillerColumns/hooks/index.ts +7 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
- package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +3 -0
- package/src/MillerColumns/types.ts +93 -0
- package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
- package/src/MillerColumns/utils.ts +75 -0
- package/src/Modal/Modal.tsx +474 -0
- package/src/Modal/ModalContext.ts +13 -0
- package/src/Modal/README.md +207 -0
- package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
- package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
- package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
- package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
- package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
- package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
- package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
- package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
- package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
- package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
- package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
- package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
- package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
- package/src/Modal/hooks/index.ts +4 -0
- package/src/Modal/hooks/useModalContent.ts +62 -0
- package/src/Modal/hooks/useModalContext.ts +1 -0
- package/src/Modal/hooks/useModalRoot.ts +81 -0
- package/src/Modal/hooks/useModalTrigger.ts +25 -0
- package/src/Modal/index.ts +3 -0
- package/src/Modal/types.ts +76 -0
- package/src/Portal/Portal.tsx +28 -0
- package/src/Portal/README.md +70 -0
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
- package/src/Portal/index.ts +2 -0
- package/src/Portal/types.ts +6 -0
- package/src/Progress/Progress.tsx +178 -0
- package/src/Progress/ProgressContext.ts +15 -0
- package/src/Progress/README.md +112 -0
- package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
- package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
- package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
- package/src/Progress/hooks/index.ts +2 -0
- package/src/Progress/hooks/useProgressContext.ts +1 -0
- package/src/Progress/hooks/useProgressRoot.ts +45 -0
- package/src/Progress/index.ts +3 -0
- package/src/Progress/types.ts +43 -0
- package/src/RadioCard/README.md +133 -0
- package/src/RadioCard/RadioCard.tsx +334 -0
- package/src/RadioCard/RadioCardContext.ts +23 -0
- package/src/RadioCard/RadioCardItemContext.ts +10 -0
- package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
- package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
- package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
- package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
- package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
- package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
- package/src/RadioCard/hooks/index.ts +3 -0
- package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
- package/src/RadioCard/index.ts +4 -0
- package/src/RadioCard/types.ts +51 -0
- package/src/RadioGroup/README.md +185 -0
- package/src/RadioGroup/RadioGroup.tsx +353 -0
- package/src/RadioGroup/RadioGroupContext.ts +23 -0
- package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
- package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
- package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
- package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
- package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
- package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
- package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
- package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
- package/src/RadioGroup/hooks/index.ts +3 -0
- package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
- package/src/RadioGroup/index.ts +1 -0
- package/src/RadioGroup/types.ts +51 -0
- package/src/Select/README.md +203 -0
- package/src/Select/Select.tsx +204 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
- package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
- package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
- package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
- package/src/Select/__tests__/Select.group.test.tsx +42 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
- package/src/Select/index.ts +2 -0
- package/src/Select/types.ts +89 -0
- package/src/SkipNav/README.md +87 -0
- package/src/SkipNav/SkipNav.tsx +116 -0
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
- package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
- package/src/SkipNav/index.ts +1 -0
- package/src/SkipNav/types.ts +26 -0
- package/src/Slider/README.md +215 -0
- package/src/Slider/Slider.tsx +308 -0
- package/src/Slider/SliderContext.ts +24 -0
- package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
- package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
- package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
- package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
- package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
- package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
- package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
- package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
- package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
- package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
- package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
- package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
- package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
- package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
- package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
- package/src/Slider/hooks/index.ts +3 -0
- package/src/Slider/hooks/useSliderContext.ts +1 -0
- package/src/Slider/hooks/useSliderRoot.ts +197 -0
- package/src/Slider/hooks/useSliderThumb.ts +77 -0
- package/src/Slider/index.ts +3 -0
- package/src/Slider/types.ts +48 -0
- package/src/Slider/utils.ts +155 -0
- package/src/Slot/Slot.tsx +158 -0
- package/src/Slot/__tests__/Slot.test.tsx +163 -0
- package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
- package/src/Slot/composeEventHandlers.ts +38 -0
- package/src/Slot/index.ts +3 -0
- package/src/Slot/types.ts +5 -0
- package/src/Status/README.md +50 -0
- package/src/Status/Status.tsx +44 -0
- package/src/Status/__tests__/Status.test.tsx +28 -0
- package/src/Status/index.ts +2 -0
- package/src/Status/types.ts +5 -0
- package/src/Switch/README.md +121 -0
- package/src/Switch/Switch.tsx +167 -0
- package/src/Switch/SwitchContext.ts +10 -0
- package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
- package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
- package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
- package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
- package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
- package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
- package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
- package/src/Switch/hooks/index.ts +2 -0
- package/src/Switch/hooks/useSwitchContext.ts +1 -0
- package/src/Switch/hooks/useSwitchRoot.ts +28 -0
- package/src/Switch/index.ts +3 -0
- package/src/Switch/types.ts +37 -0
- package/src/Table/README.md +205 -0
- package/src/Table/Table.tsx +380 -0
- package/src/Table/__tests__/Table.Body.test.tsx +61 -0
- package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
- package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
- package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
- package/src/Table/__tests__/Table.Head.test.tsx +61 -0
- package/src/Table/__tests__/Table.Header.test.tsx +73 -0
- package/src/Table/__tests__/Table.Root.test.tsx +49 -0
- package/src/Table/__tests__/Table.Row.test.tsx +67 -0
- package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
- package/src/Table/index.ts +1 -0
- package/src/Table/types.ts +63 -0
- package/src/Tabs/README.md +110 -0
- package/src/Tabs/Tabs.tsx +434 -0
- package/src/Tabs/TabsContext.ts +13 -0
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
- package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
- package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
- package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
- package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
- package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
- package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
- package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
- package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
- package/src/Tabs/hooks/index.ts +4 -0
- package/src/Tabs/hooks/useTabsContent.ts +27 -0
- package/src/Tabs/hooks/useTabsContext.ts +1 -0
- package/src/Tabs/hooks/useTabsRoot.ts +148 -0
- package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
- package/src/Tabs/index.ts +3 -0
- package/src/Tabs/types.ts +99 -0
- package/src/Tabs/utils.ts +8 -0
- package/src/Textarea/README.md +98 -0
- package/src/Textarea/Textarea.tsx +93 -0
- package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
- package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
- package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
- package/src/Textarea/index.ts +2 -0
- package/src/Textarea/types.ts +7 -0
- package/src/Toggle/README.md +97 -0
- package/src/Toggle/Toggle.tsx +81 -0
- package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
- package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
- package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
- package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
- package/src/Toggle/index.ts +2 -0
- package/src/Toggle/types.ts +23 -0
- package/src/ToggleGroup/README.md +137 -0
- package/src/ToggleGroup/ToggleGroup.tsx +298 -0
- package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
- package/src/ToggleGroup/hooks/index.ts +2 -0
- package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
- package/src/ToggleGroup/index.ts +2 -0
- package/src/ToggleGroup/types.ts +72 -0
- package/src/Tooltip/README.md +214 -0
- package/src/Tooltip/Tooltip.tsx +260 -0
- package/src/Tooltip/TooltipContext.ts +20 -0
- package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
- package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
- package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
- package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
- package/src/Tooltip/hooks/index.ts +4 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
- package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
- package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/types.ts +64 -0
- package/src/Tree/README.md +339 -0
- package/src/Tree/Tree.tsx +571 -0
- package/src/Tree/TreeContext.ts +24 -0
- package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
- package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
- package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
- package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
- package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
- package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
- package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
- package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
- package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
- package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
- package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
- package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
- package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
- package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
- package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
- package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
- package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
- package/src/Tree/hooks/index.ts +3 -0
- package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
- package/src/Tree/hooks/useTreePath.ts +68 -0
- package/src/Tree/hooks/useTreeRoot.ts +279 -0
- package/src/Tree/index.ts +3 -0
- package/src/Tree/types.ts +224 -0
- package/src/Tree/utils.ts +59 -0
- package/src/VisuallyHidden/README.md +58 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
- package/src/VisuallyHidden/index.ts +2 -0
- package/src/VisuallyHidden/types.ts +5 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCollection.ts +74 -0
- package/src/hooks/useControllableState.ts +81 -0
- package/src/hooks/useRovingTabindex.ts +178 -0
- package/src/index.ts +38 -0
- package/src/test/intersectionObserverPolyfill.ts +83 -0
- package/src/test/popoverPolyfill.ts +86 -0
- package/src/test/scrollPolyfill.ts +23 -0
- package/src/types.ts +13 -0
- package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
- package/src/utils/__tests__/deriveId.test.ts +28 -0
- package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
- package/src/utils/createStrictContext.ts +49 -0
- package/src/utils/deriveId.ts +31 -0
- package/src/utils/getKeyToActionMap.ts +95 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { RefObject, useRef } from "react";
|
|
2
|
+
import { act, render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import { Carousel } from "..";
|
|
6
|
+
import type { CarouselImperativeApi } from "..";
|
|
7
|
+
import { MockIntersectionObserver } from "../../test/intersectionObserverPolyfill";
|
|
8
|
+
|
|
9
|
+
describe("Carousel IntersectionObserver fallback + isInView", () => {
|
|
10
|
+
it("isInView(slideIndex) should reflect the observer's last reported intersection state", () => {
|
|
11
|
+
const ref = {
|
|
12
|
+
current: null,
|
|
13
|
+
} as unknown as RefObject<CarouselImperativeApi>;
|
|
14
|
+
|
|
15
|
+
render(
|
|
16
|
+
<Carousel.Root ref={ref} ariaLabel="Featured products">
|
|
17
|
+
<Carousel.Viewport>
|
|
18
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
19
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
20
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
21
|
+
</Carousel.Viewport>
|
|
22
|
+
</Carousel.Root>,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(ref.current!.isInView(0)).toBe(false);
|
|
26
|
+
expect(ref.current!.isInView(1)).toBe(false);
|
|
27
|
+
|
|
28
|
+
const io = MockIntersectionObserver.latest!;
|
|
29
|
+
act(() => {
|
|
30
|
+
io.fire([
|
|
31
|
+
{
|
|
32
|
+
target: screen.getByTestId("slide-0"),
|
|
33
|
+
isIntersecting: true,
|
|
34
|
+
intersectionRatio: 1,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
target: screen.getByTestId("slide-1"),
|
|
38
|
+
isIntersecting: false,
|
|
39
|
+
intersectionRatio: 0,
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(ref.current!.isInView(0)).toBe(true);
|
|
45
|
+
expect(ref.current!.isInView(1)).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should drive currentPage from IO entries crossing the 0.6 threshold", () => {
|
|
49
|
+
function Parent() {
|
|
50
|
+
const ref = useRef<CarouselImperativeApi>(null);
|
|
51
|
+
return (
|
|
52
|
+
<Carousel.Root ref={ref} ariaLabel="Featured products">
|
|
53
|
+
<Carousel.Viewport data-testid="viewport">
|
|
54
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
55
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
56
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
57
|
+
</Carousel.Viewport>
|
|
58
|
+
</Carousel.Root>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render(<Parent />);
|
|
63
|
+
|
|
64
|
+
// Settle the carousel's initial mount-time scroll so the IO callback
|
|
65
|
+
// is not gated by the in-flight programmatic scroll guard.
|
|
66
|
+
act(() => {
|
|
67
|
+
screen.getByTestId("viewport").dispatchEvent(new Event("scrollend"));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const io = MockIntersectionObserver.latest!;
|
|
71
|
+
act(() => {
|
|
72
|
+
io.fire([
|
|
73
|
+
{
|
|
74
|
+
target: screen.getByTestId("slide-0"),
|
|
75
|
+
isIntersecting: false,
|
|
76
|
+
intersectionRatio: 0.1,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
target: screen.getByTestId("slide-2"),
|
|
80
|
+
isIntersecting: true,
|
|
81
|
+
intersectionRatio: 0.9,
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
87
|
+
"data-state",
|
|
88
|
+
"active",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should not change the page when no observed slides cross the visibility threshold", () => {
|
|
93
|
+
const onPageChange = vi.fn();
|
|
94
|
+
render(
|
|
95
|
+
<Carousel.Root
|
|
96
|
+
ariaLabel="Featured products"
|
|
97
|
+
page={0}
|
|
98
|
+
onPageChange={onPageChange}
|
|
99
|
+
>
|
|
100
|
+
<Carousel.Viewport>
|
|
101
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
102
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
103
|
+
</Carousel.Viewport>
|
|
104
|
+
</Carousel.Root>,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const io = MockIntersectionObserver.latest!;
|
|
108
|
+
act(() => {
|
|
109
|
+
io.fire([
|
|
110
|
+
{
|
|
111
|
+
target: screen.getByTestId("slide-0"),
|
|
112
|
+
isIntersecting: false,
|
|
113
|
+
intersectionRatio: 0.2,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
target: screen.getByTestId("slide-1"),
|
|
117
|
+
isIntersecting: false,
|
|
118
|
+
intersectionRatio: 0.1,
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(onPageChange).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should derive the page index from floor(slideIndex / slidesPerPage) when slidesPerPage > 1", () => {
|
|
127
|
+
render(
|
|
128
|
+
<Carousel.Root ariaLabel="Featured products" slidesPerPage={2}>
|
|
129
|
+
<Carousel.Viewport data-testid="viewport">
|
|
130
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
131
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
132
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
133
|
+
<Carousel.Slide data-testid="slide-3" />
|
|
134
|
+
</Carousel.Viewport>
|
|
135
|
+
</Carousel.Root>,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Settle the carousel's initial mount-time scroll so the IO callback
|
|
139
|
+
// is not gated by the in-flight programmatic scroll guard.
|
|
140
|
+
act(() => {
|
|
141
|
+
screen.getByTestId("viewport").dispatchEvent(new Event("scrollend"));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const io = MockIntersectionObserver.latest!;
|
|
145
|
+
act(() => {
|
|
146
|
+
io.fire([
|
|
147
|
+
{
|
|
148
|
+
target: screen.getByTestId("slide-3"),
|
|
149
|
+
isIntersecting: true,
|
|
150
|
+
intersectionRatio: 0.8,
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// floor(3 / 2) = page 1; slides 2 and 3 active.
|
|
156
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
157
|
+
"data-state",
|
|
158
|
+
"active",
|
|
159
|
+
);
|
|
160
|
+
expect(screen.getByTestId("slide-3")).toHaveAttribute(
|
|
161
|
+
"data-state",
|
|
162
|
+
"active",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should not drive the page from the IntersectionObserver when the browser supports scrollsnapchange", () => {
|
|
167
|
+
// scrollsnapchange reports the precise centred snap target, so it owns
|
|
168
|
+
// the active page. The IO's lowest-index-visible heuristic is only a
|
|
169
|
+
// fallback for browsers without the event — and for a centre-aligned
|
|
170
|
+
// carousel that shows several slides at once it would override the
|
|
171
|
+
// correct page with the *leftmost* visible slide. When the event is
|
|
172
|
+
// available the observer must feed isInView only, not the page.
|
|
173
|
+
const ref = {
|
|
174
|
+
current: null,
|
|
175
|
+
} as unknown as RefObject<CarouselImperativeApi>;
|
|
176
|
+
// The presence of the on* handler property is the feature-detection
|
|
177
|
+
// signal; assigning it (even null) makes `"onscrollsnapchange" in window`
|
|
178
|
+
// true the way a supporting browser does.
|
|
179
|
+
(
|
|
180
|
+
window as unknown as { onscrollsnapchange: unknown }
|
|
181
|
+
).onscrollsnapchange = null;
|
|
182
|
+
try {
|
|
183
|
+
render(
|
|
184
|
+
<Carousel.Root
|
|
185
|
+
ref={ref}
|
|
186
|
+
ariaLabel="Featured products"
|
|
187
|
+
snapAlign="center"
|
|
188
|
+
>
|
|
189
|
+
<Carousel.Viewport data-testid="viewport">
|
|
190
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
191
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
192
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
193
|
+
</Carousel.Viewport>
|
|
194
|
+
</Carousel.Root>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
act(() => {
|
|
198
|
+
screen.getByTestId("viewport").dispatchEvent(new Event("scrollend"));
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const io = MockIntersectionObserver.latest!;
|
|
202
|
+
act(() => {
|
|
203
|
+
io.fire([
|
|
204
|
+
{
|
|
205
|
+
target: screen.getByTestId("slide-2"),
|
|
206
|
+
isIntersecting: true,
|
|
207
|
+
intersectionRatio: 0.9,
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// The page stays put — scrollsnapchange owns it…
|
|
213
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
214
|
+
"data-state",
|
|
215
|
+
"active",
|
|
216
|
+
);
|
|
217
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
218
|
+
"data-state",
|
|
219
|
+
"inactive",
|
|
220
|
+
);
|
|
221
|
+
// …but isInView still reflects what the observer reported.
|
|
222
|
+
expect(ref.current!.isInView(2)).toBe(true);
|
|
223
|
+
} finally {
|
|
224
|
+
delete (window as unknown as { onscrollsnapchange?: unknown })
|
|
225
|
+
.onscrollsnapchange;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should not let an IO callback reset the page while a programmatic scroll is in flight", async () => {
|
|
230
|
+
// Regression test: clicking NextTrigger calls next() which sets
|
|
231
|
+
// internalPage to 1 and triggers a scrollIntoView. If the IO callback
|
|
232
|
+
// fires before the scroll settles (slide-0 still ≥0.6 visible), the
|
|
233
|
+
// callback must not call goTo(0) and undo the navigation.
|
|
234
|
+
const user = userEvent.setup();
|
|
235
|
+
render(
|
|
236
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
237
|
+
<Carousel.Viewport>
|
|
238
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
239
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
240
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
241
|
+
</Carousel.Viewport>
|
|
242
|
+
<Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
243
|
+
</Carousel.Root>,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
247
|
+
|
|
248
|
+
// Simulate the IO callback firing mid-scroll: slide-0 still highly
|
|
249
|
+
// visible because the smooth-scroll animation hasn't finished yet.
|
|
250
|
+
const io = MockIntersectionObserver.latest!;
|
|
251
|
+
act(() => {
|
|
252
|
+
io.fire([
|
|
253
|
+
{
|
|
254
|
+
target: screen.getByTestId("slide-0"),
|
|
255
|
+
isIntersecting: true,
|
|
256
|
+
intersectionRatio: 0.9,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
target: screen.getByTestId("slide-1"),
|
|
260
|
+
isIntersecting: false,
|
|
261
|
+
intersectionRatio: 0.1,
|
|
262
|
+
},
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// The page must remain at 1, not be reset to 0.
|
|
267
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
268
|
+
"data-state",
|
|
269
|
+
"active",
|
|
270
|
+
);
|
|
271
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
272
|
+
"data-state",
|
|
273
|
+
"inactive",
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { Carousel } from "..";
|
|
5
|
+
|
|
6
|
+
describe("Carousel keyboard navigation", () => {
|
|
7
|
+
it("should advance the active page when ArrowRight is pressed with the viewport focused", async () => {
|
|
8
|
+
const user = userEvent.setup();
|
|
9
|
+
render(
|
|
10
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
11
|
+
<Carousel.Viewport data-testid="viewport">
|
|
12
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
13
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
14
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
15
|
+
</Carousel.Viewport>
|
|
16
|
+
</Carousel.Root>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Tab into the carousel — the Viewport must be in the tab order so
|
|
20
|
+
// keyboard users can reach the rotation control without first
|
|
21
|
+
// tabbing through every slide's interactive content.
|
|
22
|
+
await user.tab();
|
|
23
|
+
expect(screen.getByTestId("viewport")).toHaveFocus();
|
|
24
|
+
|
|
25
|
+
await user.keyboard("{ArrowRight}");
|
|
26
|
+
|
|
27
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
28
|
+
"data-state",
|
|
29
|
+
"active",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should retreat the active page when ArrowLeft is pressed with the viewport focused", async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
render(
|
|
36
|
+
<Carousel.Root ariaLabel="Featured products" defaultPage={2}>
|
|
37
|
+
<Carousel.Viewport data-testid="viewport">
|
|
38
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
39
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
40
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
41
|
+
</Carousel.Viewport>
|
|
42
|
+
</Carousel.Root>,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
await user.tab();
|
|
46
|
+
await user.keyboard("{ArrowLeft}");
|
|
47
|
+
|
|
48
|
+
expect(screen.getByTestId("slide-1")).toHaveAttribute(
|
|
49
|
+
"data-state",
|
|
50
|
+
"active",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should jump to the first page when Home is pressed with the viewport focused", async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
render(
|
|
57
|
+
<Carousel.Root ariaLabel="Featured products" defaultPage={2}>
|
|
58
|
+
<Carousel.Viewport data-testid="viewport">
|
|
59
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
60
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
61
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
62
|
+
</Carousel.Viewport>
|
|
63
|
+
</Carousel.Root>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await user.tab();
|
|
67
|
+
await user.keyboard("{Home}");
|
|
68
|
+
|
|
69
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
70
|
+
"data-state",
|
|
71
|
+
"active",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should jump to the last page when End is pressed with the viewport focused", async () => {
|
|
76
|
+
const user = userEvent.setup();
|
|
77
|
+
render(
|
|
78
|
+
<Carousel.Root ariaLabel="Featured products">
|
|
79
|
+
<Carousel.Viewport data-testid="viewport">
|
|
80
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
81
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
82
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
83
|
+
</Carousel.Viewport>
|
|
84
|
+
</Carousel.Root>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
await user.tab();
|
|
88
|
+
await user.keyboard("{End}");
|
|
89
|
+
|
|
90
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
91
|
+
"data-state",
|
|
92
|
+
"active",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should clamp at the boundaries when arrow keys are pressed", async () => {
|
|
97
|
+
function Fixture({ start }: { start: number }) {
|
|
98
|
+
return (
|
|
99
|
+
<Carousel.Root ariaLabel="Featured products" defaultPage={start}>
|
|
100
|
+
<Carousel.Viewport data-testid="viewport">
|
|
101
|
+
<Carousel.Slide data-testid="slide-0" />
|
|
102
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
103
|
+
<Carousel.Slide data-testid="slide-2" />
|
|
104
|
+
</Carousel.Viewport>
|
|
105
|
+
</Carousel.Root>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const user = userEvent.setup();
|
|
110
|
+
|
|
111
|
+
const { unmount } = render(<Fixture start={2} />);
|
|
112
|
+
await user.tab();
|
|
113
|
+
await user.keyboard("{ArrowRight}");
|
|
114
|
+
expect(screen.getByTestId("slide-2")).toHaveAttribute(
|
|
115
|
+
"data-state",
|
|
116
|
+
"active",
|
|
117
|
+
);
|
|
118
|
+
unmount();
|
|
119
|
+
|
|
120
|
+
render(<Fixture start={0} />);
|
|
121
|
+
await user.tab();
|
|
122
|
+
await user.keyboard("{ArrowLeft}");
|
|
123
|
+
expect(screen.getByTestId("slide-0")).toHaveAttribute(
|
|
124
|
+
"data-state",
|
|
125
|
+
"active",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should leave focus inside a slide alone — arrow keys don't navigate when a child element is the focus target", async () => {
|
|
130
|
+
const onPageChange = vi.fn();
|
|
131
|
+
const user = userEvent.setup();
|
|
132
|
+
render(
|
|
133
|
+
<Carousel.Root
|
|
134
|
+
ariaLabel="Featured products"
|
|
135
|
+
page={0}
|
|
136
|
+
onPageChange={onPageChange}
|
|
137
|
+
>
|
|
138
|
+
<Carousel.Viewport data-testid="viewport">
|
|
139
|
+
<Carousel.Slide>
|
|
140
|
+
<button data-testid="slide-link">Open</button>
|
|
141
|
+
</Carousel.Slide>
|
|
142
|
+
<Carousel.Slide data-testid="slide-1" />
|
|
143
|
+
</Carousel.Viewport>
|
|
144
|
+
</Carousel.Root>,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Tab past the Viewport onto the link inside slide-0.
|
|
148
|
+
await user.tab();
|
|
149
|
+
await user.tab();
|
|
150
|
+
expect(screen.getByTestId("slide-link")).toHaveFocus();
|
|
151
|
+
|
|
152
|
+
// ArrowRight while a slide-internal control has focus must keep its
|
|
153
|
+
// native semantics rather than steal the keypress for navigation.
|
|
154
|
+
await user.keyboard("{ArrowRight}");
|
|
155
|
+
|
|
156
|
+
expect(onPageChange).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import { Carousel } from "..";
|
|
6
|
+
|
|
7
|
+
describe("Carousel.PlayPauseTrigger basic shape", () => {
|
|
8
|
+
it("should render the PlayPauseTrigger as <button type='button'>", () => {
|
|
9
|
+
render(
|
|
10
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
11
|
+
<Carousel.PlayPauseTrigger />
|
|
12
|
+
</Carousel.Root>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const trigger = screen.getByRole("button");
|
|
16
|
+
expect(trigger).toBeVisible();
|
|
17
|
+
expect(trigger).toHaveAttribute("type", "button");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should accept a className prop", () => {
|
|
21
|
+
const testClass = "custom-play-pause";
|
|
22
|
+
render(
|
|
23
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
24
|
+
<Carousel.PlayPauseTrigger className={testClass} />
|
|
25
|
+
</Carousel.Root>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(screen.getByRole("button")).toHaveAttribute("class", testClass);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should apply a className of empty string by default when not specified", () => {
|
|
32
|
+
render(
|
|
33
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
34
|
+
<Carousel.PlayPauseTrigger />
|
|
35
|
+
</Carousel.Root>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(screen.getByRole("button")).toHaveAttribute("class", "");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should throw an error when used outside Carousel.Root", () => {
|
|
42
|
+
expect(() => {
|
|
43
|
+
render(<Carousel.PlayPauseTrigger />);
|
|
44
|
+
}).toThrow("Component must be rendered as a child of Carousel.Root");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Carousel.PlayPauseTrigger uncontrolled playing state", () => {
|
|
49
|
+
it("should default to playing=false (paused) and announce 'Start automatic slide show'", () => {
|
|
50
|
+
render(
|
|
51
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
52
|
+
<Carousel.PlayPauseTrigger />
|
|
53
|
+
</Carousel.Root>,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
screen.getByRole("button", { name: "Start automatic slide show" }),
|
|
58
|
+
).toBeVisible();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should accept defaultPlaying={true} to seed the initial playing state", () => {
|
|
62
|
+
render(
|
|
63
|
+
<Carousel.Root ariaLabel="Featured products" autoplay defaultPlaying>
|
|
64
|
+
<Carousel.PlayPauseTrigger />
|
|
65
|
+
</Carousel.Root>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(
|
|
69
|
+
screen.getByRole("button", { name: "Stop automatic slide show" }),
|
|
70
|
+
).toBeVisible();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should toggle playing when the trigger is clicked", async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
render(
|
|
76
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
77
|
+
<Carousel.PlayPauseTrigger />
|
|
78
|
+
</Carousel.Root>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
await user.click(
|
|
82
|
+
screen.getByRole("button", { name: "Start automatic slide show" }),
|
|
83
|
+
);
|
|
84
|
+
expect(
|
|
85
|
+
screen.getByRole("button", { name: "Stop automatic slide show" }),
|
|
86
|
+
).toBeVisible();
|
|
87
|
+
|
|
88
|
+
await user.click(
|
|
89
|
+
screen.getByRole("button", { name: "Stop automatic slide show" }),
|
|
90
|
+
);
|
|
91
|
+
expect(
|
|
92
|
+
screen.getByRole("button", { name: "Start automatic slide show" }),
|
|
93
|
+
).toBeVisible();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("Carousel.PlayPauseTrigger controlled playing state", () => {
|
|
98
|
+
it("should reflect the playing prop, not internal state", () => {
|
|
99
|
+
render(
|
|
100
|
+
<Carousel.Root
|
|
101
|
+
ariaLabel="Featured products"
|
|
102
|
+
autoplay
|
|
103
|
+
playing
|
|
104
|
+
onPlayingChange={() => {}}
|
|
105
|
+
>
|
|
106
|
+
<Carousel.PlayPauseTrigger />
|
|
107
|
+
</Carousel.Root>,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(
|
|
111
|
+
screen.getByRole("button", { name: "Stop automatic slide show" }),
|
|
112
|
+
).toBeVisible();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should call onPlayingChange when clicked, without updating internal state", async () => {
|
|
116
|
+
const user = userEvent.setup();
|
|
117
|
+
const onPlayingChange = vi.fn();
|
|
118
|
+
render(
|
|
119
|
+
<Carousel.Root
|
|
120
|
+
ariaLabel="Featured products"
|
|
121
|
+
autoplay
|
|
122
|
+
playing={false}
|
|
123
|
+
onPlayingChange={onPlayingChange}
|
|
124
|
+
>
|
|
125
|
+
<Carousel.PlayPauseTrigger />
|
|
126
|
+
</Carousel.Root>,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
await user.click(screen.getByRole("button"));
|
|
130
|
+
|
|
131
|
+
expect(onPlayingChange).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(onPlayingChange).toHaveBeenCalledWith(true);
|
|
133
|
+
// Parent has not re-rendered with a new playing prop, so the
|
|
134
|
+
// visible state must remain at the controlled value.
|
|
135
|
+
expect(
|
|
136
|
+
screen.getByRole("button", { name: "Start automatic slide show" }),
|
|
137
|
+
).toBeVisible();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should reflect external playing changes that don't go through the trigger", async () => {
|
|
141
|
+
const user = userEvent.setup();
|
|
142
|
+
function Parent() {
|
|
143
|
+
const [playing, setPlaying] = useState(false);
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<button type="button" onClick={() => setPlaying(true)}>
|
|
147
|
+
External play
|
|
148
|
+
</button>
|
|
149
|
+
<Carousel.Root
|
|
150
|
+
ariaLabel="Featured products"
|
|
151
|
+
autoplay
|
|
152
|
+
playing={playing}
|
|
153
|
+
onPlayingChange={setPlaying}
|
|
154
|
+
>
|
|
155
|
+
<Carousel.PlayPauseTrigger />
|
|
156
|
+
</Carousel.Root>
|
|
157
|
+
</>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
render(<Parent />);
|
|
162
|
+
|
|
163
|
+
await user.click(screen.getByRole("button", { name: "External play" }));
|
|
164
|
+
|
|
165
|
+
expect(
|
|
166
|
+
screen.getByRole("button", { name: "Stop automatic slide show" }),
|
|
167
|
+
).toBeVisible();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("Carousel.PlayPauseTrigger render-prop children", () => {
|
|
172
|
+
it("should pass { playing } to a function child and render its return value", () => {
|
|
173
|
+
render(
|
|
174
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
175
|
+
<Carousel.PlayPauseTrigger>
|
|
176
|
+
{({ playing }) => (playing ? "Pause slideshow" : "Start slideshow")}
|
|
177
|
+
</Carousel.PlayPauseTrigger>
|
|
178
|
+
</Carousel.Root>,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(screen.getByRole("button")).toHaveTextContent("Start slideshow");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should re-render the function child when playing flips", async () => {
|
|
185
|
+
const user = userEvent.setup();
|
|
186
|
+
render(
|
|
187
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
188
|
+
<Carousel.PlayPauseTrigger>
|
|
189
|
+
{({ playing }) => (playing ? "Pause slideshow" : "Start slideshow")}
|
|
190
|
+
</Carousel.PlayPauseTrigger>
|
|
191
|
+
</Carousel.Root>,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
await user.click(screen.getByRole("button"));
|
|
195
|
+
|
|
196
|
+
expect(screen.getByRole("button")).toHaveTextContent("Pause slideshow");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should render static children unchanged when no function is provided", () => {
|
|
200
|
+
render(
|
|
201
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
202
|
+
<Carousel.PlayPauseTrigger>
|
|
203
|
+
<span data-testid="play-pause-icon" />
|
|
204
|
+
</Carousel.PlayPauseTrigger>
|
|
205
|
+
</Carousel.Root>,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(screen.getByTestId("play-pause-icon")).toBeVisible();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("Carousel.PlayPauseTrigger styling hooks", () => {
|
|
213
|
+
it('should emit data-state="paused" when not playing', () => {
|
|
214
|
+
render(
|
|
215
|
+
<Carousel.Root ariaLabel="Featured products" autoplay>
|
|
216
|
+
<Carousel.PlayPauseTrigger />
|
|
217
|
+
</Carousel.Root>,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(screen.getByRole("button")).toHaveAttribute("data-state", "paused");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should emit data-state="playing" when playing', () => {
|
|
224
|
+
render(
|
|
225
|
+
<Carousel.Root ariaLabel="Featured products" autoplay defaultPlaying>
|
|
226
|
+
<Carousel.PlayPauseTrigger />
|
|
227
|
+
</Carousel.Root>,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(screen.getByRole("button")).toHaveAttribute("data-state", "playing");
|
|
231
|
+
});
|
|
232
|
+
});
|