@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,151 @@
|
|
|
1
|
+
import { useRef, useMemo, useCallback, useId, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
import { useCollection, useControllableState } from "../../hooks";
|
|
4
|
+
|
|
5
|
+
import type { AccordionReadingDirection } from "../types";
|
|
6
|
+
|
|
7
|
+
type TriggerMeta = {
|
|
8
|
+
element: HTMLButtonElement;
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useAccordionRoot(
|
|
13
|
+
controlledValue: string[] | undefined,
|
|
14
|
+
defaultValue: string | (readonly string[] & string) | undefined,
|
|
15
|
+
multiple: boolean,
|
|
16
|
+
onValueChange: ((values: string[]) => void) | undefined,
|
|
17
|
+
orientation: "vertical" | "horizontal",
|
|
18
|
+
dir: AccordionReadingDirection,
|
|
19
|
+
) {
|
|
20
|
+
const accordionId = useId();
|
|
21
|
+
// updateKeysOnCleanup is false so trigger unmounts don't fire setState
|
|
22
|
+
// outside act() after a render-time validation throw.
|
|
23
|
+
const {
|
|
24
|
+
register: registerTriggerBase,
|
|
25
|
+
itemsRef: triggersRef,
|
|
26
|
+
keys: registeredTriggerItemIds,
|
|
27
|
+
} = useCollection<string, TriggerMeta>({ updateKeysOnCleanup: false });
|
|
28
|
+
const panelsRef = useRef<Set<string>>(new Set());
|
|
29
|
+
|
|
30
|
+
const registerTrigger = useCallback(
|
|
31
|
+
(
|
|
32
|
+
itemId: string,
|
|
33
|
+
element: HTMLButtonElement | null,
|
|
34
|
+
disabled = false,
|
|
35
|
+
) => {
|
|
36
|
+
registerTriggerBase(itemId, element ? { element, disabled } : null);
|
|
37
|
+
},
|
|
38
|
+
[registerTriggerBase],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const disabledItemIds = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
new Set(
|
|
44
|
+
Array.from(triggersRef.current.entries())
|
|
45
|
+
.filter(([, meta]) => meta.disabled)
|
|
46
|
+
.map(([id]) => id),
|
|
47
|
+
),
|
|
48
|
+
// registeredTriggerItemIds is a fresh array on every register call (new
|
|
49
|
+
// identity even when the keys are the same), so the memo re-runs whenever
|
|
50
|
+
// any trigger mounts, unmounts, or toggles disabled — which is exactly
|
|
51
|
+
// the trigger we want for re-deriving disabledItemIds.
|
|
52
|
+
[registeredTriggerItemIds, triggersRef],
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const focusTrigger = useCallback(
|
|
56
|
+
(itemId: string) => {
|
|
57
|
+
triggersRef.current.get(itemId)?.element.focus();
|
|
58
|
+
},
|
|
59
|
+
[triggersRef],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Like Tabs, Accordion's existing public contract is that uncontrolled
|
|
63
|
+
// mode does NOT call onValueChange — only the controlled path notifies the
|
|
64
|
+
// consumer. So we don't pass onValueChange to useControllableState; the
|
|
65
|
+
// toggleItem branch below fires it directly in controlled mode.
|
|
66
|
+
const [expandedItems, setExpandedItems, isControlled] = useControllableState<
|
|
67
|
+
Set<string>
|
|
68
|
+
>(
|
|
69
|
+
controlledValue !== undefined ? new Set(controlledValue) : undefined,
|
|
70
|
+
defaultValue !== undefined ? new Set([defaultValue]) : new Set(),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const computeNext = (prev: Set<string>, itemId: string): Set<string> => {
|
|
74
|
+
const next = new Set(prev);
|
|
75
|
+
if (next.has(itemId)) {
|
|
76
|
+
next.delete(itemId);
|
|
77
|
+
} else if (multiple) {
|
|
78
|
+
next.add(itemId);
|
|
79
|
+
} else {
|
|
80
|
+
next.clear();
|
|
81
|
+
next.add(itemId);
|
|
82
|
+
}
|
|
83
|
+
return next;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const toggleItem = useCallback(
|
|
87
|
+
(itemId: string) => {
|
|
88
|
+
const next = computeNext(expandedItems, itemId);
|
|
89
|
+
if (isControlled) {
|
|
90
|
+
onValueChange?.(Array.from(next));
|
|
91
|
+
} else {
|
|
92
|
+
setExpandedItems(next);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[expandedItems, isControlled, multiple, setExpandedItems, onValueChange],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Panels are tracked in a ref only. registerPanel does NOT call setState —
|
|
99
|
+
// the validation effect reads panelsRef.current directly. Because React 18
|
|
100
|
+
// runs all effects in a commit before flushing batched state updates, the
|
|
101
|
+
// panel effect always executes (and updates panelsRef) before the re-render
|
|
102
|
+
// triggered by trigger registration reaches the validation effect.
|
|
103
|
+
const registerPanel = useCallback((itemId: string) => {
|
|
104
|
+
panelsRef.current.add(itemId);
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
const unregisterPanel = useCallback((itemId: string) => {
|
|
108
|
+
panelsRef.current.delete(itemId);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
for (const triggerId of registeredTriggerItemIds) {
|
|
113
|
+
if (!panelsRef.current.has(triggerId)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`AccordionTrigger (item "${triggerId}") has no corresponding AccordionContent. Every AccordionItem with a Trigger must also contain an AccordionContent.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, [registeredTriggerItemIds]);
|
|
120
|
+
|
|
121
|
+
const contextValue = useMemo(
|
|
122
|
+
() => ({
|
|
123
|
+
accordionId,
|
|
124
|
+
expandedItems,
|
|
125
|
+
orientation,
|
|
126
|
+
dir,
|
|
127
|
+
toggleItem,
|
|
128
|
+
registerTrigger,
|
|
129
|
+
registeredTriggerItemIds,
|
|
130
|
+
disabledItemIds,
|
|
131
|
+
focusTrigger,
|
|
132
|
+
registerPanel,
|
|
133
|
+
unregisterPanel,
|
|
134
|
+
}),
|
|
135
|
+
[
|
|
136
|
+
accordionId,
|
|
137
|
+
expandedItems,
|
|
138
|
+
orientation,
|
|
139
|
+
dir,
|
|
140
|
+
toggleItem,
|
|
141
|
+
registerTrigger,
|
|
142
|
+
registeredTriggerItemIds,
|
|
143
|
+
disabledItemIds,
|
|
144
|
+
focusTrigger,
|
|
145
|
+
registerPanel,
|
|
146
|
+
unregisterPanel,
|
|
147
|
+
],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return { contextValue };
|
|
151
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useMemo, useRef, useEffect, MouseEvent, KeyboardEvent } from "react";
|
|
2
|
+
|
|
3
|
+
import { useRovingTabindex } from "../../hooks";
|
|
4
|
+
import { composeRefs } from "../../Slot";
|
|
5
|
+
|
|
6
|
+
import { AccordionTriggerProps } from "../types";
|
|
7
|
+
|
|
8
|
+
import { useAccordionContext } from "./useAccordionContext";
|
|
9
|
+
import { useAccordionItemContext } from "./useAccordionItemContext";
|
|
10
|
+
|
|
11
|
+
export function useAccordionTrigger({
|
|
12
|
+
ref,
|
|
13
|
+
onClick,
|
|
14
|
+
disabled,
|
|
15
|
+
asChild = false,
|
|
16
|
+
...rest
|
|
17
|
+
}: Omit<AccordionTriggerProps, "children">) {
|
|
18
|
+
const { buttonId, panelId, itemId, isExpanded } = useAccordionItemContext();
|
|
19
|
+
const {
|
|
20
|
+
toggleItem,
|
|
21
|
+
registerTrigger,
|
|
22
|
+
registeredTriggerItemIds,
|
|
23
|
+
disabledItemIds,
|
|
24
|
+
focusTrigger,
|
|
25
|
+
orientation,
|
|
26
|
+
dir,
|
|
27
|
+
} = useAccordionContext();
|
|
28
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
29
|
+
const composedRef = ref ? composeRefs(triggerRef, ref) : triggerRef;
|
|
30
|
+
|
|
31
|
+
// Register/unregister this trigger with the context. The disabled flag
|
|
32
|
+
// is now tracked in registration metadata (via useCollection's value
|
|
33
|
+
// type) instead of being read from the rendered aria-disabled attribute,
|
|
34
|
+
// which keeps Accordion consistent with RadioGroup and is the model
|
|
35
|
+
// useRovingTabindex expects.
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
registerTrigger(itemId, triggerRef.current, disabled);
|
|
38
|
+
return () => registerTrigger(itemId, null);
|
|
39
|
+
}, [itemId, disabled, registerTrigger]);
|
|
40
|
+
|
|
41
|
+
function handleClick(e: MouseEvent<HTMLButtonElement>) {
|
|
42
|
+
if (disabled) return;
|
|
43
|
+
toggleItem(itemId);
|
|
44
|
+
onClick?.(e);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Pre-filter disabled triggers out of the navigable list — Accordion's
|
|
48
|
+
// ARIA contract is that arrow keys skip past disabled triggers (unlike
|
|
49
|
+
// Tabs, which lands on disabled triggers without activating them).
|
|
50
|
+
const enabledItemIds = useMemo(
|
|
51
|
+
() => registeredTriggerItemIds.filter((id) => !disabledItemIds.has(id)),
|
|
52
|
+
[registeredTriggerItemIds, disabledItemIds],
|
|
53
|
+
);
|
|
54
|
+
const { handleKeyDown: rovingKeyDown } = useRovingTabindex<string>({
|
|
55
|
+
orientation,
|
|
56
|
+
dir,
|
|
57
|
+
navigable: enabledItemIds,
|
|
58
|
+
currentKey: itemId,
|
|
59
|
+
onNavigate: focusTrigger,
|
|
60
|
+
includeHomeEnd: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function handleKeyDown(e: KeyboardEvent<HTMLButtonElement>) {
|
|
64
|
+
// Accordion-specific: Enter / Space toggle the focused item rather than
|
|
65
|
+
// activate something else, so they're handled here before delegating
|
|
66
|
+
// arrow / Home / End to the shared hook.
|
|
67
|
+
if ((e.key === "Enter" || e.key === " ") && !disabled) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
toggleItem(itemId);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
rovingKeyDown(e);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const triggerProps = {
|
|
76
|
+
ref: composedRef,
|
|
77
|
+
"aria-expanded": isExpanded,
|
|
78
|
+
id: buttonId,
|
|
79
|
+
"aria-controls": panelId,
|
|
80
|
+
"aria-disabled": disabled,
|
|
81
|
+
"data-disabled": disabled,
|
|
82
|
+
...(asChild && disabled ? { role: "button" } : {}),
|
|
83
|
+
onClick: handleClick,
|
|
84
|
+
onKeyDown: handleKeyDown,
|
|
85
|
+
"data-state": isExpanded ? "open" : "closed",
|
|
86
|
+
...rest,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return { triggerProps };
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Accordion';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
|
+
import { HeadingLevel } from "../types";
|
|
3
|
+
|
|
4
|
+
type AccordionRootBaseProps = ComponentProps<"div"> & {
|
|
5
|
+
multiple?: boolean;
|
|
6
|
+
orientation?: "vertical" | "horizontal";
|
|
7
|
+
dir?: AccordionReadingDirection;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type AccordionRootUncontrolledProps = AccordionRootBaseProps & {
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
value?: never;
|
|
13
|
+
onValueChange?: never;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type AccordionRootControlledProps = AccordionRootBaseProps & {
|
|
17
|
+
defaultValue?: never;
|
|
18
|
+
value: string[];
|
|
19
|
+
onValueChange: (values: string[]) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AccordionReadingDirection = "ltr" | "rtl";
|
|
23
|
+
|
|
24
|
+
export type AccordionRootProps =
|
|
25
|
+
| AccordionRootUncontrolledProps
|
|
26
|
+
| AccordionRootControlledProps;
|
|
27
|
+
|
|
28
|
+
export type AccordionItemProps = ComponentProps<"div"> & {
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
value?: string; // Optional - if not provided, useId() will generate one
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type AccordionTriggerProps<
|
|
34
|
+
T extends HTMLElement = HTMLButtonElement,
|
|
35
|
+
> = Omit<ComponentProps<"button">, "disabled" | "ref"> & {
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
/** Ref to the rendered element. Defaults to `HTMLButtonElement`; when using
|
|
40
|
+
* `asChild`, specify the child's element type (e.g. `HTMLAnchorElement`). */
|
|
41
|
+
ref?: Ref<T>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type AccordionHeaderProps = ComponentProps<"h3"> & {
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
level?: HeadingLevel;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type AccordionContentProps = ComponentProps<"div"> & {
|
|
50
|
+
children: ReactNode;
|
|
51
|
+
forceMount?: boolean;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type AccordionTriggerIconProps = ComponentProps<"span"> & {
|
|
55
|
+
children: ReactNode;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type AccordionContextValue = {
|
|
59
|
+
accordionId: string;
|
|
60
|
+
expandedItems: Set<string>;
|
|
61
|
+
orientation: "vertical" | "horizontal";
|
|
62
|
+
dir: AccordionReadingDirection;
|
|
63
|
+
toggleItem: (itemId: string) => void;
|
|
64
|
+
registerTrigger: (
|
|
65
|
+
itemId: string,
|
|
66
|
+
element: HTMLButtonElement | null,
|
|
67
|
+
disabled?: boolean,
|
|
68
|
+
) => void;
|
|
69
|
+
registeredTriggerItemIds: string[];
|
|
70
|
+
disabledItemIds: Set<string>;
|
|
71
|
+
focusTrigger: (itemId: string) => void;
|
|
72
|
+
registerPanel: (itemId: string) => void;
|
|
73
|
+
unregisterPanel: (itemId: string) => void;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type AccordionItemContextValue = {
|
|
77
|
+
buttonId: string;
|
|
78
|
+
panelId: string;
|
|
79
|
+
itemId: string;
|
|
80
|
+
isExpanded: boolean;
|
|
81
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Slot } from "../Slot";
|
|
2
|
+
import { AlertProps } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An assertive live region for high-priority, time-sensitive messages.
|
|
6
|
+
*
|
|
7
|
+
* Renders a `<div role="alert">`. The `alert` role carries an implicit
|
|
8
|
+
* `aria-live="assertive"` and `aria-atomic="true"`, so assistive
|
|
9
|
+
* technology interrupts the user to announce the message as soon as it
|
|
10
|
+
* appears — use it for errors and other content the user must see now.
|
|
11
|
+
* For non-urgent updates, reach for `Status` instead.
|
|
12
|
+
*
|
|
13
|
+
* Render the `Alert` conditionally: the live region announces content
|
|
14
|
+
* that appears *after* it is already in the DOM. Mounting an `Alert`
|
|
15
|
+
* that already contains its message may not be announced reliably.
|
|
16
|
+
*
|
|
17
|
+
* **`asChild` composition.** Renders the consumer's element instead of
|
|
18
|
+
* a `<div>`, merging `role="alert"` and all other props in via the
|
|
19
|
+
* {@link Slot} utility.
|
|
20
|
+
*
|
|
21
|
+
* @example Form error
|
|
22
|
+
* ```tsx
|
|
23
|
+
* {error && <Alert>{error}</Alert>}
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example asChild — keep semantic markup
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <Alert asChild>
|
|
29
|
+
* <section>Upload failed — try again.</section>
|
|
30
|
+
* </Alert>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function Alert({ asChild = false, children, ...rest }: AlertProps) {
|
|
34
|
+
const rootProps = { role: "alert", ...rest };
|
|
35
|
+
|
|
36
|
+
if (asChild) {
|
|
37
|
+
return <Slot {...rootProps}>{children}</Slot>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return <div {...rootProps}>{children}</div>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Alert.displayName = "Alert";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Alert
|
|
2
|
+
|
|
3
|
+
An assertive live region for high-priority, time-sensitive messages,
|
|
4
|
+
implementing the
|
|
5
|
+
[WAI-ARIA `alert` role](https://www.w3.org/TR/wai-aria-1.2/#alert).
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { Alert } from "@primitiv-ui/react";
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
error && <Alert>{error}</Alert>;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Props
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Notes |
|
|
18
|
+
| ----------- | ----------------------- | ------- | -------------------------------------------------- |
|
|
19
|
+
| `asChild` | `boolean` | `false` | Render the consumer's element instead of a `<div>` |
|
|
20
|
+
| `className` | `string` | — | Applied directly to the rendered element |
|
|
21
|
+
| `...rest` | `ComponentProps<"div">` | — | All other `<div>` props, including `aria-*` |
|
|
22
|
+
|
|
23
|
+
## Behaviour
|
|
24
|
+
|
|
25
|
+
`Alert` renders a `<div role="alert">`. The `alert` role carries an
|
|
26
|
+
implicit `aria-live="assertive"` and `aria-atomic="true"`, so assistive
|
|
27
|
+
technology interrupts the user to announce the message as soon as it
|
|
28
|
+
appears. Use it for errors and other content the user must see now —
|
|
29
|
+
for non-urgent updates, reach for [`Status`](../Status/README.md).
|
|
30
|
+
|
|
31
|
+
## Announce on appearance
|
|
32
|
+
|
|
33
|
+
A live region announces content that changes *after* the region is in
|
|
34
|
+
the DOM. Render the `Alert` **conditionally** so the message appears
|
|
35
|
+
into an already-mounted tree:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
{
|
|
39
|
+
submitError && <Alert>{submitError}</Alert>;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Mounting an `Alert` that already contains its message may not be
|
|
44
|
+
announced reliably.
|
|
45
|
+
|
|
46
|
+
## asChild
|
|
47
|
+
|
|
48
|
+
Pass `asChild` to apply `role="alert"` to the consumer's own element:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<Alert asChild>
|
|
52
|
+
<section>Upload failed — try again.</section>
|
|
53
|
+
</Alert>
|
|
54
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Alert } from "..";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
describe("Alert component", () => {
|
|
5
|
+
it("should render a div with role alert containing its children", () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
render(<Alert>Your changes could not be saved</Alert>);
|
|
8
|
+
|
|
9
|
+
// Assert
|
|
10
|
+
const alert = screen.getByRole("alert");
|
|
11
|
+
expect(alert.tagName).toBe("DIV");
|
|
12
|
+
expect(alert).toHaveTextContent("Your changes could not be saved");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should render the consumer element with asChild, keeping role alert", () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
render(
|
|
18
|
+
<Alert asChild>
|
|
19
|
+
<section>Upload failed</section>
|
|
20
|
+
</Alert>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Assert
|
|
24
|
+
const alert = screen.getByRole("alert");
|
|
25
|
+
expect(alert.tagName).toBe("SECTION");
|
|
26
|
+
expect(alert).toHaveTextContent("Upload failed");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { Slot } from "../Slot";
|
|
4
|
+
|
|
5
|
+
import { AvatarContext } from "./AvatarContext";
|
|
6
|
+
import { useAvatarContext, useAvatarImage } from "./hooks";
|
|
7
|
+
import {
|
|
8
|
+
AvatarFallbackProps,
|
|
9
|
+
AvatarImageLoadingStatus,
|
|
10
|
+
AvatarImageProps,
|
|
11
|
+
AvatarRootProps,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The root of an Avatar — a `<span>` container that owns the image loading
|
|
16
|
+
* status and provides {@link AvatarContext} to a descendant
|
|
17
|
+
* {@link Avatar.Image | `Avatar.Image`} and
|
|
18
|
+
* {@link Avatar.Fallback | `Avatar.Fallback`}.
|
|
19
|
+
*
|
|
20
|
+
* **Styling hooks.** `data-status="idle" | "loading" | "loaded" | "error"`.
|
|
21
|
+
*
|
|
22
|
+
* **`asChild` prop.** Pass `asChild` to render the consumer's own element as
|
|
23
|
+
* the container, with the `data-status` hook merged in.
|
|
24
|
+
*/
|
|
25
|
+
function AvatarRoot({ asChild = false, children, ...rest }: AvatarRootProps) {
|
|
26
|
+
const [status, setStatus] = useState<AvatarImageLoadingStatus>("idle");
|
|
27
|
+
|
|
28
|
+
const contextValue = useMemo(() => ({ status, setStatus }), [status]);
|
|
29
|
+
|
|
30
|
+
const rootProps = { ...rest, "data-status": status };
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<AvatarContext.Provider value={contextValue}>
|
|
34
|
+
{asChild ? (
|
|
35
|
+
<Slot {...rootProps}>{children}</Slot>
|
|
36
|
+
) : (
|
|
37
|
+
<span {...rootProps}>{children}</span>
|
|
38
|
+
)}
|
|
39
|
+
</AvatarContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
AvatarRoot.displayName = "AvatarRoot";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The image of an Avatar — an `<img>` that reports its load lifecycle to the
|
|
47
|
+
* parent {@link Avatar.Root}.
|
|
48
|
+
*
|
|
49
|
+
* **Styling hooks.** `data-status` mirrors the root's status. The image stays
|
|
50
|
+
* mounted on error; hide a broken image with CSS, e.g.
|
|
51
|
+
* `img:not([data-status="loaded"]) { display: none }`.
|
|
52
|
+
*
|
|
53
|
+
* **`asChild` prop.** Pass `asChild` to render the consumer's own `<img>`,
|
|
54
|
+
* with the load handlers, ref, and `data-status` hook merged in.
|
|
55
|
+
*
|
|
56
|
+
* @throws if rendered outside an `Avatar.Root`.
|
|
57
|
+
*/
|
|
58
|
+
function AvatarImage({ asChild = false, children, ...rest }: AvatarImageProps) {
|
|
59
|
+
const { status, setStatus } = useAvatarContext();
|
|
60
|
+
const { ref, onLoad, onError } = useAvatarImage(setStatus);
|
|
61
|
+
|
|
62
|
+
const imageProps = {
|
|
63
|
+
...rest,
|
|
64
|
+
"data-status": status,
|
|
65
|
+
onLoad,
|
|
66
|
+
onError,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (asChild) {
|
|
70
|
+
return (
|
|
71
|
+
<Slot {...imageProps} ref={ref}>
|
|
72
|
+
{children}
|
|
73
|
+
</Slot>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return <img {...imageProps} ref={ref} />;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
AvatarImage.displayName = "AvatarImage";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The fallback of an Avatar — a `<span>` shown while the parent
|
|
84
|
+
* {@link Avatar.Root}'s image is anything other than `"loaded"` (missing,
|
|
85
|
+
* loading, or failed). Once the image loads, the fallback unmounts.
|
|
86
|
+
*
|
|
87
|
+
* Pass `delayMs` to withhold the fallback for that many milliseconds after
|
|
88
|
+
* mount, avoiding a flash of fallback content when the image loads quickly.
|
|
89
|
+
*
|
|
90
|
+
* **`asChild` prop.** Pass `asChild` to render the consumer's own element as
|
|
91
|
+
* the fallback, with the `data-status` hook merged in.
|
|
92
|
+
*
|
|
93
|
+
* @throws if rendered outside an `Avatar.Root`.
|
|
94
|
+
*/
|
|
95
|
+
function AvatarFallback({
|
|
96
|
+
delayMs,
|
|
97
|
+
asChild = false,
|
|
98
|
+
children,
|
|
99
|
+
...rest
|
|
100
|
+
}: AvatarFallbackProps) {
|
|
101
|
+
const { status } = useAvatarContext();
|
|
102
|
+
const [delayElapsed, setDelayElapsed] = useState(delayMs === undefined);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (delayMs === undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const timer = window.setTimeout(() => setDelayElapsed(true), delayMs);
|
|
109
|
+
return () => window.clearTimeout(timer);
|
|
110
|
+
}, [delayMs]);
|
|
111
|
+
|
|
112
|
+
if (status === "loaded" || !delayElapsed) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fallbackProps = { ...rest, "data-status": status };
|
|
117
|
+
|
|
118
|
+
if (asChild) {
|
|
119
|
+
return <Slot {...fallbackProps}>{children}</Slot>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return <span {...fallbackProps}>{children}</span>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
AvatarFallback.displayName = "AvatarFallback";
|
|
126
|
+
|
|
127
|
+
type TAvatarCompound = typeof AvatarRoot & {
|
|
128
|
+
Root: typeof AvatarRoot;
|
|
129
|
+
Image: typeof AvatarImage;
|
|
130
|
+
Fallback: typeof AvatarFallback;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Headless, accessible **Avatar** — a compound component for a user image
|
|
135
|
+
* with a graceful fallback. Zero styles ship.
|
|
136
|
+
*
|
|
137
|
+
* - {@link Avatar.Root | `Avatar.Root`} — container, owns loading status.
|
|
138
|
+
* - {@link Avatar.Image | `Avatar.Image`} — the `<img>`, reports its status.
|
|
139
|
+
* - {@link Avatar.Fallback | `Avatar.Fallback`} — shown until the image loads.
|
|
140
|
+
*/
|
|
141
|
+
const AvatarCompound: TAvatarCompound = Object.assign(AvatarRoot, {
|
|
142
|
+
Root: AvatarRoot,
|
|
143
|
+
Image: AvatarImage,
|
|
144
|
+
Fallback: AvatarFallback,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
AvatarCompound.displayName = "Avatar";
|
|
148
|
+
|
|
149
|
+
export { AvatarCompound as Avatar };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createStrictContext } from "../utils";
|
|
2
|
+
|
|
3
|
+
import { AvatarImageLoadingStatus } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Value shared by {@link Avatar.Root} with its descendant
|
|
7
|
+
* {@link Avatar.Image} and {@link Avatar.Fallback}.
|
|
8
|
+
*/
|
|
9
|
+
export type AvatarContextValue = {
|
|
10
|
+
/** Current image loading status, owned by the root. */
|
|
11
|
+
status: AvatarImageLoadingStatus;
|
|
12
|
+
/** Reports a new loading status up to the root. */
|
|
13
|
+
setStatus: (status: AvatarImageLoadingStatus) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const [AvatarContext, useAvatarContext] =
|
|
17
|
+
createStrictContext<AvatarContextValue>(
|
|
18
|
+
"Avatar.Image and Avatar.Fallback must be rendered inside an <Avatar.Root>.",
|
|
19
|
+
"AvatarContext",
|
|
20
|
+
);
|