@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,234 @@
|
|
|
1
|
+
import { Slot } from "../Slot";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BreadcrumbItemProps,
|
|
5
|
+
BreadcrumbLinkProps,
|
|
6
|
+
BreadcrumbListProps,
|
|
7
|
+
BreadcrumbPageProps,
|
|
8
|
+
BreadcrumbRootProps,
|
|
9
|
+
BreadcrumbSeparatorProps,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The root of a Breadcrumb — a `<nav>` landmark that wraps the breadcrumb
|
|
14
|
+
* trail.
|
|
15
|
+
*
|
|
16
|
+
* The `<nav>` defaults to `aria-label="Breadcrumb"` so assistive technology
|
|
17
|
+
* announces it as the breadcrumb navigation landmark. Override `aria-label`
|
|
18
|
+
* if your product uses different terminology.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Breadcrumb.Root>
|
|
23
|
+
* <Breadcrumb.List>…</Breadcrumb.List>
|
|
24
|
+
* </Breadcrumb.Root>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function BreadcrumbRoot({ children, ...rest }: BreadcrumbRootProps) {
|
|
28
|
+
return (
|
|
29
|
+
<nav aria-label="Breadcrumb" {...rest}>
|
|
30
|
+
{children}
|
|
31
|
+
</nav>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
BreadcrumbRoot.displayName = "BreadcrumbRoot";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The ordered list of breadcrumb entries — renders an `<ol>`.
|
|
39
|
+
*
|
|
40
|
+
* An ordered list is the correct semantic: breadcrumb entries have a
|
|
41
|
+
* meaningful sequence from the site root to the current page. Contains
|
|
42
|
+
* {@link BreadcrumbItem | `Breadcrumb.Item`}s interleaved with
|
|
43
|
+
* {@link BreadcrumbSeparator | `Breadcrumb.Separator`}s.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Breadcrumb.List>
|
|
48
|
+
* <Breadcrumb.Item>…</Breadcrumb.Item>
|
|
49
|
+
* <Breadcrumb.Separator />
|
|
50
|
+
* <Breadcrumb.Item>…</Breadcrumb.Item>
|
|
51
|
+
* </Breadcrumb.List>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
function BreadcrumbList({ children, ...rest }: BreadcrumbListProps) {
|
|
55
|
+
return <ol {...rest}>{children}</ol>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
BreadcrumbList.displayName = "BreadcrumbList";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A single entry in the breadcrumb trail — renders an `<li>`.
|
|
62
|
+
*
|
|
63
|
+
* Wraps either a {@link BreadcrumbLink | `Breadcrumb.Link`} (an ancestor
|
|
64
|
+
* page) or a {@link BreadcrumbPage | `Breadcrumb.Page`} (the current page).
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <Breadcrumb.Item>
|
|
69
|
+
* <Breadcrumb.Link href="/library">Library</Breadcrumb.Link>
|
|
70
|
+
* </Breadcrumb.Item>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function BreadcrumbItem({ children, ...rest }: BreadcrumbItemProps) {
|
|
74
|
+
return <li {...rest}>{children}</li>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
BreadcrumbItem.displayName = "BreadcrumbItem";
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A link to an ancestor page — renders an `<a>`.
|
|
81
|
+
*
|
|
82
|
+
* Use for every entry except the current page. Pass `href` (and any other
|
|
83
|
+
* anchor attributes) directly.
|
|
84
|
+
*
|
|
85
|
+
* **`asChild` prop.** Pass `asChild` to render a consumer-supplied element —
|
|
86
|
+
* typically a routing library's `<Link>` — with the breadcrumb link's props
|
|
87
|
+
* merged in. The native `<a>` is dropped.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* <Breadcrumb.Link href="/library">Library</Breadcrumb.Link>
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example With a router link
|
|
95
|
+
* ```tsx
|
|
96
|
+
* <Breadcrumb.Link asChild>
|
|
97
|
+
* <RouterLink to="/library">Library</RouterLink>
|
|
98
|
+
* </Breadcrumb.Link>
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
function BreadcrumbLink({
|
|
102
|
+
children,
|
|
103
|
+
asChild = false,
|
|
104
|
+
...rest
|
|
105
|
+
}: BreadcrumbLinkProps) {
|
|
106
|
+
if (asChild) {
|
|
107
|
+
return <Slot {...rest}>{children}</Slot>;
|
|
108
|
+
}
|
|
109
|
+
return <a {...rest}>{children}</a>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
BreadcrumbLink.displayName = "BreadcrumbLink";
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The current page in the trail — renders a `<span aria-current="page">`.
|
|
116
|
+
*
|
|
117
|
+
* The last entry of a breadcrumb is the page the user is on, so it is not a
|
|
118
|
+
* link. `aria-current="page"` tells assistive technology this entry
|
|
119
|
+
* represents the current location.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* <Breadcrumb.Item>
|
|
124
|
+
* <Breadcrumb.Page>Current article</Breadcrumb.Page>
|
|
125
|
+
* </Breadcrumb.Item>
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
function BreadcrumbPage({ children, ...rest }: BreadcrumbPageProps) {
|
|
129
|
+
return (
|
|
130
|
+
<span aria-current="page" {...rest}>
|
|
131
|
+
{children}
|
|
132
|
+
</span>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
BreadcrumbPage.displayName = "BreadcrumbPage";
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The visual divider between two breadcrumb entries — renders an
|
|
140
|
+
* `<li role="presentation" aria-hidden="true">`.
|
|
141
|
+
*
|
|
142
|
+
* The separator sits inside the `<ol>` as a sibling of the
|
|
143
|
+
* {@link BreadcrumbItem | `Breadcrumb.Item`}s, but `role="presentation"`
|
|
144
|
+
* removes it from the list semantics and `aria-hidden` hides it from the
|
|
145
|
+
* accessibility tree — it is purely decorative.
|
|
146
|
+
*
|
|
147
|
+
* Defaults to a `"/"` glyph; pass `children` to use a custom separator (an
|
|
148
|
+
* icon, a chevron, etc.).
|
|
149
|
+
*
|
|
150
|
+
* @example Default separator
|
|
151
|
+
* ```tsx
|
|
152
|
+
* <Breadcrumb.Separator />
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @example Custom separator
|
|
156
|
+
* ```tsx
|
|
157
|
+
* <Breadcrumb.Separator><ChevronRight /></Breadcrumb.Separator>
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
function BreadcrumbSeparator({
|
|
161
|
+
children = "/",
|
|
162
|
+
...rest
|
|
163
|
+
}: BreadcrumbSeparatorProps) {
|
|
164
|
+
return (
|
|
165
|
+
<li role="presentation" aria-hidden="true" {...rest}>
|
|
166
|
+
{children}
|
|
167
|
+
</li>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
|
172
|
+
|
|
173
|
+
type TBreadcrumbCompound = typeof BreadcrumbRoot & {
|
|
174
|
+
Root: typeof BreadcrumbRoot;
|
|
175
|
+
List: typeof BreadcrumbList;
|
|
176
|
+
Item: typeof BreadcrumbItem;
|
|
177
|
+
Link: typeof BreadcrumbLink;
|
|
178
|
+
Page: typeof BreadcrumbPage;
|
|
179
|
+
Separator: typeof BreadcrumbSeparator;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Headless, accessible **Breadcrumb** — a compound component implementing the
|
|
184
|
+
* [WAI-ARIA breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/):
|
|
185
|
+
* a `<nav>` landmark wrapping an ordered list of links to ancestor pages,
|
|
186
|
+
* ending with the current page. Stateless — purely structural. Zero styles
|
|
187
|
+
* ship.
|
|
188
|
+
*
|
|
189
|
+
* `Breadcrumb` is both callable (an alias of
|
|
190
|
+
* {@link BreadcrumbRoot | `Breadcrumb.Root`}) and carries its sub-components
|
|
191
|
+
* as static properties.
|
|
192
|
+
*
|
|
193
|
+
* - {@link BreadcrumbRoot | `Breadcrumb.Root`} — `<nav aria-label="Breadcrumb">`.
|
|
194
|
+
* - {@link BreadcrumbList | `Breadcrumb.List`} — `<ol>`, the ordered trail.
|
|
195
|
+
* - {@link BreadcrumbItem | `Breadcrumb.Item`} — `<li>`, one entry.
|
|
196
|
+
* - {@link BreadcrumbLink | `Breadcrumb.Link`} — `<a>`, an ancestor-page link.
|
|
197
|
+
* - {@link BreadcrumbPage | `Breadcrumb.Page`} — `<span aria-current="page">`, the current page.
|
|
198
|
+
* - {@link BreadcrumbSeparator | `Breadcrumb.Separator`} — decorative `<li>` divider.
|
|
199
|
+
*
|
|
200
|
+
* @example Minimal usage
|
|
201
|
+
* ```tsx
|
|
202
|
+
* import { Breadcrumb } from "@primitiv-ui/react";
|
|
203
|
+
*
|
|
204
|
+
* <Breadcrumb.Root>
|
|
205
|
+
* <Breadcrumb.List>
|
|
206
|
+
* <Breadcrumb.Item>
|
|
207
|
+
* <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
|
|
208
|
+
* </Breadcrumb.Item>
|
|
209
|
+
* <Breadcrumb.Separator />
|
|
210
|
+
* <Breadcrumb.Item>
|
|
211
|
+
* <Breadcrumb.Link href="/library">Library</Breadcrumb.Link>
|
|
212
|
+
* </Breadcrumb.Item>
|
|
213
|
+
* <Breadcrumb.Separator />
|
|
214
|
+
* <Breadcrumb.Item>
|
|
215
|
+
* <Breadcrumb.Page>Current article</Breadcrumb.Page>
|
|
216
|
+
* </Breadcrumb.Item>
|
|
217
|
+
* </Breadcrumb.List>
|
|
218
|
+
* </Breadcrumb.Root>
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* @see {@link BreadcrumbSeparator} for customising the divider glyph.
|
|
222
|
+
*/
|
|
223
|
+
const BreadcrumbCompound: TBreadcrumbCompound = Object.assign(BreadcrumbRoot, {
|
|
224
|
+
Root: BreadcrumbRoot,
|
|
225
|
+
List: BreadcrumbList,
|
|
226
|
+
Item: BreadcrumbItem,
|
|
227
|
+
Link: BreadcrumbLink,
|
|
228
|
+
Page: BreadcrumbPage,
|
|
229
|
+
Separator: BreadcrumbSeparator,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
BreadcrumbCompound.displayName = "Breadcrumb";
|
|
233
|
+
|
|
234
|
+
export { BreadcrumbCompound as Breadcrumb };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Breadcrumb
|
|
2
|
+
|
|
3
|
+
Headless, accessible **Breadcrumb** — a compound component implementing the
|
|
4
|
+
[WAI-ARIA breadcrumb pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/):
|
|
5
|
+
a `<nav>` landmark wrapping an ordered list of links to ancestor pages, ending
|
|
6
|
+
with the current page. Stateless — purely structural. Zero styles ship.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { Breadcrumb } from "@primitiv-ui/react";
|
|
10
|
+
|
|
11
|
+
<Breadcrumb.Root>
|
|
12
|
+
<Breadcrumb.List>
|
|
13
|
+
<Breadcrumb.Item>
|
|
14
|
+
<Breadcrumb.Link href="/">Home</Breadcrumb.Link>
|
|
15
|
+
</Breadcrumb.Item>
|
|
16
|
+
<Breadcrumb.Separator />
|
|
17
|
+
<Breadcrumb.Item>
|
|
18
|
+
<Breadcrumb.Link href="/library">Library</Breadcrumb.Link>
|
|
19
|
+
</Breadcrumb.Item>
|
|
20
|
+
<Breadcrumb.Separator />
|
|
21
|
+
<Breadcrumb.Item>
|
|
22
|
+
<Breadcrumb.Page>Current article</Breadcrumb.Page>
|
|
23
|
+
</Breadcrumb.Item>
|
|
24
|
+
</Breadcrumb.List>
|
|
25
|
+
</Breadcrumb.Root>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Sub-components
|
|
29
|
+
|
|
30
|
+
| Export | Element | ARIA hooks | `asChild` |
|
|
31
|
+
|--------|---------|------------|-----------|
|
|
32
|
+
| `Breadcrumb.Root` | `<nav>` | `aria-label="Breadcrumb"` (overridable) | — |
|
|
33
|
+
| `Breadcrumb.List` | `<ol>` | — | — |
|
|
34
|
+
| `Breadcrumb.Item` | `<li>` | — | — |
|
|
35
|
+
| `Breadcrumb.Link` | `<a>` | — | yes |
|
|
36
|
+
| `Breadcrumb.Page` | `<span>` | `aria-current="page"` | — |
|
|
37
|
+
| `Breadcrumb.Separator` | `<li>` | `role="presentation"`, `aria-hidden="true"` | — |
|
|
38
|
+
|
|
39
|
+
Every sub-component passes all of its element's native attributes straight
|
|
40
|
+
through to the DOM.
|
|
41
|
+
|
|
42
|
+
## Structure
|
|
43
|
+
|
|
44
|
+
A breadcrumb is an **ordered list** — its entries have a meaningful sequence
|
|
45
|
+
from the site root to the current page — wrapped in a `<nav>` landmark.
|
|
46
|
+
|
|
47
|
+
- The `<nav>` defaults to `aria-label="Breadcrumb"`, which is how assistive
|
|
48
|
+
technology identifies the breadcrumb navigation landmark. Override it only
|
|
49
|
+
if your product uses different terminology.
|
|
50
|
+
- The final entry is the **current page**, so it is a `Breadcrumb.Page`
|
|
51
|
+
(a non-link `<span>`) rather than a `Breadcrumb.Link`.
|
|
52
|
+
- `Breadcrumb.Page` carries `aria-current="page"` to mark the user's current
|
|
53
|
+
location.
|
|
54
|
+
|
|
55
|
+
## The current page
|
|
56
|
+
|
|
57
|
+
The last entry should always be a `Breadcrumb.Page`:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
<Breadcrumb.Item>
|
|
61
|
+
<Breadcrumb.Page>Current article</Breadcrumb.Page>
|
|
62
|
+
</Breadcrumb.Item>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It renders a `<span aria-current="page">` — not a link — because the page the
|
|
66
|
+
user is already on is not a navigation target.
|
|
67
|
+
|
|
68
|
+
## Separators
|
|
69
|
+
|
|
70
|
+
`Breadcrumb.Separator` is a decorative `<li role="presentation" aria-hidden="true">`.
|
|
71
|
+
It sits inside the `<ol>` as a sibling of the items, but `role="presentation"`
|
|
72
|
+
removes it from the list semantics and `aria-hidden` hides it from the
|
|
73
|
+
accessibility tree — screen readers never announce it.
|
|
74
|
+
|
|
75
|
+
It defaults to a `"/"` glyph. Pass `children` to use a custom separator:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<Breadcrumb.Separator>›</Breadcrumb.Separator>
|
|
79
|
+
<Breadcrumb.Separator><ChevronRight /></Breadcrumb.Separator>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## `asChild` composition
|
|
83
|
+
|
|
84
|
+
`Breadcrumb.Link` accepts `asChild` so it can render a routing library's link
|
|
85
|
+
component instead of a bare `<a>`. The breadcrumb link's props are merged onto
|
|
86
|
+
the consumer's element and the native `<a>` is dropped.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { Link as RouterLink } from "react-router-dom";
|
|
90
|
+
|
|
91
|
+
<Breadcrumb.Link asChild>
|
|
92
|
+
<RouterLink to="/library">Library</RouterLink>
|
|
93
|
+
</Breadcrumb.Link>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Styling hooks
|
|
97
|
+
|
|
98
|
+
No styles ship with the component. Target the rendered elements directly or
|
|
99
|
+
pass a `className` to any sub-component:
|
|
100
|
+
|
|
101
|
+
```css
|
|
102
|
+
nav[aria-label="Breadcrumb"] ol {
|
|
103
|
+
display: flex;
|
|
104
|
+
gap: 0.5rem;
|
|
105
|
+
list-style: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
nav[aria-label="Breadcrumb"] [aria-current="page"] {
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Breadcrumb } from "../Breadcrumb";
|
|
4
|
+
|
|
5
|
+
describe("Breadcrumb asChild", () => {
|
|
6
|
+
it("renders Breadcrumb.Link as the consumer element, with no wrapping <a>", () => {
|
|
7
|
+
// Arrange & Act
|
|
8
|
+
render(
|
|
9
|
+
<Breadcrumb.Link asChild>
|
|
10
|
+
<button type="button">Library</button>
|
|
11
|
+
</Breadcrumb.Link>,
|
|
12
|
+
);
|
|
13
|
+
const link = screen.getByRole("button", { name: "Library" });
|
|
14
|
+
|
|
15
|
+
// Assert — the native <a> is dropped entirely
|
|
16
|
+
expect(link.closest("a")).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("merges Breadcrumb.Link props onto the asChild element", () => {
|
|
20
|
+
// Arrange & Act
|
|
21
|
+
render(
|
|
22
|
+
<Breadcrumb.Link asChild className="crumb" data-testid="link">
|
|
23
|
+
<a href="/library">Library</a>
|
|
24
|
+
</Breadcrumb.Link>,
|
|
25
|
+
);
|
|
26
|
+
const link = screen.getByTestId("link");
|
|
27
|
+
|
|
28
|
+
// Assert — consumer's <a> kept; className and href both present on it
|
|
29
|
+
expect(link.tagName).toBe("A");
|
|
30
|
+
expect(link).toHaveClass("crumb");
|
|
31
|
+
expect(link).toHaveAttribute("href", "/library");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
|
|
3
|
+
import { Breadcrumb } from "../Breadcrumb";
|
|
4
|
+
|
|
5
|
+
describe("Breadcrumb basic rendering", () => {
|
|
6
|
+
it('renders the Root as a <nav aria-label="Breadcrumb">', () => {
|
|
7
|
+
// Arrange & Act
|
|
8
|
+
render(<Breadcrumb.Root />);
|
|
9
|
+
const nav = screen.getByRole("navigation", { name: "Breadcrumb" });
|
|
10
|
+
|
|
11
|
+
// Assert
|
|
12
|
+
expect(nav.tagName).toBe("NAV");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("lets the consumer override the Root aria-label", () => {
|
|
16
|
+
// Arrange & Act
|
|
17
|
+
render(<Breadcrumb.Root aria-label="You are here" />);
|
|
18
|
+
|
|
19
|
+
// Assert
|
|
20
|
+
expect(
|
|
21
|
+
screen.getByRole("navigation", { name: "You are here" }),
|
|
22
|
+
).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("renders the List as an <ol>", () => {
|
|
26
|
+
// Arrange & Act
|
|
27
|
+
render(
|
|
28
|
+
<Breadcrumb.Root>
|
|
29
|
+
<Breadcrumb.List />
|
|
30
|
+
</Breadcrumb.Root>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Assert
|
|
34
|
+
expect(screen.getByRole("list").tagName).toBe("OL");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders the Item as an <li>", () => {
|
|
38
|
+
// Arrange & Act
|
|
39
|
+
render(
|
|
40
|
+
<Breadcrumb.Root>
|
|
41
|
+
<Breadcrumb.List>
|
|
42
|
+
<Breadcrumb.Item>Home</Breadcrumb.Item>
|
|
43
|
+
</Breadcrumb.List>
|
|
44
|
+
</Breadcrumb.Root>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(screen.getByRole("listitem").tagName).toBe("LI");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("renders the Link as an <a> carrying its href", () => {
|
|
52
|
+
// Arrange & Act
|
|
53
|
+
render(<Breadcrumb.Link href="/library">Library</Breadcrumb.Link>);
|
|
54
|
+
const link = screen.getByRole("link", { name: "Library" });
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
expect(link.tagName).toBe("A");
|
|
58
|
+
expect(link).toHaveAttribute("href", "/library");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders the Page as a <span aria-current="page">', () => {
|
|
62
|
+
// Arrange & Act
|
|
63
|
+
render(<Breadcrumb.Page>Current article</Breadcrumb.Page>);
|
|
64
|
+
const page = screen.getByText("Current article");
|
|
65
|
+
|
|
66
|
+
// Assert
|
|
67
|
+
expect(page.tagName).toBe("SPAN");
|
|
68
|
+
expect(page).toHaveAttribute("aria-current", "page");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders the Separator as a decorative <li> with a default glyph", () => {
|
|
72
|
+
// Arrange & Act
|
|
73
|
+
render(
|
|
74
|
+
<Breadcrumb.Root>
|
|
75
|
+
<Breadcrumb.List>
|
|
76
|
+
<Breadcrumb.Separator data-testid="sep" />
|
|
77
|
+
</Breadcrumb.List>
|
|
78
|
+
</Breadcrumb.Root>,
|
|
79
|
+
);
|
|
80
|
+
const separator = screen.getByTestId("sep");
|
|
81
|
+
|
|
82
|
+
// Assert — presentation role keeps it out of the list semantics
|
|
83
|
+
expect(separator.tagName).toBe("LI");
|
|
84
|
+
expect(separator).toHaveAttribute("role", "presentation");
|
|
85
|
+
expect(separator).toHaveAttribute("aria-hidden", "true");
|
|
86
|
+
expect(separator).toHaveTextContent("/");
|
|
87
|
+
expect(screen.queryByRole("listitem")).not.toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("lets the consumer override the Separator glyph", () => {
|
|
91
|
+
// Arrange & Act
|
|
92
|
+
render(
|
|
93
|
+
<Breadcrumb.Root>
|
|
94
|
+
<Breadcrumb.List>
|
|
95
|
+
<Breadcrumb.Separator data-testid="sep">›</Breadcrumb.Separator>
|
|
96
|
+
</Breadcrumb.List>
|
|
97
|
+
</Breadcrumb.Root>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Assert
|
|
101
|
+
expect(screen.getByTestId("sep")).toHaveTextContent("›");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("composes a full trail of links, separators, and the current page", () => {
|
|
105
|
+
// Arrange & Act
|
|
106
|
+
render(
|
|
107
|
+
<Breadcrumb.Root>
|
|
108
|
+
<Breadcrumb.List>
|
|
109
|
+
<Breadcrumb.Item>
|
|
110
|
+
<Breadcrumb.Link href="/">Home</Breadcrumb.Link>
|
|
111
|
+
</Breadcrumb.Item>
|
|
112
|
+
<Breadcrumb.Separator />
|
|
113
|
+
<Breadcrumb.Item>
|
|
114
|
+
<Breadcrumb.Link href="/library">Library</Breadcrumb.Link>
|
|
115
|
+
</Breadcrumb.Item>
|
|
116
|
+
<Breadcrumb.Separator />
|
|
117
|
+
<Breadcrumb.Item>
|
|
118
|
+
<Breadcrumb.Page>Current article</Breadcrumb.Page>
|
|
119
|
+
</Breadcrumb.Item>
|
|
120
|
+
</Breadcrumb.List>
|
|
121
|
+
</Breadcrumb.Root>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Assert — two links, three list items, current page marked
|
|
125
|
+
expect(screen.getAllByRole("link")).toHaveLength(2);
|
|
126
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(3);
|
|
127
|
+
expect(screen.getByText("Current article")).toHaveAttribute(
|
|
128
|
+
"aria-current",
|
|
129
|
+
"page",
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
|
|
3
|
+
/** Props for {@link Breadcrumb.Root} — all `<nav>` attributes. */
|
|
4
|
+
export type BreadcrumbRootProps = ComponentProps<"nav">;
|
|
5
|
+
|
|
6
|
+
/** Props for {@link Breadcrumb.List} — all `<ol>` attributes. */
|
|
7
|
+
export type BreadcrumbListProps = ComponentProps<"ol">;
|
|
8
|
+
|
|
9
|
+
/** Props for {@link Breadcrumb.Item} — all `<li>` attributes. */
|
|
10
|
+
export type BreadcrumbItemProps = ComponentProps<"li">;
|
|
11
|
+
|
|
12
|
+
/** Props for {@link Breadcrumb.Link} — all `<a>` attributes plus `asChild`. */
|
|
13
|
+
export type BreadcrumbLinkProps = ComponentProps<"a"> & {
|
|
14
|
+
/** Render the consumer's own element instead of the native `<a>`. */
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Props for {@link Breadcrumb.Page} — all `<span>` attributes. */
|
|
19
|
+
export type BreadcrumbPageProps = ComponentProps<"span">;
|
|
20
|
+
|
|
21
|
+
/** Props for {@link Breadcrumb.Separator} — all `<li>` attributes. */
|
|
22
|
+
export type BreadcrumbSeparatorProps = ComponentProps<"li">;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Slot } from "../Slot";
|
|
2
|
+
import { ButtonProps } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A headless, accessible `<button>` element.
|
|
6
|
+
*
|
|
7
|
+
* Renders a native `<button type="button">` by default, preventing
|
|
8
|
+
* accidental form submission. The `type` prop can override this to
|
|
9
|
+
* `"submit"` or `"reset"` when the button lives inside a form.
|
|
10
|
+
*
|
|
11
|
+
* **Ref forwarding.** Pass a `ref` prop to access the underlying
|
|
12
|
+
* `HTMLButtonElement` directly:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const ref = useRef<HTMLButtonElement>(null);
|
|
16
|
+
* <Button ref={ref}>Click me</Button>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* **Disabled.** Sets native `disabled` (removing the button from the
|
|
20
|
+
* tab order and suppressing clicks) plus `data-disabled=""` so CSS can
|
|
21
|
+
* target `[data-disabled]` without relying on the `:disabled` pseudo-class:
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <Button disabled>Save</Button>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ```css
|
|
28
|
+
* button[data-disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* **`asChild` composition.** Renders the consumer's element instead of
|
|
32
|
+
* `<button>`, merging all props (aria-*, data-*, event handlers, ref)
|
|
33
|
+
* via the {@link Slot} utility. Event handlers compose with the child's
|
|
34
|
+
* own handlers (child runs first). `type` is **not** forwarded in this
|
|
35
|
+
* mode — the child element owns its own type semantics.
|
|
36
|
+
*
|
|
37
|
+
* **Icon-only buttons.** Provide `aria-label` or `aria-labelledby`
|
|
38
|
+
* when the button has no visible text label. Mark decorative icons
|
|
39
|
+
* `aria-hidden="true"`:
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <Button aria-label="Close dialog">
|
|
43
|
+
* <CloseIcon aria-hidden="true" />
|
|
44
|
+
* </Button>
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example Text button
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <Button onClick={handleSave}>Save</Button>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example Submit button inside a form
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <Button type="submit">Send</Button>
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @example Disabled
|
|
58
|
+
* ```tsx
|
|
59
|
+
* <Button disabled>Save</Button>
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example asChild — render a router link styled as a button
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <Button asChild>
|
|
65
|
+
* <a href="/settings">Settings</a>
|
|
66
|
+
* </Button>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function Button({
|
|
70
|
+
asChild = false,
|
|
71
|
+
type = "button",
|
|
72
|
+
disabled,
|
|
73
|
+
children,
|
|
74
|
+
ref,
|
|
75
|
+
...rest
|
|
76
|
+
}: ButtonProps) {
|
|
77
|
+
const rootProps = {
|
|
78
|
+
...rest,
|
|
79
|
+
ref,
|
|
80
|
+
disabled,
|
|
81
|
+
"data-disabled": disabled ? "" : undefined,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (asChild) {
|
|
85
|
+
return <Slot {...rootProps}>{children}</Slot>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<button type={type} {...rootProps}>
|
|
90
|
+
{children}
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Button.displayName = "Button";
|