@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,708 @@
|
|
|
1
|
+
import { forwardRef, MouseEvent, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
import { Slot } from "../Slot";
|
|
4
|
+
import { CarouselProvider } from "./CarouselContext";
|
|
5
|
+
import {
|
|
6
|
+
useCarouselContext,
|
|
7
|
+
useCarouselRoot,
|
|
8
|
+
useCarouselSlide,
|
|
9
|
+
useCarouselViewport,
|
|
10
|
+
} from "./hooks";
|
|
11
|
+
import type {
|
|
12
|
+
CarouselImperativeApi,
|
|
13
|
+
CarouselRootProps,
|
|
14
|
+
CarouselViewportProps,
|
|
15
|
+
CarouselSlideProps,
|
|
16
|
+
CarouselNextTriggerProps,
|
|
17
|
+
CarouselPreviousTriggerProps,
|
|
18
|
+
CarouselIndicatorGroupProps,
|
|
19
|
+
CarouselIndicatorProps,
|
|
20
|
+
CarouselIndicatorsProps,
|
|
21
|
+
CarouselPlayPauseTriggerProps,
|
|
22
|
+
} from "./types";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The root of a Carousel widget. Renders a `<section>` with
|
|
26
|
+
* `aria-roledescription="carousel"` so assistive technology announces
|
|
27
|
+
* the widget as a carousel rather than a generic region, per the
|
|
28
|
+
* WAI-ARIA Carousel pattern.
|
|
29
|
+
*
|
|
30
|
+
* Every carousel must have an accessible name. Pass exactly one of:
|
|
31
|
+
*
|
|
32
|
+
* - `ariaLabel` — a short human-readable description (e.g.
|
|
33
|
+
* `"Featured products"`).
|
|
34
|
+
* - `ariaLabelledBy` — the `id` of an existing heading or label element.
|
|
35
|
+
*
|
|
36
|
+
* The discriminated union on the props type rejects both-or-neither at
|
|
37
|
+
* compile time.
|
|
38
|
+
*
|
|
39
|
+
* Supports two **page-state modes**, statically discriminated at the type
|
|
40
|
+
* level so only one of the two shapes is accepted by TypeScript:
|
|
41
|
+
*
|
|
42
|
+
* - **Uncontrolled** — pass `defaultPage` (or omit it and start at `0`).
|
|
43
|
+
* The component owns and updates the active page internally.
|
|
44
|
+
* - **Controlled** — pass `page` *and* `onPageChange` together. The
|
|
45
|
+
* parent owns the active page; the component defers every state
|
|
46
|
+
* change back through the callback.
|
|
47
|
+
*
|
|
48
|
+
* @example Labelled inline, uncontrolled
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <Carousel.Root ariaLabel="Featured products" defaultPage={0}>…</Carousel.Root>
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Controlled
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const [page, setPage] = useState(0);
|
|
56
|
+
*
|
|
57
|
+
* <Carousel.Root
|
|
58
|
+
* ariaLabel="Featured products"
|
|
59
|
+
* page={page}
|
|
60
|
+
* onPageChange={setPage}
|
|
61
|
+
* >
|
|
62
|
+
* …
|
|
63
|
+
* </Carousel.Root>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example Labelled by an existing heading
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <h2 id="promos">Promotions</h2>
|
|
69
|
+
* <Carousel.Root ariaLabelledBy="promos">…</Carousel.Root>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export const CarouselRoot = forwardRef<
|
|
73
|
+
CarouselImperativeApi,
|
|
74
|
+
CarouselRootProps
|
|
75
|
+
>(function CarouselRoot(
|
|
76
|
+
{
|
|
77
|
+
className = "",
|
|
78
|
+
ariaLabel,
|
|
79
|
+
ariaLabelledBy,
|
|
80
|
+
defaultPage,
|
|
81
|
+
page,
|
|
82
|
+
onPageChange,
|
|
83
|
+
defaultPlaying,
|
|
84
|
+
playing,
|
|
85
|
+
onPlayingChange,
|
|
86
|
+
autoplay,
|
|
87
|
+
slidesPerPage,
|
|
88
|
+
slidesPerMove,
|
|
89
|
+
translations,
|
|
90
|
+
ids,
|
|
91
|
+
transition,
|
|
92
|
+
snapAlign,
|
|
93
|
+
children,
|
|
94
|
+
...rest
|
|
95
|
+
},
|
|
96
|
+
imperativeRef,
|
|
97
|
+
) {
|
|
98
|
+
const { contextValue, rootHandlers } = useCarouselRoot(
|
|
99
|
+
{
|
|
100
|
+
defaultPage,
|
|
101
|
+
page,
|
|
102
|
+
onPageChange,
|
|
103
|
+
defaultPlaying,
|
|
104
|
+
playing,
|
|
105
|
+
onPlayingChange,
|
|
106
|
+
autoplay,
|
|
107
|
+
slidesPerPage,
|
|
108
|
+
slidesPerMove,
|
|
109
|
+
translations,
|
|
110
|
+
ids,
|
|
111
|
+
transition,
|
|
112
|
+
snapAlign,
|
|
113
|
+
},
|
|
114
|
+
imperativeRef,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<CarouselProvider value={contextValue}>
|
|
119
|
+
<section
|
|
120
|
+
aria-roledescription="carousel"
|
|
121
|
+
className={className}
|
|
122
|
+
{...(ariaLabel !== undefined && { "aria-label": ariaLabel })}
|
|
123
|
+
{...(ariaLabelledBy !== undefined && {
|
|
124
|
+
"aria-labelledby": ariaLabelledBy,
|
|
125
|
+
})}
|
|
126
|
+
{...(contextValue.ids.root !== undefined && {
|
|
127
|
+
id: contextValue.ids.root,
|
|
128
|
+
})}
|
|
129
|
+
{...rootHandlers}
|
|
130
|
+
{...rest}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</section>
|
|
134
|
+
</CarouselProvider>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
CarouselRoot.displayName = "CarouselRoot";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The slide container — the scrollable surface that holds
|
|
142
|
+
* `Carousel.Slide` children. Rendered as a `<div>` with a
|
|
143
|
+
* `data-carousel-viewport` attribute that the recommended scroll-snap
|
|
144
|
+
* CSS targets (see this component's README for the recipe).
|
|
145
|
+
*
|
|
146
|
+
* The viewport must be rendered as a descendant of `Carousel.Root`;
|
|
147
|
+
* rendering it elsewhere throws a descriptive error so misuse surfaces
|
|
148
|
+
* during development rather than as silent ARIA breakage.
|
|
149
|
+
*
|
|
150
|
+
* **Live region.** The viewport is also the live region for slide
|
|
151
|
+
* changes: `aria-live="polite"` so paged navigation is announced to
|
|
152
|
+
* assistive tech, flipping to `aria-live="off"` while autoplay is
|
|
153
|
+
* actively rotating so screen readers don't get spammed with every
|
|
154
|
+
* tick.
|
|
155
|
+
*
|
|
156
|
+
* **Styling hooks.** `data-carousel-viewport` is set on the rendered
|
|
157
|
+
* element. The component ships no styles — apply your own scroll-snap
|
|
158
|
+
* recipe via this attribute.
|
|
159
|
+
*
|
|
160
|
+
* **Keyboard navigation.** The Viewport is in the tab order
|
|
161
|
+
* (`tabIndex={0}`) so keyboard users can reach the rotation control
|
|
162
|
+
* without first tabbing through every slide's interactive content.
|
|
163
|
+
* With the Viewport focused:
|
|
164
|
+
*
|
|
165
|
+
* - `ArrowRight` advances by one page (same as `Carousel.NextTrigger`).
|
|
166
|
+
* - `ArrowLeft` retreats by one page (same as `Carousel.PreviousTrigger`).
|
|
167
|
+
* - `Home` jumps to the first page.
|
|
168
|
+
* - `End` jumps to the last page.
|
|
169
|
+
*
|
|
170
|
+
* Arrow keys clamp at the boundaries, mirroring the trigger buttons.
|
|
171
|
+
* Keypresses are only intercepted when the Viewport itself is the focus
|
|
172
|
+
* target — focus inside a slide (e.g. on a link or form control) keeps
|
|
173
|
+
* its native arrow-key semantics.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```tsx
|
|
177
|
+
* <Carousel.Root ariaLabel="Featured products">
|
|
178
|
+
* <Carousel.Viewport>
|
|
179
|
+
* <Carousel.Slide>…</Carousel.Slide>
|
|
180
|
+
* </Carousel.Viewport>
|
|
181
|
+
* </Carousel.Root>
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function CarouselViewport({
|
|
185
|
+
className = "",
|
|
186
|
+
children,
|
|
187
|
+
...rest
|
|
188
|
+
}: CarouselViewportProps) {
|
|
189
|
+
const { isAutoRotating, ids } = useCarouselContext();
|
|
190
|
+
const { viewportRef, onKeyDown } = useCarouselViewport();
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<div
|
|
194
|
+
ref={viewportRef}
|
|
195
|
+
data-carousel-viewport=""
|
|
196
|
+
tabIndex={0}
|
|
197
|
+
className={className}
|
|
198
|
+
aria-live={isAutoRotating ? "off" : "polite"}
|
|
199
|
+
onKeyDown={onKeyDown}
|
|
200
|
+
{...(ids.viewport !== undefined && { id: ids.viewport })}
|
|
201
|
+
{...rest}
|
|
202
|
+
>
|
|
203
|
+
{children}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
CarouselViewport.displayName = "CarouselViewport";
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* An individual slide. Renders a `<div>` with `role="group"` and
|
|
212
|
+
* `aria-roledescription="slide"` per the WAI-ARIA Carousel pattern, so
|
|
213
|
+
* assistive technology announces each slide as a discrete group rather
|
|
214
|
+
* than a generic region.
|
|
215
|
+
*
|
|
216
|
+
* **Self-registration.** On mount, every slide registers itself with
|
|
217
|
+
* `Carousel.Root` via a callback ref. The Root maintains an ordered list
|
|
218
|
+
* of registered slide keys, which is how the slide knows its own
|
|
219
|
+
* zero-based `data-index` and how every slide receives the live
|
|
220
|
+
* `data-total` count. Slides may be added or removed at runtime; the
|
|
221
|
+
* indices and totals update automatically.
|
|
222
|
+
*
|
|
223
|
+
* **Auto-labelling.** Each slide is labelled `"N of M"` (e.g. `"1 of 3"`)
|
|
224
|
+
* using its live index and the live total — the format the WAI-ARIA
|
|
225
|
+
* Carousel APG example uses, and what most screen readers expect. Pass
|
|
226
|
+
* {@link CarouselSlideProps.ariaLabel | `ariaLabel`} to override the
|
|
227
|
+
* auto-label with a more meaningful description (e.g.
|
|
228
|
+
* `"Hand-picked for you"`).
|
|
229
|
+
*
|
|
230
|
+
* **Styling hooks.**
|
|
231
|
+
* - `data-carousel-slide` — CSS-targeting attribute (recommended scroll-snap
|
|
232
|
+
* recipe targets `[data-carousel-slide]`).
|
|
233
|
+
* - `data-index="N"` — the slide's zero-based position in registration order.
|
|
234
|
+
* - `data-total="N"` — the live total slide count.
|
|
235
|
+
*
|
|
236
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
237
|
+
* elsewhere throws a descriptive error.
|
|
238
|
+
*
|
|
239
|
+
* @example Auto-labelled
|
|
240
|
+
* ```tsx
|
|
241
|
+
* <Carousel.Viewport>
|
|
242
|
+
* <Carousel.Slide>First slide</Carousel.Slide>
|
|
243
|
+
* <Carousel.Slide>Second slide</Carousel.Slide>
|
|
244
|
+
* </Carousel.Viewport>
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @example Override the auto-label
|
|
248
|
+
* ```tsx
|
|
249
|
+
* <Carousel.Slide ariaLabel="Hand-picked for you">…</Carousel.Slide>
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
export function CarouselSlide({
|
|
253
|
+
className = "",
|
|
254
|
+
ariaLabel,
|
|
255
|
+
children,
|
|
256
|
+
...rest
|
|
257
|
+
}: CarouselSlideProps) {
|
|
258
|
+
const { slideRef, index, total, state } = useCarouselSlide();
|
|
259
|
+
const { translations } = useCarouselContext();
|
|
260
|
+
const autoLabel =
|
|
261
|
+
index >= 0 && total > 0
|
|
262
|
+
? translations.slideLabel({ index: index + 1, total })
|
|
263
|
+
: undefined;
|
|
264
|
+
const label = ariaLabel ?? autoLabel;
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<div
|
|
268
|
+
ref={slideRef}
|
|
269
|
+
role="group"
|
|
270
|
+
aria-roledescription="slide"
|
|
271
|
+
data-carousel-slide=""
|
|
272
|
+
data-index={index}
|
|
273
|
+
data-total={total}
|
|
274
|
+
data-state={state}
|
|
275
|
+
className={className}
|
|
276
|
+
{...(label !== undefined && { "aria-label": label })}
|
|
277
|
+
{...rest}
|
|
278
|
+
>
|
|
279
|
+
{children}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
CarouselSlide.displayName = "CarouselSlide";
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Advances the active page by one. Renders as
|
|
288
|
+
* `<button type="button">` and dispatches the consumer's `onClick`
|
|
289
|
+
* before invoking the navigation, so analytics handlers and similar
|
|
290
|
+
* still fire when the user advances the carousel.
|
|
291
|
+
*
|
|
292
|
+
* **Boundary clamping.** The trigger is `disabled` once the active page
|
|
293
|
+
* reaches the last slide; the click is also a no-op at boundaries
|
|
294
|
+
* because `next()` short-circuits when there's nowhere to go. The
|
|
295
|
+
* button is always disabled when there are zero or one slides
|
|
296
|
+
* registered. Consumer-supplied `disabled={true}` is also honoured (it
|
|
297
|
+
* OR's with the boundary check).
|
|
298
|
+
*
|
|
299
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
300
|
+
* elsewhere throws a descriptive error.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```tsx
|
|
304
|
+
* <Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export function CarouselNextTrigger({
|
|
308
|
+
className = "",
|
|
309
|
+
onClick,
|
|
310
|
+
disabled,
|
|
311
|
+
asChild = false,
|
|
312
|
+
children,
|
|
313
|
+
...rest
|
|
314
|
+
}: CarouselNextTriggerProps) {
|
|
315
|
+
const { next, canGoNext, ids } = useCarouselContext();
|
|
316
|
+
const isDisabled = disabled === true || !canGoNext;
|
|
317
|
+
|
|
318
|
+
const handleClick = useCallback(
|
|
319
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
320
|
+
onClick?.(event);
|
|
321
|
+
// Guard runs in addition to the HTML disabled attribute on the
|
|
322
|
+
// default <button> path. With asChild on a non-button element
|
|
323
|
+
// there's no native disabled to block the click, so this is the
|
|
324
|
+
// only barrier.
|
|
325
|
+
if (isDisabled) return;
|
|
326
|
+
next();
|
|
327
|
+
},
|
|
328
|
+
[next, onClick, isDisabled],
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const triggerProps = {
|
|
332
|
+
className,
|
|
333
|
+
onClick: handleClick,
|
|
334
|
+
disabled: isDisabled,
|
|
335
|
+
"aria-disabled": isDisabled,
|
|
336
|
+
...(ids.nextTrigger !== undefined && { id: ids.nextTrigger }),
|
|
337
|
+
...rest,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (asChild) {
|
|
341
|
+
return <Slot {...triggerProps}>{children}</Slot>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<button type="button" {...triggerProps}>
|
|
346
|
+
{children}
|
|
347
|
+
</button>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
CarouselNextTrigger.displayName = "CarouselNextTrigger";
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Retreats the active page by one. Renders as
|
|
355
|
+
* `<button type="button">` and dispatches the consumer's `onClick`
|
|
356
|
+
* before invoking the navigation, so analytics handlers and similar
|
|
357
|
+
* still fire when the user retreats the carousel.
|
|
358
|
+
*
|
|
359
|
+
* **Boundary clamping.** The trigger is `disabled` while the active
|
|
360
|
+
* page is the first slide; the click is also a no-op at boundaries
|
|
361
|
+
* because `previous()` short-circuits when there's nowhere to go. The
|
|
362
|
+
* button is always disabled when there are zero or one slides
|
|
363
|
+
* registered. Consumer-supplied `disabled={true}` is also honoured.
|
|
364
|
+
*
|
|
365
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
366
|
+
* elsewhere throws a descriptive error.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```tsx
|
|
370
|
+
* <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export function CarouselPreviousTrigger({
|
|
374
|
+
className = "",
|
|
375
|
+
onClick,
|
|
376
|
+
disabled,
|
|
377
|
+
asChild = false,
|
|
378
|
+
children,
|
|
379
|
+
...rest
|
|
380
|
+
}: CarouselPreviousTriggerProps) {
|
|
381
|
+
const { previous, canGoPrevious, ids } = useCarouselContext();
|
|
382
|
+
const isDisabled = disabled === true || !canGoPrevious;
|
|
383
|
+
|
|
384
|
+
const handleClick = useCallback(
|
|
385
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
386
|
+
onClick?.(event);
|
|
387
|
+
if (isDisabled) return;
|
|
388
|
+
previous();
|
|
389
|
+
},
|
|
390
|
+
[previous, onClick, isDisabled],
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const triggerProps = {
|
|
394
|
+
className,
|
|
395
|
+
onClick: handleClick,
|
|
396
|
+
disabled: isDisabled,
|
|
397
|
+
"aria-disabled": isDisabled,
|
|
398
|
+
...(ids.previousTrigger !== undefined && { id: ids.previousTrigger }),
|
|
399
|
+
...rest,
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
if (asChild) {
|
|
403
|
+
return <Slot {...triggerProps}>{children}</Slot>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
<button type="button" {...triggerProps}>
|
|
408
|
+
{children}
|
|
409
|
+
</button>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
CarouselPreviousTrigger.displayName = "CarouselPreviousTrigger";
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* A `<div role="group">` wrapping consumer-mapped `Carousel.Indicator`
|
|
417
|
+
* dots. Use this when you want manual control over how the dots are
|
|
418
|
+
* rendered (e.g. mixing custom content into each one). For the common
|
|
419
|
+
* case of one auto-rendered dot per page, prefer `Carousel.Indicators`.
|
|
420
|
+
*
|
|
421
|
+
* Every group must have an accessible name. Pass exactly one of:
|
|
422
|
+
*
|
|
423
|
+
* - `label` — a short human-readable description of the picker (e.g.
|
|
424
|
+
* `"Choose slide"`).
|
|
425
|
+
* - `ariaLabelledBy` — the `id` of an existing heading or label element.
|
|
426
|
+
*
|
|
427
|
+
* The discriminated union on the props type rejects both-or-neither at
|
|
428
|
+
* compile time.
|
|
429
|
+
*
|
|
430
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
431
|
+
* elsewhere throws a descriptive error.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```tsx
|
|
435
|
+
* <Carousel.IndicatorGroup label="Choose slide">
|
|
436
|
+
* <Carousel.Indicator index={0} />
|
|
437
|
+
* <Carousel.Indicator index={1} />
|
|
438
|
+
* <Carousel.Indicator index={2} />
|
|
439
|
+
* </Carousel.IndicatorGroup>
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
export function CarouselIndicatorGroup({
|
|
443
|
+
className = "",
|
|
444
|
+
label,
|
|
445
|
+
ariaLabelledBy,
|
|
446
|
+
children,
|
|
447
|
+
...rest
|
|
448
|
+
}: CarouselIndicatorGroupProps) {
|
|
449
|
+
const { ids } = useCarouselContext();
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<div
|
|
453
|
+
role="group"
|
|
454
|
+
className={className}
|
|
455
|
+
{...(label !== undefined && { "aria-label": label })}
|
|
456
|
+
{...(ariaLabelledBy !== undefined && {
|
|
457
|
+
"aria-labelledby": ariaLabelledBy,
|
|
458
|
+
})}
|
|
459
|
+
{...(ids.indicatorGroup !== undefined && { id: ids.indicatorGroup })}
|
|
460
|
+
{...rest}
|
|
461
|
+
>
|
|
462
|
+
{children}
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
CarouselIndicatorGroup.displayName = "CarouselIndicatorGroup";
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* An individual indicator dot — a `<button>` that jumps to the page at
|
|
471
|
+
* `index` (zero-based) when clicked. Auto-labelled `"Slide N"` (where
|
|
472
|
+
* `N = index + 1`) so the page-position is announced to assistive tech.
|
|
473
|
+
*
|
|
474
|
+
* **Active-state ARIA.** The indicator at `currentPage` carries
|
|
475
|
+
* `aria-disabled="true"` per the WAI-ARIA Carousel APG — a soft disable
|
|
476
|
+
* that tells screen readers "you're already on this slide" without
|
|
477
|
+
* removing it from the focus order. Non-active indicators carry
|
|
478
|
+
* `aria-disabled="false"`. Both states also flip `data-state` between
|
|
479
|
+
* `"active"` and `"inactive"` so consumer CSS can paint the active dot.
|
|
480
|
+
*
|
|
481
|
+
* **Styling hooks.**
|
|
482
|
+
* - `data-carousel-indicator` — CSS-targeting attribute.
|
|
483
|
+
* - `data-state="active" | "inactive"` — tracks the current page.
|
|
484
|
+
*
|
|
485
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
486
|
+
* elsewhere throws a descriptive error.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```tsx
|
|
490
|
+
* <Carousel.Indicator index={0} />
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
export function CarouselIndicator({
|
|
494
|
+
className = "",
|
|
495
|
+
index,
|
|
496
|
+
onClick,
|
|
497
|
+
asChild = false,
|
|
498
|
+
children,
|
|
499
|
+
...rest
|
|
500
|
+
}: CarouselIndicatorProps) {
|
|
501
|
+
const { goTo, currentPage, translations } = useCarouselContext();
|
|
502
|
+
const isActive = index === currentPage;
|
|
503
|
+
|
|
504
|
+
const handleClick = useCallback(
|
|
505
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
506
|
+
onClick?.(event);
|
|
507
|
+
goTo(index);
|
|
508
|
+
},
|
|
509
|
+
[goTo, index, onClick],
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const indicatorProps = {
|
|
513
|
+
className,
|
|
514
|
+
"aria-label": translations.indicatorLabel({ index: index + 1 }),
|
|
515
|
+
"aria-disabled": isActive,
|
|
516
|
+
"data-carousel-indicator": "",
|
|
517
|
+
"data-state": isActive ? "active" : "inactive",
|
|
518
|
+
onClick: handleClick,
|
|
519
|
+
...rest,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
if (asChild) {
|
|
523
|
+
return <Slot {...indicatorProps}>{children}</Slot>;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<button type="button" {...indicatorProps}>
|
|
528
|
+
{children}
|
|
529
|
+
</button>
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
CarouselIndicator.displayName = "CarouselIndicator";
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Convenience wrapper that auto-renders one `Carousel.Indicator` per
|
|
537
|
+
* registered slide — the "dots between Prev and Next" you'd reach for
|
|
538
|
+
* in 90% of carousels. Internally renders a `Carousel.IndicatorGroup`
|
|
539
|
+
* containing one `Carousel.Indicator` per entry in the slide-key list,
|
|
540
|
+
* keyed by the slide's stable `useId` so React doesn't shuffle the
|
|
541
|
+
* dots when slides mount or unmount in the middle of the range.
|
|
542
|
+
*
|
|
543
|
+
* For full control over indicator content (e.g. dots that show
|
|
544
|
+
* thumbnail previews on hover), drop down to
|
|
545
|
+
* `Carousel.IndicatorGroup` + `Carousel.Indicator` instead.
|
|
546
|
+
*
|
|
547
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
548
|
+
* elsewhere throws a descriptive error.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```tsx
|
|
552
|
+
* <Carousel.Indicators label="Choose slide" />
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
export function CarouselIndicators(props: CarouselIndicatorsProps) {
|
|
556
|
+
const { totalPages } = useCarouselContext();
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<CarouselIndicatorGroup {...props}>
|
|
560
|
+
{Array.from({ length: totalPages }, (_, index) => (
|
|
561
|
+
<CarouselIndicator key={index} index={index} />
|
|
562
|
+
))}
|
|
563
|
+
</CarouselIndicatorGroup>
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
CarouselIndicators.displayName = "CarouselIndicators";
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* A `<button>` that toggles the carousel's `playing` flag. Renders the
|
|
571
|
+
* accessible name dictated by the WAI-ARIA Carousel APG by default —
|
|
572
|
+
* `"Start automatic slide show"` when paused, `"Stop automatic slide
|
|
573
|
+
* show"` when playing — and exposes the live `playing` flag both as a
|
|
574
|
+
* `data-state="playing" | "paused"` styling hook and to a function
|
|
575
|
+
* `children` render prop, so consumers can swap icons or labels per
|
|
576
|
+
* state without re-implementing the toggle:
|
|
577
|
+
*
|
|
578
|
+
* ```tsx
|
|
579
|
+
* <Carousel.PlayPauseTrigger>
|
|
580
|
+
* {({ playing }) => (playing ? <PauseIcon /> : <PlayIcon />)}
|
|
581
|
+
* </Carousel.PlayPauseTrigger>
|
|
582
|
+
* ```
|
|
583
|
+
*
|
|
584
|
+
* Static children also work — useful when you want a single icon and
|
|
585
|
+
* style it via `[data-state]` selectors.
|
|
586
|
+
*
|
|
587
|
+
* Must be rendered as a descendant of `Carousel.Root`; rendering it
|
|
588
|
+
* elsewhere throws a descriptive error. The autoplay timer that
|
|
589
|
+
* advances the page when `playing` flips to `true` lands in cycle 12.
|
|
590
|
+
*/
|
|
591
|
+
export function CarouselPlayPauseTrigger({
|
|
592
|
+
className = "",
|
|
593
|
+
onClick,
|
|
594
|
+
asChild = false,
|
|
595
|
+
children,
|
|
596
|
+
...rest
|
|
597
|
+
}: CarouselPlayPauseTriggerProps) {
|
|
598
|
+
const { playing, togglePlaying, translations, ids, autoplayEnabled } =
|
|
599
|
+
useCarouselContext();
|
|
600
|
+
|
|
601
|
+
if (!autoplayEnabled) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
"Carousel.PlayPauseTrigger requires autoplay to be enabled on Carousel.Root",
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const handleClick = useCallback(
|
|
608
|
+
(event: MouseEvent<HTMLButtonElement>) => {
|
|
609
|
+
onClick?.(event);
|
|
610
|
+
togglePlaying();
|
|
611
|
+
},
|
|
612
|
+
[onClick, togglePlaying],
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const renderedChildren =
|
|
616
|
+
typeof children === "function" ? children({ playing }) : children;
|
|
617
|
+
|
|
618
|
+
const triggerProps = {
|
|
619
|
+
className,
|
|
620
|
+
"aria-label": playing
|
|
621
|
+
? translations.stopSlideshow
|
|
622
|
+
: translations.startSlideshow,
|
|
623
|
+
"data-state": playing ? "playing" : "paused",
|
|
624
|
+
onClick: handleClick,
|
|
625
|
+
...(ids.playPauseTrigger !== undefined && { id: ids.playPauseTrigger }),
|
|
626
|
+
...rest,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
if (asChild) {
|
|
630
|
+
return <Slot {...triggerProps}>{renderedChildren}</Slot>;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return (
|
|
634
|
+
<button type="button" {...triggerProps}>
|
|
635
|
+
{renderedChildren}
|
|
636
|
+
</button>
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
CarouselPlayPauseTrigger.displayName = "CarouselPlayPauseTrigger";
|
|
641
|
+
|
|
642
|
+
type CarouselCompound = typeof CarouselRoot & {
|
|
643
|
+
Root: typeof CarouselRoot;
|
|
644
|
+
Viewport: typeof CarouselViewport;
|
|
645
|
+
Slide: typeof CarouselSlide;
|
|
646
|
+
NextTrigger: typeof CarouselNextTrigger;
|
|
647
|
+
PreviousTrigger: typeof CarouselPreviousTrigger;
|
|
648
|
+
IndicatorGroup: typeof CarouselIndicatorGroup;
|
|
649
|
+
Indicator: typeof CarouselIndicator;
|
|
650
|
+
Indicators: typeof CarouselIndicators;
|
|
651
|
+
PlayPauseTrigger: typeof CarouselPlayPauseTrigger;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Headless, accessible **Carousel** — a compound component implementing the
|
|
656
|
+
* WAI-ARIA Carousel pattern with zero styles.
|
|
657
|
+
*
|
|
658
|
+
* `Carousel` is both callable (it's an alias of {@link CarouselRoot |
|
|
659
|
+
* `Carousel.Root`}) and carries its sub-components as static properties.
|
|
660
|
+
* Prefer the namespaced form in application code for readability:
|
|
661
|
+
*
|
|
662
|
+
* - {@link CarouselRoot | `Carousel.Root`} — the labelled `<section>`
|
|
663
|
+
* that wraps the entire widget.
|
|
664
|
+
* - {@link CarouselViewport | `Carousel.Viewport`} — the slide
|
|
665
|
+
* container that the recommended scroll-snap CSS targets.
|
|
666
|
+
* - {@link CarouselSlide | `Carousel.Slide`} — an individual slide,
|
|
667
|
+
* self-registering with the Root for live index / total tracking.
|
|
668
|
+
* - {@link CarouselNextTrigger | `Carousel.NextTrigger`} — advances
|
|
669
|
+
* the active page by one.
|
|
670
|
+
* - {@link CarouselPreviousTrigger | `Carousel.PreviousTrigger`} —
|
|
671
|
+
* retreats the active page by one.
|
|
672
|
+
* - {@link CarouselIndicatorGroup | `Carousel.IndicatorGroup`} — a
|
|
673
|
+
* labelled `<div role="group">` for consumer-mapped dot indicators.
|
|
674
|
+
* - {@link CarouselIndicator | `Carousel.Indicator`} — an individual
|
|
675
|
+
* `<button>` that jumps to a target page when clicked.
|
|
676
|
+
* - {@link CarouselIndicators | `Carousel.Indicators`} — convenience
|
|
677
|
+
* wrapper that auto-renders one indicator per registered slide.
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```tsx
|
|
681
|
+
* import { Carousel } from "@primitiv-ui/react";
|
|
682
|
+
*
|
|
683
|
+
* <Carousel.Root ariaLabel="Featured products">
|
|
684
|
+
* <Carousel.Viewport>
|
|
685
|
+
* <Carousel.Slide>First</Carousel.Slide>
|
|
686
|
+
* <Carousel.Slide>Second</Carousel.Slide>
|
|
687
|
+
* </Carousel.Viewport>
|
|
688
|
+
* <Carousel.PreviousTrigger>Previous</Carousel.PreviousTrigger>
|
|
689
|
+
* <Carousel.Indicators label="Choose slide" />
|
|
690
|
+
* <Carousel.NextTrigger>Next</Carousel.NextTrigger>
|
|
691
|
+
* </Carousel.Root>
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
const CarouselCompound: CarouselCompound = Object.assign(CarouselRoot, {
|
|
695
|
+
Root: CarouselRoot,
|
|
696
|
+
Viewport: CarouselViewport,
|
|
697
|
+
Slide: CarouselSlide,
|
|
698
|
+
NextTrigger: CarouselNextTrigger,
|
|
699
|
+
PreviousTrigger: CarouselPreviousTrigger,
|
|
700
|
+
IndicatorGroup: CarouselIndicatorGroup,
|
|
701
|
+
Indicator: CarouselIndicator,
|
|
702
|
+
Indicators: CarouselIndicators,
|
|
703
|
+
PlayPauseTrigger: CarouselPlayPauseTrigger,
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
CarouselCompound.displayName = "Carousel";
|
|
707
|
+
|
|
708
|
+
export { CarouselCompound as Carousel };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
import type { CarouselContextValue } from "./types";
|
|
4
|
+
|
|
5
|
+
export const CarouselContext = createContext<CarouselContextValue | null>(null);
|
|
6
|
+
|
|
7
|
+
CarouselContext.displayName = "CarouselContext";
|
|
8
|
+
|
|
9
|
+
const CarouselProvider = CarouselContext.Provider;
|
|
10
|
+
|
|
11
|
+
export { CarouselProvider };
|