@primitiv-ui/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/package.json +59 -0
- package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
- package/src/AccessibleIcon/README.md +42 -0
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
- package/src/AccessibleIcon/index.ts +2 -0
- package/src/AccessibleIcon/types.ts +8 -0
- package/src/Accordion/Accordion.tsx +412 -0
- package/src/Accordion/AccordionContext.ts +12 -0
- package/src/Accordion/README.md +202 -0
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
- package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
- package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
- package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
- package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
- package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
- package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
- package/src/Accordion/hooks/index.ts +6 -0
- package/src/Accordion/hooks/useAccordionContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
- package/src/Accordion/hooks/useAccordionItem.ts +22 -0
- package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
- package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
- package/src/Accordion/index.ts +1 -0
- package/src/Accordion/types.ts +81 -0
- package/src/Alert/Alert.tsx +43 -0
- package/src/Alert/README.md +54 -0
- package/src/Alert/__tests__/Alert.test.tsx +28 -0
- package/src/Alert/index.ts +2 -0
- package/src/Alert/types.ts +5 -0
- package/src/Avatar/Avatar.tsx +149 -0
- package/src/Avatar/AvatarContext.ts +20 -0
- package/src/Avatar/README.md +116 -0
- package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
- package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
- package/src/Avatar/hooks/index.ts +2 -0
- package/src/Avatar/hooks/useAvatarContext.ts +1 -0
- package/src/Avatar/hooks/useAvatarImage.ts +40 -0
- package/src/Avatar/index.ts +3 -0
- package/src/Avatar/types.ts +44 -0
- package/src/Breadcrumb/Breadcrumb.tsx +234 -0
- package/src/Breadcrumb/README.md +111 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
- package/src/Breadcrumb/index.ts +2 -0
- package/src/Breadcrumb/types.ts +22 -0
- package/src/Button/Button.tsx +95 -0
- package/src/Button/README.md +112 -0
- package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
- package/src/Button/__tests__/Button.contract.test.tsx +72 -0
- package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
- package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
- package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +8 -0
- package/src/Carousel/Carousel.tsx +708 -0
- package/src/Carousel/CarouselContext.ts +11 -0
- package/src/Carousel/README.md +848 -0
- package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
- package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
- package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
- package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
- package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
- package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
- package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
- package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
- package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
- package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
- package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
- package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
- package/src/Carousel/hooks/index.ts +4 -0
- package/src/Carousel/hooks/useCarouselContext.ts +13 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
- package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
- package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
- package/src/Carousel/index.ts +3 -0
- package/src/Carousel/types.ts +400 -0
- package/src/Checkbox/Checkbox.tsx +228 -0
- package/src/Checkbox/CheckboxContext.ts +12 -0
- package/src/Checkbox/README.md +156 -0
- package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
- package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
- package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
- package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
- package/src/Checkbox/hooks/index.ts +2 -0
- package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
- package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
- package/src/Checkbox/index.ts +1 -0
- package/src/Checkbox/types.ts +33 -0
- package/src/CheckboxCard/CheckboxCard.tsx +208 -0
- package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
- package/src/CheckboxCard/README.md +114 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
- package/src/CheckboxCard/hooks/index.ts +2 -0
- package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
- package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
- package/src/CheckboxCard/index.ts +3 -0
- package/src/CheckboxCard/types.ts +33 -0
- package/src/Collapsible/Collapsible.tsx +316 -0
- package/src/Collapsible/CollapsibleContext.ts +7 -0
- package/src/Collapsible/README.md +174 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
- package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
- package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
- package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
- package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
- package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
- package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
- package/src/Collapsible/hooks/index.ts +2 -0
- package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/types.ts +48 -0
- package/src/ContextMenu/ContextMenu.tsx +1004 -0
- package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
- package/src/ContextMenu/ContextMenuContext.ts +21 -0
- package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
- package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
- package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
- package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
- package/src/ContextMenu/README.md +275 -0
- package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
- package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
- package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
- package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
- package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
- package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
- package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
- package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
- package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
- package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
- package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
- package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
- package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
- package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
- package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
- package/src/ContextMenu/constants.ts +4 -0
- package/src/ContextMenu/index.ts +1 -0
- package/src/ContextMenu/types.ts +199 -0
- package/src/DirectionProvider/DirectionContext.ts +21 -0
- package/src/DirectionProvider/DirectionProvider.tsx +31 -0
- package/src/DirectionProvider/README.md +62 -0
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
- package/src/DirectionProvider/index.ts +3 -0
- package/src/DirectionProvider/types.ts +10 -0
- package/src/Divider/Divider.tsx +57 -0
- package/src/Divider/README.md +57 -0
- package/src/Divider/__tests__/Divider.test.tsx +41 -0
- package/src/Divider/index.ts +1 -0
- package/src/Divider/types.ts +5 -0
- package/src/Dropdown/Dropdown.tsx +842 -0
- package/src/Dropdown/DropdownContentContext.ts +15 -0
- package/src/Dropdown/DropdownContext.ts +17 -0
- package/src/Dropdown/DropdownGroupContext.ts +8 -0
- package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
- package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
- package/src/Dropdown/DropdownSubContext.ts +15 -0
- package/src/Dropdown/README.md +284 -0
- package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
- package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
- package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
- package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
- package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
- package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
- package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
- package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
- package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
- package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
- package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
- package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
- package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
- package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
- package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
- package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
- package/src/Dropdown/constants.ts +4 -0
- package/src/Dropdown/hooks/index.ts +9 -0
- package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
- package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
- package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
- package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
- package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
- package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
- package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Dropdown/types.ts +200 -0
- package/src/EmptyState/EmptyState.tsx +245 -0
- package/src/EmptyState/README.md +129 -0
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
- package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
- package/src/EmptyState/index.ts +2 -0
- package/src/EmptyState/types.ts +21 -0
- package/src/Field/Field.tsx +239 -0
- package/src/Field/FieldContext.ts +22 -0
- package/src/Field/README.md +167 -0
- package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
- package/src/Field/hooks/index.ts +2 -0
- package/src/Field/hooks/useFieldContext.ts +1 -0
- package/src/Field/hooks/useFieldProps.ts +57 -0
- package/src/Field/index.ts +2 -0
- package/src/Field/types.ts +33 -0
- package/src/Fieldset/Fieldset.tsx +104 -0
- package/src/Fieldset/README.md +74 -0
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
- package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
- package/src/Fieldset/index.ts +2 -0
- package/src/Fieldset/types.ts +5 -0
- package/src/Input/Input.tsx +120 -0
- package/src/Input/README.md +180 -0
- package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
- package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
- package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
- package/src/Input/index.ts +2 -0
- package/src/Input/types.ts +7 -0
- package/src/InputGroup/InputGroup.tsx +228 -0
- package/src/InputGroup/README.md +178 -0
- package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
- package/src/InputGroup/index.ts +2 -0
- package/src/InputGroup/types.ts +13 -0
- package/src/MillerColumns/MillerColumns.tsx +329 -0
- package/src/MillerColumns/MillerColumnsContext.ts +25 -0
- package/src/MillerColumns/README.md +278 -0
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
- package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
- package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
- package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
- package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
- package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
- package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
- package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
- package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
- package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
- package/src/MillerColumns/hooks/index.ts +7 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
- package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +3 -0
- package/src/MillerColumns/types.ts +93 -0
- package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
- package/src/MillerColumns/utils.ts +75 -0
- package/src/Modal/Modal.tsx +474 -0
- package/src/Modal/ModalContext.ts +13 -0
- package/src/Modal/README.md +207 -0
- package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
- package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
- package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
- package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
- package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
- package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
- package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
- package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
- package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
- package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
- package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
- package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
- package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
- package/src/Modal/hooks/index.ts +4 -0
- package/src/Modal/hooks/useModalContent.ts +62 -0
- package/src/Modal/hooks/useModalContext.ts +1 -0
- package/src/Modal/hooks/useModalRoot.ts +81 -0
- package/src/Modal/hooks/useModalTrigger.ts +25 -0
- package/src/Modal/index.ts +3 -0
- package/src/Modal/types.ts +76 -0
- package/src/Portal/Portal.tsx +28 -0
- package/src/Portal/README.md +70 -0
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
- package/src/Portal/index.ts +2 -0
- package/src/Portal/types.ts +6 -0
- package/src/Progress/Progress.tsx +178 -0
- package/src/Progress/ProgressContext.ts +15 -0
- package/src/Progress/README.md +112 -0
- package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
- package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
- package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
- package/src/Progress/hooks/index.ts +2 -0
- package/src/Progress/hooks/useProgressContext.ts +1 -0
- package/src/Progress/hooks/useProgressRoot.ts +45 -0
- package/src/Progress/index.ts +3 -0
- package/src/Progress/types.ts +43 -0
- package/src/RadioCard/README.md +133 -0
- package/src/RadioCard/RadioCard.tsx +334 -0
- package/src/RadioCard/RadioCardContext.ts +23 -0
- package/src/RadioCard/RadioCardItemContext.ts +10 -0
- package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
- package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
- package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
- package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
- package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
- package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
- package/src/RadioCard/hooks/index.ts +3 -0
- package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
- package/src/RadioCard/index.ts +4 -0
- package/src/RadioCard/types.ts +51 -0
- package/src/RadioGroup/README.md +185 -0
- package/src/RadioGroup/RadioGroup.tsx +353 -0
- package/src/RadioGroup/RadioGroupContext.ts +23 -0
- package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
- package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
- package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
- package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
- package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
- package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
- package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
- package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
- package/src/RadioGroup/hooks/index.ts +3 -0
- package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
- package/src/RadioGroup/index.ts +1 -0
- package/src/RadioGroup/types.ts +51 -0
- package/src/Select/README.md +203 -0
- package/src/Select/Select.tsx +204 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
- package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
- package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
- package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
- package/src/Select/__tests__/Select.group.test.tsx +42 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
- package/src/Select/index.ts +2 -0
- package/src/Select/types.ts +89 -0
- package/src/SkipNav/README.md +87 -0
- package/src/SkipNav/SkipNav.tsx +116 -0
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
- package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
- package/src/SkipNav/index.ts +1 -0
- package/src/SkipNav/types.ts +26 -0
- package/src/Slider/README.md +215 -0
- package/src/Slider/Slider.tsx +308 -0
- package/src/Slider/SliderContext.ts +24 -0
- package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
- package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
- package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
- package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
- package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
- package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
- package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
- package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
- package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
- package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
- package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
- package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
- package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
- package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
- package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
- package/src/Slider/hooks/index.ts +3 -0
- package/src/Slider/hooks/useSliderContext.ts +1 -0
- package/src/Slider/hooks/useSliderRoot.ts +197 -0
- package/src/Slider/hooks/useSliderThumb.ts +77 -0
- package/src/Slider/index.ts +3 -0
- package/src/Slider/types.ts +48 -0
- package/src/Slider/utils.ts +155 -0
- package/src/Slot/Slot.tsx +158 -0
- package/src/Slot/__tests__/Slot.test.tsx +163 -0
- package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
- package/src/Slot/composeEventHandlers.ts +38 -0
- package/src/Slot/index.ts +3 -0
- package/src/Slot/types.ts +5 -0
- package/src/Status/README.md +50 -0
- package/src/Status/Status.tsx +44 -0
- package/src/Status/__tests__/Status.test.tsx +28 -0
- package/src/Status/index.ts +2 -0
- package/src/Status/types.ts +5 -0
- package/src/Switch/README.md +121 -0
- package/src/Switch/Switch.tsx +167 -0
- package/src/Switch/SwitchContext.ts +10 -0
- package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
- package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
- package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
- package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
- package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
- package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
- package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
- package/src/Switch/hooks/index.ts +2 -0
- package/src/Switch/hooks/useSwitchContext.ts +1 -0
- package/src/Switch/hooks/useSwitchRoot.ts +28 -0
- package/src/Switch/index.ts +3 -0
- package/src/Switch/types.ts +37 -0
- package/src/Table/README.md +205 -0
- package/src/Table/Table.tsx +380 -0
- package/src/Table/__tests__/Table.Body.test.tsx +61 -0
- package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
- package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
- package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
- package/src/Table/__tests__/Table.Head.test.tsx +61 -0
- package/src/Table/__tests__/Table.Header.test.tsx +73 -0
- package/src/Table/__tests__/Table.Root.test.tsx +49 -0
- package/src/Table/__tests__/Table.Row.test.tsx +67 -0
- package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
- package/src/Table/index.ts +1 -0
- package/src/Table/types.ts +63 -0
- package/src/Tabs/README.md +110 -0
- package/src/Tabs/Tabs.tsx +434 -0
- package/src/Tabs/TabsContext.ts +13 -0
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
- package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
- package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
- package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
- package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
- package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
- package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
- package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
- package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
- package/src/Tabs/hooks/index.ts +4 -0
- package/src/Tabs/hooks/useTabsContent.ts +27 -0
- package/src/Tabs/hooks/useTabsContext.ts +1 -0
- package/src/Tabs/hooks/useTabsRoot.ts +148 -0
- package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
- package/src/Tabs/index.ts +3 -0
- package/src/Tabs/types.ts +99 -0
- package/src/Tabs/utils.ts +8 -0
- package/src/Textarea/README.md +98 -0
- package/src/Textarea/Textarea.tsx +93 -0
- package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
- package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
- package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
- package/src/Textarea/index.ts +2 -0
- package/src/Textarea/types.ts +7 -0
- package/src/Toggle/README.md +97 -0
- package/src/Toggle/Toggle.tsx +81 -0
- package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
- package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
- package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
- package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
- package/src/Toggle/index.ts +2 -0
- package/src/Toggle/types.ts +23 -0
- package/src/ToggleGroup/README.md +137 -0
- package/src/ToggleGroup/ToggleGroup.tsx +298 -0
- package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
- package/src/ToggleGroup/hooks/index.ts +2 -0
- package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
- package/src/ToggleGroup/index.ts +2 -0
- package/src/ToggleGroup/types.ts +72 -0
- package/src/Tooltip/README.md +214 -0
- package/src/Tooltip/Tooltip.tsx +260 -0
- package/src/Tooltip/TooltipContext.ts +20 -0
- package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
- package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
- package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
- package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
- package/src/Tooltip/hooks/index.ts +4 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
- package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
- package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/types.ts +64 -0
- package/src/Tree/README.md +339 -0
- package/src/Tree/Tree.tsx +571 -0
- package/src/Tree/TreeContext.ts +24 -0
- package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
- package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
- package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
- package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
- package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
- package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
- package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
- package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
- package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
- package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
- package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
- package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
- package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
- package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
- package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
- package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
- package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
- package/src/Tree/hooks/index.ts +3 -0
- package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
- package/src/Tree/hooks/useTreePath.ts +68 -0
- package/src/Tree/hooks/useTreeRoot.ts +279 -0
- package/src/Tree/index.ts +3 -0
- package/src/Tree/types.ts +224 -0
- package/src/Tree/utils.ts +59 -0
- package/src/VisuallyHidden/README.md +58 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
- package/src/VisuallyHidden/index.ts +2 -0
- package/src/VisuallyHidden/types.ts +5 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCollection.ts +74 -0
- package/src/hooks/useControllableState.ts +81 -0
- package/src/hooks/useRovingTabindex.ts +178 -0
- package/src/index.ts +38 -0
- package/src/test/intersectionObserverPolyfill.ts +83 -0
- package/src/test/popoverPolyfill.ts +86 -0
- package/src/test/scrollPolyfill.ts +23 -0
- package/src/types.ts +13 -0
- package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
- package/src/utils/__tests__/deriveId.test.ts +28 -0
- package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
- package/src/utils/createStrictContext.ts +49 -0
- package/src/utils/deriveId.ts +31 -0
- package/src/utils/getKeyToActionMap.ts +95 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { Ref, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { useDirection } from "../DirectionProvider";
|
|
4
|
+
import { Slot } from "../Slot";
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AccordionRootProps,
|
|
8
|
+
AccordionItemProps,
|
|
9
|
+
AccordionTriggerProps,
|
|
10
|
+
AccordionHeaderProps,
|
|
11
|
+
AccordionContentProps,
|
|
12
|
+
AccordionTriggerIconProps,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
import type { HeadingTag } from "../types";
|
|
16
|
+
|
|
17
|
+
import { AccordionContext, AccordionItemContext } from "./AccordionContext";
|
|
18
|
+
import {
|
|
19
|
+
useAccordionContext,
|
|
20
|
+
useAccordionHeaderContext,
|
|
21
|
+
useAccordionItem,
|
|
22
|
+
useAccordionItemContext,
|
|
23
|
+
useAccordionRoot,
|
|
24
|
+
} from "./hooks";
|
|
25
|
+
import { useAccordionTrigger } from "./hooks/useAccordionTrigger";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The root of an Accordion widget — owns the expanded-items state, provides
|
|
29
|
+
* context to descendants, and renders a plain `<div>`.
|
|
30
|
+
*
|
|
31
|
+
* Supports two state modes, statically discriminated at the type level:
|
|
32
|
+
*
|
|
33
|
+
* - **Uncontrolled** — pass {@link AccordionRootUncontrolledProps.defaultValue | `defaultValue`}
|
|
34
|
+
* (or omit it to start with all items collapsed). The component owns and
|
|
35
|
+
* updates the expanded set internally.
|
|
36
|
+
* - **Controlled** — pass {@link AccordionRootControlledProps.value | `value`} *and*
|
|
37
|
+
* {@link AccordionRootControlledProps.onValueChange | `onValueChange`} together.
|
|
38
|
+
* The parent owns the expanded set; the component defers every state change
|
|
39
|
+
* back through the callback.
|
|
40
|
+
*
|
|
41
|
+
* In both modes, `multiple` controls whether more than one item can be open
|
|
42
|
+
* simultaneously. By default only one item can be expanded at a time — opening
|
|
43
|
+
* a new item collapses the previous one.
|
|
44
|
+
*
|
|
45
|
+
* **Styling hooks.** `data-orientation="vertical" | "horizontal"` is set on
|
|
46
|
+
* the rendered container. No `aria-orientation` is emitted because it is not
|
|
47
|
+
* valid on a `<div>` according to the WAI-ARIA spec.
|
|
48
|
+
*
|
|
49
|
+
* **Reading direction.** `dir` (`"ltr"` / `"rtl"`) sets the arrow-key
|
|
50
|
+
* direction for horizontal accordions and the container's `dir` attribute.
|
|
51
|
+
* When omitted, it is inherited from the nearest {@link DirectionProvider},
|
|
52
|
+
* falling back to `"ltr"` when there is no provider.
|
|
53
|
+
*
|
|
54
|
+
* @example Uncontrolled — single open item
|
|
55
|
+
* ```tsx
|
|
56
|
+
* <Accordion.Root defaultValue="item-1">
|
|
57
|
+
* <Accordion.Item value="item-1">…</Accordion.Item>
|
|
58
|
+
* <Accordion.Item value="item-2">…</Accordion.Item>
|
|
59
|
+
* </Accordion.Root>
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Controlled — multiple open items
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const [expanded, setExpanded] = useState<string[]>([]);
|
|
65
|
+
*
|
|
66
|
+
* <Accordion.Root multiple value={expanded} onValueChange={setExpanded}>
|
|
67
|
+
* <Accordion.Item value="shipping">…</Accordion.Item>
|
|
68
|
+
* <Accordion.Item value="returns">…</Accordion.Item>
|
|
69
|
+
* </Accordion.Root>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function AccordionRoot({
|
|
73
|
+
children,
|
|
74
|
+
multiple = false,
|
|
75
|
+
defaultValue,
|
|
76
|
+
value: controlledValue,
|
|
77
|
+
onValueChange,
|
|
78
|
+
orientation = "vertical",
|
|
79
|
+
dir,
|
|
80
|
+
...rest
|
|
81
|
+
}: AccordionRootProps) {
|
|
82
|
+
const resolvedDir = dir ?? useDirection();
|
|
83
|
+
const { contextValue } = useAccordionRoot(
|
|
84
|
+
controlledValue,
|
|
85
|
+
defaultValue,
|
|
86
|
+
multiple,
|
|
87
|
+
onValueChange,
|
|
88
|
+
orientation,
|
|
89
|
+
resolvedDir,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<AccordionContext.Provider value={contextValue}>
|
|
94
|
+
<div data-orientation={orientation} dir={resolvedDir} {...rest}>
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
</AccordionContext.Provider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
AccordionRoot.displayName = "AccordionRoot";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A single collapsible section within an accordion. Wraps one
|
|
105
|
+
* `Accordion.Header` + `Accordion.Content` pair and tracks their shared
|
|
106
|
+
* expanded / collapsed state via context.
|
|
107
|
+
*
|
|
108
|
+
* The {@link AccordionItemProps.value | `value`} prop is the stable identifier
|
|
109
|
+
* used to match this item against the root's expanded set. If omitted, a
|
|
110
|
+
* stable auto-generated ID is used via `useId()` — useful for fully anonymous
|
|
111
|
+
* items where the consumer doesn't need to drive state from outside.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* <Accordion.Item value="shipping">
|
|
116
|
+
* <Accordion.Header>
|
|
117
|
+
* <Accordion.Trigger>Shipping</Accordion.Trigger>
|
|
118
|
+
* </Accordion.Header>
|
|
119
|
+
* <Accordion.Content>Free on orders over £50.</Accordion.Content>
|
|
120
|
+
* </Accordion.Item>
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function AccordionItem({
|
|
124
|
+
children,
|
|
125
|
+
value,
|
|
126
|
+
...rest
|
|
127
|
+
}: AccordionItemProps) {
|
|
128
|
+
const { contextValue } = useAccordionItem(value);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<AccordionItemContext.Provider value={contextValue}>
|
|
132
|
+
<div {...rest}>{children}</div>
|
|
133
|
+
</AccordionItemContext.Provider>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
AccordionItem.displayName = "AccordionItem";
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Renders the heading element that wraps an `Accordion.Trigger`. Defaults to
|
|
141
|
+
* `<h3>` but the level is configurable via the
|
|
142
|
+
* {@link AccordionHeaderProps.level | `level`} prop so the heading hierarchy
|
|
143
|
+
* of the surrounding page is preserved.
|
|
144
|
+
*
|
|
145
|
+
* The WAI-ARIA Accordion pattern requires each trigger to be wrapped in a
|
|
146
|
+
* heading at the appropriate level for its position in the document outline.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```tsx
|
|
150
|
+
* <Accordion.Header level={2}>
|
|
151
|
+
* <Accordion.Trigger>Shipping policy</Accordion.Trigger>
|
|
152
|
+
* </Accordion.Header>
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function AccordionHeader({
|
|
156
|
+
children,
|
|
157
|
+
level = 3,
|
|
158
|
+
...rest
|
|
159
|
+
}: AccordionHeaderProps) {
|
|
160
|
+
useAccordionHeaderContext();
|
|
161
|
+
const HeadingTag: HeadingTag = `h${level}`;
|
|
162
|
+
|
|
163
|
+
return <HeadingTag {...rest}>{children}</HeadingTag>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
AccordionHeader.displayName = "AccordionHeader";
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The button that toggles an accordion item open or closed. Renders
|
|
170
|
+
* `<button type="button">` by default and wires up all required ARIA
|
|
171
|
+
* attributes, click handling, and keyboard navigation automatically.
|
|
172
|
+
*
|
|
173
|
+
* **Disabled behaviour.** When `disabled` is `true` the trigger is rendered
|
|
174
|
+
* with `aria-disabled="true"` and `data-disabled="true"` instead of the
|
|
175
|
+
* native HTML `disabled` attribute. This keeps the button focusable so
|
|
176
|
+
* keyboard users can discover it, while preventing activation. Disabled
|
|
177
|
+
* triggers are excluded from arrow-key navigation.
|
|
178
|
+
*
|
|
179
|
+
* **`asChild` prop.** Pass `asChild` to render an arbitrary child element
|
|
180
|
+
* instead of the default `<button>`. All accordion ARIA attributes, event
|
|
181
|
+
* handlers, and the internal ref are merged onto the child following the
|
|
182
|
+
* Composition pattern:
|
|
183
|
+
* - Event handlers compose — the child's handler runs first, then the trigger's.
|
|
184
|
+
* - `style` is shallow-merged (child wins on collisions).
|
|
185
|
+
* - `className` strings are concatenated.
|
|
186
|
+
* - Refs from both sides are composed via `composeRefs`.
|
|
187
|
+
*
|
|
188
|
+
* When `asChild` is `true` and `disabled` is `true`, `role="button"` is
|
|
189
|
+
* automatically injected so that `aria-disabled` is semantically valid on
|
|
190
|
+
* non-button elements (e.g. `<a>`, `<div>`). Without a button role the
|
|
191
|
+
* `aria-disabled` attribute has no defined meaning in the ARIA spec.
|
|
192
|
+
*
|
|
193
|
+
* **Keyboard support** (WAI-ARIA Accordion pattern):
|
|
194
|
+
*
|
|
195
|
+
* | Key | Behaviour |
|
|
196
|
+
* | ----------------- | -------------------------------------------- |
|
|
197
|
+
* | `Enter` / `Space` | Toggle the focused item |
|
|
198
|
+
* | `ArrowDown` | Move focus to next trigger (vertical) |
|
|
199
|
+
* | `ArrowUp` | Move focus to previous trigger (vertical) |
|
|
200
|
+
* | `ArrowRight` | Move focus to next trigger (horizontal) |
|
|
201
|
+
* | `ArrowLeft` | Move focus to previous trigger (horizontal) |
|
|
202
|
+
* | `Home` | Move focus to first enabled trigger |
|
|
203
|
+
* | `End` | Move focus to last enabled trigger |
|
|
204
|
+
*
|
|
205
|
+
* Movement **wraps** at the ends. For horizontal orientation with
|
|
206
|
+
* `dir="rtl"`, `ArrowLeft` moves forward and `ArrowRight` moves backward.
|
|
207
|
+
*
|
|
208
|
+
* **Ref forwarding.** A `ref` prop (React 19 ref-as-prop style) is forwarded
|
|
209
|
+
* to the underlying DOM element — useful for imperative focus management.
|
|
210
|
+
*
|
|
211
|
+
* **Styling hooks.**
|
|
212
|
+
* - `data-state="open" | "closed"` on the rendered element.
|
|
213
|
+
* - `data-disabled="true" | "false"`.
|
|
214
|
+
*
|
|
215
|
+
* @example Basic
|
|
216
|
+
* ```tsx
|
|
217
|
+
* <Accordion.Trigger>Shipping policy</Accordion.Trigger>
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @example Disabled
|
|
221
|
+
* ```tsx
|
|
222
|
+
* <Accordion.Trigger disabled>Unavailable section</Accordion.Trigger>
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example asChild — render a link with accordion semantics
|
|
226
|
+
* ```tsx
|
|
227
|
+
* <Accordion.Trigger asChild>
|
|
228
|
+
* <a href="#shipping">Shipping policy</a>
|
|
229
|
+
* </Accordion.Trigger>
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export function AccordionTrigger<
|
|
233
|
+
T extends HTMLElement = HTMLButtonElement,
|
|
234
|
+
>({
|
|
235
|
+
ref,
|
|
236
|
+
children,
|
|
237
|
+
onClick,
|
|
238
|
+
disabled = false,
|
|
239
|
+
asChild = false,
|
|
240
|
+
...rest
|
|
241
|
+
}: AccordionTriggerProps<T>) {
|
|
242
|
+
// Cast the external ref to match the internal button ref's element type —
|
|
243
|
+
// RefObject<T> is invariant in React's types, but at runtime the callback
|
|
244
|
+
// receives whatever DOM element is actually rendered (button or asChild).
|
|
245
|
+
const { triggerProps } = useAccordionTrigger({
|
|
246
|
+
ref: ref as Ref<HTMLButtonElement>,
|
|
247
|
+
onClick,
|
|
248
|
+
disabled,
|
|
249
|
+
asChild,
|
|
250
|
+
...rest,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (asChild) {
|
|
254
|
+
return <Slot {...triggerProps}>{children}</Slot>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<button type="button" {...triggerProps}>
|
|
259
|
+
{children}
|
|
260
|
+
</button>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
AccordionTrigger.displayName = "AccordionTrigger";
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The panel that is revealed when the associated `Accordion.Trigger` is
|
|
268
|
+
* activated. Renders a `<div role="region" aria-labelledby="…">` whose
|
|
269
|
+
* visibility is controlled by the `hidden` attribute.
|
|
270
|
+
*
|
|
271
|
+
* **`forceMount` prop.** By default the panel is removed from visibility with
|
|
272
|
+
* `hidden` when closed. Pass `forceMount` to keep the panel in the DOM at all
|
|
273
|
+
* times. In `forceMount` mode the `hidden` attribute is never set, so
|
|
274
|
+
* CSS transitions on open / close work correctly — consumers can use
|
|
275
|
+
* `[data-state="closed"] { display: none; }` (or equivalent animation
|
|
276
|
+
* classes) to control visibility themselves.
|
|
277
|
+
*
|
|
278
|
+
* When `forceMount` is `true` and the panel is closed, `aria-hidden="true"` is
|
|
279
|
+
* set automatically so assistive technology ignores the off-screen content.
|
|
280
|
+
* It is removed when the panel opens. Consumers can override this by passing
|
|
281
|
+
* `aria-hidden` explicitly (it appears after the automatic value in the spread).
|
|
282
|
+
*
|
|
283
|
+
* **`role="region"` escape hatch.** Each panel renders with `role="region"` by
|
|
284
|
+
* default, creating an ARIA landmark. Accordions with many items can produce
|
|
285
|
+
* landmark overload in screen readers. Opt out by passing `role={undefined}`:
|
|
286
|
+
* ```tsx
|
|
287
|
+
* <Accordion.Content role={undefined}>…</Accordion.Content>
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* **Styling hooks.**
|
|
291
|
+
* - `data-state="open" | "closed"` on the rendered element.
|
|
292
|
+
*
|
|
293
|
+
* @example Default (hidden attribute)
|
|
294
|
+
* ```tsx
|
|
295
|
+
* <Accordion.Content>Free shipping on orders over £50.</Accordion.Content>
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @example With forceMount for CSS animations
|
|
299
|
+
* ```tsx
|
|
300
|
+
* <Accordion.Content forceMount className="panel">
|
|
301
|
+
* Content that animates in and out.
|
|
302
|
+
* </Accordion.Content>
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export function AccordionContent({
|
|
306
|
+
children,
|
|
307
|
+
forceMount = false,
|
|
308
|
+
...rest
|
|
309
|
+
}: AccordionContentProps) {
|
|
310
|
+
const { panelId, buttonId, itemId, isExpanded } = useAccordionItemContext();
|
|
311
|
+
const { registerPanel, unregisterPanel } = useAccordionContext();
|
|
312
|
+
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
registerPanel(itemId);
|
|
315
|
+
return () => unregisterPanel(itemId);
|
|
316
|
+
}, [itemId, registerPanel, unregisterPanel]);
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div
|
|
320
|
+
id={panelId}
|
|
321
|
+
aria-labelledby={buttonId}
|
|
322
|
+
role="region"
|
|
323
|
+
hidden={forceMount ? undefined : !isExpanded}
|
|
324
|
+
aria-hidden={forceMount && !isExpanded ? true : undefined}
|
|
325
|
+
data-state={isExpanded ? "open" : "closed"}
|
|
326
|
+
{...rest}
|
|
327
|
+
>
|
|
328
|
+
{children}
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
AccordionContent.displayName = "AccordionContent";
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* A wrapper that hides its icon child from the accessibility tree and
|
|
337
|
+
* provides a `data-state` hook for open/close animations. Accepts any
|
|
338
|
+
* renderable React content as a child — an inline `<svg>`, a component
|
|
339
|
+
* from a third-party icon library (lucide-react, react-icons, etc.), or
|
|
340
|
+
* any custom icon component.
|
|
341
|
+
*
|
|
342
|
+
* Renders a `<span>` with `aria-hidden="true"` around the child so the
|
|
343
|
+
* icon is hidden from assistive technology regardless of whether the child
|
|
344
|
+
* component forwards unknown props. Any additional props (`className`,
|
|
345
|
+
* `data-*`, `ref`, etc.) are forwarded to that `<span>`.
|
|
346
|
+
*
|
|
347
|
+
* **Styling hooks.**
|
|
348
|
+
* - `data-state="open" | "closed"` on the rendered `<span>`.
|
|
349
|
+
* - `aria-hidden="true"` on the rendered `<span>`.
|
|
350
|
+
*
|
|
351
|
+
* @example Inline SVG
|
|
352
|
+
* ```tsx
|
|
353
|
+
* <Accordion.Trigger>
|
|
354
|
+
* Shipping policy
|
|
355
|
+
* <Accordion.TriggerIcon>
|
|
356
|
+
* <svg …><path d="…" /></svg>
|
|
357
|
+
* </Accordion.TriggerIcon>
|
|
358
|
+
* </Accordion.Trigger>
|
|
359
|
+
* ```
|
|
360
|
+
*
|
|
361
|
+
* @example Third-party icon component
|
|
362
|
+
* ```tsx
|
|
363
|
+
* import { ChevronDown } from "lucide-react";
|
|
364
|
+
*
|
|
365
|
+
* <Accordion.Trigger>
|
|
366
|
+
* Shipping policy
|
|
367
|
+
* <Accordion.TriggerIcon>
|
|
368
|
+
* <ChevronDown />
|
|
369
|
+
* </Accordion.TriggerIcon>
|
|
370
|
+
* </Accordion.Trigger>
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export function AccordionTriggerIcon({
|
|
374
|
+
children,
|
|
375
|
+
...rest
|
|
376
|
+
}: AccordionTriggerIconProps) {
|
|
377
|
+
const { isExpanded } = useAccordionItemContext();
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<span
|
|
381
|
+
{...rest}
|
|
382
|
+
aria-hidden="true"
|
|
383
|
+
data-state={isExpanded ? "open" : "closed"}
|
|
384
|
+
>
|
|
385
|
+
{children}
|
|
386
|
+
</span>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
AccordionTriggerIcon.displayName = "AccordionTriggerIcon";
|
|
391
|
+
|
|
392
|
+
type AccordionCompound = typeof AccordionRoot & {
|
|
393
|
+
Root: typeof AccordionRoot;
|
|
394
|
+
Item: typeof AccordionItem;
|
|
395
|
+
Header: typeof AccordionHeader;
|
|
396
|
+
Trigger: typeof AccordionTrigger;
|
|
397
|
+
Content: typeof AccordionContent;
|
|
398
|
+
TriggerIcon: typeof AccordionTriggerIcon;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const AccordionCompound: AccordionCompound = Object.assign(AccordionRoot, {
|
|
402
|
+
Root: AccordionRoot,
|
|
403
|
+
Item: AccordionItem,
|
|
404
|
+
Header: AccordionHeader,
|
|
405
|
+
Trigger: AccordionTrigger,
|
|
406
|
+
Content: AccordionContent,
|
|
407
|
+
TriggerIcon: AccordionTriggerIcon,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
AccordionCompound.displayName = "Accordion";
|
|
411
|
+
|
|
412
|
+
export { AccordionCompound as Accordion };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createStrictContext } from "../utils";
|
|
2
|
+
import { AccordionContextValue, AccordionItemContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
export const [AccordionContext, useAccordionContext] =
|
|
5
|
+
createStrictContext<AccordionContextValue>(
|
|
6
|
+
"AccordionItem must be used within AccordionRoot",
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export const [AccordionItemContext, useAccordionItemContext] =
|
|
10
|
+
createStrictContext<AccordionItemContextValue>(
|
|
11
|
+
"Component must be used within AccordionItem",
|
|
12
|
+
);
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
A compound component implementing the
|
|
4
|
+
[WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { Accordion } from "@primitiv-ui/react";
|
|
8
|
+
|
|
9
|
+
<Accordion.Root defaultValue="shipping">
|
|
10
|
+
<Accordion.Item value="shipping">
|
|
11
|
+
<Accordion.Header>
|
|
12
|
+
<Accordion.Trigger>Shipping policy</Accordion.Trigger>
|
|
13
|
+
</Accordion.Header>
|
|
14
|
+
<Accordion.Content>Free on orders over £50.</Accordion.Content>
|
|
15
|
+
</Accordion.Item>
|
|
16
|
+
<Accordion.Item value="returns">
|
|
17
|
+
<Accordion.Header>
|
|
18
|
+
<Accordion.Trigger>Returns</Accordion.Trigger>
|
|
19
|
+
</Accordion.Header>
|
|
20
|
+
<Accordion.Content>30-day returns accepted.</Accordion.Content>
|
|
21
|
+
</Accordion.Item>
|
|
22
|
+
</Accordion.Root>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Sub-components
|
|
26
|
+
|
|
27
|
+
| Export | Role | Notes |
|
|
28
|
+
| ----------------------- | -------------- | ----------------------------------------------------------------------- |
|
|
29
|
+
| `Accordion.Root` | State owner | Uncontrolled (`defaultValue`) or controlled (`value` + `onValueChange`) |
|
|
30
|
+
| `Accordion.Item` | Item wrapper | Owns the trigger–panel pair; optional `value` prop |
|
|
31
|
+
| `Accordion.Header` | Heading | Configurable level (`h1`–`h6`); defaults to `h3` |
|
|
32
|
+
| `Accordion.Trigger` | Toggle button | Supports `asChild`, ref forwarding, and `disabled` |
|
|
33
|
+
| `Accordion.Content` | `region` panel | Supports `forceMount` for CSS animation |
|
|
34
|
+
| `Accordion.TriggerIcon` | Icon wrapper | Injects `aria-hidden` and `data-state` onto a decorative icon |
|
|
35
|
+
|
|
36
|
+
## Keyboard interaction
|
|
37
|
+
|
|
38
|
+
| Key | Behaviour |
|
|
39
|
+
| ----------------- | ------------------------------------------------- |
|
|
40
|
+
| `Enter` / `Space` | Toggle the focused item |
|
|
41
|
+
| `ArrowDown` | Move focus to next trigger (vertical orientation) |
|
|
42
|
+
| `ArrowUp` | Move focus to previous trigger (vertical) |
|
|
43
|
+
| `ArrowRight` | Move focus to next trigger (horizontal) |
|
|
44
|
+
| `ArrowLeft` | Move focus to previous trigger (horizontal) |
|
|
45
|
+
| `Home` | Jump to first enabled trigger |
|
|
46
|
+
| `End` | Jump to last enabled trigger |
|
|
47
|
+
|
|
48
|
+
Focus movement **wraps** at the ends. Disabled triggers are excluded from
|
|
49
|
+
arrow-key navigation but remain focusable via `Tab`.
|
|
50
|
+
|
|
51
|
+
## State modes
|
|
52
|
+
|
|
53
|
+
- **Uncontrolled** — pass `defaultValue` (or omit to start with all items collapsed).
|
|
54
|
+
- **Controlled** — pass `value` (a `string[]`) and `onValueChange` together.
|
|
55
|
+
|
|
56
|
+
## Multiple mode
|
|
57
|
+
|
|
58
|
+
By default only one item can be open at a time — opening a second item
|
|
59
|
+
collapses the first. Pass `multiple` to allow any number of items open
|
|
60
|
+
simultaneously:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Accordion.Root multiple defaultValue="shipping">
|
|
64
|
+
…
|
|
65
|
+
</Accordion.Root>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Disabled items
|
|
69
|
+
|
|
70
|
+
Disabled triggers are rendered with `aria-disabled="true"` (not the native
|
|
71
|
+
`disabled` attribute) so they remain focusable for keyboard discovery:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Accordion.Trigger disabled>Unavailable section</Accordion.Trigger>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## `forceMount` for animations
|
|
78
|
+
|
|
79
|
+
By default the content panel is hidden with the `hidden` attribute. Pass
|
|
80
|
+
`forceMount` to keep the panel in the DOM and control visibility via CSS:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<Accordion.Content forceMount className="panel">
|
|
84
|
+
Content that animates in and out.
|
|
85
|
+
</Accordion.Content>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
.panel[data-state="closed"] {
|
|
90
|
+
display: none; /* or use an animation */
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When `forceMount` is active and the panel is closed, `aria-hidden="true"` is
|
|
95
|
+
applied automatically so assistive technology skips the off-screen content.
|
|
96
|
+
It is removed when the panel opens. Consumers can override this by passing
|
|
97
|
+
`aria-hidden` explicitly.
|
|
98
|
+
|
|
99
|
+
## `role="region"` escape hatch
|
|
100
|
+
|
|
101
|
+
Each `Accordion.Content` renders with `role="region"`, creating an ARIA
|
|
102
|
+
landmark. This is appropriate for panels whose content benefits from
|
|
103
|
+
landmark navigation, but accordions with many items can produce landmark
|
|
104
|
+
overload in screen readers (JAWS/NVDA expose all landmarks in their landmark
|
|
105
|
+
list).
|
|
106
|
+
|
|
107
|
+
Opt out on individual panels by passing `role={undefined}`:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<Accordion.Content role={undefined}>
|
|
111
|
+
Simple prose that doesn't need landmark navigation.
|
|
112
|
+
</Accordion.Content>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## `asChild` composition
|
|
116
|
+
|
|
117
|
+
`Accordion.Trigger` accepts an `asChild` prop to render any child element
|
|
118
|
+
with full accordion semantics. All ARIA attributes, event handlers, and
|
|
119
|
+
the internal ref are merged onto the child (child handler runs first, then
|
|
120
|
+
the trigger's):
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<Accordion.Trigger asChild>
|
|
124
|
+
<a href="#shipping">Shipping policy</a>
|
|
125
|
+
</Accordion.Trigger>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`Enter` and `Space` are handled in `onKeyDown` so non-button elements (e.g.
|
|
129
|
+
`<a>`) toggle correctly without relying on native click behaviour.
|
|
130
|
+
|
|
131
|
+
When `asChild` is combined with `disabled`, `role="button"` is injected
|
|
132
|
+
automatically so that `aria-disabled` is semantically valid on non-button
|
|
133
|
+
elements. Without a button role, `aria-disabled` has no defined meaning:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<Accordion.Trigger asChild disabled>
|
|
137
|
+
<a href="#shipping">Temporarily unavailable</a>
|
|
138
|
+
{/* rendered with role="button" aria-disabled="true" */}
|
|
139
|
+
</Accordion.Trigger>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Trigger icon
|
|
143
|
+
|
|
144
|
+
Wrap a decorative icon in `Accordion.TriggerIcon` to hide it from assistive
|
|
145
|
+
technology and expose a `data-state` hook for rotation animations. The child
|
|
146
|
+
can be any renderable React content — an inline `<svg>`, a component from
|
|
147
|
+
a third-party icon library (lucide-react, react-icons, etc.), or a custom
|
|
148
|
+
icon component. `aria-hidden` and `data-state` are placed on a wrapping
|
|
149
|
+
`<span>`, so they work regardless of whether the icon component forwards
|
|
150
|
+
unknown props.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { ChevronDown } from "lucide-react";
|
|
154
|
+
|
|
155
|
+
<Accordion.Trigger>
|
|
156
|
+
Shipping policy
|
|
157
|
+
<Accordion.TriggerIcon>
|
|
158
|
+
<ChevronDown />
|
|
159
|
+
</Accordion.TriggerIcon>
|
|
160
|
+
</Accordion.Trigger>;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Reading direction (RTL)
|
|
164
|
+
|
|
165
|
+
Pass `dir="rtl"` on `Accordion.Root` combined with `orientation="horizontal"`
|
|
166
|
+
to invert the arrow-key direction so `ArrowLeft` moves forward and
|
|
167
|
+
`ArrowRight` moves backward, matching right-to-left reading order:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Accordion.Root orientation="horizontal" dir="rtl">
|
|
171
|
+
…
|
|
172
|
+
</Accordion.Root>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When `dir` is omitted, it is inherited from the nearest
|
|
176
|
+
[`DirectionProvider`](../DirectionProvider/README.md), falling back to `"ltr"`
|
|
177
|
+
when there is no provider. An explicit `dir` prop always wins over the
|
|
178
|
+
inherited value.
|
|
179
|
+
|
|
180
|
+
## Styling hooks
|
|
181
|
+
|
|
182
|
+
```css
|
|
183
|
+
/* Trigger open/closed */
|
|
184
|
+
[data-state="open"] .chevron {
|
|
185
|
+
transform: rotate(180deg);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Content panel */
|
|
189
|
+
[role="region"][data-state="closed"] {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Disabled trigger */
|
|
194
|
+
[aria-disabled="true"] {
|
|
195
|
+
opacity: 0.5;
|
|
196
|
+
cursor: not-allowed;
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`data-state` (`"open"` | `"closed"`), `data-disabled` (`"true"` | `"false"`),
|
|
201
|
+
and `data-orientation` (`"vertical"` | `"horizontal"`) are available on
|
|
202
|
+
relevant rendered elements.
|