@primitiv-ui/react 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/package.json +6 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +32 -9
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +5 -0
- package/src/AccessibleIcon/types.ts +15 -2
- package/src/Accordion/Accordion.tsx +32 -11
- package/src/Accordion/README.md +2 -2
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +46 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +15 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +42 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +33 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +44 -7
- package/src/Accordion/hooks/useAccordionTrigger.ts +9 -1
- package/src/Accordion/types.ts +72 -21
- package/src/Alert/Alert.tsx +36 -7
- package/src/Alert/__tests__/Alert.test.tsx +5 -0
- package/src/Alert/types.ts +18 -2
- package/src/Avatar/Avatar.tsx +99 -15
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +10 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +35 -0
- package/src/Avatar/hooks/useAvatarImage.ts +16 -0
- package/src/Avatar/types.ts +15 -3
- package/src/Breadcrumb/Breadcrumb.tsx +139 -21
- package/src/Breadcrumb/README.md +47 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +11 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +110 -0
- package/src/Breadcrumb/types.ts +73 -7
- package/src/Button/Button.tsx +2 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +6 -0
- package/src/Button/__tests__/Button.contract.test.tsx +1 -1
- package/src/Button/types.ts +21 -0
- package/src/Carousel/Carousel.tsx +268 -20
- package/src/Carousel/README.md +764 -87
- package/src/Carousel/__tests__/Carousel.autoplay-status.test.tsx +239 -0
- package/src/Carousel/__tests__/Carousel.drag-status.test.tsx +292 -0
- package/src/Carousel/__tests__/Carousel.dynamic-slides.test.tsx +38 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +101 -0
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +1453 -0
- package/src/Carousel/__tests__/Carousel.instant-scroll.test.tsx +124 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +90 -2
- package/src/Carousel/__tests__/Carousel.loop.test.tsx +163 -0
- package/src/Carousel/__tests__/Carousel.loopEngine.test.ts +77 -0
- package/src/Carousel/__tests__/Carousel.mouse-drag.test.tsx +504 -0
- package/src/Carousel/__tests__/Carousel.multi-slide-bounds.test.tsx +91 -0
- package/src/Carousel/__tests__/Carousel.orientation.test.tsx +207 -0
- package/src/Carousel/__tests__/Carousel.overscroll.test.tsx +532 -0
- package/src/Carousel/__tests__/Carousel.page-snap-points.test.tsx +50 -0
- package/src/Carousel/__tests__/Carousel.progress-text.test.tsx +76 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +3 -6
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +2 -5
- package/src/Carousel/__tests__/Carousel.scroll-margin.test.tsx +167 -0
- package/src/Carousel/__tests__/Carousel.scroll-progress.test.tsx +518 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +32 -11
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +237 -24
- package/src/Carousel/__tests__/Carousel.scroll-to-index.test.tsx +115 -0
- package/src/Carousel/__tests__/Carousel.slide-snap.test.tsx +172 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +179 -1
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +91 -7
- package/src/Carousel/__tests__/Carousel.snap-type.test.tsx +35 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +30 -5
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +23 -0
- package/src/Carousel/__tests__/Carousel.wheel.test.tsx +160 -0
- package/src/Carousel/hooks/index.ts +1 -0
- package/src/Carousel/hooks/useCarouselLoop.ts +869 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +338 -42
- package/src/Carousel/hooks/useCarouselSlide.ts +44 -9
- package/src/Carousel/hooks/useCarouselViewport.ts +649 -46
- package/src/Carousel/loopEngine.ts +54 -0
- package/src/Carousel/types.ts +458 -36
- package/src/Checkbox/Checkbox.tsx +11 -3
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +14 -0
- package/src/Checkbox/hooks/useCheckboxInput.ts +4 -0
- package/src/Checkbox/types.ts +19 -0
- package/src/CheckboxCard/CheckboxCard.tsx +14 -5
- package/src/CheckboxCard/README.md +1 -1
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +7 -0
- package/src/CheckboxCard/types.ts +65 -17
- package/src/Collapsible/Collapsible.tsx +49 -3
- package/src/Collapsible/README.md +41 -1
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +89 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +27 -0
- package/src/Collapsible/__tests__/Collapsible.collapsedHeight.test.tsx +140 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +3 -1
- package/src/Collapsible/types.ts +89 -22
- package/src/Combobox/Combobox.tsx +508 -0
- package/src/Combobox/ComboboxContext.ts +23 -0
- package/src/Combobox/README.md +170 -0
- package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
- package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
- package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
- package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
- package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
- package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
- package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
- package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
- package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
- package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
- package/src/Combobox/hooks/index.ts +1 -0
- package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
- package/src/Combobox/index.ts +3 -0
- package/src/Combobox/types.ts +133 -0
- package/src/ContextMenu/ContextMenu.tsx +270 -4
- package/src/ContextMenu/README.md +1 -1
- package/src/ContextMenu/types.ts +148 -6
- package/src/DirectionProvider/DirectionProvider.tsx +41 -9
- package/src/DirectionProvider/README.md +4 -4
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +5 -0
- package/src/DirectionProvider/types.ts +28 -3
- package/src/Divider/Divider.tsx +24 -10
- package/src/Divider/__tests__/Divider.contract.test.tsx +1 -1
- package/src/Divider/__tests__/Divider.test.tsx +5 -0
- package/src/Divider/types.ts +10 -3
- package/src/Drawer/Drawer.tsx +315 -0
- package/src/Drawer/README.md +124 -0
- package/src/Drawer/__tests__/Drawer.behavior.test.tsx +100 -0
- package/src/Drawer/__tests__/Drawer.focus-trap.test.tsx +55 -0
- package/src/Drawer/__tests__/Drawer.rendering.test.tsx +111 -0
- package/src/Drawer/__tests__/Drawer.side.test.tsx +69 -0
- package/src/Drawer/__tests__/dialog-polyfill.ts +39 -0
- package/src/Drawer/index.ts +2 -0
- package/src/Drawer/types.ts +66 -0
- package/src/Dropdown/Dropdown.tsx +119 -2
- package/src/Dropdown/README.md +7 -5
- package/src/Dropdown/types.ts +241 -11
- package/src/EmptyState/EmptyState.tsx +74 -4
- package/src/EmptyState/README.md +2 -2
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +7 -2
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +3 -1
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +10 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +3 -1
- package/src/EmptyState/types.ts +49 -6
- package/src/Field/Field.tsx +23 -0
- package/src/Field/README.md +1 -1
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +11 -0
- package/src/Field/__tests__/Field.error-handling.test.tsx +23 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +21 -0
- package/src/Field/hooks/__tests__/useFieldProps.test.tsx +104 -9
- package/src/Field/hooks/useFieldProps.ts +1 -1
- package/src/Field/types.ts +60 -12
- package/src/Fieldset/Fieldset.tsx +55 -19
- package/src/Fieldset/README.md +2 -2
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +9 -0
- package/src/Fieldset/types.ts +20 -2
- package/src/Input/Input.tsx +15 -10
- package/src/Input/README.md +2 -2
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +5 -0
- package/src/Input/types.ts +19 -2
- package/src/InputGroup/InputGroup.tsx +11 -3
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +14 -0
- package/src/InputGroup/types.ts +26 -5
- package/src/Listbox/Listbox.tsx +475 -0
- package/src/Listbox/ListboxContext.ts +48 -0
- package/src/Listbox/README.md +248 -0
- package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
- package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
- package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
- package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
- package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
- package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
- package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
- package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
- package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
- package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
- package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
- package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
- package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
- package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
- package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
- package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
- package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
- package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
- package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
- package/src/Listbox/constants.ts +2 -0
- package/src/Listbox/hooks/index.ts +1 -0
- package/src/Listbox/hooks/useListboxRoot.ts +407 -0
- package/src/Listbox/index.ts +3 -0
- package/src/Listbox/types.ts +238 -0
- package/src/MillerColumns/MillerColumns.tsx +186 -45
- package/src/MillerColumns/README.md +90 -20
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
- package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
- package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
- package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
- package/src/MillerColumns/constants.ts +2 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +4 -0
- package/src/MillerColumns/types.ts +136 -30
- package/src/MillerColumns/utils.ts +69 -3
- package/src/Modal/Modal.tsx +35 -8
- package/src/Modal/README.md +24 -15
- package/src/Modal/__tests__/Modal.focus-trap.test.tsx +162 -0
- package/src/Modal/hooks/useModalContent.ts +32 -0
- package/src/Modal/types.ts +110 -29
- package/src/NavigationMenu/NavigationMenu.tsx +890 -0
- package/src/NavigationMenu/NavigationMenuContext.ts +67 -0
- package/src/NavigationMenu/README.md +355 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.anchor-positioning.test.tsx +132 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.asChild.test.tsx +73 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.basic-rendering.test.tsx +92 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.contract.test.tsx +45 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.controlled-state.test.tsx +134 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.error-handling.test.tsx +62 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.fixtures.tsx +79 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.forceMount.test.tsx +52 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.hover-interaction.test.tsx +254 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.indicator.test.tsx +252 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-interaction.test.tsx +294 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.keyboard-navigation.test.tsx +126 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.link.test.tsx +139 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.motion.test.tsx +568 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.mouse-interaction.test.tsx +76 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.uncontrolled-state.test.tsx +30 -0
- package/src/NavigationMenu/__tests__/NavigationMenu.viewport.test.tsx +113 -0
- package/src/NavigationMenu/constants.ts +5 -0
- package/src/NavigationMenu/hooks/index.ts +7 -0
- package/src/NavigationMenu/hooks/useNavigationMenuEntry.ts +33 -0
- package/src/NavigationMenu/hooks/useNavigationMenuIndicator.ts +70 -0
- package/src/NavigationMenu/hooks/useNavigationMenuLink.ts +72 -0
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +301 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTopLevelEntry.ts +65 -0
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +158 -0
- package/src/NavigationMenu/hooks/useNavigationMenuViewport.ts +101 -0
- package/src/NavigationMenu/index.ts +3 -0
- package/src/NavigationMenu/types.ts +259 -0
- package/src/NavigationMenu/utils.ts +89 -0
- package/src/Popover/Popover.tsx +427 -0
- package/src/Popover/PopoverContext.ts +13 -0
- package/src/Popover/README.md +106 -0
- package/src/Popover/__tests__/Popover.anchor.test.tsx +38 -0
- package/src/Popover/__tests__/Popover.asChild.test.tsx +160 -0
- package/src/Popover/__tests__/Popover.basic-rendering.test.tsx +106 -0
- package/src/Popover/__tests__/Popover.click-outside.test.tsx +149 -0
- package/src/Popover/__tests__/Popover.close.test.tsx +70 -0
- package/src/Popover/__tests__/Popover.content.test.tsx +58 -0
- package/src/Popover/__tests__/Popover.controlled-state.test.tsx +45 -0
- package/src/Popover/__tests__/Popover.error-handling.test.tsx +51 -0
- package/src/Popover/__tests__/Popover.focus.test.tsx +43 -0
- package/src/Popover/__tests__/Popover.keyboard-interaction.test.tsx +71 -0
- package/src/Popover/__tests__/Popover.mouse-interaction.test.tsx +69 -0
- package/src/Popover/__tests__/Popover.uncontrolled-state.test.tsx +44 -0
- package/src/Popover/hooks/index.ts +3 -0
- package/src/Popover/hooks/usePopoverContent.ts +98 -0
- package/src/Popover/hooks/usePopoverRoot.ts +84 -0
- package/src/Popover/hooks/usePopoverTrigger.ts +33 -0
- package/src/Popover/index.ts +2 -0
- package/src/Popover/types.ts +197 -0
- package/src/Portal/Portal.tsx +50 -10
- package/src/Portal/README.md +2 -2
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +5 -0
- package/src/Portal/types.ts +5 -0
- package/src/Progress/Progress.tsx +33 -1
- package/src/Progress/README.md +1 -1
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +10 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +21 -0
- package/src/Progress/types.ts +20 -4
- package/src/Radio/Radio.tsx +10 -2
- package/src/Radio/__tests__/Radio.basic-rendering.test.tsx +14 -0
- package/src/Radio/hooks/useRadioRoot.ts +3 -0
- package/src/Radio/types.ts +20 -0
- package/src/RadioCard/RadioCard.tsx +11 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +8 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +27 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +53 -0
- package/src/RadioCard/__tests__/RadioCard.ref-forwarding.test.tsx +54 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +21 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +11 -0
- package/src/RadioCard/types.ts +95 -22
- package/src/RadioGroup/README.md +2 -2
- package/src/RadioGroup/RadioGroup.tsx +12 -1
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +8 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +27 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +29 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +31 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +11 -0
- package/src/RadioGroup/types.ts +73 -9
- package/src/SegmentedControl/README.md +154 -0
- package/src/SegmentedControl/SegmentedControl.tsx +309 -0
- package/src/SegmentedControl/SegmentedControlContext.ts +28 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.asChild.test.tsx +50 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.basic-rendering.test.tsx +100 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.controlled-state.test.tsx +107 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled-keydown-guards.test.tsx +62 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.disabled.test.tsx +151 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.error-handling.test.tsx +17 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.keyboard-interaction.test.tsx +117 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.orientation.test.tsx +54 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.reading-direction.test.tsx +56 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.ref-forwarding.test.tsx +45 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.roving-tabindex.test.tsx +104 -0
- package/src/SegmentedControl/__tests__/SegmentedControl.uncontrolled-state.test.tsx +94 -0
- package/src/SegmentedControl/hooks/index.ts +2 -0
- package/src/SegmentedControl/hooks/useSegmentedControlContext.ts +1 -0
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +87 -0
- package/src/SegmentedControl/index.ts +2 -0
- package/src/SegmentedControl/types.ts +138 -0
- package/src/Select/README.md +122 -157
- package/src/Select/Select.tsx +596 -69
- package/src/Select/SelectContext.ts +38 -0
- package/src/Select/SelectItemIndicatorContext.ts +29 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +2 -2
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +22 -3
- package/src/Select/__tests__/Select.controlled-state.test.tsx +6 -6
- package/src/Select/__tests__/Select.data-attributes.test.tsx +4 -4
- package/src/Select/__tests__/Select.field-integration.test.tsx +11 -11
- package/src/Select/__tests__/Select.form.test.tsx +38 -0
- package/src/Select/__tests__/Select.group.test.tsx +6 -6
- package/src/Select/__tests__/Select.keyboard-edge.test.tsx +95 -0
- package/src/Select/__tests__/Select.keyboard.test.tsx +124 -0
- package/src/Select/__tests__/Select.mutation-hardening.test.tsx +317 -0
- package/src/Select/__tests__/Select.native-item.test.tsx +30 -0
- package/src/Select/__tests__/Select.open-close.test.tsx +57 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +7 -7
- package/src/Select/__tests__/Select.rich-asChild.test.tsx +60 -0
- package/src/Select/__tests__/Select.rich-mode.test.tsx +16 -0
- package/src/Select/__tests__/Select.selection.test.tsx +104 -0
- package/src/Select/__tests__/Select.separator.test.tsx +71 -0
- package/src/Select/__tests__/Select.strict-context.test.tsx +36 -0
- package/src/Select/__tests__/Select.typeahead.test.tsx +156 -0
- package/src/Select/__tests__/Select.value-mirror.test.tsx +240 -0
- package/src/Select/constants.ts +8 -0
- package/src/Select/hooks/index.ts +2 -0
- package/src/Select/hooks/useSelectContent.ts +149 -0
- package/src/Select/hooks/useSelectRoot.ts +149 -0
- package/src/Select/types.ts +199 -31
- package/src/SkipNav/README.md +3 -3
- package/src/SkipNav/SkipNav.tsx +72 -32
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +6 -0
- package/src/SkipNav/types.ts +17 -9
- package/src/Slider/README.md +37 -7
- package/src/Slider/Slider.tsx +42 -4
- package/src/Slider/SliderContext.ts +28 -1
- package/src/Slider/types.ts +157 -9
- package/src/Slot/Slot.tsx +94 -19
- package/src/Slot/__tests__/Slot.test.tsx +112 -2
- package/src/Slot/composeEventHandlers.ts +26 -2
- package/src/Slot/types.ts +16 -0
- package/src/SplitButton/README.md +225 -0
- package/src/SplitButton/SplitButton.tsx +457 -0
- package/src/SplitButton/SplitButtonContext.ts +26 -0
- package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
- package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
- package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
- package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
- package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
- package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
- package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
- package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
- package/src/SplitButton/index.ts +2 -0
- package/src/SplitButton/types.ts +186 -0
- package/src/Status/Status.tsx +36 -15
- package/src/Status/__tests__/Status.test.tsx +5 -0
- package/src/Status/types.ts +12 -1
- package/src/Switch/README.md +1 -1
- package/src/Switch/Switch.tsx +17 -4
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +7 -0
- package/src/Switch/hooks/useSwitchRoot.ts +3 -0
- package/src/Switch/types.ts +49 -22
- package/src/Table/README.md +110 -8
- package/src/Table/Table.tsx +246 -16
- package/src/Table/TableExpandableContext.ts +7 -0
- package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
- package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
- package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
- package/src/Table/types.ts +191 -17
- package/src/Tabs/README.md +4 -4
- package/src/Tabs/Tabs.tsx +13 -4
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
- package/src/Tabs/hooks/useTabsContent.ts +5 -0
- package/src/Tabs/hooks/useTabsRoot.ts +29 -5
- package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
- package/src/Tabs/types.ts +29 -4
- package/src/Textarea/Textarea.tsx +9 -1
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +5 -0
- package/src/Textarea/types.ts +20 -3
- package/src/Toggle/Toggle.tsx +51 -13
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +5 -0
- package/src/Toggle/types.ts +54 -4
- package/src/ToggleGroup/README.md +4 -4
- package/src/ToggleGroup/ToggleGroup.tsx +14 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +20 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.dynamic-items.test.tsx +70 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.error-handling.test.tsx +23 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +23 -1
- package/src/ToggleGroup/__tests__/ToggleGroup.ref-forwarding.test.tsx +58 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +21 -1
- package/src/ToggleGroup/types.ts +55 -2
- package/src/Tooltip/README.md +7 -7
- package/src/Tooltip/Tooltip.tsx +100 -22
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
- package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
- package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
- package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
- package/src/Tooltip/hooks/__tests__/useTooltipProvider.test.tsx +40 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +46 -8
- package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
- package/src/Tooltip/types.ts +89 -8
- package/src/Tree/README.md +69 -5
- package/src/Tree/Tree.tsx +188 -34
- package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
- package/src/Tree/hooks/useTreePath.ts +33 -1
- package/src/Tree/index.ts +6 -1
- package/src/Tree/types.ts +54 -12
- package/src/Tree/utils.ts +46 -4
- package/src/VisuallyHidden/VisuallyHidden.tsx +33 -8
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +15 -1
- package/src/VisuallyHidden/types.ts +11 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useTypeahead.ts +95 -0
- package/src/index.ts +10 -0
- package/src/test/intersectionObserverPolyfill.ts +7 -0
- package/src/test/resizeObserverPolyfill.ts +80 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
- package/src/useLocalStorage/index.ts +1 -0
- package/src/useLocalStorage/useLocalStorage.ts +140 -0
- package/src/useMediaQuery/README.md +66 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
- package/src/useMediaQuery/index.ts +1 -0
- package/src/useMediaQuery/useMediaQuery.ts +49 -0
- package/src/usePagination/README.md +170 -0
- package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
- package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
- package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
- package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
- package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
- package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
- package/src/usePagination/index.ts +1 -0
- package/src/usePagination/usePagination.ts +292 -0
- package/src/utils/focusable.ts +15 -0
- package/src/utils/getKeyToActionMap.ts +1 -1
- package/src/utils/index.ts +1 -0
|
@@ -3,6 +3,7 @@ import type { ReactElement } from "react";
|
|
|
3
3
|
import { Slot } from "../Slot/index.ts";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
+
BreadcrumbEllipsisProps,
|
|
6
7
|
BreadcrumbItemProps,
|
|
7
8
|
BreadcrumbLinkProps,
|
|
8
9
|
BreadcrumbListProps,
|
|
@@ -15,14 +16,30 @@ import {
|
|
|
15
16
|
* The root of a Breadcrumb — a `<nav>` landmark that wraps the breadcrumb
|
|
16
17
|
* trail.
|
|
17
18
|
*
|
|
19
|
+
* Stateless: `Breadcrumb.Root` holds no context and derives no state — it
|
|
20
|
+
* is purely a semantic wrapper. All native `<nav>` attributes pass straight
|
|
21
|
+
* through to the DOM, so there is no controlled/uncontrolled shape to
|
|
22
|
+
* reason about.
|
|
23
|
+
*
|
|
18
24
|
* The `<nav>` defaults to `aria-label="Breadcrumb"` so assistive technology
|
|
19
|
-
* announces it as the breadcrumb navigation landmark
|
|
20
|
-
*
|
|
25
|
+
* announces it as the breadcrumb navigation landmark, distinguishing it
|
|
26
|
+
* from any other `<nav>` on the page (e.g. primary site navigation).
|
|
27
|
+
* Override `aria-label` if your product uses different terminology (e.g.
|
|
28
|
+
* `"You are here"`).
|
|
29
|
+
*
|
|
30
|
+
* @extends HTMLElement
|
|
21
31
|
*
|
|
22
32
|
* @example
|
|
23
33
|
* ```tsx
|
|
24
34
|
* <Breadcrumb.Root>
|
|
25
|
-
* <Breadcrumb.List
|
|
35
|
+
* <Breadcrumb.List>...</Breadcrumb.List>
|
|
36
|
+
* </Breadcrumb.Root>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example Custom accessible name
|
|
40
|
+
* ```tsx
|
|
41
|
+
* <Breadcrumb.Root aria-label="You are here">
|
|
42
|
+
* <Breadcrumb.List>...</Breadcrumb.List>
|
|
26
43
|
* </Breadcrumb.Root>
|
|
27
44
|
* ```
|
|
28
45
|
*/
|
|
@@ -37,23 +54,32 @@ export function BreadcrumbRoot({
|
|
|
37
54
|
);
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
|
|
57
|
+
// Runtime-dead: the compound alias below (same object via Object.assign)
|
|
58
|
+
// overwrites this to "Breadcrumb" at load, so the value is never observable.
|
|
59
|
+
// The assignment stays because it declares `displayName` on
|
|
60
|
+
// `typeof BreadcrumbRoot`, which TBreadcrumbCompound extends.
|
|
61
|
+
// Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
|
|
41
62
|
BreadcrumbRoot.displayName = "BreadcrumbRoot";
|
|
42
63
|
|
|
43
64
|
/**
|
|
44
65
|
* The ordered list of breadcrumb entries — renders an `<ol>`.
|
|
45
66
|
*
|
|
46
67
|
* An ordered list is the correct semantic: breadcrumb entries have a
|
|
47
|
-
* meaningful sequence from the site root to the current page
|
|
68
|
+
* meaningful sequence from the site root to the current page (unlike a
|
|
69
|
+
* `<ul>`, whose items are unordered). Renders as a direct child of
|
|
70
|
+
* {@link BreadcrumbRoot | `Breadcrumb.Root`}'s `<nav>`, and contains
|
|
48
71
|
* {@link BreadcrumbItem | `Breadcrumb.Item`}s interleaved with
|
|
49
|
-
* {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s.
|
|
72
|
+
* {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s. Stateless — no
|
|
73
|
+
* context is read or provided.
|
|
74
|
+
*
|
|
75
|
+
* @extends HTMLOListElement
|
|
50
76
|
*
|
|
51
77
|
* @example
|
|
52
78
|
* ```tsx
|
|
53
79
|
* <Breadcrumb.List>
|
|
54
|
-
* <Breadcrumb.Item
|
|
80
|
+
* <Breadcrumb.Item>...</Breadcrumb.Item>
|
|
55
81
|
* <Breadcrumb.Separator />
|
|
56
|
-
* <Breadcrumb.Item
|
|
82
|
+
* <Breadcrumb.Item>...</Breadcrumb.Item>
|
|
57
83
|
* </Breadcrumb.List>
|
|
58
84
|
* ```
|
|
59
85
|
*/
|
|
@@ -71,7 +97,13 @@ BreadcrumbList.displayName = "BreadcrumbList";
|
|
|
71
97
|
* A single entry in the breadcrumb trail — renders an `<li>`.
|
|
72
98
|
*
|
|
73
99
|
* Wraps either a {@link BreadcrumbLink | `Breadcrumb.Link`} (an ancestor
|
|
74
|
-
* page) or a {@link BreadcrumbPage | `Breadcrumb.Page`} (the current page)
|
|
100
|
+
* page) or a {@link BreadcrumbPage | `Breadcrumb.Page`} (the current page),
|
|
101
|
+
* as a child of {@link BreadcrumbList | `Breadcrumb.List`}'s `<ol>`.
|
|
102
|
+
* {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s are siblings of
|
|
103
|
+
* `Breadcrumb.Item`, not nested inside it. Purely structural — no ARIA
|
|
104
|
+
* attributes or state of its own.
|
|
105
|
+
*
|
|
106
|
+
* @extends HTMLLIElement
|
|
75
107
|
*
|
|
76
108
|
* @example
|
|
77
109
|
* ```tsx
|
|
@@ -93,12 +125,18 @@ BreadcrumbItem.displayName = "BreadcrumbItem";
|
|
|
93
125
|
/**
|
|
94
126
|
* A link to an ancestor page — renders an `<a>`.
|
|
95
127
|
*
|
|
96
|
-
* Use for every entry except the current page
|
|
97
|
-
*
|
|
128
|
+
* Use for every entry except the current page (the last entry should be a
|
|
129
|
+
* {@link BreadcrumbPage | `Breadcrumb.Page`} instead). Pass `href` (and any
|
|
130
|
+
* other anchor attributes) directly; nothing is inferred or defaulted.
|
|
98
131
|
*
|
|
99
|
-
* **`asChild`
|
|
100
|
-
* typically a routing library's `<Link>` —
|
|
101
|
-
*
|
|
132
|
+
* **`asChild` composition.** Pass `asChild` to render a consumer-supplied
|
|
133
|
+
* element — typically a routing library's `<Link>` — instead of the native
|
|
134
|
+
* `<a>`, with all of `Breadcrumb.Link`'s props (`href`, event handlers,
|
|
135
|
+
* `className`, `ref`, ...) merged onto it via the {@link Slot} pattern. The
|
|
136
|
+
* child **must** be a single React element that accepts a `ref`; the
|
|
137
|
+
* native `<a>` is dropped entirely rather than wrapping the child.
|
|
138
|
+
*
|
|
139
|
+
* @extends HTMLAnchorElement
|
|
102
140
|
*
|
|
103
141
|
* @example
|
|
104
142
|
* ```tsx
|
|
@@ -129,9 +167,13 @@ BreadcrumbLink.displayName = "BreadcrumbLink";
|
|
|
129
167
|
/**
|
|
130
168
|
* The current page in the trail — renders a `<span aria-current="page">`.
|
|
131
169
|
*
|
|
132
|
-
* The last entry of a breadcrumb is the page the user is on, so it
|
|
133
|
-
* link
|
|
134
|
-
*
|
|
170
|
+
* The last entry of a breadcrumb is the page the user is already on, so it
|
|
171
|
+
* is deliberately not a {@link BreadcrumbLink | `Breadcrumb.Link`} — a link
|
|
172
|
+
* to the current page is not a meaningful navigation target.
|
|
173
|
+
* `aria-current="page"` is fixed by the component (not overridable) and
|
|
174
|
+
* tells assistive technology this entry represents the current location.
|
|
175
|
+
*
|
|
176
|
+
* @extends HTMLSpanElement
|
|
135
177
|
*
|
|
136
178
|
* @example
|
|
137
179
|
* ```tsx
|
|
@@ -159,12 +201,18 @@ BreadcrumbPage.displayName = "BreadcrumbPage";
|
|
|
159
201
|
* `<li role="presentation" aria-hidden="true">`.
|
|
160
202
|
*
|
|
161
203
|
* The separator sits inside the `<ol>` as a sibling of the
|
|
162
|
-
* {@link BreadcrumbItem | `Breadcrumb.Item`}s
|
|
163
|
-
*
|
|
164
|
-
*
|
|
204
|
+
* {@link BreadcrumbItem | `Breadcrumb.Item`}s (an `<ol>`'s valid children
|
|
205
|
+
* are all `<li>`s, so the separator is one too), but `role="presentation"`
|
|
206
|
+
* removes it from the list semantics — screen readers don't announce it as
|
|
207
|
+
* a list item — and `aria-hidden="true"` hides it from the accessibility
|
|
208
|
+
* tree entirely. Both attributes are fixed by the component and not
|
|
209
|
+
* overridable; it is purely decorative.
|
|
165
210
|
*
|
|
166
211
|
* Defaults to a `"/"` glyph; pass `children` to use a custom separator (an
|
|
167
|
-
* icon, a chevron, etc.)
|
|
212
|
+
* icon, a chevron, `"›"`, etc.) — whatever is passed replaces the default
|
|
213
|
+
* entirely rather than being appended to it.
|
|
214
|
+
*
|
|
215
|
+
* @extends HTMLLIElement
|
|
168
216
|
*
|
|
169
217
|
* @example Default separator
|
|
170
218
|
* ```tsx
|
|
@@ -190,6 +238,69 @@ export function BreadcrumbSeparator({
|
|
|
190
238
|
/** @internal */
|
|
191
239
|
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
|
192
240
|
|
|
241
|
+
/**
|
|
242
|
+
* The decorative "more" glyph for an overflow trigger — renders a
|
|
243
|
+
* `<span role="presentation" aria-hidden="true">`.
|
|
244
|
+
*
|
|
245
|
+
* Purely a visual glyph, not itself the interactive element. Breadcrumb
|
|
246
|
+
* owns no truncation or menu-open state of its own — composing an overflow
|
|
247
|
+
* trigger means wrapping this glyph in a real interactive element (a
|
|
248
|
+
* `<button>`, or any {@link Slot}-compatible component) and reaching for
|
|
249
|
+
* {@link https://primitiv-ui.dev/docs/components/dropdown | `Dropdown`} for
|
|
250
|
+
* the menu itself. `Breadcrumb.Ellipsis` sits inside that button as its
|
|
251
|
+
* decorative content; the button's *accessible name* must come from
|
|
252
|
+
* elsewhere (visible text, or a
|
|
253
|
+
* {@link https://primitiv-ui.dev/docs/components/visually-hidden | `VisuallyHidden`}
|
|
254
|
+
* label) — `aria-hidden="true"` excludes this glyph from accessible-name
|
|
255
|
+
* computation entirely, exactly like {@link BreadcrumbSeparator}.
|
|
256
|
+
*
|
|
257
|
+
* Defaults to a `"..."` glyph; pass `children` to use a custom one (an icon,
|
|
258
|
+
* `"···"`, etc.) — whatever is passed replaces the default entirely rather
|
|
259
|
+
* than being appended to it.
|
|
260
|
+
*
|
|
261
|
+
* @extends HTMLSpanElement
|
|
262
|
+
*
|
|
263
|
+
* @example Default glyph
|
|
264
|
+
* ```tsx
|
|
265
|
+
* <Breadcrumb.Ellipsis />
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example Composed as an overflow trigger with Dropdown
|
|
269
|
+
* ```tsx
|
|
270
|
+
* <Breadcrumb.Item>
|
|
271
|
+
* <Dropdown.Root>
|
|
272
|
+
* <Dropdown.Trigger asChild>
|
|
273
|
+
* <button type="button">
|
|
274
|
+
* <Breadcrumb.Ellipsis />
|
|
275
|
+
* <VisuallyHidden>Show hidden pages</VisuallyHidden>
|
|
276
|
+
* </button>
|
|
277
|
+
* </Dropdown.Trigger>
|
|
278
|
+
* <Dropdown.Content>
|
|
279
|
+
* <Dropdown.Item onSelect={() => navigate("/products")}>
|
|
280
|
+
* Products
|
|
281
|
+
* </Dropdown.Item>
|
|
282
|
+
* </Dropdown.Content>
|
|
283
|
+
* </Dropdown.Root>
|
|
284
|
+
* </Breadcrumb.Item>
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
export function BreadcrumbEllipsis({
|
|
288
|
+
children = "...",
|
|
289
|
+
asChild = false,
|
|
290
|
+
...rest
|
|
291
|
+
}: BreadcrumbEllipsisProps): ReactElement {
|
|
292
|
+
const ellipsisProps = { ...rest, role: "presentation" as const, "aria-hidden": true as const };
|
|
293
|
+
|
|
294
|
+
if (asChild) {
|
|
295
|
+
return <Slot {...ellipsisProps}>{children}</Slot>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return <span {...ellipsisProps}>{children}</span>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** @internal */
|
|
302
|
+
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis";
|
|
303
|
+
|
|
193
304
|
/** Type of the {@link Breadcrumb} compound: the root callable plus its attached sub-components. */
|
|
194
305
|
export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
|
|
195
306
|
Root: typeof BreadcrumbRoot;
|
|
@@ -198,6 +309,7 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
|
|
|
198
309
|
Link: typeof BreadcrumbLink;
|
|
199
310
|
Page: typeof BreadcrumbPage;
|
|
200
311
|
Separator: typeof BreadcrumbSeparator;
|
|
312
|
+
Ellipsis: typeof BreadcrumbEllipsis;
|
|
201
313
|
};
|
|
202
314
|
|
|
203
315
|
/**
|
|
@@ -217,6 +329,7 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
|
|
|
217
329
|
* - {@link BreadcrumbLink | `Breadcrumb.Link`} — `<a>`, an ancestor-page link.
|
|
218
330
|
* - {@link BreadcrumbPage | `Breadcrumb.Page`} — `<span aria-current="page">`, the current page.
|
|
219
331
|
* - {@link BreadcrumbSeparator | `Breadcrumb.Separator`} — decorative `<li>` divider.
|
|
332
|
+
* - {@link BreadcrumbEllipsis | `Breadcrumb.Ellipsis`} — decorative `<span>` overflow-trigger glyph.
|
|
220
333
|
*
|
|
221
334
|
* @example Minimal usage
|
|
222
335
|
* ```tsx
|
|
@@ -239,7 +352,11 @@ export type TBreadcrumbCompound = typeof BreadcrumbRoot & {
|
|
|
239
352
|
* </Breadcrumb.Root>
|
|
240
353
|
* ```
|
|
241
354
|
*
|
|
355
|
+
* @see {@link BreadcrumbRoot} for the `aria-label` landmark default.
|
|
356
|
+
* @see {@link BreadcrumbLink} for the `asChild` router-link pattern.
|
|
357
|
+
* @see {@link BreadcrumbPage} for marking the current, non-linked entry.
|
|
242
358
|
* @see {@link BreadcrumbSeparator} for customising the divider glyph.
|
|
359
|
+
* @see {@link BreadcrumbEllipsis} for composing an overflow trigger with `Dropdown`.
|
|
243
360
|
*/
|
|
244
361
|
const BreadcrumbCompound: TBreadcrumbCompound = Object.assign(BreadcrumbRoot, {
|
|
245
362
|
Root: BreadcrumbRoot,
|
|
@@ -248,6 +365,7 @@ const BreadcrumbCompound: TBreadcrumbCompound = Object.assign(BreadcrumbRoot, {
|
|
|
248
365
|
Link: BreadcrumbLink,
|
|
249
366
|
Page: BreadcrumbPage,
|
|
250
367
|
Separator: BreadcrumbSeparator,
|
|
368
|
+
Ellipsis: BreadcrumbEllipsis,
|
|
251
369
|
});
|
|
252
370
|
|
|
253
371
|
/** @internal */
|
package/src/Breadcrumb/README.md
CHANGED
|
@@ -35,6 +35,7 @@ import { Breadcrumb } from "@primitiv-ui/react";
|
|
|
35
35
|
| `Breadcrumb.Link` | `<a>` | — | yes |
|
|
36
36
|
| `Breadcrumb.Page` | `<span>` | `aria-current="page"` | — |
|
|
37
37
|
| `Breadcrumb.Separator` | `<li>` | `role="presentation"`, `aria-hidden="true"` | — |
|
|
38
|
+
| `Breadcrumb.Ellipsis` | `<span>` | `role="presentation"`, `aria-hidden="true"` | yes |
|
|
38
39
|
|
|
39
40
|
Every sub-component passes all of its element's native attributes straight
|
|
40
41
|
through to the DOM.
|
|
@@ -79,6 +80,52 @@ It defaults to a `"/"` glyph. Pass `children` to use a custom separator:
|
|
|
79
80
|
<Breadcrumb.Separator><ChevronRight /></Breadcrumb.Separator>
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
## Overflow trigger
|
|
84
|
+
|
|
85
|
+
`Breadcrumb` owns no truncation or menu-open state of its own — deciding
|
|
86
|
+
which crumbs collapse is a styled-surface concern (see the registry `card`
|
|
87
|
+
composite for the same "no data model in the primitive" philosophy). What
|
|
88
|
+
the headless layer provides is `Breadcrumb.Ellipsis`: a purely decorative
|
|
89
|
+
`"..."` glyph for the trigger's visual content.
|
|
90
|
+
|
|
91
|
+
Compose it with [`Dropdown`](../Dropdown/README.md) to build the actual
|
|
92
|
+
trigger — a real interactive element (a `<button>`, or anything
|
|
93
|
+
`asChild`-compatible) wrapping the glyph, with the accessible name coming
|
|
94
|
+
from visible text or a `VisuallyHidden` label rather than the glyph itself
|
|
95
|
+
(`Breadcrumb.Ellipsis` is `aria-hidden`, exactly like `Breadcrumb.Separator`,
|
|
96
|
+
so it never contributes to accessible-name computation):
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { Breadcrumb, Dropdown, VisuallyHidden } from "@primitiv-ui/react";
|
|
100
|
+
|
|
101
|
+
<Breadcrumb.Item>
|
|
102
|
+
<Dropdown.Root>
|
|
103
|
+
<Dropdown.Trigger asChild>
|
|
104
|
+
<button type="button">
|
|
105
|
+
<Breadcrumb.Ellipsis />
|
|
106
|
+
<VisuallyHidden>Show hidden pages</VisuallyHidden>
|
|
107
|
+
</button>
|
|
108
|
+
</Dropdown.Trigger>
|
|
109
|
+
<Dropdown.Content>
|
|
110
|
+
<Dropdown.Item onSelect={() => navigate("/products")}>
|
|
111
|
+
Products
|
|
112
|
+
</Dropdown.Item>
|
|
113
|
+
<Dropdown.Item onSelect={() => navigate("/products/electronics")}>
|
|
114
|
+
Electronics
|
|
115
|
+
</Dropdown.Item>
|
|
116
|
+
</Dropdown.Content>
|
|
117
|
+
</Dropdown.Root>
|
|
118
|
+
</Breadcrumb.Item>;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`Breadcrumb.Ellipsis` defaults to a `"..."` glyph; pass `children` to use a
|
|
122
|
+
custom one:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<Breadcrumb.Ellipsis>···</Breadcrumb.Ellipsis>
|
|
126
|
+
<Breadcrumb.Ellipsis><MoreIcon /></Breadcrumb.Ellipsis>
|
|
127
|
+
```
|
|
128
|
+
|
|
82
129
|
## `asChild` composition
|
|
83
130
|
|
|
84
131
|
`Breadcrumb.Link` accepts `asChild` so it can render a routing library's link
|
|
@@ -129,4 +129,15 @@ describe("Breadcrumb basic rendering", () => {
|
|
|
129
129
|
"page",
|
|
130
130
|
);
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
it("sets a displayName on the compound and each sub-component", () => {
|
|
134
|
+
// Assert — an empty displayName would render each as anonymous in DevTools.
|
|
135
|
+
// Root aliases the compound (Object.assign), so its name is "Breadcrumb".
|
|
136
|
+
expect(Breadcrumb.displayName).toBe("Breadcrumb");
|
|
137
|
+
expect(Breadcrumb.List.displayName).toBe("BreadcrumbList");
|
|
138
|
+
expect(Breadcrumb.Item.displayName).toBe("BreadcrumbItem");
|
|
139
|
+
expect(Breadcrumb.Link.displayName).toBe("BreadcrumbLink");
|
|
140
|
+
expect(Breadcrumb.Page.displayName).toBe("BreadcrumbPage");
|
|
141
|
+
expect(Breadcrumb.Separator.displayName).toBe("BreadcrumbSeparator");
|
|
142
|
+
});
|
|
132
143
|
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { Breadcrumb } from "../Breadcrumb";
|
|
5
|
+
import { Dropdown } from "../../Dropdown/Dropdown";
|
|
6
|
+
|
|
7
|
+
describe("Breadcrumb ellipsis", () => {
|
|
8
|
+
it("renders the Ellipsis as a decorative <span> with a default glyph", () => {
|
|
9
|
+
// Arrange & Act
|
|
10
|
+
render(<Breadcrumb.Ellipsis data-testid="ellipsis" />);
|
|
11
|
+
const ellipsis = screen.getByTestId("ellipsis");
|
|
12
|
+
|
|
13
|
+
// Assert — presentation + aria-hidden keep it out of the accessibility
|
|
14
|
+
// tree entirely, matching Breadcrumb.Separator's own convention: it is
|
|
15
|
+
// the interactive element composed AROUND it that must carry the
|
|
16
|
+
// accessible name, not this glyph.
|
|
17
|
+
expect(ellipsis.tagName).toBe("SPAN");
|
|
18
|
+
expect(ellipsis).toHaveAttribute("role", "presentation");
|
|
19
|
+
expect(ellipsis).toHaveAttribute("aria-hidden", "true");
|
|
20
|
+
expect(ellipsis).toHaveTextContent("...");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("lets the consumer override the Ellipsis glyph", () => {
|
|
24
|
+
// Arrange & Act
|
|
25
|
+
render(
|
|
26
|
+
<Breadcrumb.Ellipsis data-testid="ellipsis">···</Breadcrumb.Ellipsis>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Assert
|
|
30
|
+
expect(screen.getByTestId("ellipsis")).toHaveTextContent("···");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("renders Breadcrumb.Ellipsis as the consumer element via asChild, with no wrapping <span>", () => {
|
|
34
|
+
// Arrange & Act
|
|
35
|
+
render(
|
|
36
|
+
<Breadcrumb.Ellipsis asChild>
|
|
37
|
+
<i data-testid="custom-ellipsis" />
|
|
38
|
+
</Breadcrumb.Ellipsis>,
|
|
39
|
+
);
|
|
40
|
+
const ellipsis = screen.getByTestId("custom-ellipsis");
|
|
41
|
+
|
|
42
|
+
// Assert — the native <span> is dropped entirely
|
|
43
|
+
expect(ellipsis.tagName).toBe("I");
|
|
44
|
+
expect(ellipsis.closest("span")).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("merges Breadcrumb.Ellipsis props onto the asChild element", () => {
|
|
48
|
+
// Arrange & Act
|
|
49
|
+
render(
|
|
50
|
+
<Breadcrumb.Ellipsis asChild className="crumb-ellipsis">
|
|
51
|
+
<i data-testid="custom-ellipsis" />
|
|
52
|
+
</Breadcrumb.Ellipsis>,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Assert — role/aria-hidden still land on the consumer's element
|
|
56
|
+
const ellipsis = screen.getByTestId("custom-ellipsis");
|
|
57
|
+
expect(ellipsis).toHaveClass("crumb-ellipsis");
|
|
58
|
+
expect(ellipsis).toHaveAttribute("role", "presentation");
|
|
59
|
+
expect(ellipsis).toHaveAttribute("aria-hidden", "true");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("sets a displayName on Ellipsis", () => {
|
|
63
|
+
// Assert — an empty displayName would render it as anonymous in DevTools.
|
|
64
|
+
expect(Breadcrumb.Ellipsis.displayName).toBe("BreadcrumbEllipsis");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("composes cleanly as Dropdown.Trigger's asChild child — the trigger owns the interactive element and accessible name, the Ellipsis is purely decorative inside it", async () => {
|
|
68
|
+
// Arrange — the documented composition pattern: the actual <button> is
|
|
69
|
+
// a sibling of the aria-hidden Ellipsis glyph, not the Ellipsis itself,
|
|
70
|
+
// so the button's accessible name comes from the visible text rather
|
|
71
|
+
// than being swallowed by the glyph's own aria-hidden.
|
|
72
|
+
const user = userEvent.setup();
|
|
73
|
+
render(
|
|
74
|
+
<Breadcrumb.Item>
|
|
75
|
+
<Dropdown.Root>
|
|
76
|
+
<Dropdown.Trigger asChild>
|
|
77
|
+
<button type="button" data-testid="overflow-trigger">
|
|
78
|
+
<Breadcrumb.Ellipsis />
|
|
79
|
+
<span>More</span>
|
|
80
|
+
</button>
|
|
81
|
+
</Dropdown.Trigger>
|
|
82
|
+
<Dropdown.Content>
|
|
83
|
+
<Dropdown.Item>Products</Dropdown.Item>
|
|
84
|
+
</Dropdown.Content>
|
|
85
|
+
</Dropdown.Root>
|
|
86
|
+
</Breadcrumb.Item>,
|
|
87
|
+
);
|
|
88
|
+
const trigger = screen.getByTestId("overflow-trigger");
|
|
89
|
+
|
|
90
|
+
// Assert — ARIA wiring from Dropdown lands on the real button, and its
|
|
91
|
+
// accessible name is the visible "More" text, not swallowed by the
|
|
92
|
+
// aria-hidden ellipsis glyph.
|
|
93
|
+
expect(trigger).toHaveAttribute("aria-haspopup", "menu");
|
|
94
|
+
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
95
|
+
expect(
|
|
96
|
+
screen.getByRole("button", { name: "More" }),
|
|
97
|
+
).toBe(trigger);
|
|
98
|
+
|
|
99
|
+
// Act
|
|
100
|
+
await user.click(trigger);
|
|
101
|
+
|
|
102
|
+
// Assert — the menu opens with the hidden crumb as a real menu item.
|
|
103
|
+
// `hidden: true` because jsdom does not compute Popover-API visibility —
|
|
104
|
+
// the same convention every Dropdown test in this repo already uses.
|
|
105
|
+
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
106
|
+
expect(
|
|
107
|
+
screen.getByRole("menuitem", { name: "Products", hidden: true }),
|
|
108
|
+
).toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/Breadcrumb/types.ts
CHANGED
|
@@ -1,22 +1,88 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Props for {@link BreadcrumbRoot | `Breadcrumb.Root`} — all native `<nav>`
|
|
5
|
+
* attributes. `aria-label` defaults to `"Breadcrumb"` when omitted; pass it
|
|
6
|
+
* explicitly to override the accessible name announced to assistive
|
|
7
|
+
* technology.
|
|
8
|
+
*
|
|
9
|
+
* @extends HTMLElement
|
|
10
|
+
*/
|
|
4
11
|
export type BreadcrumbRootProps = ComponentProps<"nav">;
|
|
5
12
|
|
|
6
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Props for {@link BreadcrumbList | `Breadcrumb.List`} — all native `<ol>`
|
|
15
|
+
* attributes. No props are added; content is
|
|
16
|
+
* {@link BreadcrumbItem | `Breadcrumb.Item`}s interleaved with
|
|
17
|
+
* {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s.
|
|
18
|
+
*
|
|
19
|
+
* @extends HTMLOListElement
|
|
20
|
+
*/
|
|
7
21
|
export type BreadcrumbListProps = ComponentProps<"ol">;
|
|
8
22
|
|
|
9
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Props for {@link BreadcrumbItem | `Breadcrumb.Item`} — all native `<li>`
|
|
25
|
+
* attributes. No props are added; wraps a single
|
|
26
|
+
* {@link BreadcrumbLink | `Breadcrumb.Link`} or
|
|
27
|
+
* {@link BreadcrumbPage | `Breadcrumb.Page`}.
|
|
28
|
+
*
|
|
29
|
+
* @extends HTMLLIElement
|
|
30
|
+
*/
|
|
10
31
|
export type BreadcrumbItemProps = ComponentProps<"li">;
|
|
11
32
|
|
|
12
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Props for {@link BreadcrumbLink | `Breadcrumb.Link`} — all native `<a>`
|
|
35
|
+
* attributes plus the `asChild` escape hatch.
|
|
36
|
+
*
|
|
37
|
+
* @extends HTMLAnchorElement
|
|
38
|
+
*/
|
|
13
39
|
export type BreadcrumbLinkProps = ComponentProps<"a"> & {
|
|
14
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Renders the child element instead of a native `<a>`, merging all of
|
|
42
|
+
* `Breadcrumb.Link`'s props — `href`, event handlers, `className`,
|
|
43
|
+
* `ref` — onto it via {@link Slot}. Use for routing-library link
|
|
44
|
+
* components (e.g. React Router's `<Link>`) that need breadcrumb
|
|
45
|
+
* semantics. See the `asChild` example on {@link BreadcrumbLink}.
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
15
48
|
asChild?: boolean;
|
|
16
49
|
};
|
|
17
50
|
|
|
18
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Props for {@link BreadcrumbPage | `Breadcrumb.Page`} — all native
|
|
53
|
+
* `<span>` attributes. `aria-current="page"` is fixed by the component and
|
|
54
|
+
* cannot be overridden through these props.
|
|
55
|
+
*
|
|
56
|
+
* @extends HTMLSpanElement
|
|
57
|
+
*/
|
|
19
58
|
export type BreadcrumbPageProps = ComponentProps<"span">;
|
|
20
59
|
|
|
21
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Props for {@link BreadcrumbSeparator | `Breadcrumb.Separator`} — all
|
|
62
|
+
* native `<li>` attributes. `role="presentation"` and `aria-hidden="true"`
|
|
63
|
+
* are fixed by the component; `children` defaults to a `"/"` glyph when
|
|
64
|
+
* omitted (see {@link BreadcrumbSeparator}'s custom-separator example).
|
|
65
|
+
*
|
|
66
|
+
* @extends HTMLLIElement
|
|
67
|
+
*/
|
|
22
68
|
export type BreadcrumbSeparatorProps = ComponentProps<"li">;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Props for {@link BreadcrumbEllipsis | `Breadcrumb.Ellipsis`} — all native
|
|
72
|
+
* `<span>` attributes plus the `asChild` escape hatch. `role="presentation"`
|
|
73
|
+
* and `aria-hidden="true"` are fixed by the component; `children` defaults
|
|
74
|
+
* to a `"..."` glyph when omitted (see {@link BreadcrumbEllipsis}'s
|
|
75
|
+
* custom-glyph example).
|
|
76
|
+
*
|
|
77
|
+
* @extends HTMLSpanElement
|
|
78
|
+
*/
|
|
79
|
+
export type BreadcrumbEllipsisProps = ComponentProps<"span"> & {
|
|
80
|
+
/**
|
|
81
|
+
* Renders the child element instead of a native `<span>`, merging all of
|
|
82
|
+
* `Breadcrumb.Ellipsis`'s props — `role`, `aria-hidden`, `className`,
|
|
83
|
+
* `ref` — onto it via {@link Slot}. See the `asChild` example on
|
|
84
|
+
* {@link BreadcrumbEllipsis}.
|
|
85
|
+
* @default false
|
|
86
|
+
*/
|
|
87
|
+
asChild?: boolean;
|
|
88
|
+
};
|
package/src/Button/Button.tsx
CHANGED
|
@@ -123,4 +123,10 @@ describe("Button basic rendering", () => {
|
|
|
123
123
|
// Assert
|
|
124
124
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
125
125
|
});
|
|
126
|
+
|
|
127
|
+
it('sets displayName to "Button" for React DevTools and stack traces', () => {
|
|
128
|
+
// Assert — the component's identity is observable in DevTools and error
|
|
129
|
+
// output; an empty displayName would render it as an anonymous component.
|
|
130
|
+
expect(Button.displayName).toBe("Button");
|
|
131
|
+
});
|
|
126
132
|
});
|
|
@@ -60,7 +60,7 @@ describe("Button styling contract", () => {
|
|
|
60
60
|
);
|
|
61
61
|
|
|
62
62
|
render(<Button disabled>Save</Button>);
|
|
63
|
-
// Present with the documented value when the documented condition holds
|
|
63
|
+
// Present with the documented value when the documented condition holds...
|
|
64
64
|
expect(screen.getByRole("button")).toHaveAttribute(entry.name, entry.value);
|
|
65
65
|
});
|
|
66
66
|
|
package/src/Button/types.ts
CHANGED
|
@@ -3,10 +3,31 @@ import { ComponentProps, ReactNode, Ref } from "react";
|
|
|
3
3
|
/**
|
|
4
4
|
* Props for {@link Button} — all native `<button>` attributes (with `type`
|
|
5
5
|
* defaulting to `"button"`) plus the `asChild` escape hatch and a typed `ref`.
|
|
6
|
+
*
|
|
7
|
+
* @extends HTMLButtonElement
|
|
6
8
|
*/
|
|
7
9
|
export type ButtonProps = Omit<ComponentProps<"button">, "type"> & {
|
|
10
|
+
/**
|
|
11
|
+
* The button's native `type` attribute, restricted to the three valid
|
|
12
|
+
* values. Defaults to `"button"` (not the DOM's own default of
|
|
13
|
+
* `"submit"`), so a `Button` placed inside a `<form>` never triggers an
|
|
14
|
+
* accidental submit unless set explicitly.
|
|
15
|
+
* @default "button"
|
|
16
|
+
*/
|
|
8
17
|
type?: "button" | "submit" | "reset";
|
|
18
|
+
/**
|
|
19
|
+
* Renders the child element instead of a native `<button>`, merging all
|
|
20
|
+
* props — aria-*, data-*, event handlers, ref — onto it via
|
|
21
|
+
* {@link Slot}. `type` is not forwarded in this mode; the child owns its
|
|
22
|
+
* own type semantics. See the `asChild` example on {@link Button}.
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
9
25
|
asChild?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Forwarded to the underlying `HTMLButtonElement`, or — under
|
|
28
|
+
* `asChild` — merged onto the rendered child via {@link Slot}.
|
|
29
|
+
*/
|
|
10
30
|
ref?: Ref<HTMLButtonElement>;
|
|
31
|
+
/** Button content. Under `asChild`, becomes the single child element `Slot` merges props onto. */
|
|
11
32
|
children?: ReactNode;
|
|
12
33
|
};
|