@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,15 @@
|
|
|
1
|
+
import { createContext, RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Content-scoped context provided by {@link DropdownContent} and
|
|
5
|
+
* {@link DropdownSubContent}. Holds a ref to a single "currently open
|
|
6
|
+
* direct-child sub" close callback so that sibling items can dismiss the
|
|
7
|
+
* open sub on hover, mirroring the keyboard contract (focus returning to
|
|
8
|
+
* the parent menu closes the sub).
|
|
9
|
+
*/
|
|
10
|
+
export type DropdownContentContextValue = {
|
|
11
|
+
closeOpenSubRef: RefObject<(() => void) | null>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const DropdownContentContext =
|
|
15
|
+
createContext<DropdownContentContextValue | null>(null);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import { Direction } from "../DirectionProvider";
|
|
4
|
+
import { createStrictContext } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type DropdownContextValue = {
|
|
7
|
+
open: boolean;
|
|
8
|
+
setOpen: (open: boolean) => void;
|
|
9
|
+
contentId: string;
|
|
10
|
+
triggerRef: RefObject<HTMLButtonElement | null>;
|
|
11
|
+
dir: Direction;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const [DropdownContext, useDropdownContext] =
|
|
15
|
+
createStrictContext<DropdownContextValue>(
|
|
16
|
+
"Dropdown sub-components must be rendered inside a <Dropdown.Root>.",
|
|
17
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provided by {@link DropdownCheckboxItem} and {@link DropdownRadioItem}.
|
|
5
|
+
* {@link DropdownItemIndicator} reads it to decide whether to render and
|
|
6
|
+
* what `data-state` to expose.
|
|
7
|
+
*/
|
|
8
|
+
export type DropdownItemIndicatorContextValue = {
|
|
9
|
+
checked: boolean | "indeterminate";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const DropdownItemIndicatorContext =
|
|
13
|
+
createContext<DropdownItemIndicatorContextValue | null>(null);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type DropdownRadioGroupContextValue = {
|
|
4
|
+
value: string | undefined;
|
|
5
|
+
select: (value: string) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const DropdownRadioGroupContext =
|
|
9
|
+
createContext<DropdownRadioGroupContextValue | null>(null);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import { createStrictContext } from "../utils";
|
|
4
|
+
|
|
5
|
+
export type DropdownSubContextValue = {
|
|
6
|
+
open: boolean;
|
|
7
|
+
setOpen: (open: boolean) => void;
|
|
8
|
+
contentId: string;
|
|
9
|
+
triggerRef: RefObject<HTMLLIElement | null>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const [DropdownSubContext, useDropdownSubContext] =
|
|
13
|
+
createStrictContext<DropdownSubContextValue>(
|
|
14
|
+
"Dropdown.SubTrigger and Dropdown.SubContent must be rendered inside a <Dropdown.Sub>.",
|
|
15
|
+
);
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Dropdown
|
|
2
|
+
|
|
3
|
+
A compound component implementing the
|
|
4
|
+
[WAI-ARIA Menu Button / Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/)
|
|
5
|
+
on top of the native HTML
|
|
6
|
+
[Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API).
|
|
7
|
+
No portal, no floating-ui — the browser handles layering and light-dismiss.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Dropdown } from "@primitiv-ui/react";
|
|
11
|
+
|
|
12
|
+
<Dropdown.Root>
|
|
13
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
14
|
+
<Dropdown.Content>
|
|
15
|
+
<Dropdown.Item onSelect={() => rename()}>Rename</Dropdown.Item>
|
|
16
|
+
<Dropdown.Item onSelect={() => duplicate()}>Duplicate</Dropdown.Item>
|
|
17
|
+
<Dropdown.Separator />
|
|
18
|
+
<Dropdown.Item disabled>Archive</Dropdown.Item>
|
|
19
|
+
</Dropdown.Content>
|
|
20
|
+
</Dropdown.Root>;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Sub-components
|
|
24
|
+
|
|
25
|
+
| Export | Role | Notes |
|
|
26
|
+
| ------------------------ | -------------------- | ------------------------------------------------------------------------------------ |
|
|
27
|
+
| `Dropdown.Root` | State owner | Uncontrolled (`defaultOpen`) or controlled (`open` + `onOpenChange`) |
|
|
28
|
+
| `Dropdown.Trigger` | `aria-haspopup=menu` | Toggles the menu; supports `asChild` |
|
|
29
|
+
| `Dropdown.Content` | `menu` | Native `popover="auto"`; handles arrow keys, typeahead, Escape |
|
|
30
|
+
| `Dropdown.Item` | `menuitem` | Activatable row with `onSelect` escape hatch |
|
|
31
|
+
| `Dropdown.CheckboxItem` | `menuitemcheckbox` | Tri-state toggle (`true` / `false` / `"indeterminate"`) |
|
|
32
|
+
| `Dropdown.RadioGroup` | `group` | Single-selection container for `RadioItem`s |
|
|
33
|
+
| `Dropdown.RadioItem` | `menuitemradio` | Must live inside a `RadioGroup` |
|
|
34
|
+
| `Dropdown.ItemIndicator` | — | Icon slot inside a `CheckboxItem` / `RadioItem`; exposes `data-state` + `forceMount` |
|
|
35
|
+
| `Dropdown.Label` | — | Non-interactive label; auto-wired to the enclosing `Group` via `aria-labelledby` |
|
|
36
|
+
| `Dropdown.Group` | `group` | Semantic grouping for related items |
|
|
37
|
+
| `Dropdown.Separator` | `separator` | Visual divider; skipped by focus and typeahead |
|
|
38
|
+
| `Dropdown.Sub` | State owner | Submenu boundary; same state modes as `Root` |
|
|
39
|
+
| `Dropdown.SubTrigger` | `menuitem` | Opens the submenu on click or the inline-forward arrow (`ArrowRight` LTR / `ArrowLeft` RTL) |
|
|
40
|
+
| `Dropdown.SubContent` | `menu` | Submenu panel; the inline-backward arrow (`ArrowLeft` LTR / `ArrowRight` RTL) closes it and returns focus to the trigger |
|
|
41
|
+
|
|
42
|
+
All sub-components that render an element accept `asChild` to compose
|
|
43
|
+
their ARIA and behaviour onto a caller-supplied child.
|
|
44
|
+
|
|
45
|
+
## Keyboard interaction
|
|
46
|
+
|
|
47
|
+
| Key | Behaviour |
|
|
48
|
+
| ----------------------- | ------------------------------------------------- |
|
|
49
|
+
| `ArrowDown` / `ArrowUp` | Move focus to next / previous item (wraps) |
|
|
50
|
+
| `Home` / `End` | Jump to first / last enabled item |
|
|
51
|
+
| `Enter` / `Space` | Activate the focused item |
|
|
52
|
+
| `Escape` | Close the menu and return focus to the trigger |
|
|
53
|
+
| any printable character | Typeahead — focuses the next item matching prefix |
|
|
54
|
+
| `ArrowRight` | Open a focused submenu (`SubTrigger`, LTR) / close the submenu (inside `SubContent`, RTL) |
|
|
55
|
+
| `ArrowLeft` | Close the current submenu (inside `SubContent`, LTR) / open a focused submenu (`SubTrigger`, RTL) |
|
|
56
|
+
|
|
57
|
+
Typeahead accumulates keystrokes within a 500 ms window; pressing the
|
|
58
|
+
same character repeatedly cycles through items sharing that first letter.
|
|
59
|
+
Disabled items are skipped by arrow navigation, typeahead, and activation.
|
|
60
|
+
|
|
61
|
+
## Reading direction
|
|
62
|
+
|
|
63
|
+
Pass `dir="ltr"` or `dir="rtl"` on `Dropdown.Root` to invert the submenu
|
|
64
|
+
open / close arrow keys. When omitted, the component reads the inherited
|
|
65
|
+
`DirectionProvider` value, falling back to `"ltr"`:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<DirectionProvider dir="rtl">
|
|
69
|
+
<Dropdown.Root>{/* submenus now open on ArrowLeft */}</Dropdown.Root>
|
|
70
|
+
</DirectionProvider>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
An explicit `dir` prop on `Dropdown.Root` always wins over the inherited
|
|
74
|
+
value. The reading direction only affects keyboard handling — submenu
|
|
75
|
+
visual placement is the consumer's CSS concern (use logical properties
|
|
76
|
+
or `position-try-fallbacks` with `flip-inline`).
|
|
77
|
+
|
|
78
|
+
## State modes
|
|
79
|
+
|
|
80
|
+
- **Uncontrolled** — pass `defaultOpen` (or omit to start closed). Optional
|
|
81
|
+
`onOpenChange` observes user-driven transitions.
|
|
82
|
+
- **Controlled** — pass `open` and `onOpenChange` together.
|
|
83
|
+
|
|
84
|
+
`Dropdown.Sub` follows the same contract for its own open state.
|
|
85
|
+
|
|
86
|
+
`Dropdown.CheckboxItem` and `Dropdown.RadioGroup` each expose the same
|
|
87
|
+
uncontrolled / controlled split for their `checked` / `value` state.
|
|
88
|
+
|
|
89
|
+
## The `onSelect` escape hatch
|
|
90
|
+
|
|
91
|
+
Every activatable item (`Item`, `CheckboxItem`, `RadioItem`) fires
|
|
92
|
+
`onSelect` with a cancellable `Event` on activation. By default the menu
|
|
93
|
+
auto-closes; call `event.preventDefault()` to keep it open — useful for
|
|
94
|
+
rapidly toggling multiple checkboxes, or running an action whose outcome
|
|
95
|
+
is shown inline in the menu.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<Dropdown.CheckboxItem
|
|
99
|
+
onSelect={(event) => event.preventDefault()}
|
|
100
|
+
onCheckedChange={setGridVisible}
|
|
101
|
+
>
|
|
102
|
+
Show grid
|
|
103
|
+
</Dropdown.CheckboxItem>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Disabled items
|
|
107
|
+
|
|
108
|
+
Disabled items receive `aria-disabled="true"` rather than the native
|
|
109
|
+
`disabled` attribute, so they remain visible to assistive technology but
|
|
110
|
+
no-op on activation. Arrow navigation and typeahead skip them.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<Dropdown.Item disabled>Archive (coming soon)</Dropdown.Item>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A disabled `SubTrigger` refuses to open on both click and the
|
|
117
|
+
inline-forward arrow key.
|
|
118
|
+
|
|
119
|
+
## Checkbox and radio items
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<Dropdown.Content>
|
|
123
|
+
<Dropdown.Label>View</Dropdown.Label>
|
|
124
|
+
<Dropdown.CheckboxItem defaultChecked>Show grid</Dropdown.CheckboxItem>
|
|
125
|
+
<Dropdown.CheckboxItem>Show ruler</Dropdown.CheckboxItem>
|
|
126
|
+
<Dropdown.Separator />
|
|
127
|
+
<Dropdown.RadioGroup defaultValue="system">
|
|
128
|
+
<Dropdown.RadioItem value="light">Light</Dropdown.RadioItem>
|
|
129
|
+
<Dropdown.RadioItem value="dark">Dark</Dropdown.RadioItem>
|
|
130
|
+
<Dropdown.RadioItem value="system">Match system</Dropdown.RadioItem>
|
|
131
|
+
</Dropdown.RadioGroup>
|
|
132
|
+
</Dropdown.Content>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`CheckboxItem` supports a tri-state: `true`, `false`, or `"indeterminate"`
|
|
136
|
+
(which renders as `aria-checked="mixed"`). An indeterminate item resolves
|
|
137
|
+
to `true` on the next activation, matching the native
|
|
138
|
+
`<input type="checkbox">` contract.
|
|
139
|
+
|
|
140
|
+
### `ItemIndicator`
|
|
141
|
+
|
|
142
|
+
Render the visible mark (usually a checkmark or a bullet) inside the
|
|
143
|
+
item via `Dropdown.ItemIndicator`. It defaults to a `<span>`, supports
|
|
144
|
+
`asChild` so consumers can compose onto an SVG icon, and exposes
|
|
145
|
+
`data-state` for styling:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<Dropdown.CheckboxItem
|
|
149
|
+
checked={showBookmarks}
|
|
150
|
+
onCheckedChange={setShowBookmarks}
|
|
151
|
+
>
|
|
152
|
+
<Dropdown.ItemIndicator>
|
|
153
|
+
<CheckIcon />
|
|
154
|
+
</Dropdown.ItemIndicator>
|
|
155
|
+
Show bookmarks
|
|
156
|
+
</Dropdown.CheckboxItem>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| `data-state` | When |
|
|
160
|
+
| ----------------- | ----------------------------------------------------------------------------------- |
|
|
161
|
+
| `"checked"` | Parent `CheckboxItem` is `true`, or parent `RadioItem` is the group's current value |
|
|
162
|
+
| `"unchecked"` | Parent is `false` (only reachable when `forceMount` is set — see below) |
|
|
163
|
+
| `"indeterminate"` | Parent `CheckboxItem` is `"indeterminate"` |
|
|
164
|
+
|
|
165
|
+
By default the indicator **unmounts** when its parent is unchecked. Pass
|
|
166
|
+
`forceMount` to keep the DOM node in both states so CSS transitions or a
|
|
167
|
+
React animation library can drive the visual state off `data-state`:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Dropdown.ItemIndicator forceMount>
|
|
171
|
+
<CheckIcon className="indicator" /> {/* fade in/out via data-state */}
|
|
172
|
+
</Dropdown.ItemIndicator>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Rendering `Dropdown.ItemIndicator` outside a `CheckboxItem` or `RadioItem`
|
|
176
|
+
throws a descriptive error.
|
|
177
|
+
|
|
178
|
+
## Submenus
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<Dropdown.Content>
|
|
182
|
+
<Dropdown.Item>New</Dropdown.Item>
|
|
183
|
+
<Dropdown.Sub>
|
|
184
|
+
<Dropdown.SubTrigger>Open Recent</Dropdown.SubTrigger>
|
|
185
|
+
<Dropdown.SubContent>
|
|
186
|
+
<Dropdown.Item>Project A</Dropdown.Item>
|
|
187
|
+
<Dropdown.Item>Project B</Dropdown.Item>
|
|
188
|
+
</Dropdown.SubContent>
|
|
189
|
+
</Dropdown.Sub>
|
|
190
|
+
</Dropdown.Content>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Open a submenu with the inline-forward arrow key (`ArrowRight` in LTR,
|
|
194
|
+
`ArrowLeft` in RTL) or a click on the trigger; close it with the
|
|
195
|
+
inline-backward arrow or by selecting an item. Focus returns to the
|
|
196
|
+
`SubTrigger` when the submenu closes.
|
|
197
|
+
|
|
198
|
+
Hovering the `SubTrigger` opens the submenu automatically; hovering onto
|
|
199
|
+
a sibling item in the parent menu closes it, mirroring the keyboard
|
|
200
|
+
contract in which focus returning to the parent menu dismisses the sub.
|
|
201
|
+
|
|
202
|
+
Arrow-key navigation is scoped to the popover that currently holds
|
|
203
|
+
focus. Items inside a `SubContent` aren't pulled into the parent's
|
|
204
|
+
navigation cycle, so `ArrowDown` from a `SubTrigger` lands on the next
|
|
205
|
+
sibling item in the parent menu — not on the first item inside a closed
|
|
206
|
+
submenu, which is unfocusable. While focus lives inside an open
|
|
207
|
+
`SubContent`, `ArrowDown` and `ArrowUp` wrap within that sub's items.
|
|
208
|
+
|
|
209
|
+
## Groups and labels
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<Dropdown.Content>
|
|
213
|
+
<Dropdown.Group>
|
|
214
|
+
<Dropdown.Label>File</Dropdown.Label>
|
|
215
|
+
<Dropdown.Item>New</Dropdown.Item>
|
|
216
|
+
<Dropdown.Item>Open…</Dropdown.Item>
|
|
217
|
+
</Dropdown.Group>
|
|
218
|
+
<Dropdown.Separator />
|
|
219
|
+
<Dropdown.Group>
|
|
220
|
+
<Dropdown.Label>Edit</Dropdown.Label>
|
|
221
|
+
<Dropdown.Item>Undo</Dropdown.Item>
|
|
222
|
+
</Dropdown.Group>
|
|
223
|
+
</Dropdown.Content>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Nesting a `Dropdown.Label` inside a `Dropdown.Group` wires the group's
|
|
227
|
+
`aria-labelledby` to the label automatically — no manual `id` threading
|
|
228
|
+
is needed.
|
|
229
|
+
|
|
230
|
+
## `asChild` composition
|
|
231
|
+
|
|
232
|
+
Every rendering sub-component accepts `asChild` to compose its
|
|
233
|
+
ARIA attributes, event handlers, and ref onto a caller-supplied child.
|
|
234
|
+
All ARIA attributes and handlers merge onto the child following the
|
|
235
|
+
[Slot](../Slot.tsx) composition rules (child handler runs first, then
|
|
236
|
+
the component's):
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<Dropdown.Trigger asChild>
|
|
240
|
+
<MyStyledButton>Options</MyStyledButton>
|
|
241
|
+
</Dropdown.Trigger>
|
|
242
|
+
|
|
243
|
+
<Dropdown.Item asChild>
|
|
244
|
+
<a href="/rename">Rename</a>
|
|
245
|
+
</Dropdown.Item>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Styling hooks
|
|
249
|
+
|
|
250
|
+
```css
|
|
251
|
+
/* Open state on the menu panel */
|
|
252
|
+
[role="menu"][data-popover-open] {
|
|
253
|
+
animation: fade-in 120ms ease-out;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Highlighted item — pointer focus */
|
|
257
|
+
[role="menuitem"][data-highlighted],
|
|
258
|
+
[role="menuitemcheckbox"][data-highlighted],
|
|
259
|
+
[role="menuitemradio"][data-highlighted] {
|
|
260
|
+
background: rgba(0 0 0 / 0.06);
|
|
261
|
+
outline: none;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Disabled items */
|
|
265
|
+
[aria-disabled="true"] {
|
|
266
|
+
opacity: 0.5;
|
|
267
|
+
pointer-events: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Checked state for checkbox / radio items */
|
|
271
|
+
[role="menuitemcheckbox"][aria-checked="true"]::before,
|
|
272
|
+
[role="menuitemradio"][aria-checked="true"]::before {
|
|
273
|
+
content: "✓";
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The native popover API adds `data-popover-open` on the element while the
|
|
278
|
+
popover is showing; combine it with the standard ARIA attributes for
|
|
279
|
+
state-driven styling.
|
|
280
|
+
|
|
281
|
+
`data-highlighted` is present on `Item`, `CheckboxItem`, `RadioItem`, and
|
|
282
|
+
`SubTrigger` while the item has pointer focus (mouseenter). On `SubTrigger`
|
|
283
|
+
it also remains present for the duration its sub-menu is open, so the
|
|
284
|
+
active path stays highlighted as the user navigates nested levels.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { Dropdown } from "../Dropdown";
|
|
5
|
+
|
|
6
|
+
describe("Dropdown asChild", () => {
|
|
7
|
+
it("delegates Trigger to the child element via asChild, preserving ARIA wiring", async () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const user = userEvent.setup();
|
|
10
|
+
render(
|
|
11
|
+
<Dropdown.Root>
|
|
12
|
+
<Dropdown.Trigger asChild>
|
|
13
|
+
<a href="#menu" data-testid="custom-trigger">
|
|
14
|
+
Options
|
|
15
|
+
</a>
|
|
16
|
+
</Dropdown.Trigger>
|
|
17
|
+
<Dropdown.Content>
|
|
18
|
+
<Dropdown.Item>Rename</Dropdown.Item>
|
|
19
|
+
</Dropdown.Content>
|
|
20
|
+
</Dropdown.Root>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Assert — Trigger renders as the <a>, not a <button>
|
|
24
|
+
const trigger = screen.getByTestId("custom-trigger");
|
|
25
|
+
expect(trigger.tagName).toBe("A");
|
|
26
|
+
expect(trigger).toHaveAttribute("aria-haspopup", "menu");
|
|
27
|
+
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
await user.click(trigger);
|
|
31
|
+
|
|
32
|
+
// Assert
|
|
33
|
+
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("delegates Content to the child element via asChild", () => {
|
|
37
|
+
// Arrange & Act
|
|
38
|
+
render(
|
|
39
|
+
<Dropdown.Root defaultOpen>
|
|
40
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
41
|
+
<Dropdown.Content asChild>
|
|
42
|
+
<div data-testid="custom-content">
|
|
43
|
+
<Dropdown.Item>Rename</Dropdown.Item>
|
|
44
|
+
</div>
|
|
45
|
+
</Dropdown.Content>
|
|
46
|
+
</Dropdown.Root>,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
const content = screen.getByTestId("custom-content");
|
|
51
|
+
expect(content.tagName).toBe("DIV");
|
|
52
|
+
expect(content).toHaveAttribute("role", "menu");
|
|
53
|
+
expect(content).toHaveAttribute("popover", "auto");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("delegates Item to the child element via asChild and still auto-closes on click", async () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
const user = userEvent.setup();
|
|
59
|
+
const onSelect = vi.fn();
|
|
60
|
+
render(
|
|
61
|
+
<Dropdown.Root defaultOpen>
|
|
62
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
63
|
+
<Dropdown.Content>
|
|
64
|
+
<Dropdown.Item asChild onSelect={onSelect}>
|
|
65
|
+
<a href="#rename" data-testid="custom-item">
|
|
66
|
+
Rename
|
|
67
|
+
</a>
|
|
68
|
+
</Dropdown.Item>
|
|
69
|
+
</Dropdown.Content>
|
|
70
|
+
</Dropdown.Root>,
|
|
71
|
+
);
|
|
72
|
+
const item = screen.getByTestId("custom-item");
|
|
73
|
+
expect(item.tagName).toBe("A");
|
|
74
|
+
expect(item).toHaveAttribute("role", "menuitem");
|
|
75
|
+
const menu = screen.getByRole("menu", { hidden: true });
|
|
76
|
+
expect(menu).toHaveAttribute("data-popover-open");
|
|
77
|
+
|
|
78
|
+
// Act
|
|
79
|
+
await user.click(item);
|
|
80
|
+
|
|
81
|
+
// Assert
|
|
82
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(menu).not.toHaveAttribute("data-popover-open");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("delegates Separator to the child element via asChild", () => {
|
|
87
|
+
// Arrange & Act
|
|
88
|
+
render(
|
|
89
|
+
<Dropdown.Root defaultOpen>
|
|
90
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
91
|
+
<Dropdown.Content>
|
|
92
|
+
<Dropdown.Item>Rename</Dropdown.Item>
|
|
93
|
+
<Dropdown.Separator asChild>
|
|
94
|
+
<hr data-testid="custom-sep" />
|
|
95
|
+
</Dropdown.Separator>
|
|
96
|
+
<Dropdown.Item>Delete</Dropdown.Item>
|
|
97
|
+
</Dropdown.Content>
|
|
98
|
+
</Dropdown.Root>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
const sep = screen.getByTestId("custom-sep");
|
|
103
|
+
expect(sep.tagName).toBe("HR");
|
|
104
|
+
expect(sep).toHaveAttribute("role", "separator");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("delegates Group to the child element via asChild while still labelling it", () => {
|
|
108
|
+
// Arrange & Act
|
|
109
|
+
render(
|
|
110
|
+
<Dropdown.Root defaultOpen>
|
|
111
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
112
|
+
<Dropdown.Content>
|
|
113
|
+
<Dropdown.Group asChild>
|
|
114
|
+
<section data-testid="custom-group">
|
|
115
|
+
<Dropdown.Label>Actions</Dropdown.Label>
|
|
116
|
+
<Dropdown.Item>Rename</Dropdown.Item>
|
|
117
|
+
</section>
|
|
118
|
+
</Dropdown.Group>
|
|
119
|
+
</Dropdown.Content>
|
|
120
|
+
</Dropdown.Root>,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
const group = screen.getByTestId("custom-group");
|
|
125
|
+
expect(group.tagName).toBe("SECTION");
|
|
126
|
+
expect(group).toHaveAttribute("role", "group");
|
|
127
|
+
const labelId = group.getAttribute("aria-labelledby");
|
|
128
|
+
expect(labelId).toBeTruthy();
|
|
129
|
+
const label = screen.getByText("Actions");
|
|
130
|
+
expect(label).toHaveAttribute("id", labelId);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("delegates Label to the child element via asChild", () => {
|
|
134
|
+
// Arrange & Act
|
|
135
|
+
render(
|
|
136
|
+
<Dropdown.Root defaultOpen>
|
|
137
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
138
|
+
<Dropdown.Content>
|
|
139
|
+
<Dropdown.Group>
|
|
140
|
+
<Dropdown.Label asChild>
|
|
141
|
+
<h2 data-testid="custom-label">Actions</h2>
|
|
142
|
+
</Dropdown.Label>
|
|
143
|
+
<Dropdown.Item>Rename</Dropdown.Item>
|
|
144
|
+
</Dropdown.Group>
|
|
145
|
+
</Dropdown.Content>
|
|
146
|
+
</Dropdown.Root>,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Assert
|
|
150
|
+
const label = screen.getByTestId("custom-label");
|
|
151
|
+
expect(label.tagName).toBe("H2");
|
|
152
|
+
expect(label).toHaveAttribute("id");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("delegates CheckboxItem to the child element via asChild", async () => {
|
|
156
|
+
// Arrange
|
|
157
|
+
const user = userEvent.setup();
|
|
158
|
+
const onCheckedChange = vi.fn();
|
|
159
|
+
render(
|
|
160
|
+
<Dropdown.Root defaultOpen>
|
|
161
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
162
|
+
<Dropdown.Content>
|
|
163
|
+
<Dropdown.CheckboxItem asChild onCheckedChange={onCheckedChange}>
|
|
164
|
+
<a href="#show-hidden" data-testid="custom-check">
|
|
165
|
+
Show hidden
|
|
166
|
+
</a>
|
|
167
|
+
</Dropdown.CheckboxItem>
|
|
168
|
+
</Dropdown.Content>
|
|
169
|
+
</Dropdown.Root>,
|
|
170
|
+
);
|
|
171
|
+
const item = screen.getByTestId("custom-check");
|
|
172
|
+
expect(item.tagName).toBe("A");
|
|
173
|
+
expect(item).toHaveAttribute("role", "menuitemcheckbox");
|
|
174
|
+
expect(item).toHaveAttribute("aria-checked", "false");
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
await user.click(item);
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
expect(onCheckedChange).toHaveBeenCalledWith(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("delegates RadioGroup to the child element via asChild", () => {
|
|
184
|
+
// Arrange & Act
|
|
185
|
+
render(
|
|
186
|
+
<Dropdown.Root defaultOpen>
|
|
187
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
188
|
+
<Dropdown.Content>
|
|
189
|
+
<Dropdown.RadioGroup asChild defaultValue="a">
|
|
190
|
+
<section data-testid="custom-radiogroup">
|
|
191
|
+
<Dropdown.RadioItem value="a">A</Dropdown.RadioItem>
|
|
192
|
+
<Dropdown.RadioItem value="b">B</Dropdown.RadioItem>
|
|
193
|
+
</section>
|
|
194
|
+
</Dropdown.RadioGroup>
|
|
195
|
+
</Dropdown.Content>
|
|
196
|
+
</Dropdown.Root>,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Assert
|
|
200
|
+
const group = screen.getByTestId("custom-radiogroup");
|
|
201
|
+
expect(group.tagName).toBe("SECTION");
|
|
202
|
+
expect(group).toHaveAttribute("role", "group");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("delegates RadioItem to the child element via asChild", async () => {
|
|
206
|
+
// Arrange
|
|
207
|
+
const user = userEvent.setup();
|
|
208
|
+
const onValueChange = vi.fn();
|
|
209
|
+
render(
|
|
210
|
+
<Dropdown.Root defaultOpen>
|
|
211
|
+
<Dropdown.Trigger>Options</Dropdown.Trigger>
|
|
212
|
+
<Dropdown.Content>
|
|
213
|
+
<Dropdown.RadioGroup onValueChange={onValueChange}>
|
|
214
|
+
<Dropdown.RadioItem asChild value="a">
|
|
215
|
+
<a href="#a" data-testid="custom-radio">
|
|
216
|
+
A
|
|
217
|
+
</a>
|
|
218
|
+
</Dropdown.RadioItem>
|
|
219
|
+
</Dropdown.RadioGroup>
|
|
220
|
+
</Dropdown.Content>
|
|
221
|
+
</Dropdown.Root>,
|
|
222
|
+
);
|
|
223
|
+
const item = screen.getByTestId("custom-radio");
|
|
224
|
+
expect(item.tagName).toBe("A");
|
|
225
|
+
expect(item).toHaveAttribute("role", "menuitemradio");
|
|
226
|
+
|
|
227
|
+
// Act
|
|
228
|
+
await user.click(item);
|
|
229
|
+
|
|
230
|
+
// Assert
|
|
231
|
+
expect(onValueChange).toHaveBeenCalledWith("a");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("delegates SubTrigger to the child element via asChild", () => {
|
|
235
|
+
// Arrange & Act
|
|
236
|
+
render(
|
|
237
|
+
<Dropdown.Root defaultOpen>
|
|
238
|
+
<Dropdown.Trigger>File</Dropdown.Trigger>
|
|
239
|
+
<Dropdown.Content>
|
|
240
|
+
<Dropdown.Sub>
|
|
241
|
+
<Dropdown.SubTrigger asChild>
|
|
242
|
+
<a href="#open-recent" data-testid="custom-subtrigger">
|
|
243
|
+
Open Recent
|
|
244
|
+
</a>
|
|
245
|
+
</Dropdown.SubTrigger>
|
|
246
|
+
<Dropdown.SubContent>
|
|
247
|
+
<Dropdown.Item>Project A</Dropdown.Item>
|
|
248
|
+
</Dropdown.SubContent>
|
|
249
|
+
</Dropdown.Sub>
|
|
250
|
+
</Dropdown.Content>
|
|
251
|
+
</Dropdown.Root>,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Assert
|
|
255
|
+
const subTrigger = screen.getByTestId("custom-subtrigger");
|
|
256
|
+
expect(subTrigger.tagName).toBe("A");
|
|
257
|
+
expect(subTrigger).toHaveAttribute("role", "menuitem");
|
|
258
|
+
expect(subTrigger).toHaveAttribute("aria-haspopup", "menu");
|
|
259
|
+
expect(subTrigger).toHaveAttribute("aria-expanded", "false");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("delegates SubContent to the child element via asChild", () => {
|
|
263
|
+
// Arrange & Act
|
|
264
|
+
render(
|
|
265
|
+
<Dropdown.Root defaultOpen>
|
|
266
|
+
<Dropdown.Trigger>File</Dropdown.Trigger>
|
|
267
|
+
<Dropdown.Content>
|
|
268
|
+
<Dropdown.Sub defaultOpen>
|
|
269
|
+
<Dropdown.SubTrigger>Open Recent</Dropdown.SubTrigger>
|
|
270
|
+
<Dropdown.SubContent asChild>
|
|
271
|
+
<div data-testid="custom-subcontent">
|
|
272
|
+
<Dropdown.Item>Project A</Dropdown.Item>
|
|
273
|
+
</div>
|
|
274
|
+
</Dropdown.SubContent>
|
|
275
|
+
</Dropdown.Sub>
|
|
276
|
+
</Dropdown.Content>
|
|
277
|
+
</Dropdown.Root>,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Assert
|
|
281
|
+
const subContent = screen.getByTestId("custom-subcontent");
|
|
282
|
+
expect(subContent.tagName).toBe("DIV");
|
|
283
|
+
expect(subContent).toHaveAttribute("role", "menu");
|
|
284
|
+
expect(subContent).toHaveAttribute("popover", "auto");
|
|
285
|
+
});
|
|
286
|
+
});
|