@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 ContextMenuContent} and
|
|
5
|
+
* {@link ContextMenuSubContent}. 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 ContextMenuContentContextValue = {
|
|
11
|
+
closeOpenSubRef: RefObject<(() => void) | null>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const ContextMenuContentContext =
|
|
15
|
+
createContext<ContextMenuContentContextValue | null>(null);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import { Direction } from "../DirectionProvider";
|
|
4
|
+
import { createStrictContext } from "../utils";
|
|
5
|
+
|
|
6
|
+
export type ContextMenuPosition = { x: number; y: number };
|
|
7
|
+
|
|
8
|
+
export type ContextMenuContextValue = {
|
|
9
|
+
open: boolean;
|
|
10
|
+
setOpen: (open: boolean) => void;
|
|
11
|
+
position: ContextMenuPosition | null;
|
|
12
|
+
setPosition: (position: ContextMenuPosition | null) => void;
|
|
13
|
+
contentId: string;
|
|
14
|
+
triggerRef: RefObject<HTMLElement | null>;
|
|
15
|
+
dir: Direction;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const [ContextMenuContext, useContextMenuContext] =
|
|
19
|
+
createStrictContext<ContextMenuContextValue>(
|
|
20
|
+
"ContextMenu sub-components must be rendered inside a <ContextMenu.Root>.",
|
|
21
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type ContextMenuRadioGroupContextValue = {
|
|
4
|
+
value: string | undefined;
|
|
5
|
+
select: (value: string) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const ContextMenuRadioGroupContext =
|
|
9
|
+
createContext<ContextMenuRadioGroupContextValue | null>(null);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import { createStrictContext } from "../utils";
|
|
4
|
+
|
|
5
|
+
export type ContextMenuSubContextValue = {
|
|
6
|
+
open: boolean;
|
|
7
|
+
setOpen: (open: boolean) => void;
|
|
8
|
+
contentId: string;
|
|
9
|
+
triggerRef: RefObject<HTMLLIElement | null>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const [ContextMenuSubContext, useContextMenuSubContext] =
|
|
13
|
+
createStrictContext<ContextMenuSubContextValue>(
|
|
14
|
+
"ContextMenu.SubTrigger and ContextMenu.SubContent must be rendered inside a <ContextMenu.Sub>.",
|
|
15
|
+
);
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# ContextMenu
|
|
2
|
+
|
|
3
|
+
A compound component implementing the
|
|
4
|
+
[WAI-ARIA Menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/)
|
|
5
|
+
for right-click / long-press / context-key menus, layered on top of the
|
|
6
|
+
native HTML
|
|
7
|
+
[Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API).
|
|
8
|
+
No portal, no floating-ui — the browser handles layering via the top
|
|
9
|
+
layer, and the component handles close on outside click / Escape.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { ContextMenu } from "@primitiv-ui/react";
|
|
13
|
+
|
|
14
|
+
<ContextMenu.Root>
|
|
15
|
+
<ContextMenu.Trigger>
|
|
16
|
+
<div className="canvas">Right-click anywhere on this canvas</div>
|
|
17
|
+
</ContextMenu.Trigger>
|
|
18
|
+
<ContextMenu.Content>
|
|
19
|
+
<ContextMenu.Item onSelect={() => paste()}>Paste</ContextMenu.Item>
|
|
20
|
+
<ContextMenu.Item onSelect={() => duplicate()}>Duplicate</ContextMenu.Item>
|
|
21
|
+
<ContextMenu.Separator />
|
|
22
|
+
<ContextMenu.Item disabled>Archive</ContextMenu.Item>
|
|
23
|
+
</ContextMenu.Content>
|
|
24
|
+
</ContextMenu.Root>;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Sub-components
|
|
28
|
+
|
|
29
|
+
| Export | Role | Notes |
|
|
30
|
+
| --------------------------- | -------------------- | ------------------------------------------------------------------------------------ |
|
|
31
|
+
| `ContextMenu.Root` | State owner | Uncontrolled (`defaultOpen`) or controlled (`open` + `onOpenChange`) |
|
|
32
|
+
| `ContextMenu.Trigger` | Right-click target | Renders `<span>`; intercepts `contextmenu`, suppresses the native menu |
|
|
33
|
+
| `ContextMenu.Content` | `menu` | Native `popover="manual"`, positioned at the cursor; arrow keys, typeahead, Escape |
|
|
34
|
+
| `ContextMenu.Item` | `menuitem` | Activatable row with `onSelect` escape hatch |
|
|
35
|
+
| `ContextMenu.CheckboxItem` | `menuitemcheckbox` | Tri-state toggle (`true` / `false` / `"indeterminate"`) |
|
|
36
|
+
| `ContextMenu.RadioGroup` | `group` | Single-selection container for `RadioItem`s |
|
|
37
|
+
| `ContextMenu.RadioItem` | `menuitemradio` | Must live inside a `RadioGroup` |
|
|
38
|
+
| `ContextMenu.ItemIndicator` | — | Icon slot inside a `CheckboxItem` / `RadioItem`; exposes `data-state` + `forceMount` |
|
|
39
|
+
| `ContextMenu.Label` | — | Non-interactive label; auto-wired to the enclosing `Group` via `aria-labelledby` |
|
|
40
|
+
| `ContextMenu.Group` | `group` | Semantic grouping for related items |
|
|
41
|
+
| `ContextMenu.Separator` | `separator` | Visual divider; skipped by focus and typeahead |
|
|
42
|
+
| `ContextMenu.Sub` | State owner | Submenu boundary; same state modes as `Root` |
|
|
43
|
+
| `ContextMenu.SubTrigger` | `menuitem` | Opens the submenu on click, hover, or the inline-forward arrow (`ArrowRight` LTR / `ArrowLeft` RTL) |
|
|
44
|
+
| `ContextMenu.SubContent` | `menu` | Submenu panel; the inline-backward arrow (`ArrowLeft` LTR / `ArrowRight` RTL) closes it and returns focus to the trigger |
|
|
45
|
+
|
|
46
|
+
All sub-components that render an element accept `asChild` to compose
|
|
47
|
+
their ARIA and behaviour onto a caller-supplied child.
|
|
48
|
+
|
|
49
|
+
## Opening
|
|
50
|
+
|
|
51
|
+
The Trigger listens for the DOM `contextmenu` event on its rendered
|
|
52
|
+
element, calls `preventDefault()` to suppress the platform menu, and
|
|
53
|
+
opens the Content positioned at the pointer coordinates via
|
|
54
|
+
`position: fixed`.
|
|
55
|
+
|
|
56
|
+
By default, Trigger renders a `<span>` wrapping its children — a
|
|
57
|
+
neutral host that doesn't disturb the wrapped content's semantics. Pass
|
|
58
|
+
`asChild` to attach the right-click behaviour to any element (a `<div>`
|
|
59
|
+
canvas, a list row, an image, etc.).
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<ContextMenu.Trigger asChild>
|
|
63
|
+
<ImageCard src="…" alt="…" />
|
|
64
|
+
</ContextMenu.Trigger>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A disabled Trigger ignores `contextmenu` entirely, letting the native
|
|
68
|
+
browser menu through.
|
|
69
|
+
|
|
70
|
+
## Keyboard interaction
|
|
71
|
+
|
|
72
|
+
| Key | Behaviour |
|
|
73
|
+
| ----------------------- | ------------------------------------------------- |
|
|
74
|
+
| `ArrowDown` / `ArrowUp` | Move focus to next / previous item (wraps) |
|
|
75
|
+
| `Home` / `End` | Jump to first / last enabled item |
|
|
76
|
+
| `Enter` / `Space` | Activate the focused item |
|
|
77
|
+
| `Escape` | Close the menu and return focus to the Trigger |
|
|
78
|
+
| any printable character | Typeahead — focuses the next item matching prefix |
|
|
79
|
+
| `ArrowRight` | Open a focused submenu (`SubTrigger`, LTR) / close the submenu (inside `SubContent`, RTL) |
|
|
80
|
+
| `ArrowLeft` | Close the current submenu (inside `SubContent`, LTR) / open a focused submenu (`SubTrigger`, RTL) |
|
|
81
|
+
|
|
82
|
+
Typeahead accumulates keystrokes within a 500 ms window; pressing the
|
|
83
|
+
same character repeatedly cycles through items sharing that first letter.
|
|
84
|
+
Disabled items are skipped by arrow navigation, typeahead, and activation.
|
|
85
|
+
|
|
86
|
+
## Reading direction
|
|
87
|
+
|
|
88
|
+
Pass `dir="ltr"` or `dir="rtl"` on `ContextMenu.Root` to invert the
|
|
89
|
+
submenu open / close arrow keys. When omitted, the component reads the
|
|
90
|
+
inherited `DirectionProvider` value, falling back to `"ltr"`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<DirectionProvider dir="rtl">
|
|
94
|
+
<ContextMenu.Root>{/* submenus now open on ArrowLeft */}</ContextMenu.Root>
|
|
95
|
+
</DirectionProvider>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
An explicit `dir` prop on `ContextMenu.Root` always wins over the
|
|
99
|
+
inherited value. The reading direction only affects keyboard handling —
|
|
100
|
+
submenu visual placement is the consumer's CSS concern (the workbench
|
|
101
|
+
example uses logical positioning + `position-try-fallbacks: flip-inline`
|
|
102
|
+
so subs reflow automatically in RTL).
|
|
103
|
+
|
|
104
|
+
## State modes
|
|
105
|
+
|
|
106
|
+
- **Uncontrolled** — pass `defaultOpen` (or omit to start closed). Optional
|
|
107
|
+
`onOpenChange` observes user-driven transitions.
|
|
108
|
+
- **Controlled** — pass `open` and `onOpenChange` together.
|
|
109
|
+
|
|
110
|
+
`ContextMenu.Sub` follows the same contract for its own open state.
|
|
111
|
+
|
|
112
|
+
External flips of the controlled `open` prop do **not** invoke
|
|
113
|
+
`onOpenChange` — only user-driven transitions (right-click, Escape,
|
|
114
|
+
selection, outside click) do.
|
|
115
|
+
|
|
116
|
+
`ContextMenu.CheckboxItem` and `ContextMenu.RadioGroup` each expose the
|
|
117
|
+
same uncontrolled / controlled split for their `checked` / `value` state.
|
|
118
|
+
|
|
119
|
+
## The `onSelect` escape hatch
|
|
120
|
+
|
|
121
|
+
Every activatable item (`Item`, `CheckboxItem`, `RadioItem`) fires
|
|
122
|
+
`onSelect` with a cancellable `Event` on activation. By default the menu
|
|
123
|
+
auto-closes; call `event.preventDefault()` to keep it open — useful for
|
|
124
|
+
rapidly toggling multiple checkboxes, or running an action whose outcome
|
|
125
|
+
is shown inline in the menu.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
<ContextMenu.CheckboxItem
|
|
129
|
+
onSelect={(event) => event.preventDefault()}
|
|
130
|
+
onCheckedChange={setGridVisible}
|
|
131
|
+
>
|
|
132
|
+
Show grid
|
|
133
|
+
</ContextMenu.CheckboxItem>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Disabled items
|
|
137
|
+
|
|
138
|
+
Disabled items receive `aria-disabled="true"` rather than the native
|
|
139
|
+
`disabled` attribute, so they remain visible to assistive technology but
|
|
140
|
+
no-op on activation. Arrow navigation and typeahead skip them.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<ContextMenu.Item disabled>Archive (coming soon)</ContextMenu.Item>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
A disabled `SubTrigger` refuses to open on both click and the
|
|
147
|
+
inline-forward arrow key.
|
|
148
|
+
|
|
149
|
+
## Checkbox and radio items
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<ContextMenu.Content>
|
|
153
|
+
<ContextMenu.Label>View</ContextMenu.Label>
|
|
154
|
+
<ContextMenu.CheckboxItem defaultChecked>Show grid</ContextMenu.CheckboxItem>
|
|
155
|
+
<ContextMenu.CheckboxItem>Show ruler</ContextMenu.CheckboxItem>
|
|
156
|
+
<ContextMenu.Separator />
|
|
157
|
+
<ContextMenu.RadioGroup defaultValue="system">
|
|
158
|
+
<ContextMenu.RadioItem value="light">Light</ContextMenu.RadioItem>
|
|
159
|
+
<ContextMenu.RadioItem value="dark">Dark</ContextMenu.RadioItem>
|
|
160
|
+
<ContextMenu.RadioItem value="system">Match system</ContextMenu.RadioItem>
|
|
161
|
+
</ContextMenu.RadioGroup>
|
|
162
|
+
</ContextMenu.Content>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`CheckboxItem` supports a tri-state: `true`, `false`, or `"indeterminate"`
|
|
166
|
+
(which renders as `aria-checked="mixed"`). An indeterminate item resolves
|
|
167
|
+
to `true` on the next activation.
|
|
168
|
+
|
|
169
|
+
### `ItemIndicator`
|
|
170
|
+
|
|
171
|
+
Render the visible mark inside the item via `ContextMenu.ItemIndicator`.
|
|
172
|
+
It defaults to a `<span>`, supports `asChild` so consumers can compose
|
|
173
|
+
onto an SVG icon, and exposes `data-state` for styling:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<ContextMenu.CheckboxItem
|
|
177
|
+
checked={showBookmarks}
|
|
178
|
+
onCheckedChange={setShowBookmarks}
|
|
179
|
+
>
|
|
180
|
+
<ContextMenu.ItemIndicator>
|
|
181
|
+
<CheckIcon />
|
|
182
|
+
</ContextMenu.ItemIndicator>
|
|
183
|
+
Show bookmarks
|
|
184
|
+
</ContextMenu.CheckboxItem>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
| `data-state` | When |
|
|
188
|
+
| ----------------- | ----------------------------------------------------------------------------------- |
|
|
189
|
+
| `"checked"` | Parent `CheckboxItem` is `true`, or parent `RadioItem` is the group's current value |
|
|
190
|
+
| `"unchecked"` | Parent is `false` (only reachable when `forceMount` is set — see below) |
|
|
191
|
+
| `"indeterminate"` | Parent `CheckboxItem` is `"indeterminate"` |
|
|
192
|
+
|
|
193
|
+
By default the indicator **unmounts** when its parent is unchecked. Pass
|
|
194
|
+
`forceMount` to keep the DOM node in both states so CSS transitions or a
|
|
195
|
+
React animation library can drive the visual state off `data-state`.
|
|
196
|
+
|
|
197
|
+
Rendering `ContextMenu.ItemIndicator` outside a `CheckboxItem` or
|
|
198
|
+
`RadioItem` throws a descriptive error.
|
|
199
|
+
|
|
200
|
+
## Submenus
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<ContextMenu.Content>
|
|
204
|
+
<ContextMenu.Item>Cut</ContextMenu.Item>
|
|
205
|
+
<ContextMenu.Sub>
|
|
206
|
+
<ContextMenu.SubTrigger>Share</ContextMenu.SubTrigger>
|
|
207
|
+
<ContextMenu.SubContent>
|
|
208
|
+
<ContextMenu.Item>Email</ContextMenu.Item>
|
|
209
|
+
<ContextMenu.Item>Copy link</ContextMenu.Item>
|
|
210
|
+
</ContextMenu.SubContent>
|
|
211
|
+
</ContextMenu.Sub>
|
|
212
|
+
</ContextMenu.Content>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Open a submenu with the inline-forward arrow key (`ArrowRight` in LTR,
|
|
216
|
+
`ArrowLeft` in RTL), a click on the trigger, or pointer hover; close it
|
|
217
|
+
with the inline-backward arrow or by selecting an item. Focus returns to
|
|
218
|
+
the `SubTrigger` when the submenu closes.
|
|
219
|
+
|
|
220
|
+
## Groups and labels
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
<ContextMenu.Content>
|
|
224
|
+
<ContextMenu.Group>
|
|
225
|
+
<ContextMenu.Label>Edit</ContextMenu.Label>
|
|
226
|
+
<ContextMenu.Item>Undo</ContextMenu.Item>
|
|
227
|
+
<ContextMenu.Item>Redo</ContextMenu.Item>
|
|
228
|
+
</ContextMenu.Group>
|
|
229
|
+
</ContextMenu.Content>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Nesting a `ContextMenu.Label` inside a `ContextMenu.Group` wires the
|
|
233
|
+
group's `aria-labelledby` to the label automatically.
|
|
234
|
+
|
|
235
|
+
## `asChild` composition
|
|
236
|
+
|
|
237
|
+
Every rendering sub-component accepts `asChild` to compose its
|
|
238
|
+
ARIA attributes, event handlers, and ref onto a caller-supplied child:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
<ContextMenu.Trigger asChild>
|
|
242
|
+
<div className="canvas">Right-click here</div>
|
|
243
|
+
</ContextMenu.Trigger>
|
|
244
|
+
|
|
245
|
+
<ContextMenu.Item asChild>
|
|
246
|
+
<a href="/rename">Rename</a>
|
|
247
|
+
</ContextMenu.Item>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Styling hooks
|
|
251
|
+
|
|
252
|
+
```css
|
|
253
|
+
/* Open state on the menu panel */
|
|
254
|
+
[role="menu"][data-state="open"] {
|
|
255
|
+
animation: fade-in 120ms ease-out;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Highlighted item — pointer focus */
|
|
259
|
+
[role="menuitem"][data-highlighted],
|
|
260
|
+
[role="menuitemcheckbox"][data-highlighted],
|
|
261
|
+
[role="menuitemradio"][data-highlighted] {
|
|
262
|
+
background: rgba(0 0 0 / 0.06);
|
|
263
|
+
outline: none;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Disabled items */
|
|
267
|
+
[aria-disabled="true"] {
|
|
268
|
+
opacity: 0.5;
|
|
269
|
+
pointer-events: none;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`data-state="open" | "closed"` is present on the Content for state-driven
|
|
274
|
+
styling. `data-highlighted` is present on `Item`, `CheckboxItem`,
|
|
275
|
+
`RadioItem`, and `SubTrigger` while the item has pointer focus.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { ContextMenu } from "../ContextMenu";
|
|
4
|
+
|
|
5
|
+
describe("ContextMenu asChild", () => {
|
|
6
|
+
it("delegates Trigger to the child element via asChild", () => {
|
|
7
|
+
// Arrange & Act
|
|
8
|
+
render(
|
|
9
|
+
<ContextMenu.Root>
|
|
10
|
+
<ContextMenu.Trigger asChild>
|
|
11
|
+
<div data-testid="custom-trigger">Area</div>
|
|
12
|
+
</ContextMenu.Trigger>
|
|
13
|
+
<ContextMenu.Content>
|
|
14
|
+
<ContextMenu.Item>Rename</ContextMenu.Item>
|
|
15
|
+
</ContextMenu.Content>
|
|
16
|
+
</ContextMenu.Root>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Assert
|
|
20
|
+
const trigger = screen.getByTestId("custom-trigger");
|
|
21
|
+
expect(trigger.tagName).toBe("DIV");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("delegates Content to the child element via asChild", () => {
|
|
25
|
+
// Arrange & Act
|
|
26
|
+
render(
|
|
27
|
+
<ContextMenu.Root defaultOpen>
|
|
28
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
29
|
+
<ContextMenu.Content asChild>
|
|
30
|
+
<div data-testid="custom-content">
|
|
31
|
+
<ContextMenu.Item>Rename</ContextMenu.Item>
|
|
32
|
+
</div>
|
|
33
|
+
</ContextMenu.Content>
|
|
34
|
+
</ContextMenu.Root>,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
const content = screen.getByTestId("custom-content");
|
|
39
|
+
expect(content.tagName).toBe("DIV");
|
|
40
|
+
expect(content).toHaveAttribute("role", "menu");
|
|
41
|
+
expect(content).toHaveAttribute("popover", "manual");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("delegates Item to the child element and auto-closes on click", () => {
|
|
45
|
+
// Arrange
|
|
46
|
+
const onSelect = vi.fn();
|
|
47
|
+
const onOpenChange = vi.fn();
|
|
48
|
+
render(
|
|
49
|
+
<ContextMenu.Root defaultOpen onOpenChange={onOpenChange}>
|
|
50
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
51
|
+
<ContextMenu.Content>
|
|
52
|
+
<ContextMenu.Item asChild onSelect={onSelect}>
|
|
53
|
+
<a href="#rename" data-testid="custom-item">
|
|
54
|
+
Rename
|
|
55
|
+
</a>
|
|
56
|
+
</ContextMenu.Item>
|
|
57
|
+
</ContextMenu.Content>
|
|
58
|
+
</ContextMenu.Root>,
|
|
59
|
+
);
|
|
60
|
+
const item = screen.getByTestId("custom-item");
|
|
61
|
+
expect(item.tagName).toBe("A");
|
|
62
|
+
expect(item).toHaveAttribute("role", "menuitem");
|
|
63
|
+
|
|
64
|
+
// Act
|
|
65
|
+
fireEvent.click(item);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(onSelect).toHaveBeenCalledOnce();
|
|
69
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("delegates Separator to the child element via asChild", () => {
|
|
73
|
+
// Arrange & Act
|
|
74
|
+
render(
|
|
75
|
+
<ContextMenu.Root defaultOpen>
|
|
76
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
77
|
+
<ContextMenu.Content>
|
|
78
|
+
<ContextMenu.Item>Rename</ContextMenu.Item>
|
|
79
|
+
<ContextMenu.Separator asChild>
|
|
80
|
+
<hr data-testid="custom-separator" />
|
|
81
|
+
</ContextMenu.Separator>
|
|
82
|
+
<ContextMenu.Item>Delete</ContextMenu.Item>
|
|
83
|
+
</ContextMenu.Content>
|
|
84
|
+
</ContextMenu.Root>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Assert
|
|
88
|
+
const separator = screen.getByTestId("custom-separator");
|
|
89
|
+
expect(separator.tagName).toBe("HR");
|
|
90
|
+
expect(separator).toHaveAttribute("role", "separator");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("delegates Group and Label to the child elements via asChild", () => {
|
|
94
|
+
// Arrange & Act
|
|
95
|
+
render(
|
|
96
|
+
<ContextMenu.Root defaultOpen>
|
|
97
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
98
|
+
<ContextMenu.Content>
|
|
99
|
+
<ContextMenu.Group asChild>
|
|
100
|
+
<section data-testid="custom-group">
|
|
101
|
+
<ContextMenu.Label asChild>
|
|
102
|
+
<h2 data-testid="custom-label">Actions</h2>
|
|
103
|
+
</ContextMenu.Label>
|
|
104
|
+
<ContextMenu.Item>Rename</ContextMenu.Item>
|
|
105
|
+
</section>
|
|
106
|
+
</ContextMenu.Group>
|
|
107
|
+
</ContextMenu.Content>
|
|
108
|
+
</ContextMenu.Root>,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Assert
|
|
112
|
+
expect(screen.getByTestId("custom-group").tagName).toBe("SECTION");
|
|
113
|
+
expect(screen.getByTestId("custom-label").tagName).toBe("H2");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("delegates CheckboxItem and ItemIndicator to the child elements via asChild", () => {
|
|
117
|
+
// Arrange & Act
|
|
118
|
+
render(
|
|
119
|
+
<ContextMenu.Root defaultOpen>
|
|
120
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
121
|
+
<ContextMenu.Content>
|
|
122
|
+
<ContextMenu.CheckboxItem asChild defaultChecked>
|
|
123
|
+
<a href="#grid" data-testid="custom-check">
|
|
124
|
+
<ContextMenu.ItemIndicator asChild>
|
|
125
|
+
<svg data-testid="custom-indicator" />
|
|
126
|
+
</ContextMenu.ItemIndicator>
|
|
127
|
+
Show grid
|
|
128
|
+
</a>
|
|
129
|
+
</ContextMenu.CheckboxItem>
|
|
130
|
+
</ContextMenu.Content>
|
|
131
|
+
</ContextMenu.Root>,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Assert
|
|
135
|
+
const check = screen.getByTestId("custom-check");
|
|
136
|
+
expect(check.tagName).toBe("A");
|
|
137
|
+
expect(check).toHaveAttribute("role", "menuitemcheckbox");
|
|
138
|
+
const indicator = screen.getByTestId("custom-indicator");
|
|
139
|
+
expect(indicator.tagName).toBe("svg");
|
|
140
|
+
expect(indicator).toHaveAttribute("data-state", "checked");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("delegates RadioGroup, RadioItem, Sub, SubTrigger, and SubContent via asChild", () => {
|
|
144
|
+
// Arrange & Act
|
|
145
|
+
render(
|
|
146
|
+
<ContextMenu.Root defaultOpen>
|
|
147
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
148
|
+
<ContextMenu.Content>
|
|
149
|
+
<ContextMenu.RadioGroup asChild defaultValue="a">
|
|
150
|
+
<section data-testid="custom-rg">
|
|
151
|
+
<ContextMenu.RadioItem asChild value="a">
|
|
152
|
+
<a href="#a" data-testid="custom-ri">
|
|
153
|
+
Alpha
|
|
154
|
+
</a>
|
|
155
|
+
</ContextMenu.RadioItem>
|
|
156
|
+
</section>
|
|
157
|
+
</ContextMenu.RadioGroup>
|
|
158
|
+
<ContextMenu.Sub defaultOpen>
|
|
159
|
+
<ContextMenu.SubTrigger asChild>
|
|
160
|
+
<a href="#more" data-testid="custom-st">
|
|
161
|
+
More
|
|
162
|
+
</a>
|
|
163
|
+
</ContextMenu.SubTrigger>
|
|
164
|
+
<ContextMenu.SubContent asChild>
|
|
165
|
+
<div data-testid="custom-sc">
|
|
166
|
+
<ContextMenu.Item>Nested</ContextMenu.Item>
|
|
167
|
+
</div>
|
|
168
|
+
</ContextMenu.SubContent>
|
|
169
|
+
</ContextMenu.Sub>
|
|
170
|
+
</ContextMenu.Content>
|
|
171
|
+
</ContextMenu.Root>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Assert
|
|
175
|
+
expect(screen.getByTestId("custom-rg").tagName).toBe("SECTION");
|
|
176
|
+
expect(screen.getByTestId("custom-ri")).toHaveAttribute(
|
|
177
|
+
"role",
|
|
178
|
+
"menuitemradio",
|
|
179
|
+
);
|
|
180
|
+
expect(screen.getByTestId("custom-st")).toHaveAttribute(
|
|
181
|
+
"aria-haspopup",
|
|
182
|
+
"menu",
|
|
183
|
+
);
|
|
184
|
+
expect(screen.getByTestId("custom-sc")).toHaveAttribute("role", "menu");
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { ContextMenu } from "../ContextMenu";
|
|
4
|
+
|
|
5
|
+
describe("ContextMenu basic rendering", () => {
|
|
6
|
+
it("renders the Trigger's children so the area that should respond to right-click is in the DOM", () => {
|
|
7
|
+
// Arrange & Act
|
|
8
|
+
render(
|
|
9
|
+
<ContextMenu.Root>
|
|
10
|
+
<ContextMenu.Trigger>
|
|
11
|
+
<div>Right-click area</div>
|
|
12
|
+
</ContextMenu.Trigger>
|
|
13
|
+
</ContextMenu.Root>,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Assert
|
|
17
|
+
expect(screen.getByText("Right-click area")).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("renders Content as a native popover with popover=manual so the browser does not light-dismiss the menu on the right-click's mouseup", () => {
|
|
21
|
+
// Arrange & Act
|
|
22
|
+
render(
|
|
23
|
+
<ContextMenu.Root defaultOpen>
|
|
24
|
+
<ContextMenu.Trigger>Area</ContextMenu.Trigger>
|
|
25
|
+
<ContextMenu.Content>Items go here</ContextMenu.Content>
|
|
26
|
+
</ContextMenu.Root>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Assert — popover=manual is critical: with "auto" the browser treats
|
|
30
|
+
// the right-click's pointerdown (which fires before the popover opened)
|
|
31
|
+
// and the subsequent pointerup as an outside-dismiss gesture, closing
|
|
32
|
+
// the menu the instant the user releases the button. We close on
|
|
33
|
+
// outside click and Escape ourselves instead.
|
|
34
|
+
const menu = screen.getByRole("menu", { hidden: true });
|
|
35
|
+
expect(menu.tagName).toBe("MENU");
|
|
36
|
+
expect(menu).toHaveAttribute("popover", "manual");
|
|
37
|
+
expect(menu.id).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
});
|