@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,87 @@
|
|
|
1
|
+
# SkipNav
|
|
2
|
+
|
|
3
|
+
A "skip to main content" link and its focus target, letting keyboard and
|
|
4
|
+
screen-reader users bypass repeated navigation. Implements the
|
|
5
|
+
[WCAG 2.4.1 Bypass Blocks](https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html)
|
|
6
|
+
technique.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
import { SkipNav } from "@primitiv-ui/react";
|
|
10
|
+
|
|
11
|
+
<>
|
|
12
|
+
<SkipNav.Link>Skip to main content</SkipNav.Link>
|
|
13
|
+
<header>…site navigation…</header>
|
|
14
|
+
<SkipNav.Content>
|
|
15
|
+
<main>…</main>
|
|
16
|
+
</SkipNav.Content>
|
|
17
|
+
</>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`SkipNav.Link` and `SkipNav.Content` are stateless siblings, not nested.
|
|
21
|
+
Render the link as the very first focusable element on the page and wrap
|
|
22
|
+
the main content in the target.
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
`SkipNav.Link` is a plain in-page anchor: its `href` is a URL fragment
|
|
27
|
+
(`#primitiv-skip-nav`) pointing at the `id` of `SkipNav.Content`.
|
|
28
|
+
`SkipNav.Content` carries `tabIndex={-1}` so it is a valid focus
|
|
29
|
+
destination — following the link scrolls to it _and_ moves focus into it,
|
|
30
|
+
so the next <kbd>Tab</kbd> continues from the main content. No JavaScript
|
|
31
|
+
runs.
|
|
32
|
+
|
|
33
|
+
## Sub-components
|
|
34
|
+
|
|
35
|
+
### `SkipNav.Link`
|
|
36
|
+
|
|
37
|
+
Renders an `<a>`.
|
|
38
|
+
|
|
39
|
+
| Prop | Type | Default | Notes |
|
|
40
|
+
| ----------- | --------------------------------- | ------------------- | ------------------------------------------------ |
|
|
41
|
+
| `contentId` | `string` | `"primitiv-skip-nav"` | Id of the target; `href` is derived as `#${contentId}` |
|
|
42
|
+
| `children` | `ReactNode` | — | The visible link text |
|
|
43
|
+
| `...rest` | `AnchorHTMLAttributes` | — | All other `<a>` props, including `className` |
|
|
44
|
+
|
|
45
|
+
### `SkipNav.Content`
|
|
46
|
+
|
|
47
|
+
Renders a `<div>` with `tabIndex={-1}`.
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Notes |
|
|
50
|
+
| ---------- | --------------------- | --------------------- | ------------------------------------------------------ |
|
|
51
|
+
| `id` | `string` | `"primitiv-skip-nav"` | Must match `SkipNav.Link`'s `contentId` |
|
|
52
|
+
| `children` | `ReactNode` | — | The main page content |
|
|
53
|
+
| `...rest` | `HTMLAttributes` | — | All other `<div>` props |
|
|
54
|
+
|
|
55
|
+
## Matching a custom id
|
|
56
|
+
|
|
57
|
+
The default content id connects the pair with no configuration. To use a
|
|
58
|
+
custom id, set it on both sides:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<SkipNav.Link contentId="main">Skip to main content</SkipNav.Link>
|
|
62
|
+
<SkipNav.Content id="main">
|
|
63
|
+
<main>…</main>
|
|
64
|
+
</SkipNav.Content>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Styling hooks
|
|
68
|
+
|
|
69
|
+
No styles ship with the component. The conventional pattern keeps the link
|
|
70
|
+
visually hidden until it receives focus, so it does not disrupt the visual
|
|
71
|
+
design but is the first thing a keyboard user reaches:
|
|
72
|
+
|
|
73
|
+
```css
|
|
74
|
+
a[href="#primitiv-skip-nav"] {
|
|
75
|
+
position: absolute;
|
|
76
|
+
left: -9999px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
a[href="#primitiv-skip-nav"]:focus {
|
|
80
|
+
left: 0;
|
|
81
|
+
top: 0;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or pass a `className` and target that instead. Do **not** use
|
|
86
|
+
`display: none` or `visibility: hidden` — those remove the link from the
|
|
87
|
+
tab order, defeating its purpose.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { SkipNavContentProps, SkipNavLinkProps } from "./types";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_CONTENT_ID = "primitiv-skip-nav";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The skip link itself — an `<a>` that jumps keyboard and screen-reader
|
|
7
|
+
* users past repeated navigation straight to the main content.
|
|
8
|
+
*
|
|
9
|
+
* Place it as the very first focusable element on the page (typically the
|
|
10
|
+
* first child of `<body>`). It is a normal in-page anchor: its `href` is a
|
|
11
|
+
* URL fragment pointing at the `id` of the matching {@link SkipNavContent |
|
|
12
|
+
* `SkipNav.Content`}, so following it moves focus there with no JavaScript.
|
|
13
|
+
*
|
|
14
|
+
* **`contentId`.** The `href` is derived as `#${contentId}` and defaults to
|
|
15
|
+
* a shared id, so an unconfigured `SkipNav.Link` / `SkipNav.Content` pair
|
|
16
|
+
* works out of the box. Override `contentId` on the link only if you also
|
|
17
|
+
* set a matching `id` on the content.
|
|
18
|
+
*
|
|
19
|
+
* **Visibility.** The link ships with no styles. The conventional pattern
|
|
20
|
+
* is to keep it visually hidden until focused — see the component README
|
|
21
|
+
* for the canonical CSS.
|
|
22
|
+
*
|
|
23
|
+
* @example Default — shared content id
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <SkipNav.Link>Skip to main content</SkipNav.Link>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Custom content id
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <SkipNav.Link contentId="main">Skip to main content</SkipNav.Link>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function SkipNavLink({
|
|
34
|
+
children,
|
|
35
|
+
contentId = DEFAULT_CONTENT_ID,
|
|
36
|
+
...rest
|
|
37
|
+
}: SkipNavLinkProps) {
|
|
38
|
+
return (
|
|
39
|
+
<a href={`#${contentId}`} {...rest}>
|
|
40
|
+
{children}
|
|
41
|
+
</a>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
SkipNavLink.displayName = "SkipNavLink";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The skip target — a `<div>` placed at the start of the main content that
|
|
49
|
+
* {@link SkipNavLink | `SkipNav.Link`} jumps to.
|
|
50
|
+
*
|
|
51
|
+
* It carries `tabIndex={-1}` so it is a valid focus destination for an
|
|
52
|
+
* in-page fragment navigation: when the link is followed, the browser
|
|
53
|
+
* scrolls to this element *and* moves focus into it, so the next Tab press
|
|
54
|
+
* continues from the main content rather than the top of the page.
|
|
55
|
+
*
|
|
56
|
+
* **`id`.** Defaults to the same shared id as `SkipNav.Link`'s `contentId`.
|
|
57
|
+
* Pass a custom `id` only if you set a matching `contentId` on the link.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* <SkipNav.Content>
|
|
62
|
+
* <main>…</main>
|
|
63
|
+
* </SkipNav.Content>
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function SkipNavContent({ children, ...rest }: SkipNavContentProps) {
|
|
67
|
+
return (
|
|
68
|
+
<div id={DEFAULT_CONTENT_ID} tabIndex={-1} {...rest}>
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
SkipNavContent.displayName = "SkipNavContent";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Headless, accessible **Skip Nav** — a "skip to main content" link and its
|
|
78
|
+
* focus target, letting keyboard and screen-reader users bypass repeated
|
|
79
|
+
* navigation.
|
|
80
|
+
*
|
|
81
|
+
* It is a stateless pair of sub-components, used as siblings rather than
|
|
82
|
+
* nested:
|
|
83
|
+
*
|
|
84
|
+
* - {@link SkipNavLink | `SkipNav.Link`} — the `<a>` skip link; render it
|
|
85
|
+
* first on the page.
|
|
86
|
+
* - {@link SkipNavContent | `SkipNav.Content`} — the focusable `<div>`
|
|
87
|
+
* target wrapping the main content.
|
|
88
|
+
*
|
|
89
|
+
* The two connect by a shared content id with a sensible default, so the
|
|
90
|
+
* common case needs no configuration.
|
|
91
|
+
*
|
|
92
|
+
* @example Minimal usage
|
|
93
|
+
* ```tsx
|
|
94
|
+
* import { SkipNav } from "@primitiv-ui/react";
|
|
95
|
+
*
|
|
96
|
+
* export function App() {
|
|
97
|
+
* return (
|
|
98
|
+
* <>
|
|
99
|
+
* <SkipNav.Link>Skip to main content</SkipNav.Link>
|
|
100
|
+
* <header>…site navigation…</header>
|
|
101
|
+
* <SkipNav.Content>
|
|
102
|
+
* <main>…</main>
|
|
103
|
+
* </SkipNav.Content>
|
|
104
|
+
* </>
|
|
105
|
+
* );
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @see {@link SkipNavLink} for the `contentId` contract and the visually-hidden-until-focused CSS pattern.
|
|
110
|
+
*/
|
|
111
|
+
const SkipNav = {
|
|
112
|
+
Link: SkipNavLink,
|
|
113
|
+
Content: SkipNavContent,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { SkipNav };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SkipNav } from "../SkipNav";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
describe("SkipNav component", () => {
|
|
5
|
+
it("should render a skip link as an anchor", () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
render(<SkipNav.Link href="#main">Skip to content</SkipNav.Link>);
|
|
8
|
+
|
|
9
|
+
// Assert
|
|
10
|
+
const link = screen.getByRole("link", { name: "Skip to content" });
|
|
11
|
+
expect(link).toHaveAttribute("href", "#main");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should render Content as a programmatically focusable region", () => {
|
|
15
|
+
// Arrange
|
|
16
|
+
render(<SkipNav.Content>Main content</SkipNav.Content>);
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
const content = screen.getByText("Main content");
|
|
20
|
+
expect(content).toHaveAttribute("id", "primitiv-skip-nav");
|
|
21
|
+
expect(content).toHaveAttribute("tabindex", "-1");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SkipNav } from "..";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
describe("SkipNav id wiring", () => {
|
|
5
|
+
it("should derive SkipNav.Link href from the contentId prop", () => {
|
|
6
|
+
render(<SkipNav.Link contentId="main-content">Skip to content</SkipNav.Link>);
|
|
7
|
+
|
|
8
|
+
expect(screen.getByRole("link")).toHaveAttribute("href", "#main-content");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should default SkipNav.Link href to the shared content id", () => {
|
|
12
|
+
render(<SkipNav.Link>Skip to content</SkipNav.Link>);
|
|
13
|
+
|
|
14
|
+
expect(screen.getByRole("link")).toHaveAttribute(
|
|
15
|
+
"href",
|
|
16
|
+
"#primitiv-skip-nav",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SkipNav";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props for {@link SkipNav.Link} — `children`, an optional `contentId`,
|
|
5
|
+
* plus all `AnchorHTMLAttributes` on the rendered `<a>`.
|
|
6
|
+
*/
|
|
7
|
+
export type SkipNavLinkProps = {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Id of the matching {@link SkipNav.Content} target. The link's `href`
|
|
11
|
+
* is derived as `#${contentId}`. Defaults to a shared id, so an
|
|
12
|
+
* unconfigured `SkipNav.Link` / `SkipNav.Content` pair works as-is.
|
|
13
|
+
*/
|
|
14
|
+
contentId?: string;
|
|
15
|
+
} & AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for {@link SkipNav.Content} — `children` plus all `HTMLAttributes`
|
|
19
|
+
* on the rendered `<div>`.
|
|
20
|
+
*
|
|
21
|
+
* Pass a custom `id` only if you set a matching `contentId` on
|
|
22
|
+
* {@link SkipNav.Link}; it defaults to the same shared id.
|
|
23
|
+
*/
|
|
24
|
+
export type SkipNavContentProps = {
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Slider
|
|
2
|
+
|
|
3
|
+
Headless, accessible **Slider** — a compound component implementing the
|
|
4
|
+
[WAI-ARIA Slider pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider/)
|
|
5
|
+
and the
|
|
6
|
+
[Multi-Thumb Slider pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/).
|
|
7
|
+
The value is always an **array of numbers**, with one thumb per entry. Zero
|
|
8
|
+
styles ship.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { Slider } from "@primitiv-ui/react";
|
|
12
|
+
|
|
13
|
+
<Slider.Root defaultValue={[40]} aria-label="Volume">
|
|
14
|
+
<Slider.Track>
|
|
15
|
+
<Slider.Range />
|
|
16
|
+
</Slider.Track>
|
|
17
|
+
<Slider.Thumb />
|
|
18
|
+
</Slider.Root>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Sub-components
|
|
22
|
+
|
|
23
|
+
| Export | Element | ARIA / data hooks | `asChild` |
|
|
24
|
+
|--------|---------|-------------------|-----------|
|
|
25
|
+
| `Slider.Root` | `<span>` | `data-orientation`, `data-disabled` | yes |
|
|
26
|
+
| `Slider.Track` | `<span>` | `data-orientation`, `data-disabled` | yes |
|
|
27
|
+
| `Slider.Range` | `<span>` | `data-orientation`, `data-disabled`, inline position `style` | yes |
|
|
28
|
+
| `Slider.Thumb` | `<span>` | `role="slider"`, `aria-valuemin/max/now`, `aria-orientation`, `aria-disabled`, `data-orientation`, `data-disabled`, inline position `style` | yes |
|
|
29
|
+
|
|
30
|
+
Render **one `Slider.Thumb` per entry** in the value array, in order. A
|
|
31
|
+
single-thumb slider has one thumb; a range slider has two; any number is
|
|
32
|
+
supported.
|
|
33
|
+
|
|
34
|
+
## Root props
|
|
35
|
+
|
|
36
|
+
| Prop | Type | Default | Description |
|
|
37
|
+
|------|------|---------|-------------|
|
|
38
|
+
| `min` | `number` | `0` | Lowest value. |
|
|
39
|
+
| `max` | `number` | `100` | Highest value. Must be greater than `min`. |
|
|
40
|
+
| `step` | `number` | `1` | Granularity. Must be greater than `0`. |
|
|
41
|
+
| `minStepsBetweenThumbs` | `number` | `0` | Minimum gap (in steps) kept between adjacent thumbs. |
|
|
42
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout axis. |
|
|
43
|
+
| `dir` | `"ltr" \| "rtl"` | inherited | Reading direction (horizontal). Inherited from `DirectionProvider`, else `"ltr"`. |
|
|
44
|
+
| `inverted` | `boolean` | `false` | Reverse the direction the value increases. |
|
|
45
|
+
| `disabled` | `boolean` | `false` | Disable all interaction. |
|
|
46
|
+
| `name` | `string` | — | Render hidden inputs for form submission. |
|
|
47
|
+
| `defaultValue` / `value` | `number[]` | `[min]` | Uncontrolled / controlled value. |
|
|
48
|
+
| `onValueChange` | `(value: number[]) => void` | — | Fires on every change. |
|
|
49
|
+
| `onValueCommit` | `(value: number[]) => void` | — | Fires once when an interaction ends. |
|
|
50
|
+
|
|
51
|
+
## State modes
|
|
52
|
+
|
|
53
|
+
### Uncontrolled
|
|
54
|
+
|
|
55
|
+
Pass `defaultValue` (or omit it for a single thumb seeded at `min`). The
|
|
56
|
+
component owns the value internally.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<Slider.Root defaultValue={[25, 75]} aria-label="Price range">
|
|
60
|
+
<Slider.Track>
|
|
61
|
+
<Slider.Range />
|
|
62
|
+
</Slider.Track>
|
|
63
|
+
<Slider.Thumb />
|
|
64
|
+
<Slider.Thumb />
|
|
65
|
+
</Slider.Root>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Controlled
|
|
69
|
+
|
|
70
|
+
Pass `value` and `onValueChange` together. The parent owns the value.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
const [value, setValue] = useState([20, 80]);
|
|
74
|
+
|
|
75
|
+
<Slider.Root value={value} onValueChange={setValue}>
|
|
76
|
+
<Slider.Track>
|
|
77
|
+
<Slider.Range />
|
|
78
|
+
</Slider.Track>
|
|
79
|
+
<Slider.Thumb />
|
|
80
|
+
<Slider.Thumb />
|
|
81
|
+
</Slider.Root>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Committing the final value
|
|
85
|
+
|
|
86
|
+
`onValueChange` fires on every increment during a drag. `onValueCommit` fires
|
|
87
|
+
**once** with the settled array when the interaction ends — use it to persist
|
|
88
|
+
only the final value.
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<Slider.Root
|
|
92
|
+
defaultValue={[40]}
|
|
93
|
+
onValueCommit={(value) => save(value)}
|
|
94
|
+
>
|
|
95
|
+
…
|
|
96
|
+
</Slider.Root>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Keyboard interaction
|
|
100
|
+
|
|
101
|
+
Each thumb is independently focusable.
|
|
102
|
+
|
|
103
|
+
| Key | Behaviour |
|
|
104
|
+
|-----|-----------|
|
|
105
|
+
| `Arrow Right` / `Arrow Up` | Increase by `step` |
|
|
106
|
+
| `Arrow Left` / `Arrow Down` | Decrease by `step` |
|
|
107
|
+
| `Page Up` / `Page Down` | Increase / decrease by ten steps |
|
|
108
|
+
| `Home` / `End` | Jump to `min` / `max` |
|
|
109
|
+
|
|
110
|
+
Arrow direction follows `orientation`, `dir`, and `inverted`. A thumb cannot
|
|
111
|
+
cross its neighbours, and `minStepsBetweenThumbs` enforces a configurable gap.
|
|
112
|
+
|
|
113
|
+
## Orientation
|
|
114
|
+
|
|
115
|
+
Pass `orientation="vertical"` for a vertical slider. Pointer interaction maps
|
|
116
|
+
the Y axis to the value and thumbs are positioned from the bottom edge.
|
|
117
|
+
|
|
118
|
+
## Reading direction & inversion
|
|
119
|
+
|
|
120
|
+
`dir="rtl"` flips a horizontal slider so the value increases leftwards.
|
|
121
|
+
`inverted` reverses the direction the value increases on either axis. Both
|
|
122
|
+
affect keyboard arrows, pointer mapping, and the inline positioning styles.
|
|
123
|
+
|
|
124
|
+
When `dir` is omitted, it is inherited from the nearest
|
|
125
|
+
[`DirectionProvider`](../DirectionProvider/README.md), falling back to `"ltr"`
|
|
126
|
+
when there is no provider. An explicit `dir` prop always wins over the
|
|
127
|
+
inherited value.
|
|
128
|
+
|
|
129
|
+
## Disabled
|
|
130
|
+
|
|
131
|
+
Pass `disabled` on the Root. Every part receives `data-disabled=""`, thumbs
|
|
132
|
+
leave the tab order and gain `aria-disabled="true"`, and keyboard and pointer
|
|
133
|
+
interaction are ignored.
|
|
134
|
+
|
|
135
|
+
## Form submission
|
|
136
|
+
|
|
137
|
+
Pass `name` to render a hidden `<input>` per thumb so the values post with a
|
|
138
|
+
surrounding `<form>`. Multi-thumb sliders suffix the name with `[]`
|
|
139
|
+
(`name="range[]"`).
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<form>
|
|
143
|
+
<Slider.Root defaultValue={[10, 90]} name="price">
|
|
144
|
+
…
|
|
145
|
+
</Slider.Root>
|
|
146
|
+
</form>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## `asChild` composition
|
|
150
|
+
|
|
151
|
+
Every sub-component accepts `asChild`. The library's ARIA attributes,
|
|
152
|
+
`data-*` hooks, positioning `style`, event handlers, and ref are merged onto
|
|
153
|
+
the consumer's element.
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<Slider.Thumb asChild>
|
|
157
|
+
<MyHandle />
|
|
158
|
+
</Slider.Thumb>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Positioning
|
|
162
|
+
|
|
163
|
+
`Slider.Range` and `Slider.Thumb` ship an inline `style` placing them along
|
|
164
|
+
the track as a percentage offset. Give `Slider.Root` / `Slider.Track` a size
|
|
165
|
+
and `position: relative`; the parts position themselves absolutely. Centre a
|
|
166
|
+
thumb on its point with a `translate`.
|
|
167
|
+
|
|
168
|
+
```scss
|
|
169
|
+
.slider-root {
|
|
170
|
+
position: relative;
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
width: 12rem;
|
|
174
|
+
height: 1.25rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.slider-track {
|
|
178
|
+
position: relative;
|
|
179
|
+
flex: 1;
|
|
180
|
+
height: 0.25rem;
|
|
181
|
+
border-radius: 9999px;
|
|
182
|
+
background: #d1d5db;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.slider-range {
|
|
186
|
+
height: 100%;
|
|
187
|
+
border-radius: inherit;
|
|
188
|
+
background: #6366f1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.slider-thumb {
|
|
192
|
+
width: 1rem;
|
|
193
|
+
height: 1rem;
|
|
194
|
+
border-radius: 50%;
|
|
195
|
+
background: white;
|
|
196
|
+
box-shadow: 0 0 0 1px #6366f1;
|
|
197
|
+
translate: -50% 0;
|
|
198
|
+
|
|
199
|
+
&[data-disabled] {
|
|
200
|
+
opacity: 0.5;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Styling hooks
|
|
206
|
+
|
|
207
|
+
| Attribute | Values | Set on |
|
|
208
|
+
|-----------|--------|--------|
|
|
209
|
+
| `data-orientation` | `"horizontal"` \| `"vertical"` | every part |
|
|
210
|
+
| `data-disabled` | `""` (present when disabled) | every part |
|
|
211
|
+
|
|
212
|
+
## Validation
|
|
213
|
+
|
|
214
|
+
`Slider.Root` throws a descriptive error during render when `min` is not less
|
|
215
|
+
than `max`, or when `step` is not greater than `0`.
|