@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,117 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { Checkbox } from "../Checkbox";
|
|
5
|
+
|
|
6
|
+
describe("Checkbox.Indicator", () => {
|
|
7
|
+
it("does not render its children when the checkbox is unchecked", () => {
|
|
8
|
+
// Arrange & Act
|
|
9
|
+
render(
|
|
10
|
+
<Checkbox.Root aria-label="Accept terms">
|
|
11
|
+
<Checkbox.Indicator>tick</Checkbox.Indicator>
|
|
12
|
+
</Checkbox.Root>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// Assert
|
|
16
|
+
expect(screen.queryByText("tick")).not.toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("renders its children when the checkbox is checked", () => {
|
|
20
|
+
// Arrange & Act
|
|
21
|
+
render(
|
|
22
|
+
<Checkbox.Root defaultChecked aria-label="Accept terms">
|
|
23
|
+
<Checkbox.Indicator>tick</Checkbox.Indicator>
|
|
24
|
+
</Checkbox.Root>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Assert
|
|
28
|
+
expect(screen.getByText("tick")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("renders its children when the checkbox is indeterminate", () => {
|
|
32
|
+
// Arrange & Act
|
|
33
|
+
render(
|
|
34
|
+
<Checkbox.Root defaultChecked="indeterminate" aria-label="Accept terms">
|
|
35
|
+
<Checkbox.Indicator>tick</Checkbox.Indicator>
|
|
36
|
+
</Checkbox.Root>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(screen.getByText("tick")).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("mounts and unmounts in response to toggling", async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
render(
|
|
47
|
+
<Checkbox.Root aria-label="Accept terms">
|
|
48
|
+
<Checkbox.Indicator>tick</Checkbox.Indicator>
|
|
49
|
+
</Checkbox.Root>,
|
|
50
|
+
);
|
|
51
|
+
expect(screen.queryByText("tick")).not.toBeInTheDocument();
|
|
52
|
+
|
|
53
|
+
// Act
|
|
54
|
+
await user.click(screen.getByRole("checkbox", { name: "Accept terms" }));
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
expect(screen.getByText("tick")).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('carries aria-hidden="true" since it is decorative', () => {
|
|
61
|
+
// Arrange & Act
|
|
62
|
+
render(
|
|
63
|
+
<Checkbox.Root defaultChecked aria-label="Accept terms">
|
|
64
|
+
<Checkbox.Indicator data-testid="indicator">tick</Checkbox.Indicator>
|
|
65
|
+
</Checkbox.Root>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Assert
|
|
69
|
+
expect(screen.getByTestId("indicator")).toHaveAttribute(
|
|
70
|
+
"aria-hidden",
|
|
71
|
+
"true",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("mirrors the checkbox's data-state on the indicator", () => {
|
|
76
|
+
// Arrange & Act
|
|
77
|
+
render(
|
|
78
|
+
<Checkbox.Root defaultChecked="indeterminate" aria-label="Accept terms">
|
|
79
|
+
<Checkbox.Indicator data-testid="indicator">tick</Checkbox.Indicator>
|
|
80
|
+
</Checkbox.Root>,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Assert
|
|
84
|
+
expect(screen.getByTestId("indicator")).toHaveAttribute(
|
|
85
|
+
"data-state",
|
|
86
|
+
"indeterminate",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("stays in the DOM while unchecked when forceMount is set", () => {
|
|
91
|
+
// Arrange & Act
|
|
92
|
+
render(
|
|
93
|
+
<Checkbox.Root aria-label="Accept terms">
|
|
94
|
+
<Checkbox.Indicator forceMount data-testid="indicator">
|
|
95
|
+
tick
|
|
96
|
+
</Checkbox.Indicator>
|
|
97
|
+
</Checkbox.Root>,
|
|
98
|
+
);
|
|
99
|
+
const indicator = screen.getByTestId("indicator");
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
expect(indicator).toBeInTheDocument();
|
|
103
|
+
expect(indicator).toHaveAttribute("data-state", "unchecked");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("throws when rendered outside Checkbox.Root", () => {
|
|
107
|
+
// Arrange
|
|
108
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect(() =>
|
|
112
|
+
render(<Checkbox.Indicator>tick</Checkbox.Indicator>),
|
|
113
|
+
).toThrow();
|
|
114
|
+
|
|
115
|
+
error.mockRestore();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { Checkbox } from "../Checkbox";
|
|
5
|
+
|
|
6
|
+
describe("Checkbox uncontrolled state", () => {
|
|
7
|
+
it("starts checked when defaultChecked is true", () => {
|
|
8
|
+
// Arrange & Act
|
|
9
|
+
render(<Checkbox.Root defaultChecked aria-label="Accept terms" />);
|
|
10
|
+
const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
|
|
11
|
+
|
|
12
|
+
// Assert
|
|
13
|
+
expect(checkbox).toHaveAttribute("aria-checked", "true");
|
|
14
|
+
expect(checkbox).toHaveAttribute("data-state", "checked");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("toggles on click from unchecked to checked", async () => {
|
|
18
|
+
// Arrange
|
|
19
|
+
const user = userEvent.setup();
|
|
20
|
+
render(<Checkbox.Root aria-label="Accept terms" />);
|
|
21
|
+
const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
|
|
22
|
+
|
|
23
|
+
// Act
|
|
24
|
+
await user.click(checkbox);
|
|
25
|
+
|
|
26
|
+
// Assert
|
|
27
|
+
expect(checkbox).toHaveAttribute("aria-checked", "true");
|
|
28
|
+
expect(checkbox).toHaveAttribute("data-state", "checked");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("toggles on click from checked to unchecked", async () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const user = userEvent.setup();
|
|
34
|
+
render(<Checkbox.Root defaultChecked aria-label="Accept terms" />);
|
|
35
|
+
const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
|
|
36
|
+
// Precondition: defaultChecked really did initialise as checked.
|
|
37
|
+
expect(checkbox).toHaveAttribute("aria-checked", "true");
|
|
38
|
+
|
|
39
|
+
// Act
|
|
40
|
+
await user.click(checkbox);
|
|
41
|
+
|
|
42
|
+
// Assert
|
|
43
|
+
expect(checkbox).toHaveAttribute("aria-checked", "false");
|
|
44
|
+
expect(checkbox).toHaveAttribute("data-state", "unchecked");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("calls onCheckedChange with the new boolean value", async () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
const user = userEvent.setup();
|
|
50
|
+
const onCheckedChange = vi.fn();
|
|
51
|
+
render(
|
|
52
|
+
<Checkbox.Root
|
|
53
|
+
onCheckedChange={onCheckedChange}
|
|
54
|
+
aria-label="Accept terms"
|
|
55
|
+
/>,
|
|
56
|
+
);
|
|
57
|
+
const checkbox = screen.getByRole("checkbox", { name: "Accept terms" });
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
await user.click(checkbox);
|
|
61
|
+
await user.click(checkbox);
|
|
62
|
+
|
|
63
|
+
// Assert
|
|
64
|
+
expect(onCheckedChange).toHaveBeenNthCalledWith(1, true);
|
|
65
|
+
expect(onCheckedChange).toHaveBeenNthCalledWith(2, false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("composes the consumer's onClick with the internal toggle (consumer runs first)", async () => {
|
|
69
|
+
// Arrange
|
|
70
|
+
const user = userEvent.setup();
|
|
71
|
+
const order: string[] = [];
|
|
72
|
+
const onClick = vi.fn(() => order.push("consumer"));
|
|
73
|
+
const onCheckedChange = vi.fn(() => order.push("internal"));
|
|
74
|
+
render(
|
|
75
|
+
<Checkbox.Root
|
|
76
|
+
onClick={onClick}
|
|
77
|
+
onCheckedChange={onCheckedChange}
|
|
78
|
+
aria-label="Accept terms"
|
|
79
|
+
/>,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Act
|
|
83
|
+
await user.click(screen.getByRole("checkbox", { name: "Accept terms" }));
|
|
84
|
+
|
|
85
|
+
// Assert
|
|
86
|
+
expect(order).toEqual(["consumer", "internal"]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useCheckboxContext } from "../CheckboxContext";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
import { useControllableState } from "../../hooks";
|
|
4
|
+
|
|
5
|
+
import { CheckedState } from "../types";
|
|
6
|
+
|
|
7
|
+
type UseCheckboxRootArgs = {
|
|
8
|
+
defaultChecked?: CheckedState;
|
|
9
|
+
checked?: CheckedState;
|
|
10
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function useCheckboxRoot({
|
|
14
|
+
defaultChecked,
|
|
15
|
+
checked: controlledChecked,
|
|
16
|
+
onCheckedChange,
|
|
17
|
+
}: UseCheckboxRootArgs) {
|
|
18
|
+
const [checked, setChecked] = useControllableState<CheckedState>(
|
|
19
|
+
controlledChecked,
|
|
20
|
+
defaultChecked ?? false,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const toggle = useCallback(() => {
|
|
24
|
+
// Indeterminate resolves to checked per the WAI-ARIA tri-state
|
|
25
|
+
// convention; boolean flips.
|
|
26
|
+
const next = checked === "indeterminate" ? true : !checked;
|
|
27
|
+
setChecked(next);
|
|
28
|
+
onCheckedChange?.(next);
|
|
29
|
+
}, [checked, setChecked, onCheckedChange]);
|
|
30
|
+
|
|
31
|
+
return { checked, toggle };
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Checkbox";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ComponentProps, ReactNode, Ref } from "react";
|
|
2
|
+
|
|
3
|
+
export type CheckedState = boolean | "indeterminate";
|
|
4
|
+
|
|
5
|
+
export type CheckboxIndicatorProps = ComponentProps<"span"> & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
forceMount?: boolean;
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type CheckboxRootBaseProps = Omit<
|
|
12
|
+
ComponentProps<"button">,
|
|
13
|
+
"type" | "role" | "aria-checked" | "defaultChecked"
|
|
14
|
+
> & {
|
|
15
|
+
asChild?: boolean;
|
|
16
|
+
ref?: Ref<HTMLButtonElement>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type CheckboxRootUncontrolledProps = CheckboxRootBaseProps & {
|
|
20
|
+
defaultChecked?: CheckedState;
|
|
21
|
+
checked?: never;
|
|
22
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type CheckboxRootControlledProps = CheckboxRootBaseProps & {
|
|
26
|
+
defaultChecked?: never;
|
|
27
|
+
checked: CheckedState;
|
|
28
|
+
onCheckedChange: (checked: boolean) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type CheckboxRootProps =
|
|
32
|
+
| CheckboxRootUncontrolledProps
|
|
33
|
+
| CheckboxRootControlledProps;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
|
|
3
|
+
import { Slot, composeEventHandlers } from "../Slot";
|
|
4
|
+
|
|
5
|
+
import { CheckboxCardContext } from "./CheckboxCardContext";
|
|
6
|
+
import { useCheckboxCardContext, useCheckboxCardRoot } from "./hooks";
|
|
7
|
+
import {
|
|
8
|
+
CheckboxCardIndicatorProps,
|
|
9
|
+
CheckboxCardRootProps,
|
|
10
|
+
CheckedState,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
function dataStateOf(checked: CheckedState) {
|
|
14
|
+
if (checked === "indeterminate") return "indeterminate" as const;
|
|
15
|
+
return checked ? ("checked" as const) : ("unchecked" as const);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The root of a CheckboxCard — a native `<button role="checkbox">` whose
|
|
20
|
+
* entire card surface is the interactive area. Owns the tri-state checked
|
|
21
|
+
* value and provides {@link CheckboxCardContext | `CheckboxCardContext`} to
|
|
22
|
+
* descendant {@link CheckboxCardIndicator | `CheckboxCard.Indicator`}s.
|
|
23
|
+
*
|
|
24
|
+
* Supports two state modes, statically discriminated at the type level:
|
|
25
|
+
*
|
|
26
|
+
* - **Uncontrolled** — pass
|
|
27
|
+
* {@link CheckboxCardRootProps.defaultChecked | `defaultChecked`} (or omit
|
|
28
|
+
* for unchecked-on-mount). The component owns the value internally.
|
|
29
|
+
* - **Controlled** — pass
|
|
30
|
+
* {@link CheckboxCardRootProps.checked | `checked`} *and*
|
|
31
|
+
* {@link CheckboxCardRootProps.onCheckedChange | `onCheckedChange`}
|
|
32
|
+
* together. The parent owns the value.
|
|
33
|
+
*
|
|
34
|
+
* Both `checked` and `defaultChecked` accept `boolean | "indeterminate"`.
|
|
35
|
+
* Clicking an indeterminate card resolves it to `true` per the WAI-ARIA
|
|
36
|
+
* tri-state convention, then flips boolean on subsequent clicks.
|
|
37
|
+
*
|
|
38
|
+
* **ARIA.** `role="checkbox"` and `aria-checked` are set automatically;
|
|
39
|
+
* `aria-checked="mixed"` represents the indeterminate state.
|
|
40
|
+
*
|
|
41
|
+
* **Styling hooks.** `data-state="checked" | "unchecked" | "indeterminate"`
|
|
42
|
+
* on the root, plus `data-disabled=""` when disabled.
|
|
43
|
+
*
|
|
44
|
+
* **`asChild` prop.** Pass `asChild` to render any consumer-supplied element
|
|
45
|
+
* with the checkbox's ARIA attributes, data-state, composed onClick, and ref
|
|
46
|
+
* merged in.
|
|
47
|
+
*
|
|
48
|
+
* @example Uncontrolled
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <CheckboxCard.Root defaultChecked aria-label="Enable feature">
|
|
51
|
+
* <CheckboxCard.Indicator>
|
|
52
|
+
* <CheckIcon />
|
|
53
|
+
* </CheckboxCard.Indicator>
|
|
54
|
+
* <h3>Feature name</h3>
|
|
55
|
+
* <p>Feature description</p>
|
|
56
|
+
* </CheckboxCard.Root>
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example Controlled
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const [enabled, setEnabled] = useState<CheckedState>(false);
|
|
62
|
+
*
|
|
63
|
+
* <CheckboxCard.Root checked={enabled} onCheckedChange={setEnabled} aria-label="…">
|
|
64
|
+
* <CheckboxCard.Indicator />
|
|
65
|
+
* Feature name
|
|
66
|
+
* </CheckboxCard.Root>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
function CheckboxCardRoot(props: CheckboxCardRootProps) {
|
|
70
|
+
const {
|
|
71
|
+
defaultChecked,
|
|
72
|
+
checked,
|
|
73
|
+
onCheckedChange,
|
|
74
|
+
onClick,
|
|
75
|
+
disabled,
|
|
76
|
+
asChild = false,
|
|
77
|
+
children,
|
|
78
|
+
...rest
|
|
79
|
+
} = props;
|
|
80
|
+
const { checked: isChecked, toggle } = useCheckboxCardRoot({
|
|
81
|
+
defaultChecked,
|
|
82
|
+
checked,
|
|
83
|
+
onCheckedChange,
|
|
84
|
+
});
|
|
85
|
+
const contextValue = useMemo(() => ({ checked: isChecked }), [isChecked]);
|
|
86
|
+
const rootProps = {
|
|
87
|
+
...rest,
|
|
88
|
+
role: "checkbox" as const,
|
|
89
|
+
"aria-checked":
|
|
90
|
+
isChecked === "indeterminate"
|
|
91
|
+
? ("mixed" as const)
|
|
92
|
+
: (isChecked as boolean),
|
|
93
|
+
"data-state": dataStateOf(isChecked),
|
|
94
|
+
"data-disabled": disabled ? "" : undefined,
|
|
95
|
+
disabled,
|
|
96
|
+
onClick: composeEventHandlers(onClick, toggle),
|
|
97
|
+
};
|
|
98
|
+
return (
|
|
99
|
+
<CheckboxCardContext.Provider value={contextValue}>
|
|
100
|
+
{asChild ? (
|
|
101
|
+
<Slot {...rootProps}>{children}</Slot>
|
|
102
|
+
) : (
|
|
103
|
+
<button type="button" {...rootProps}>
|
|
104
|
+
{children}
|
|
105
|
+
</button>
|
|
106
|
+
)}
|
|
107
|
+
</CheckboxCardContext.Provider>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
CheckboxCardRoot.displayName = "CheckboxCardRoot";
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A decorative `<span aria-hidden="true">` that renders its children only
|
|
115
|
+
* while the parent {@link CheckboxCardRoot | `CheckboxCard.Root`} is
|
|
116
|
+
* **checked** or **indeterminate** — never when unchecked. The checkbox's
|
|
117
|
+
* accessible state is already conveyed by `aria-checked` on the root, so
|
|
118
|
+
* the indicator is purely visual.
|
|
119
|
+
*
|
|
120
|
+
* **Styling hook.** Mirrors the root's
|
|
121
|
+
* `data-state="checked" | "unchecked" | "indeterminate"`.
|
|
122
|
+
*
|
|
123
|
+
* **`asChild` prop.** Pass `asChild` to render the consumer's own element
|
|
124
|
+
* (typically an `<svg>` tick icon) as the indicator itself.
|
|
125
|
+
*
|
|
126
|
+
* **`forceMount` prop.** Pass `forceMount` to keep the indicator in the DOM
|
|
127
|
+
* while unchecked so a CSS exit animation can play against
|
|
128
|
+
* `data-state="unchecked"`.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```tsx
|
|
132
|
+
* <CheckboxCard.Indicator>
|
|
133
|
+
* <CheckIcon />
|
|
134
|
+
* </CheckboxCard.Indicator>
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @throws if rendered outside a `CheckboxCard.Root`.
|
|
138
|
+
*/
|
|
139
|
+
function CheckboxCardIndicator({
|
|
140
|
+
children,
|
|
141
|
+
forceMount,
|
|
142
|
+
asChild = false,
|
|
143
|
+
...rest
|
|
144
|
+
}: CheckboxCardIndicatorProps) {
|
|
145
|
+
const { checked } = useCheckboxCardContext();
|
|
146
|
+
const isVisible = checked !== false;
|
|
147
|
+
if (!isVisible && !forceMount) return null;
|
|
148
|
+
const indicatorProps = {
|
|
149
|
+
...rest,
|
|
150
|
+
"aria-hidden": "true" as const,
|
|
151
|
+
"data-state": dataStateOf(checked),
|
|
152
|
+
};
|
|
153
|
+
if (asChild) {
|
|
154
|
+
return <Slot {...indicatorProps}>{children}</Slot>;
|
|
155
|
+
}
|
|
156
|
+
return <span {...indicatorProps}>{children}</span>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
CheckboxCardIndicator.displayName = "CheckboxCardIndicator";
|
|
160
|
+
|
|
161
|
+
type TCheckboxCardCompound = typeof CheckboxCardRoot & {
|
|
162
|
+
Root: typeof CheckboxCardRoot;
|
|
163
|
+
Indicator: typeof CheckboxCardIndicator;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Headless, accessible **CheckboxCard** — a card/tile-shaped checkbox
|
|
168
|
+
* implementing the
|
|
169
|
+
* [WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
|
|
170
|
+
* including the tri-state ("mixed") variant. The entire card surface is the
|
|
171
|
+
* interactive element. Zero styles ship.
|
|
172
|
+
*
|
|
173
|
+
* `CheckboxCard` is both callable (an alias of
|
|
174
|
+
* {@link CheckboxCardRoot | `CheckboxCard.Root`}) and carries its
|
|
175
|
+
* sub-components as static properties.
|
|
176
|
+
*
|
|
177
|
+
* - {@link CheckboxCardRoot | `CheckboxCard.Root`} — state owner, context
|
|
178
|
+
* provider, toggle button.
|
|
179
|
+
* - {@link CheckboxCardIndicator | `CheckboxCard.Indicator`} — decorative
|
|
180
|
+
* tick, conditional on checked state.
|
|
181
|
+
*
|
|
182
|
+
* @example Minimal usage
|
|
183
|
+
* ```tsx
|
|
184
|
+
* import { CheckboxCard } from "@primitiv-ui/react";
|
|
185
|
+
*
|
|
186
|
+
* <CheckboxCard.Root aria-label="Enable feature">
|
|
187
|
+
* <CheckboxCard.Indicator>
|
|
188
|
+
* <CheckIcon />
|
|
189
|
+
* </CheckboxCard.Indicator>
|
|
190
|
+
* <h3>Feature name</h3>
|
|
191
|
+
* <p>Enable this to unlock advanced capabilities.</p>
|
|
192
|
+
* </CheckboxCard.Root>
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* @see {@link CheckboxCardRoot} for state modes and tri-state semantics.
|
|
196
|
+
* @see {@link CheckboxCardIndicator} for the mount gate and animation hooks.
|
|
197
|
+
*/
|
|
198
|
+
const CheckboxCardCompound: TCheckboxCardCompound = Object.assign(
|
|
199
|
+
CheckboxCardRoot,
|
|
200
|
+
{
|
|
201
|
+
Root: CheckboxCardRoot,
|
|
202
|
+
Indicator: CheckboxCardIndicator,
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
CheckboxCardCompound.displayName = "CheckboxCard";
|
|
207
|
+
|
|
208
|
+
export { CheckboxCardCompound as CheckboxCard };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createStrictContext } from "../utils";
|
|
2
|
+
|
|
3
|
+
import { CheckedState } from "./types";
|
|
4
|
+
|
|
5
|
+
export type CheckboxCardContextValue = {
|
|
6
|
+
checked: CheckedState;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const [CheckboxCardContext, useCheckboxCardContext] =
|
|
10
|
+
createStrictContext<CheckboxCardContextValue>(
|
|
11
|
+
"CheckboxCard sub-components must be rendered inside a <CheckboxCard.Root>.",
|
|
12
|
+
);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# CheckboxCard
|
|
2
|
+
|
|
3
|
+
Headless, accessible **CheckboxCard** — a card/tile-shaped checkbox implementing
|
|
4
|
+
the
|
|
5
|
+
[WAI-ARIA Checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/)
|
|
6
|
+
including the tri-state ("mixed") variant. The entire card surface is the
|
|
7
|
+
interactive element. Zero styles ship.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { CheckboxCard } from "@primitiv-ui/react";
|
|
11
|
+
|
|
12
|
+
<CheckboxCard.Root aria-label="Enable dark mode">
|
|
13
|
+
<CheckboxCard.Indicator>
|
|
14
|
+
<CheckIcon />
|
|
15
|
+
</CheckboxCard.Indicator>
|
|
16
|
+
<h3>Dark mode</h3>
|
|
17
|
+
<p>Switch the interface to a dark colour scheme.</p>
|
|
18
|
+
</CheckboxCard.Root>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Sub-components
|
|
22
|
+
|
|
23
|
+
| Export | Element | ARIA / data hooks | `asChild` |
|
|
24
|
+
|--------|---------|------------------|-----------|
|
|
25
|
+
| `CheckboxCard.Root` | `<button>` | `role="checkbox"`, `aria-checked`, `data-state`, `data-disabled` | yes |
|
|
26
|
+
| `CheckboxCard.Indicator` | `<span>` | `aria-hidden="true"`, `data-state` | yes |
|
|
27
|
+
|
|
28
|
+
## State modes
|
|
29
|
+
|
|
30
|
+
### Uncontrolled
|
|
31
|
+
|
|
32
|
+
Pass `defaultChecked` (or omit for unchecked on mount). The component owns
|
|
33
|
+
the value internally.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<CheckboxCard.Root defaultChecked aria-label="Enable dark mode">
|
|
37
|
+
<CheckboxCard.Indicator />
|
|
38
|
+
Dark mode
|
|
39
|
+
</CheckboxCard.Root>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Controlled
|
|
43
|
+
|
|
44
|
+
Pass `checked` and `onCheckedChange` together. The parent owns the value.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
const [enabled, setEnabled] = useState<CheckedState>(false);
|
|
48
|
+
|
|
49
|
+
<CheckboxCard.Root checked={enabled} onCheckedChange={setEnabled} aria-label="…">
|
|
50
|
+
<CheckboxCard.Indicator />
|
|
51
|
+
Dark mode
|
|
52
|
+
</CheckboxCard.Root>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Tri-state (indeterminate)
|
|
56
|
+
|
|
57
|
+
Both `checked` and `defaultChecked` accept `boolean | "indeterminate"`.
|
|
58
|
+
`aria-checked="mixed"` and `data-state="indeterminate"` are set automatically.
|
|
59
|
+
Clicking an indeterminate card resolves it to `true` per the WAI-ARIA tri-state
|
|
60
|
+
convention, then flips boolean on subsequent clicks.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<CheckboxCard.Root defaultChecked="indeterminate" aria-label="Select all">
|
|
64
|
+
<CheckboxCard.Indicator />
|
|
65
|
+
Select all items
|
|
66
|
+
</CheckboxCard.Root>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Keyboard interaction
|
|
70
|
+
|
|
71
|
+
Clicking or pressing `Space` / `Enter` on the focused card toggles its state
|
|
72
|
+
(native `<button>` behaviour).
|
|
73
|
+
|
|
74
|
+
## Disabled
|
|
75
|
+
|
|
76
|
+
Pass `disabled` on the Root. The native attribute suppresses clicks, removes
|
|
77
|
+
the card from the focus ring, and `data-disabled=""` is set for CSS targeting.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<CheckboxCard.Root aria-label="Premium feature" disabled>
|
|
81
|
+
<CheckboxCard.Indicator />
|
|
82
|
+
Premium feature
|
|
83
|
+
</CheckboxCard.Root>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## `asChild` composition
|
|
87
|
+
|
|
88
|
+
Both sub-components accept `asChild`. The library's ARIA attributes,
|
|
89
|
+
`data-state`, event handlers, and ref are merged onto the consumer's element.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<CheckboxCard.Root asChild aria-label="Enable feature">
|
|
93
|
+
<li>Feature name</li>
|
|
94
|
+
</CheckboxCard.Root>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Indicator animation hooks
|
|
98
|
+
|
|
99
|
+
`CheckboxCard.Indicator` mounts only while the card is checked or indeterminate.
|
|
100
|
+
Pass `forceMount` to keep it in the DOM — `data-state="unchecked"` lets a CSS
|
|
101
|
+
exit animation play.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<CheckboxCard.Indicator forceMount>
|
|
105
|
+
<CheckIcon />
|
|
106
|
+
</CheckboxCard.Indicator>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Styling hooks
|
|
110
|
+
|
|
111
|
+
| Attribute | Values | Set on |
|
|
112
|
+
|-----------|--------|--------|
|
|
113
|
+
| `data-state` | `"checked"` \| `"unchecked"` \| `"indeterminate"` | `CheckboxCard.Root`, `CheckboxCard.Indicator` |
|
|
114
|
+
| `data-disabled` | `""` (present when disabled) | `CheckboxCard.Root` |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { CheckboxCard } from "../CheckboxCard";
|
|
5
|
+
|
|
6
|
+
describe("CheckboxCard asChild", () => {
|
|
7
|
+
it("renders the Root as the consumer element when asChild is set", () => {
|
|
8
|
+
// Arrange & Act
|
|
9
|
+
render(
|
|
10
|
+
<CheckboxCard.Root asChild aria-label="Enable feature">
|
|
11
|
+
<div>Feature card</div>
|
|
12
|
+
</CheckboxCard.Root>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
// Assert — div rendered, not button; role and aria-checked merged
|
|
16
|
+
const card = screen.getByRole("checkbox", { name: "Enable feature" });
|
|
17
|
+
expect(card.tagName).toBe("DIV");
|
|
18
|
+
expect(card).toHaveAttribute("aria-checked", "false");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("merges onClick onto the asChild Root element", async () => {
|
|
22
|
+
// Arrange
|
|
23
|
+
const user = userEvent.setup();
|
|
24
|
+
const onClick = vi.fn();
|
|
25
|
+
render(
|
|
26
|
+
<CheckboxCard.Root asChild aria-label="Enable feature" onClick={onClick}>
|
|
27
|
+
<div>Feature card</div>
|
|
28
|
+
</CheckboxCard.Root>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Act
|
|
32
|
+
await user.click(screen.getByRole("checkbox", { name: "Enable feature" }));
|
|
33
|
+
|
|
34
|
+
// Assert
|
|
35
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("renders the Indicator as the consumer element when asChild is set", () => {
|
|
39
|
+
// Arrange & Act
|
|
40
|
+
render(
|
|
41
|
+
<CheckboxCard.Root aria-label="Enable feature" defaultChecked>
|
|
42
|
+
<CheckboxCard.Indicator asChild>
|
|
43
|
+
<svg data-testid="icon" viewBox="0 0 10 10" />
|
|
44
|
+
</CheckboxCard.Indicator>
|
|
45
|
+
</CheckboxCard.Root>,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Assert — svg rendered, not span; aria-hidden and data-state merged
|
|
49
|
+
const icon = screen.getByTestId("icon");
|
|
50
|
+
expect(icon.tagName).toBe("svg");
|
|
51
|
+
expect(icon).toHaveAttribute("aria-hidden", "true");
|
|
52
|
+
expect(icon).toHaveAttribute("data-state", "checked");
|
|
53
|
+
});
|
|
54
|
+
});
|