@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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Table } from "../index.ts";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
describe("Table.DetailRow rendering", () => {
|
|
5
|
+
it("should render a row whose single cell spans every column", () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
render(
|
|
8
|
+
<table>
|
|
9
|
+
<tbody>
|
|
10
|
+
<Table.Expandable expanded>
|
|
11
|
+
<Table.DetailRow colSpan={4}>Detail panel</Table.DetailRow>
|
|
12
|
+
</Table.Expandable>
|
|
13
|
+
</tbody>
|
|
14
|
+
</table>,
|
|
15
|
+
);
|
|
16
|
+
const cell = screen.getByRole("cell");
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
expect(cell).toHaveAttribute("colspan", "4");
|
|
20
|
+
expect(cell).toHaveTextContent("Detail panel");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should offset the panel behind an empty gutter cell, still spanning every column between them", () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
render(
|
|
26
|
+
<table>
|
|
27
|
+
<tbody>
|
|
28
|
+
<Table.Expandable expanded>
|
|
29
|
+
<Table.DetailRow colSpan={5} gutter={2}>
|
|
30
|
+
Detail panel
|
|
31
|
+
</Table.DetailRow>
|
|
32
|
+
</Table.Expandable>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>,
|
|
35
|
+
);
|
|
36
|
+
const [gutter, panel] = screen.getAllByRole("cell");
|
|
37
|
+
|
|
38
|
+
// Assert — the gutter is what aligns the panel with the first DATA column
|
|
39
|
+
// at every size and density: it spans the control columns, so the browser's
|
|
40
|
+
// own table layout does the arithmetic and nothing has to be measured.
|
|
41
|
+
expect(gutter).toBeEmptyDOMElement();
|
|
42
|
+
expect(gutter).toHaveAttribute("colspan", "2");
|
|
43
|
+
expect(panel).toHaveAttribute("colspan", "3");
|
|
44
|
+
expect(panel).toHaveTextContent("Detail panel");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should hide the row while collapsed so it leaves the row count truthful", () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
render(
|
|
50
|
+
<table>
|
|
51
|
+
<tbody>
|
|
52
|
+
<Table.Expandable>
|
|
53
|
+
<Table.DetailRow colSpan={2}>Detail panel</Table.DetailRow>
|
|
54
|
+
</Table.Expandable>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(screen.queryByRole("row")).not.toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should stay mounted but aria-hidden while collapsed when forceMount is set", () => {
|
|
64
|
+
// Arrange
|
|
65
|
+
render(
|
|
66
|
+
<table>
|
|
67
|
+
<tbody>
|
|
68
|
+
<Table.Expandable>
|
|
69
|
+
<Table.DetailRow colSpan={2} forceMount>
|
|
70
|
+
Detail panel
|
|
71
|
+
</Table.DetailRow>
|
|
72
|
+
</Table.Expandable>
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>,
|
|
75
|
+
);
|
|
76
|
+
const row = screen.getByRole("row", { hidden: true });
|
|
77
|
+
|
|
78
|
+
// Assert — no `hidden`, so CSS can transition it; `aria-hidden` instead, so
|
|
79
|
+
// a collapsed panel still does not inflate the table's announced row count.
|
|
80
|
+
expect(row).not.toHaveAttribute("hidden");
|
|
81
|
+
expect(row).toHaveAttribute("aria-hidden", "true");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Table } from "../index.ts";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
function renderPair(props?: { expanded?: boolean }) {
|
|
6
|
+
return render(
|
|
7
|
+
<table>
|
|
8
|
+
<tbody>
|
|
9
|
+
<Table.Expandable expanded={props?.expanded}>
|
|
10
|
+
<Table.Row>
|
|
11
|
+
<Table.Cell>
|
|
12
|
+
<Table.ExpandTrigger>Show details for Ada</Table.ExpandTrigger>
|
|
13
|
+
</Table.Cell>
|
|
14
|
+
</Table.Row>
|
|
15
|
+
<Table.DetailRow colSpan={2}>Detail panel</Table.DetailRow>
|
|
16
|
+
</Table.Expandable>
|
|
17
|
+
</tbody>
|
|
18
|
+
</table>,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("Table.ExpandTrigger accessibility", () => {
|
|
23
|
+
it("should own the expanded state on the button and point aria-controls at the detail row", () => {
|
|
24
|
+
// Arrange
|
|
25
|
+
renderPair({ expanded: true });
|
|
26
|
+
const trigger = screen.getByRole("button", {
|
|
27
|
+
name: /show details for ada/i,
|
|
28
|
+
});
|
|
29
|
+
const detailRow = screen.getAllByRole("row")[1];
|
|
30
|
+
|
|
31
|
+
// Assert — the button carries the state, because a button's expanded state
|
|
32
|
+
// is announced universally while a row's is only reliable in a treegrid.
|
|
33
|
+
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
34
|
+
expect(detailRow).toHaveAttribute("id");
|
|
35
|
+
expect(trigger).toHaveAttribute(
|
|
36
|
+
"aria-controls",
|
|
37
|
+
detailRow.getAttribute("id"),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("Table.ExpandTrigger composition", () => {
|
|
43
|
+
it("should hand its aria wiring to a consumer's own control when asChild is set", () => {
|
|
44
|
+
// Arrange — the styled layer composes a real ghost Button here rather than
|
|
45
|
+
// hand-rolling a bare <button>, which is what the Figma set models.
|
|
46
|
+
render(
|
|
47
|
+
<table>
|
|
48
|
+
<tbody>
|
|
49
|
+
<Table.Expandable>
|
|
50
|
+
<Table.Row>
|
|
51
|
+
<Table.Cell>
|
|
52
|
+
<Table.ExpandTrigger asChild>
|
|
53
|
+
<button type="button" className="my-button">
|
|
54
|
+
Details
|
|
55
|
+
</button>
|
|
56
|
+
</Table.ExpandTrigger>
|
|
57
|
+
</Table.Cell>
|
|
58
|
+
</Table.Row>
|
|
59
|
+
<Table.DetailRow colSpan={1}>Panel</Table.DetailRow>
|
|
60
|
+
</Table.Expandable>
|
|
61
|
+
</tbody>
|
|
62
|
+
</table>,
|
|
63
|
+
);
|
|
64
|
+
const trigger = screen.getByRole("button", { name: "Details" });
|
|
65
|
+
|
|
66
|
+
// Assert — one button, the consumer's, carrying the wiring
|
|
67
|
+
expect(trigger).toHaveClass("my-button");
|
|
68
|
+
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
69
|
+
expect(trigger).toHaveAttribute("aria-controls");
|
|
70
|
+
expect(trigger).toHaveAttribute("data-state", "closed");
|
|
71
|
+
expect(screen.getAllByRole("button")).toHaveLength(1);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Table.ExpandTrigger activation", () => {
|
|
76
|
+
it("should report the next expanded state and reveal the detail row when uncontrolled", async () => {
|
|
77
|
+
// Arrange
|
|
78
|
+
const onExpandedChange = vi.fn();
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
render(
|
|
81
|
+
<table>
|
|
82
|
+
<tbody>
|
|
83
|
+
<Table.Expandable onExpandedChange={onExpandedChange}>
|
|
84
|
+
<Table.Row>
|
|
85
|
+
<Table.Cell>
|
|
86
|
+
<Table.ExpandTrigger>Show details for Ada</Table.ExpandTrigger>
|
|
87
|
+
</Table.Cell>
|
|
88
|
+
</Table.Row>
|
|
89
|
+
<Table.DetailRow colSpan={2}>Detail panel</Table.DetailRow>
|
|
90
|
+
</Table.Expandable>
|
|
91
|
+
</tbody>
|
|
92
|
+
</table>,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
await user.click(screen.getByRole("button"));
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(onExpandedChange).toHaveBeenCalledWith(true);
|
|
100
|
+
expect(screen.getByText("Detail panel")).toBeVisible();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Table } from "../index.ts";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
describe("Table displayNames", () => {
|
|
5
|
+
it("sets a displayName on the compound and each sub-component", () => {
|
|
6
|
+
// Assert — empty displayNames would render each as anonymous in DevTools.
|
|
7
|
+
// `Table`, `Table.Root` and the underlying function are one object
|
|
8
|
+
// (Object.assign compound), so the compound's "Table" alias is the
|
|
9
|
+
// observable Root displayName; the sub-components are distinct objects.
|
|
10
|
+
expect(Table.displayName).toBe("Table");
|
|
11
|
+
expect(Table.Head.displayName).toBe("TableHead");
|
|
12
|
+
expect(Table.Body.displayName).toBe("TableBody");
|
|
13
|
+
expect(Table.Footer.displayName).toBe("TableFooter");
|
|
14
|
+
expect(Table.Row.displayName).toBe("TableRow");
|
|
15
|
+
expect(Table.Header.displayName).toBe("TableHeader");
|
|
16
|
+
expect(Table.Cell.displayName).toBe("TableCell");
|
|
17
|
+
expect(Table.ScrollArea.displayName).toBe("TableScrollArea");
|
|
18
|
+
expect(Table.Caption.displayName).toBe("TableCaption");
|
|
19
|
+
expect(Table.Expandable.displayName).toBe("TableExpandable");
|
|
20
|
+
expect(Table.ExpandTrigger.displayName).toBe("TableExpandTrigger");
|
|
21
|
+
expect(Table.DetailRow.displayName).toBe("TableDetailRow");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("Table.Expandable contract", () => {
|
|
26
|
+
it("should require its parts to be rendered inside a provider", () => {
|
|
27
|
+
// Assert — the strict context is what stops a trigger being wired to
|
|
28
|
+
// nothing. React logs the throw, so silence the expected console noise.
|
|
29
|
+
const consoleError = vi
|
|
30
|
+
.spyOn(console, "error")
|
|
31
|
+
.mockImplementation(() => undefined);
|
|
32
|
+
|
|
33
|
+
expect(() =>
|
|
34
|
+
render(
|
|
35
|
+
<table>
|
|
36
|
+
<tbody>
|
|
37
|
+
<Table.DetailRow colSpan={2}>Orphan</Table.DetailRow>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>,
|
|
40
|
+
),
|
|
41
|
+
).toThrow(
|
|
42
|
+
"Table.ExpandTrigger and Table.DetailRow must be rendered inside a <Table.Expandable>.",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
consoleError.mockRestore();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should apply the hidden attribute — not merely leave the row unannounced — while collapsed", () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
render(
|
|
51
|
+
<table>
|
|
52
|
+
<tbody>
|
|
53
|
+
<Table.Expandable>
|
|
54
|
+
<Table.DetailRow colSpan={2}>Detail panel</Table.DetailRow>
|
|
55
|
+
</Table.Expandable>
|
|
56
|
+
</tbody>
|
|
57
|
+
</table>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Assert — pins the `forceMount` default. Without `hidden` the row is only
|
|
61
|
+
// missing from the accessibility tree, which an aria-hidden row would be too.
|
|
62
|
+
expect(screen.getByText("Detail panel").closest("tr")).toHaveAttribute(
|
|
63
|
+
"hidden",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it.each([
|
|
68
|
+
{ expanded: true, state: "open" },
|
|
69
|
+
{ expanded: false, state: "closed" },
|
|
70
|
+
])(
|
|
71
|
+
"should publish data-state=$state on both parts for the styling layer",
|
|
72
|
+
({ expanded, state }) => {
|
|
73
|
+
// Arrange
|
|
74
|
+
render(
|
|
75
|
+
<table>
|
|
76
|
+
<tbody>
|
|
77
|
+
<Table.Expandable expanded={expanded}>
|
|
78
|
+
<Table.Row>
|
|
79
|
+
<Table.Cell>
|
|
80
|
+
<Table.ExpandTrigger>Toggle</Table.ExpandTrigger>
|
|
81
|
+
</Table.Cell>
|
|
82
|
+
</Table.Row>
|
|
83
|
+
<Table.DetailRow colSpan={2} forceMount>
|
|
84
|
+
Detail panel
|
|
85
|
+
</Table.DetailRow>
|
|
86
|
+
</Table.Expandable>
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Assert — the registry stylesheet animates the panel off this attribute,
|
|
92
|
+
// because a CSS transition needs a selector, not an unmount.
|
|
93
|
+
expect(screen.getByRole("button", { name: "Toggle" })).toHaveAttribute(
|
|
94
|
+
"data-state",
|
|
95
|
+
state,
|
|
96
|
+
);
|
|
97
|
+
expect(
|
|
98
|
+
screen.getByText("Detail panel").closest("tr"),
|
|
99
|
+
).toHaveAttribute("data-state", state);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
it("should not aria-hide a force-mounted row once it is expanded", () => {
|
|
104
|
+
// Arrange
|
|
105
|
+
render(
|
|
106
|
+
<table>
|
|
107
|
+
<tbody>
|
|
108
|
+
<Table.Expandable expanded>
|
|
109
|
+
<Table.DetailRow colSpan={2} forceMount>
|
|
110
|
+
Detail panel
|
|
111
|
+
</Table.DetailRow>
|
|
112
|
+
</Table.Expandable>
|
|
113
|
+
</tbody>
|
|
114
|
+
</table>,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Assert — aria-hidden belongs to "force-mounted AND collapsed" only; an
|
|
118
|
+
// expanded panel that is not announced would be invisible to a screen reader.
|
|
119
|
+
expect(screen.getByRole("row")).not.toHaveAttribute("aria-hidden");
|
|
120
|
+
});
|
|
121
|
+
});
|
package/src/Table/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ButtonHTMLAttributes,
|
|
2
3
|
HTMLAttributes,
|
|
3
4
|
ReactNode,
|
|
4
5
|
TableHTMLAttributes,
|
|
@@ -6,58 +7,231 @@ import type {
|
|
|
6
7
|
TdHTMLAttributes,
|
|
7
8
|
} from "react";
|
|
8
9
|
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Props for {@link TableRoot | `Table.Root`} — all `TableHTMLAttributes` on
|
|
12
|
+
* the native `<table>`. No custom props are added.
|
|
13
|
+
*
|
|
14
|
+
* @extends HTMLTableElement
|
|
15
|
+
*/
|
|
10
16
|
export type TableRootProps = TableHTMLAttributes<HTMLTableElement>;
|
|
11
17
|
|
|
12
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Props for {@link TableHead | `Table.Head`} — all `HTMLAttributes` on the
|
|
20
|
+
* native `<thead>`. No custom props are added.
|
|
21
|
+
*
|
|
22
|
+
* @extends HTMLTableSectionElement
|
|
23
|
+
*/
|
|
13
24
|
export type TableHeadProps = HTMLAttributes<HTMLTableSectionElement>;
|
|
14
25
|
|
|
15
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Props for {@link TableBody | `Table.Body`} — all `HTMLAttributes` on the
|
|
28
|
+
* native `<tbody>`. No custom props are added.
|
|
29
|
+
*
|
|
30
|
+
* @extends HTMLTableSectionElement
|
|
31
|
+
*/
|
|
16
32
|
export type TableBodyProps = HTMLAttributes<HTMLTableSectionElement>;
|
|
17
33
|
|
|
18
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Props for {@link TableFooter | `Table.Footer`} — all `HTMLAttributes` on
|
|
36
|
+
* the native `<tfoot>`. No custom props are added.
|
|
37
|
+
*
|
|
38
|
+
* @extends HTMLTableSectionElement
|
|
39
|
+
*/
|
|
19
40
|
export type TableFooterProps = HTMLAttributes<HTMLTableSectionElement>;
|
|
20
41
|
|
|
21
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Props for {@link TableRow | `Table.Row`} — all `HTMLAttributes` on the
|
|
44
|
+
* native `<tr>`. No custom props are added.
|
|
45
|
+
*
|
|
46
|
+
* @extends HTMLTableRowElement
|
|
47
|
+
*/
|
|
22
48
|
export type TableRowProps = HTMLAttributes<HTMLTableRowElement>;
|
|
23
49
|
|
|
24
50
|
/**
|
|
25
|
-
* Props for {@link Table.Header} — all `ThHTMLAttributes` on
|
|
51
|
+
* Props for {@link TableHeader | `Table.Header`} — all `ThHTMLAttributes` on
|
|
52
|
+
* the native `<th>`, including `colSpan`, `rowSpan`, and `abbr`.
|
|
26
53
|
*
|
|
27
54
|
* The most important prop for accessibility is `scope`: set it to `"col"` for
|
|
28
55
|
* column headers and `"row"` for row headers so assistive technology can
|
|
29
|
-
* associate data cells with the correct header.
|
|
56
|
+
* associate data cells with the correct header. See the scope table on
|
|
57
|
+
* {@link TableHeader}.
|
|
58
|
+
*
|
|
59
|
+
* @extends HTMLTableCellElement
|
|
30
60
|
*/
|
|
31
61
|
export type TableHeaderProps = ThHTMLAttributes<HTMLTableCellElement>;
|
|
32
62
|
|
|
33
63
|
/**
|
|
34
|
-
* Props for {@link Table.Cell} — all `TdHTMLAttributes` on
|
|
64
|
+
* Props for {@link TableCell | `Table.Cell`} — all `TdHTMLAttributes` on the
|
|
65
|
+
* native `<td>`.
|
|
35
66
|
*
|
|
36
67
|
* Includes `colSpan` and `rowSpan` for spanning multiple columns or rows.
|
|
68
|
+
*
|
|
69
|
+
* @extends HTMLTableCellElement
|
|
37
70
|
*/
|
|
38
71
|
export type TableCellProps = TdHTMLAttributes<HTMLTableCellElement>;
|
|
39
72
|
|
|
40
73
|
/**
|
|
41
|
-
* Props for {@link Table.ScrollArea} —
|
|
42
|
-
* on the wrapping `<div>`.
|
|
74
|
+
* Props for {@link TableScrollArea | `Table.ScrollArea`} — a required
|
|
75
|
+
* `children` plus all `HTMLAttributes` on the wrapping `<div>`.
|
|
43
76
|
*
|
|
44
|
-
*
|
|
45
|
-
* base scroll styles (`display: block`, `overflow-x: auto`, `max-width: 100%`).
|
|
77
|
+
* @extends HTMLDivElement
|
|
46
78
|
*/
|
|
47
79
|
export type TableScrollAreaProps = {
|
|
80
|
+
/** The table (typically a single {@link TableRoot | `Table.Root`}) to
|
|
81
|
+
* wrap in the horizontally-scrolling container. */
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
/**
|
|
84
|
+
* Merged with (and taking priority over) the base scroll styles
|
|
85
|
+
* (`display: block`, `overflow-x: auto`, `max-width: 100%`), so
|
|
86
|
+
* additional styles can be layered on without repeating the scroll
|
|
87
|
+
* declarations. See {@link TableScrollArea}'s custom-styles example.
|
|
88
|
+
*/
|
|
89
|
+
style?: HTMLAttributes<HTMLDivElement>["style"];
|
|
90
|
+
} & Omit<HTMLAttributes<HTMLDivElement>, "style">;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The value shared by {@link TableExpandable | `Table.Expandable`} with its
|
|
94
|
+
* {@link TableExpandTrigger | `Table.ExpandTrigger`} and
|
|
95
|
+
* {@link TableDetailRow | `Table.DetailRow`} children.
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
export type TableExpandableContextValue = {
|
|
100
|
+
/** Whether the detail row is currently revealed. */
|
|
101
|
+
expanded: boolean;
|
|
102
|
+
/** The id wired to the detail row's `<tr>` and the trigger's `aria-controls`. */
|
|
103
|
+
detailId: string;
|
|
104
|
+
/** Flips `expanded` and notifies `onExpandedChange`. */
|
|
105
|
+
toggle: () => void;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Props for {@link TableExpandable | `Table.Expandable`} — the pairing
|
|
110
|
+
* provider for one expandable row and its detail row.
|
|
111
|
+
*/
|
|
112
|
+
export type TableExpandableProps = {
|
|
113
|
+
/** The parent {@link TableRow | `Table.Row`} and its
|
|
114
|
+
* {@link TableDetailRow | `Table.DetailRow`}, as siblings. */
|
|
48
115
|
children: ReactNode;
|
|
49
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Whether the detail row is revealed. Pass this (with
|
|
118
|
+
* `onExpandedChange`) to drive expansion from outside — which is what an
|
|
119
|
+
* external table engine such as TanStack Table does.
|
|
120
|
+
*/
|
|
121
|
+
expanded?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Initial expanded state when running uncontrolled. Ignored when
|
|
124
|
+
* `expanded` is supplied.
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
defaultExpanded?: boolean;
|
|
128
|
+
/** Called with the next expanded state whenever the trigger is activated. */
|
|
129
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Props for {@link TableExpandTrigger | `Table.ExpandTrigger`} — all
|
|
134
|
+
* `ButtonHTMLAttributes` on the native `<button>`.
|
|
135
|
+
*
|
|
136
|
+
* `aria-expanded`, `aria-controls` and `type` are owned by the component and
|
|
137
|
+
* therefore `Omit`-ted: they are derived from the surrounding
|
|
138
|
+
* {@link TableExpandable | `Table.Expandable`} and must not be overridden, or
|
|
139
|
+
* the relationship the pair exists to express would break.
|
|
140
|
+
*
|
|
141
|
+
* @extends HTMLButtonElement
|
|
142
|
+
*/
|
|
143
|
+
export type TableExpandTriggerProps = {
|
|
144
|
+
/**
|
|
145
|
+
* Render a consumer-supplied control instead of the native `<button>`, with
|
|
146
|
+
* this part's `aria-expanded`, `aria-controls`, `data-state` and click
|
|
147
|
+
* handling merged onto it via the {@link Slot} pattern.
|
|
148
|
+
*
|
|
149
|
+
* This is how a styled layer composes a real Button rather than re-creating
|
|
150
|
+
* one: the registry `data-table` renders a ghost Button through it, matching
|
|
151
|
+
* the Figma set, whose Expand Trigger is an Icon Button instance rather than a
|
|
152
|
+
* drawn glyph. The child must be a single element that accepts a `ref`, and it
|
|
153
|
+
* should be a `<button>` — the wiring assumes a control.
|
|
154
|
+
* @default false
|
|
155
|
+
*/
|
|
156
|
+
asChild?: boolean;
|
|
157
|
+
} & Omit<
|
|
158
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
159
|
+
"aria-expanded" | "aria-controls" | "type"
|
|
160
|
+
>;
|
|
50
161
|
|
|
51
162
|
/**
|
|
52
|
-
* Props for {@link Table.
|
|
53
|
-
*
|
|
163
|
+
* Props for {@link TableDetailRow | `Table.DetailRow`} — a required `colSpan`
|
|
164
|
+
* plus all `HTMLAttributes` on the native `<tr>`.
|
|
165
|
+
*
|
|
166
|
+
* `id` is component-owned (it is the target of the trigger's `aria-controls`)
|
|
167
|
+
* and so is `Omit`-ted.
|
|
168
|
+
*
|
|
169
|
+
* @extends HTMLTableRowElement
|
|
170
|
+
*/
|
|
171
|
+
export type TableDetailRowProps = {
|
|
172
|
+
/** The detail panel's content. */
|
|
173
|
+
children?: ReactNode;
|
|
174
|
+
/**
|
|
175
|
+
* How many columns the panel spans — **every** column the table renders,
|
|
176
|
+
* control columns included.
|
|
177
|
+
*
|
|
178
|
+
* Deliberately a required prop rather than a value counted from context:
|
|
179
|
+
* counting would mean the table registering its own columns, and an
|
|
180
|
+
* external table engine already knows the number (TanStack Table:
|
|
181
|
+
* `table.getVisibleLeafColumns().length`).
|
|
182
|
+
*/
|
|
183
|
+
colSpan: number;
|
|
184
|
+
/**
|
|
185
|
+
* How many leading columns to leave empty before the panel starts — the
|
|
186
|
+
* control columns (select, expand) a row renders in front of its data.
|
|
187
|
+
*
|
|
188
|
+
* The panel then spans the columns that remain, so `colSpan` keeps its
|
|
189
|
+
* meaning: the table's full column count, gutter included.
|
|
190
|
+
*
|
|
191
|
+
* This is how a panel aligns with the first **data** column instead of the
|
|
192
|
+
* table's edge (a panel starting under the checkbox reads as a new section of
|
|
193
|
+
* the table rather than an opened row). Alignment by empty cell rather than by
|
|
194
|
+
* a CSS indent is deliberate: the browser's own table layout resolves the
|
|
195
|
+
* width, so it stays exact across every size, density and control-column set
|
|
196
|
+
* with nothing measured and no arithmetic to drift.
|
|
197
|
+
* @default 0
|
|
198
|
+
*/
|
|
199
|
+
gutter?: number;
|
|
200
|
+
/**
|
|
201
|
+
* Keep the row mounted while collapsed so a CSS transition can run.
|
|
202
|
+
*
|
|
203
|
+
* Off by default, and that default matters more here than it does on a
|
|
204
|
+
* `<div>`-based disclosure: a mounted row is still a **row**, so assistive
|
|
205
|
+
* technology counts it, and a table of 24 rows with 24 force-mounted
|
|
206
|
+
* detail rows announces 48. Opt in only when you are animating, and note
|
|
207
|
+
* that the collapsed row remains in the accessibility tree while you do.
|
|
208
|
+
* @default false
|
|
209
|
+
*/
|
|
210
|
+
forceMount?: boolean;
|
|
211
|
+
/** Passed through to the `<td>` that spans the row, for styling the cell
|
|
212
|
+
* itself rather than the row. */
|
|
213
|
+
cellProps?: TdHTMLAttributes<HTMLTableCellElement>;
|
|
214
|
+
} & Omit<HTMLAttributes<HTMLTableRowElement>, "id" | "children">;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Props for {@link TableCaption | `Table.Caption`} — a required `children`,
|
|
218
|
+
* an optional `captionSide`, plus all `HTMLAttributes` on the native
|
|
219
|
+
* `<caption>`.
|
|
220
|
+
*
|
|
221
|
+
* @extends HTMLTableCaptionElement
|
|
54
222
|
*/
|
|
55
223
|
export type TableCaptionProps = {
|
|
224
|
+
/** The visible, accessible label for the table. Preferred over
|
|
225
|
+
* `aria-label`/`aria-labelledby` on the table itself — the browser
|
|
226
|
+
* programmatically associates a `<caption>` with its `<table>`. */
|
|
56
227
|
children: ReactNode;
|
|
57
228
|
/**
|
|
58
|
-
* Controls the CSS `caption-side` property
|
|
59
|
-
*
|
|
229
|
+
* Controls the CSS `caption-side` property, positioning the caption
|
|
230
|
+
* above or below the table. See {@link TableCaption}'s placement
|
|
231
|
+
* examples.
|
|
232
|
+
* - `"bottom"` — caption appears below the table.
|
|
60
233
|
* - `"top"` — caption appears above the table.
|
|
234
|
+
* @default "bottom"
|
|
61
235
|
*/
|
|
62
236
|
captionSide?: "bottom" | "top";
|
|
63
237
|
} & HTMLAttributes<HTMLTableCaptionElement>;
|
package/src/Tabs/README.md
CHANGED
|
@@ -11,8 +11,8 @@ import { Tabs } from "@primitiv-ui/react";
|
|
|
11
11
|
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
|
12
12
|
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
|
13
13
|
</Tabs.List>
|
|
14
|
-
<Tabs.Content value="overview">Dashboard
|
|
15
|
-
<Tabs.Content value="settings">Preferences
|
|
14
|
+
<Tabs.Content value="overview">Dashboard...</Tabs.Content>
|
|
15
|
+
<Tabs.Content value="settings">Preferences...</Tabs.Content>
|
|
16
16
|
</Tabs.Root>;
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -63,7 +63,7 @@ trigger and panel is always present.
|
|
|
63
63
|
|
|
64
64
|
```tsx
|
|
65
65
|
<Tabs.Root defaultValue="overview" lazyMount>
|
|
66
|
-
|
|
66
|
+
...
|
|
67
67
|
</Tabs.Root>
|
|
68
68
|
```
|
|
69
69
|
|
|
@@ -76,7 +76,7 @@ position must be computed while the panel has real dimensions.
|
|
|
76
76
|
```tsx
|
|
77
77
|
const ref = useRef<TabsImperativeApi>(null);
|
|
78
78
|
<Tabs.Root ref={ref} defaultValue="a">
|
|
79
|
-
|
|
79
|
+
...
|
|
80
80
|
</Tabs.Root>;
|
|
81
81
|
ref.current?.setActiveTab("b");
|
|
82
82
|
```
|
package/src/Tabs/Tabs.tsx
CHANGED
|
@@ -66,6 +66,8 @@ import type {
|
|
|
66
66
|
* inherited from the nearest {@link DirectionProvider}, falling back to
|
|
67
67
|
* `"ltr"` when there is no provider.
|
|
68
68
|
*
|
|
69
|
+
* @extends HTMLDivElement
|
|
70
|
+
*
|
|
69
71
|
* @example Uncontrolled
|
|
70
72
|
* ```tsx
|
|
71
73
|
* <Tabs.Root defaultValue="overview">
|
|
@@ -73,8 +75,8 @@ import type {
|
|
|
73
75
|
* <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
|
74
76
|
* <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
|
75
77
|
* </Tabs.List>
|
|
76
|
-
* <Tabs.Content value="overview">Dashboard
|
|
77
|
-
* <Tabs.Content value="settings">Preferences
|
|
78
|
+
* <Tabs.Content value="overview">Dashboard...</Tabs.Content>
|
|
79
|
+
* <Tabs.Content value="settings">Preferences...</Tabs.Content>
|
|
78
80
|
* </Tabs.Root>
|
|
79
81
|
* ```
|
|
80
82
|
*
|
|
@@ -87,7 +89,7 @@ import type {
|
|
|
87
89
|
* onValueChange={setTab}
|
|
88
90
|
* onChange={({ name, index }) => track("tab_view", { name, index })}
|
|
89
91
|
* >
|
|
90
|
-
*
|
|
92
|
+
* ...
|
|
91
93
|
* </Tabs.Root>
|
|
92
94
|
* ```
|
|
93
95
|
*
|
|
@@ -96,7 +98,7 @@ import type {
|
|
|
96
98
|
* const ref = useRef<TabsImperativeApi>(null);
|
|
97
99
|
*
|
|
98
100
|
* <Tabs.Root ref={ref} orientation="vertical" defaultValue="one">
|
|
99
|
-
*
|
|
101
|
+
* ...
|
|
100
102
|
* </Tabs.Root>
|
|
101
103
|
*
|
|
102
104
|
* <button onClick={() => ref.current?.setActiveTab("two")}>Go to two</button>
|
|
@@ -146,6 +148,7 @@ export const TabsRoot: ForwardRefExoticComponent<
|
|
|
146
148
|
);
|
|
147
149
|
});
|
|
148
150
|
|
|
151
|
+
// Stryker disable next-line StringLiteral: overwritten by the compound alias — an equivalent mutant.
|
|
149
152
|
TabsRoot.displayName = "TabsRoot";
|
|
150
153
|
|
|
151
154
|
/**
|
|
@@ -160,6 +163,8 @@ TabsRoot.displayName = "TabsRoot";
|
|
|
160
163
|
*
|
|
161
164
|
* **Styling hooks.** `data-orientation="horizontal" | "vertical"`.
|
|
162
165
|
*
|
|
166
|
+
* @extends HTMLDivElement
|
|
167
|
+
*
|
|
163
168
|
* @example
|
|
164
169
|
* ```tsx
|
|
165
170
|
* <Tabs.List label="Document sections">
|
|
@@ -241,6 +246,8 @@ TabsList.displayName = "TabsList";
|
|
|
241
246
|
* - `data-disabled=""` when disabled (omitted otherwise), so CSS can target
|
|
242
247
|
* `[data-disabled]` without the `:disabled` pseudo-class.
|
|
243
248
|
*
|
|
249
|
+
* @extends HTMLButtonElement
|
|
250
|
+
*
|
|
244
251
|
* @example Basic usage
|
|
245
252
|
* ```tsx
|
|
246
253
|
* <Tabs.Trigger value="account">Account</Tabs.Trigger>
|
|
@@ -345,6 +352,8 @@ TabsTrigger.displayName = "TabsTrigger";
|
|
|
345
352
|
* - `data-state="active" | "inactive"`.
|
|
346
353
|
* - `data-orientation="horizontal" | "vertical"`.
|
|
347
354
|
*
|
|
355
|
+
* @extends HTMLDivElement
|
|
356
|
+
*
|
|
348
357
|
* @example
|
|
349
358
|
* ```tsx
|
|
350
359
|
* <Tabs.Content value="account">
|