@astryxdesign/core 0.1.2 → 0.1.3-canary.6d57c8d
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/CHANGELOG.md +165 -0
- package/README.md +12 -12
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +0 -1
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +82 -43
- package/dist/Calendar/hooks/index.d.ts +0 -2
- package/dist/Calendar/hooks/index.d.ts.map +1 -1
- package/dist/Calendar/hooks/index.js +1 -2
- package/dist/Calendar/index.d.ts +3 -3
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/Calendar/index.js +1 -1
- package/dist/Calendar/styles.d.ts +3 -9
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +3 -11
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +17 -3
- package/dist/Center/Center.d.ts +11 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +9 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +0 -1
- package/dist/Chat/ChatMessageList.d.ts +14 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +2 -0
- package/dist/Chat/useTriggerMenu.d.ts +8 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +51 -9
- package/dist/CheckboxList/CheckboxList.d.ts +13 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +39 -9
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +0 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +9 -5
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +51 -37
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -5
- package/dist/ContextMenu/ContextMenu.d.ts +4 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +93 -11
- package/dist/DateInput/DateInput.d.ts +23 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +167 -99
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +42 -9
- package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +78 -15
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +42 -14
- package/dist/Field/Field.d.ts +19 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -0
- package/dist/Field/FieldLabel.d.ts +21 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +41 -26
- package/dist/FileInput/FileInput.d.ts +22 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +56 -10
- package/dist/Grid/Grid.d.ts +11 -1
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +32 -6
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +16 -6
- package/dist/InputGroup/InputGroupContext.d.ts +4 -0
- package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
- package/dist/InputGroup/InputGroupContext.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +3 -3
- package/dist/InputGroup/index.d.ts +1 -0
- package/dist/InputGroup/index.d.ts.map +1 -1
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +33 -1
- package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +8 -4
- package/dist/Layer/useLayer.d.ts +10 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +64 -9
- package/dist/Layout/Layout.d.ts +4 -11
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +10 -11
- package/dist/Layout/LayoutFooter.d.ts +2 -2
- package/dist/Layout/LayoutFooter.d.ts.map +1 -1
- package/dist/Layout/LayoutHeader.d.ts +2 -2
- package/dist/Layout/LayoutHeader.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.d.ts +2 -2
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.d.ts +51 -0
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +63 -0
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -2
- package/dist/Link/Link.d.ts +16 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +18 -7
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MoreMenu/MoreMenu.d.ts +1 -7
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +0 -2
- package/dist/MultiSelector/MultiSelector.d.ts +27 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +141 -42
- package/dist/MultiSelector/hooks.d.ts +12 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +14 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +34 -3
- package/dist/NumberInput/NumberInput.d.ts +23 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +62 -12
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +15 -1
- package/dist/Popover/Popover.d.ts +18 -2
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +5 -2
- package/dist/Popover/usePopover.d.ts +38 -3
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +24 -9
- package/dist/PowerSearch/PowerSearch.d.ts +11 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +6 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +3 -5
- package/dist/RadioList/RadioList.d.ts +20 -2
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +105 -9
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +78 -44
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
- package/dist/Selector/Selector.d.ts +24 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +122 -43
- package/dist/Selector/hooks.d.ts +8 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +14 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +2 -0
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +1 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +8 -2
- package/dist/Slider/Slider.d.ts +21 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +36 -9
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +1 -1
- package/dist/Stack/Stack.d.ts +38 -23
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +25 -1
- package/dist/Stack/StackItem.d.ts +12 -23
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +8 -1
- package/dist/Switch/Switch.d.ts +23 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +41 -11
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +6 -0
- package/dist/TabList/TabList.d.ts +16 -4
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +87 -10
- package/dist/TabList/TabListContext.d.ts +5 -0
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +5 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +11 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +10 -1
- package/dist/Table/TableCell.d.ts +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +10 -10
- package/dist/Table/TableHeaderCell.d.ts +8 -1
- package/dist/Table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Table/TableHeaderCell.js +16 -16
- package/dist/Table/index.d.ts +2 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +8 -3
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
- package/dist/Table/tableContextMenu.d.ts +33 -0
- package/dist/Table/tableContextMenu.d.ts.map +1 -0
- package/dist/Table/tableContextMenu.js +133 -0
- package/dist/Table/types.d.ts +66 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +22 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +41 -9
- package/dist/TextInput/TextInput.d.ts +23 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +55 -10
- package/dist/TimeInput/TimeInput.d.ts +23 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +147 -76
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +14 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +131 -3
- package/dist/Tokenizer/Tokenizer.d.ts +12 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +26 -4
- package/dist/Toolbar/Toolbar.d.ts +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +51 -7
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +56 -9
- package/dist/TopNav/TopNavHeading.d.ts +7 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +8 -0
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +72 -2
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +51 -12
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
- package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
- package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +69 -6
- package/dist/Typeahead/Typeahead.d.ts +26 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +65 -29
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
- package/dist/VisuallyHidden/index.d.ts +11 -0
- package/dist/VisuallyHidden/index.d.ts.map +1 -0
- package/dist/VisuallyHidden/index.js +13 -0
- package/dist/astryx.css +18 -0
- package/dist/astryx.umd.js +55 -55
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +22 -0
- package/dist/hooks/focusableSelector.d.ts.map +1 -0
- package/dist/hooks/focusableSelector.js +24 -0
- package/dist/hooks/index.d.ts +12 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +7 -2
- package/dist/hooks/useAnnounce.d.ts +40 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -0
- package/dist/hooks/useAnnounce.js +142 -0
- package/dist/hooks/useClickableContainer.d.ts +16 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +16 -4
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +4 -4
- package/dist/hooks/useFocusTrap.d.ts +18 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +87 -3
- package/dist/hooks/useGridFocus.d.ts +88 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +261 -62
- package/dist/hooks/useKeyboardHint.d.ts +82 -0
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHint.js +220 -0
- package/dist/hooks/useListFocus.d.ts +78 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +305 -52
- package/dist/hooks/useLongPress.d.ts +21 -0
- package/dist/hooks/useLongPress.d.ts.map +1 -0
- package/dist/hooks/useLongPress.js +89 -0
- package/dist/hooks/useTreeFocus.d.ts +157 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -0
- package/dist/hooks/useTreeFocus.js +337 -0
- package/dist/hooks/useTypeahead.d.ts +63 -0
- package/dist/hooks/useTypeahead.d.ts.map +1 -0
- package/dist/hooks/useTypeahead.js +117 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/inputAria.d.ts +29 -0
- package/dist/utils/inputAria.d.ts.map +1 -0
- package/dist/utils/inputAria.js +30 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -2
- package/docs.mjs +2 -2
- package/package.json +12 -8
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Button/Button.tsx +3 -16
- package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
- package/src/ButtonGroup/ButtonGroup.tsx +0 -1
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +168 -30
- package/src/Calendar/Calendar.tsx +121 -66
- package/src/Calendar/hooks/index.ts +0 -6
- package/src/Calendar/index.ts +1 -3
- package/src/Calendar/styles.ts +4 -9
- package/src/Carousel/Carousel.doc.mjs +7 -1
- package/src/Carousel/Carousel.test.tsx +20 -0
- package/src/Carousel/Carousel.tsx +16 -1
- package/src/Center/Center.doc.mjs +12 -2
- package/src/Center/Center.tsx +28 -3
- package/src/Chat/ChatComposerInput.test.tsx +26 -7
- package/src/Chat/ChatComposerInput.tsx +0 -1
- package/src/Chat/ChatDictationButton.doc.mjs +1 -1
- package/src/Chat/ChatMessageList.doc.mjs +8 -0
- package/src/Chat/ChatMessageList.test.tsx +18 -0
- package/src/Chat/ChatMessageList.tsx +16 -0
- package/src/Chat/useTriggerMenu.tsx +22 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
- package/src/CheckboxInput/CheckboxInput.tsx +78 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
- package/src/CheckboxList/CheckboxList.test.tsx +192 -26
- package/src/CheckboxList/CheckboxList.tsx +56 -3
- package/src/CheckboxList/CheckboxListContext.tsx +7 -0
- package/src/CheckboxList/CheckboxListItem.tsx +0 -3
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +121 -0
- package/src/Citation/Citation.tsx +15 -3
- package/src/CodeBlock/CodeBlock.test.tsx +115 -0
- package/src/CodeBlock/CodeBlock.tsx +63 -33
- package/src/CommandPalette/CommandPalette.test.tsx +47 -1
- package/src/CommandPalette/CommandPalette.tsx +9 -21
- package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
- package/src/ContextMenu/ContextMenu.test.tsx +155 -1
- package/src/ContextMenu/ContextMenu.tsx +113 -16
- package/src/DateInput/DateInput.doc.mjs +286 -56
- package/src/DateInput/DateInput.test.tsx +248 -2
- package/src/DateInput/DateInput.tsx +196 -100
- package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
- package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
- package/src/DateRangeInput/DateRangeInput.tsx +63 -5
- package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
- package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
- package/src/DateTimeInput/DateTimeInput.tsx +125 -17
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/Dialog/Dialog.tsx +12 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
- package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
- package/src/DropdownMenu/DropdownMenu.tsx +52 -18
- package/src/Field/Field.doc.mjs +59 -18
- package/src/Field/Field.test.tsx +32 -0
- package/src/Field/Field.tsx +23 -5
- package/src/Field/FieldLabel.tsx +55 -17
- package/src/FileInput/FileInput.doc.mjs +17 -0
- package/src/FileInput/FileInput.test.tsx +283 -49
- package/src/FileInput/FileInput.tsx +76 -4
- package/src/Grid/Grid.doc.mjs +17 -4
- package/src/Grid/Grid.test.tsx +43 -30
- package/src/Grid/Grid.tsx +38 -3
- package/src/Icon/Icon.doc.mjs +3 -3
- package/src/InputGroup/InputGroup.doc.mjs +111 -23
- package/src/InputGroup/InputGroup.test.tsx +257 -60
- package/src/InputGroup/InputGroup.tsx +23 -5
- package/src/InputGroup/InputGroupContext.ts +9 -4
- package/src/InputGroup/groupStyles.ts +7 -1
- package/src/InputGroup/index.ts +1 -0
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +35 -3
- package/src/Layer/layerAnimations.stylex.ts +20 -4
- package/src/Layer/useLayer.doc.mjs +5 -0
- package/src/Layer/useLayer.test.tsx +111 -0
- package/src/Layer/useLayer.tsx +90 -9
- package/src/Layout/Layout.tsx +8 -16
- package/src/Layout/LayoutFooter.doc.mjs +4 -4
- package/src/Layout/LayoutFooter.tsx +3 -3
- package/src/Layout/LayoutHeader.doc.mjs +4 -4
- package/src/Layout/LayoutHeader.tsx +3 -3
- package/src/Layout/LayoutPanel.tsx +3 -3
- package/src/Layout/padding.stylex.ts +52 -0
- package/src/Lightbox/Lightbox.doc.mjs +9 -0
- package/src/Lightbox/Lightbox.test.tsx +53 -5
- package/src/Lightbox/Lightbox.tsx +9 -4
- package/src/Link/Link.doc.mjs +14 -0
- package/src/Link/Link.test.tsx +50 -0
- package/src/Link/Link.tsx +39 -4
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +7 -0
- package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
- package/src/MultiSelector/MultiSelector.test.tsx +375 -8
- package/src/MultiSelector/MultiSelector.tsx +194 -48
- package/src/MultiSelector/hooks.ts +27 -0
- package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
- package/src/NavMenu/NavHeadingMenu.tsx +50 -3
- package/src/NumberInput/NumberInput.doc.mjs +17 -0
- package/src/NumberInput/NumberInput.test.tsx +242 -40
- package/src/NumberInput/NumberInput.tsx +89 -11
- package/src/Pagination/Pagination.doc.mjs +3 -3
- package/src/Pagination/Pagination.test.tsx +117 -5
- package/src/Pagination/Pagination.tsx +21 -1
- package/src/Popover/Popover.doc.mjs +26 -0
- package/src/Popover/Popover.test.tsx +121 -0
- package/src/Popover/Popover.tsx +23 -2
- package/src/Popover/usePopover.doc.mjs +9 -3
- package/src/Popover/usePopover.tsx +70 -12
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +97 -1
- package/src/PowerSearch/PowerSearch.tsx +16 -3
- package/src/ProgressBar/ProgressBar.test.tsx +34 -26
- package/src/ProgressBar/ProgressBar.tsx +3 -4
- package/src/RadioList/RadioList.doc.mjs +9 -0
- package/src/RadioList/RadioList.test.tsx +252 -16
- package/src/RadioList/RadioList.tsx +166 -10
- package/src/RadioList/RadioListItem.tsx +14 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
- package/src/SegmentedControl/SegmentedControl.tsx +102 -54
- package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.doc.mjs +26 -0
- package/src/Selector/Selector.test.tsx +387 -15
- package/src/Selector/Selector.tsx +175 -67
- package/src/Selector/hooks.ts +23 -0
- package/src/SideNav/SideNav.test.tsx +48 -0
- package/src/SideNav/SideNavHeading.tsx +8 -2
- package/src/SideNav/SideNavItem.tsx +2 -4
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Slider/Slider.doc.mjs +15 -0
- package/src/Slider/Slider.test.tsx +155 -2
- package/src/Slider/Slider.tsx +53 -5
- package/src/Spinner/Spinner.tsx +7 -1
- package/src/Stack/Stack.doc.mjs +77 -0
- package/src/Stack/Stack.test.tsx +55 -5
- package/src/Stack/Stack.tsx +73 -24
- package/src/Stack/StackItem.test.tsx +23 -0
- package/src/Stack/StackItem.tsx +20 -23
- package/src/Switch/Switch.doc.mjs +15 -0
- package/src/Switch/Switch.test.tsx +162 -27
- package/src/Switch/Switch.tsx +61 -21
- package/src/TabList/Tab.tsx +6 -0
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/TabList/TabList.test.tsx +251 -0
- package/src/TabList/TabList.tsx +103 -12
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +6 -3
- package/src/Table/BaseTable.tsx +21 -17
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.test.tsx +10 -0
- package/src/Table/Table.tsx +8 -0
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/index.ts +3 -2
- package/src/Table/plugins/pagination/paginateData.ts +10 -3
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
- package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
- package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +142 -0
- package/src/Table/types.ts +74 -0
- package/src/Table/useTableColumnResize.doc.mjs +3 -3
- package/src/Table/useTableStickyColumns.doc.mjs +2 -2
- package/src/Text/Text.doc.mjs +18 -18
- package/src/TextArea/TextArea.doc.mjs +17 -0
- package/src/TextArea/TextArea.test.tsx +167 -35
- package/src/TextArea/TextArea.tsx +62 -15
- package/src/TextInput/TextInput.doc.mjs +17 -0
- package/src/TextInput/TextInput.test.tsx +168 -34
- package/src/TextInput/TextInput.tsx +77 -9
- package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
- package/src/TimeInput/TimeInput.doc.mjs +340 -60
- package/src/TimeInput/TimeInput.test.tsx +255 -7
- package/src/TimeInput/TimeInput.tsx +176 -70
- package/src/Timestamp/Timestamp.doc.mjs +19 -19
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +214 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
- package/src/Tokenizer/Tokenizer.test.tsx +105 -1
- package/src/Tokenizer/Tokenizer.tsx +37 -1
- package/src/Toolbar/Toolbar.test.tsx +109 -6
- package/src/Toolbar/Toolbar.tsx +59 -5
- package/src/Tooltip/Tooltip.test.tsx +68 -0
- package/src/Tooltip/useTooltip.tsx +55 -8
- package/src/TopNav/TopNav.test.tsx +50 -0
- package/src/TopNav/TopNavHeading.tsx +17 -1
- package/src/TreeList/TreeList.test.tsx +295 -6
- package/src/TreeList/TreeList.tsx +73 -1
- package/src/TreeList/TreeListItem.tsx +51 -5
- package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
- package/src/Typeahead/BaseTypeahead.tsx +93 -9
- package/src/Typeahead/Typeahead.doc.mjs +141 -30
- package/src/Typeahead/Typeahead.test.tsx +331 -1
- package/src/Typeahead/Typeahead.tsx +105 -33
- package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
- package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
- package/src/VisuallyHidden/index.ts +15 -0
- package/src/docs-script.test.mjs +50 -0
- package/src/hooks/focusableSelector.ts +25 -0
- package/src/hooks/index.ts +28 -2
- package/src/hooks/useAnnounce.test.tsx +101 -0
- package/src/hooks/useAnnounce.ts +168 -0
- package/src/hooks/useClickableContainer.ts +16 -4
- package/src/hooks/useEntryAnimation.ts +16 -4
- package/src/hooks/useFocusTrap.test.tsx +132 -0
- package/src/hooks/useFocusTrap.ts +98 -4
- package/src/hooks/useGridFocus.doc.mjs +40 -2
- package/src/hooks/useGridFocus.test.tsx +132 -0
- package/src/hooks/useGridFocus.ts +343 -67
- package/src/hooks/useKeyboardHint.doc.mjs +101 -0
- package/src/hooks/useKeyboardHint.test.tsx +70 -0
- package/src/hooks/useKeyboardHint.tsx +332 -0
- package/src/hooks/useListFocus.doc.mjs +50 -10
- package/src/hooks/useListFocus.test.tsx +375 -0
- package/src/hooks/useListFocus.ts +395 -57
- package/src/hooks/useLongPress.test.tsx +134 -0
- package/src/hooks/useLongPress.ts +119 -0
- package/src/hooks/useTreeFocus.doc.mjs +140 -0
- package/src/hooks/useTreeFocus.test.tsx +214 -0
- package/src/hooks/useTreeFocus.ts +559 -0
- package/src/hooks/useTypeahead.test.tsx +109 -0
- package/src/hooks/useTypeahead.ts +183 -0
- package/src/index.ts +1 -0
- package/src/theme/derivedVarRegistry.test.ts +10 -5
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/inputAria.test.ts +39 -0
- package/src/utils/inputAria.ts +58 -0
- package/src/utils/timeParser.test.ts +10 -0
- package/src/utils/timeParser.ts +5 -2
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
- package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
- package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
|
@@ -9,12 +9,39 @@
|
|
|
9
9
|
* SYNC: When RadioList.tsx or RadioListItem.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
12
|
+
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
13
|
+
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {RadioList} from './RadioList';
|
|
16
16
|
import {RadioListItem} from './RadioListItem';
|
|
17
17
|
|
|
18
|
+
// Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
|
|
19
|
+
// reflects its open state via a `popover-open` attribute the tests can assert.
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
22
|
+
this.setAttribute('popover-open', '');
|
|
23
|
+
const event = new Event('toggle', {bubbles: false});
|
|
24
|
+
Object.defineProperty(event, 'newState', {value: 'open'});
|
|
25
|
+
this.dispatchEvent(event);
|
|
26
|
+
});
|
|
27
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
28
|
+
this.removeAttribute('popover-open');
|
|
29
|
+
const event = new Event('toggle', {bubbles: false});
|
|
30
|
+
Object.defineProperty(event, 'newState', {value: 'closed'});
|
|
31
|
+
this.dispatchEvent(event);
|
|
32
|
+
});
|
|
33
|
+
const originalMatches = HTMLElement.prototype.matches;
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
(HTMLElement.prototype as any).matches = function (
|
|
36
|
+
selector: string,
|
|
37
|
+
): boolean {
|
|
38
|
+
if (selector === ':popover-open') {
|
|
39
|
+
return this.hasAttribute('popover-open');
|
|
40
|
+
}
|
|
41
|
+
return originalMatches.call(this, selector);
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
18
45
|
describe('RadioList', () => {
|
|
19
46
|
it('renders with label', () => {
|
|
20
47
|
render(
|
|
@@ -104,11 +131,7 @@ describe('RadioList', () => {
|
|
|
104
131
|
const user = userEvent.setup();
|
|
105
132
|
const handleChange = vi.fn();
|
|
106
133
|
render(
|
|
107
|
-
<RadioList
|
|
108
|
-
label="Preference"
|
|
109
|
-
value=""
|
|
110
|
-
onChange={handleChange}
|
|
111
|
-
isDisabled>
|
|
134
|
+
<RadioList label="Preference" value="" onChange={handleChange} isDisabled>
|
|
112
135
|
<RadioListItem label="Option A" value="a" />
|
|
113
136
|
</RadioList>,
|
|
114
137
|
);
|
|
@@ -257,11 +280,7 @@ describe('RadioList', () => {
|
|
|
257
280
|
it('supports data-testid on RadioListItem', () => {
|
|
258
281
|
render(
|
|
259
282
|
<RadioList label="Preference" value="" onChange={() => {}}>
|
|
260
|
-
<RadioListItem
|
|
261
|
-
label="Option A"
|
|
262
|
-
value="a"
|
|
263
|
-
data-testid="my-radio-item"
|
|
264
|
-
/>
|
|
283
|
+
<RadioListItem label="Option A" value="a" data-testid="my-radio-item" />
|
|
265
284
|
</RadioList>,
|
|
266
285
|
);
|
|
267
286
|
expect(screen.getByTestId('my-radio-item')).toBeInTheDocument();
|
|
@@ -279,11 +298,32 @@ describe('RadioList', () => {
|
|
|
279
298
|
);
|
|
280
299
|
const label = screen.getByText('Hidden label');
|
|
281
300
|
expect(label).toBeInTheDocument();
|
|
282
|
-
// The radiogroup
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
'Hidden label',
|
|
301
|
+
// The radiogroup is named by the label element via aria-labelledby (not a
|
|
302
|
+
// duplicated aria-label), so its accessible name is still "Hidden label".
|
|
303
|
+
expect(
|
|
304
|
+
screen.getByRole('radiogroup', {name: 'Hidden label'}),
|
|
305
|
+
).toBeInTheDocument();
|
|
306
|
+
expect(screen.getByRole('radiogroup')).toHaveAttribute('aria-labelledby');
|
|
307
|
+
expect(screen.getByRole('radiogroup')).not.toHaveAttribute('aria-label');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('renders the group label as a span, not a label element (forms-14)', () => {
|
|
311
|
+
render(
|
|
312
|
+
<RadioList label="Plan" value="" onChange={() => {}}>
|
|
313
|
+
<RadioListItem label="Free" value="free" />
|
|
314
|
+
<RadioListItem label="Pro" value="pro" />
|
|
315
|
+
</RadioList>,
|
|
286
316
|
);
|
|
317
|
+
// A radiogroup's accessible name must not come from a literal `<label>`
|
|
318
|
+
// element — a `<label>` names a single control and can't be associated
|
|
319
|
+
// with a group. It is rendered as a `<span>` and referenced via
|
|
320
|
+
// aria-labelledby (with no orphaned htmlFor).
|
|
321
|
+
const labelEl = screen.getByText('Plan');
|
|
322
|
+
expect(labelEl.tagName).toBe('SPAN');
|
|
323
|
+
expect(labelEl.closest('label')).toBeNull();
|
|
324
|
+
expect(labelEl).not.toHaveAttribute('for');
|
|
325
|
+
const group = screen.getByRole('radiogroup', {name: 'Plan'});
|
|
326
|
+
expect(group.getAttribute('aria-labelledby')).toBe(labelEl.id);
|
|
287
327
|
});
|
|
288
328
|
|
|
289
329
|
it('renders description on items', () => {
|
|
@@ -340,4 +380,200 @@ describe('RadioList', () => {
|
|
|
340
380
|
'true',
|
|
341
381
|
);
|
|
342
382
|
});
|
|
383
|
+
|
|
384
|
+
describe('focus management (no-selection tab stop)', () => {
|
|
385
|
+
it('keeps focus on the selected radio when a value is selected', () => {
|
|
386
|
+
render(
|
|
387
|
+
<RadioList label="Preference" value="b" onChange={() => {}}>
|
|
388
|
+
<RadioListItem label="Option A" value="a" />
|
|
389
|
+
<RadioListItem label="Option B" value="b" />
|
|
390
|
+
<RadioListItem label="Option C" value="c" />
|
|
391
|
+
</RadioList>,
|
|
392
|
+
);
|
|
393
|
+
const selected = screen.getByLabelText('Option B');
|
|
394
|
+
// A selected value provides a deterministic native tab stop; focusing it
|
|
395
|
+
// must not be redirected elsewhere.
|
|
396
|
+
selected.focus();
|
|
397
|
+
expect(selected).toHaveFocus();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('redirects to the first radio when focus enters an unselected group forward', () => {
|
|
401
|
+
render(
|
|
402
|
+
<>
|
|
403
|
+
<button type="button">before</button>
|
|
404
|
+
<RadioList label="Preference" value="" onChange={() => {}}>
|
|
405
|
+
<RadioListItem label="Option A" value="a" />
|
|
406
|
+
<RadioListItem label="Option B" value="b" />
|
|
407
|
+
<RadioListItem label="Option C" value="c" />
|
|
408
|
+
</RadioList>
|
|
409
|
+
</>,
|
|
410
|
+
);
|
|
411
|
+
const radios = screen.getAllByRole('radio');
|
|
412
|
+
const outside = screen.getByText('before');
|
|
413
|
+
outside.focus();
|
|
414
|
+
// Forward entry: the browser lands on a leading radio; the group keeps the
|
|
415
|
+
// first radio as the deterministic tab stop.
|
|
416
|
+
radios[0].focus();
|
|
417
|
+
expect(radios[0]).toHaveFocus();
|
|
418
|
+
|
|
419
|
+
// Landing on a middle radio from outside is normalized to the first.
|
|
420
|
+
outside.focus();
|
|
421
|
+
radios[1].focus();
|
|
422
|
+
expect(radios[0]).toHaveFocus();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('redirects to the last radio when focus enters an unselected group backward', () => {
|
|
426
|
+
render(
|
|
427
|
+
<>
|
|
428
|
+
<RadioList label="Preference" value="" onChange={() => {}}>
|
|
429
|
+
<RadioListItem label="Option A" value="a" />
|
|
430
|
+
<RadioListItem label="Option B" value="b" />
|
|
431
|
+
<RadioListItem label="Option C" value="c" />
|
|
432
|
+
</RadioList>
|
|
433
|
+
<button type="button">after</button>
|
|
434
|
+
</>,
|
|
435
|
+
);
|
|
436
|
+
const radios = screen.getAllByRole('radio');
|
|
437
|
+
const outside = screen.getByText('after');
|
|
438
|
+
outside.focus();
|
|
439
|
+
// Backward (Shift+Tab) entry: the browser focuses the last radio; the
|
|
440
|
+
// group keeps it as the deterministic tab stop rather than jumping away.
|
|
441
|
+
radios[radios.length - 1].focus();
|
|
442
|
+
expect(radios[radios.length - 1]).toHaveFocus();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('does not hijack focus moving between radios within the group', () => {
|
|
446
|
+
render(
|
|
447
|
+
<RadioList label="Preference" value="" onChange={() => {}}>
|
|
448
|
+
<RadioListItem label="Option A" value="a" />
|
|
449
|
+
<RadioListItem label="Option B" value="b" />
|
|
450
|
+
<RadioListItem label="Option C" value="c" />
|
|
451
|
+
</RadioList>,
|
|
452
|
+
);
|
|
453
|
+
const radios = screen.getAllByRole('radio');
|
|
454
|
+
// Enter the group from outside, then move to a middle radio as arrow-key
|
|
455
|
+
// navigation would. Intra-group movement must not be redirected back.
|
|
456
|
+
radios[0].focus();
|
|
457
|
+
radios[1].focus();
|
|
458
|
+
expect(radios[1]).toHaveFocus();
|
|
459
|
+
radios[2].focus();
|
|
460
|
+
expect(radios[2]).toHaveFocus();
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('skips disabled radios when choosing the deterministic tab stop', () => {
|
|
464
|
+
render(
|
|
465
|
+
<>
|
|
466
|
+
<button type="button">before</button>
|
|
467
|
+
<RadioList label="Preference" value="" onChange={() => {}}>
|
|
468
|
+
<RadioListItem label="Option A" value="a" isDisabled />
|
|
469
|
+
<RadioListItem label="Option B" value="b" />
|
|
470
|
+
<RadioListItem label="Option C" value="c" />
|
|
471
|
+
<RadioListItem label="Option D" value="d" />
|
|
472
|
+
</RadioList>
|
|
473
|
+
</>,
|
|
474
|
+
);
|
|
475
|
+
const outside = screen.getByText('before');
|
|
476
|
+
const optionB = screen.getByLabelText('Option B');
|
|
477
|
+
const optionC = screen.getByLabelText('Option C');
|
|
478
|
+
// A middle enabled radio (C) entered from outside is normalized to the
|
|
479
|
+
// first *enabled* radio (B) — the disabled Option A is skipped.
|
|
480
|
+
outside.focus();
|
|
481
|
+
optionC.focus();
|
|
482
|
+
expect(optionB).toHaveFocus();
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('disabledMessage', () => {
|
|
487
|
+
const h = {hidden: true} as const;
|
|
488
|
+
|
|
489
|
+
function renderGroup(props?: {onChange?: (v: string) => void}) {
|
|
490
|
+
return render(
|
|
491
|
+
<RadioList
|
|
492
|
+
label="Plan"
|
|
493
|
+
value="free"
|
|
494
|
+
onChange={props?.onChange ?? (() => {})}
|
|
495
|
+
isDisabled
|
|
496
|
+
disabledMessage="Upgrade your account to change plans">
|
|
497
|
+
<RadioListItem label="Free" value="free" />
|
|
498
|
+
<RadioListItem label="Pro" value="pro" />
|
|
499
|
+
</RadioList>,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
it('shows the reason tooltip on hover when the group is disabled with a reason', async () => {
|
|
504
|
+
renderGroup();
|
|
505
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
506
|
+
expect(tooltip).toHaveTextContent('Upgrade your account to change plans');
|
|
507
|
+
const group = screen.getByRole('radiogroup');
|
|
508
|
+
fireEvent.mouseEnter(group);
|
|
509
|
+
await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
|
|
510
|
+
fireEvent.mouseLeave(group);
|
|
511
|
+
await waitFor(() => expect(tooltip).not.toHaveAttribute('popover-open'));
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('shows the reason tooltip on keyboard focus', async () => {
|
|
515
|
+
const user = userEvent.setup();
|
|
516
|
+
renderGroup();
|
|
517
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
518
|
+
await user.tab();
|
|
519
|
+
await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('does not render a tooltip when not disabled', () => {
|
|
523
|
+
render(
|
|
524
|
+
<RadioList
|
|
525
|
+
label="Plan"
|
|
526
|
+
value="free"
|
|
527
|
+
onChange={() => {}}
|
|
528
|
+
disabledMessage="Upgrade your account to change plans">
|
|
529
|
+
<RadioListItem label="Free" value="free" />
|
|
530
|
+
</RadioList>,
|
|
531
|
+
);
|
|
532
|
+
expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('does not render a tooltip when disabled without a reason', () => {
|
|
536
|
+
render(
|
|
537
|
+
<RadioList label="Plan" value="free" onChange={() => {}} isDisabled>
|
|
538
|
+
<RadioListItem label="Free" value="free" />
|
|
539
|
+
</RadioList>,
|
|
540
|
+
);
|
|
541
|
+
expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('keeps radios focusable via aria-disabled when a reason is provided', () => {
|
|
545
|
+
renderGroup();
|
|
546
|
+
for (const radio of screen.getAllByRole('radio', h)) {
|
|
547
|
+
expect(radio).not.toBeDisabled();
|
|
548
|
+
expect(radio).toHaveAttribute('aria-disabled', 'true');
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('links the reason tooltip from the group via aria-describedby', () => {
|
|
553
|
+
renderGroup();
|
|
554
|
+
const group = screen.getByRole('radiogroup');
|
|
555
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
556
|
+
expect(group.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('blocks selection while focusable-disabled', () => {
|
|
560
|
+
const onChange = vi.fn();
|
|
561
|
+
renderGroup({onChange});
|
|
562
|
+
const pro = screen.getByRole('radio', {name: 'Pro', hidden: true});
|
|
563
|
+
fireEvent.click(pro);
|
|
564
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('keeps radios natively disabled when disabled without a reason', () => {
|
|
568
|
+
render(
|
|
569
|
+
<RadioList label="Plan" value="free" onChange={() => {}} isDisabled>
|
|
570
|
+
<RadioListItem label="Free" value="free" />
|
|
571
|
+
<RadioListItem label="Pro" value="pro" />
|
|
572
|
+
</RadioList>,
|
|
573
|
+
);
|
|
574
|
+
for (const radio of screen.getAllByRole('radio', h)) {
|
|
575
|
+
expect(radio).toBeDisabled();
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
});
|
|
343
579
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file RadioList.tsx
|
|
7
|
-
* @input Uses React useId, createContext, ReactNode, Field, InputStatus
|
|
7
|
+
* @input Uses React useId, useCallback, useRef, createContext, ReactNode, Field, InputStatus
|
|
8
8
|
* @output Exports RadioList component, RadioListProps, RadioListContext
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by RadioList.test.tsx
|
|
10
10
|
*
|
|
@@ -16,11 +16,19 @@
|
|
|
16
16
|
* - /packages/cli/templates/blocks/components/RadioList/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import React, {
|
|
19
|
+
import React, {
|
|
20
|
+
createContext,
|
|
21
|
+
useCallback,
|
|
22
|
+
useId,
|
|
23
|
+
useMemo,
|
|
24
|
+
useRef,
|
|
25
|
+
type ReactNode,
|
|
26
|
+
} from 'react';
|
|
20
27
|
import * as stylex from '@stylexjs/stylex';
|
|
21
28
|
import {spacingVars} from '../theme/tokens.stylex';
|
|
22
29
|
import {Field} from '../Field/Field';
|
|
23
30
|
import type {InputStatus} from '../Field/types';
|
|
31
|
+
import {useTooltip} from '../Tooltip';
|
|
24
32
|
import {mergeProps} from '../utils';
|
|
25
33
|
import type {BaseProps} from '../BaseProps';
|
|
26
34
|
import type {SizeValue} from '../utils/types';
|
|
@@ -36,13 +44,21 @@ export interface RadioListContextValue {
|
|
|
36
44
|
value: string;
|
|
37
45
|
onChange: (value: string) => void;
|
|
38
46
|
isDisabled: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* True when the whole group is disabled *and* a `disabledMessage` is set. In
|
|
49
|
+
* that mode radios stay focusable via `aria-disabled` (instead of the native
|
|
50
|
+
* `disabled` attribute) so the disabled-reason tooltip is keyboard- and
|
|
51
|
+
* AT-discoverable; selection is still blocked in the item's onChange guard.
|
|
52
|
+
*/
|
|
53
|
+
hasDisabledMessage: boolean;
|
|
39
54
|
isRequired: boolean;
|
|
40
55
|
size: RadioListSize;
|
|
41
56
|
status?: InputStatus;
|
|
42
57
|
}
|
|
43
58
|
|
|
44
|
-
export const RadioListContext =
|
|
45
|
-
|
|
59
|
+
export const RadioListContext = createContext<RadioListContextValue | null>(
|
|
60
|
+
null,
|
|
61
|
+
);
|
|
46
62
|
RadioListContext.displayName = 'RadioListContext';
|
|
47
63
|
|
|
48
64
|
const styles = stylex.create({
|
|
@@ -95,6 +111,17 @@ export interface RadioListProps extends Omit<
|
|
|
95
111
|
* @default false
|
|
96
112
|
*/
|
|
97
113
|
isDisabled?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Explains why the radio group is disabled. Applies to the whole-group
|
|
116
|
+
* disabled state (`isDisabled`), not individual items. When set together with
|
|
117
|
+
* `isDisabled`, the group shows a tooltip with this text on hover and keyboard
|
|
118
|
+
* focus, and its radios stay focusable (via `aria-disabled`) so the reason is
|
|
119
|
+
* discoverable by keyboard and assistive technology. Selection stays blocked.
|
|
120
|
+
*
|
|
121
|
+
* Use this instead of wrapping a disabled group in `Tooltip` — disabled
|
|
122
|
+
* controls don't emit the pointer events an external tooltip needs.
|
|
123
|
+
*/
|
|
124
|
+
disabledMessage?: string;
|
|
98
125
|
/**
|
|
99
126
|
* Whether the radio group is required.
|
|
100
127
|
* @default false
|
|
@@ -161,6 +188,7 @@ export function RadioList({
|
|
|
161
188
|
onChange,
|
|
162
189
|
orientation = 'vertical',
|
|
163
190
|
isDisabled = false,
|
|
191
|
+
disabledMessage,
|
|
164
192
|
isRequired = false,
|
|
165
193
|
isOptional = false,
|
|
166
194
|
size = 'md',
|
|
@@ -175,12 +203,129 @@ export function RadioList({
|
|
|
175
203
|
}: RadioListProps) {
|
|
176
204
|
const name = useId();
|
|
177
205
|
const inputID = useId();
|
|
206
|
+
const labelID = useId();
|
|
178
207
|
const descriptionID = useId();
|
|
179
208
|
const statusMessageID = useId();
|
|
180
209
|
|
|
210
|
+
const groupRef = useRef<HTMLDivElement>(null);
|
|
211
|
+
|
|
212
|
+
// Disabled-reason tooltip. Applies to the whole-group disabled state. Disabled
|
|
213
|
+
// controls swallow pointer events, so the tooltip listeners attach to the
|
|
214
|
+
// radiogroup container and the radios stay perceivable via aria-disabled
|
|
215
|
+
// instead of the disabled attribute. Selection is blocked in the item's
|
|
216
|
+
// onChange guard.
|
|
217
|
+
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
218
|
+
const disabledMessageTooltip = useTooltip({
|
|
219
|
+
placement: 'above',
|
|
220
|
+
// The radiogroup container is not naturally focusable; focusin bubbles up
|
|
221
|
+
// from the radios, so always attach focus listeners.
|
|
222
|
+
focusTrigger: 'always',
|
|
223
|
+
isEnabled: showsDisabledMessage,
|
|
224
|
+
});
|
|
225
|
+
|
|
181
226
|
const contextValue = useMemo<RadioListContextValue>(
|
|
182
|
-
() => ({
|
|
183
|
-
|
|
227
|
+
() => ({
|
|
228
|
+
name,
|
|
229
|
+
value,
|
|
230
|
+
onChange,
|
|
231
|
+
isDisabled,
|
|
232
|
+
hasDisabledMessage: showsDisabledMessage,
|
|
233
|
+
isRequired,
|
|
234
|
+
size,
|
|
235
|
+
status,
|
|
236
|
+
}),
|
|
237
|
+
[
|
|
238
|
+
name,
|
|
239
|
+
value,
|
|
240
|
+
onChange,
|
|
241
|
+
isDisabled,
|
|
242
|
+
showsDisabledMessage,
|
|
243
|
+
isRequired,
|
|
244
|
+
size,
|
|
245
|
+
status,
|
|
246
|
+
],
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Make the tab stop deterministic when a radio group has no selected value.
|
|
251
|
+
*
|
|
252
|
+
* Native `<input type="radio">` groups (same `name`) implement roving
|
|
253
|
+
* tabindex for free: when a value is selected, that radio is the single tab
|
|
254
|
+
* stop and receives focus, so no correction is needed there. But the ARIA
|
|
255
|
+
* radio-group pattern (APG) also requires a deterministic tab stop when the
|
|
256
|
+
* group has *no* selection — and browsers disagree here. Chrome, when
|
|
257
|
+
* Shift+Tab moves focus backward into an unselected group, lands on the
|
|
258
|
+
* *last* radio; forward Tab lands on the *first*. To keep the entry point
|
|
259
|
+
* predictable we redirect focus:
|
|
260
|
+
* - forward entry → first enabled radio (APG default)
|
|
261
|
+
* - backward entry → last enabled radio (matches Chrome's backward tab)
|
|
262
|
+
*
|
|
263
|
+
* The redirect only runs when focus arrives from *outside* the group's own
|
|
264
|
+
* radios (guarded via `relatedTarget` containment). Moving between radios
|
|
265
|
+
* inside the group — arrow keys, clicks, programmatic focus of a sibling —
|
|
266
|
+
* is never hijacked.
|
|
267
|
+
*/
|
|
268
|
+
const handleFocus = useCallback(
|
|
269
|
+
(e: React.FocusEvent<HTMLDivElement>) => {
|
|
270
|
+
// Only correct the no-selection case; a selected value already provides
|
|
271
|
+
// a deterministic native tab stop.
|
|
272
|
+
if (value !== '') {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const group = groupRef.current;
|
|
277
|
+
if (!group) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Ignore focus moving *within* the group. `relatedTarget` is the element
|
|
282
|
+
// losing focus; if it was inside the group, this is intra-group movement
|
|
283
|
+
// (arrow keys, clicking a sibling) and must not be hijacked. Some browsers
|
|
284
|
+
// report a null `relatedTarget`; fall back to checking whether the group
|
|
285
|
+
// already contained the active element before this focus landed.
|
|
286
|
+
const relatedTarget = e.relatedTarget as Node | null;
|
|
287
|
+
if (relatedTarget) {
|
|
288
|
+
if (group.contains(relatedTarget)) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
} else if (
|
|
292
|
+
document.activeElement &&
|
|
293
|
+
document.activeElement !== e.target &&
|
|
294
|
+
group.contains(document.activeElement)
|
|
295
|
+
) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const radios = Array.from(
|
|
300
|
+
group.querySelectorAll<HTMLInputElement>(
|
|
301
|
+
'input[type="radio"]:not([disabled])',
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
if (radios.length === 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const target = e.target as HTMLElement;
|
|
309
|
+
const targetIndex = radios.findIndex(radio => radio === target);
|
|
310
|
+
// Only correct when the focus target is one of our enabled radios (not a
|
|
311
|
+
// nested control such as end-content).
|
|
312
|
+
if (targetIndex === -1) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Infer tab direction from which end the browser chose. On a backward
|
|
317
|
+
// (Shift+Tab) entry into an unselected group, browsers focus the *last*
|
|
318
|
+
// radio; keep it as the deterministic backward tab stop. Any other entry
|
|
319
|
+
// (forward Tab, or a browser that lands mid-group) is normalized to the
|
|
320
|
+
// *first* enabled radio, the APG default.
|
|
321
|
+
const isBackwardEntry = targetIndex === radios.length - 1;
|
|
322
|
+
const intended = isBackwardEntry ? radios[radios.length - 1] : radios[0];
|
|
323
|
+
|
|
324
|
+
if (target !== intended) {
|
|
325
|
+
intended.focus();
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
[value],
|
|
184
329
|
);
|
|
185
330
|
|
|
186
331
|
return (
|
|
@@ -191,6 +336,8 @@ export function RadioList({
|
|
|
191
336
|
isLabelHidden={isLabelHidden}
|
|
192
337
|
description={description}
|
|
193
338
|
inputID={inputID}
|
|
339
|
+
labelID={labelID}
|
|
340
|
+
isGroupLabel
|
|
194
341
|
descriptionID={description ? descriptionID : undefined}
|
|
195
342
|
isOptional={isOptional}
|
|
196
343
|
isRequired={isRequired}
|
|
@@ -211,12 +358,21 @@ export function RadioList({
|
|
|
211
358
|
className={className}
|
|
212
359
|
style={style}>
|
|
213
360
|
<div
|
|
361
|
+
ref={el => {
|
|
362
|
+
groupRef.current = el;
|
|
363
|
+
// Anchor + hover/focus listeners for the disabled-message tooltip.
|
|
364
|
+
// Handlers are gated internally by isEnabled, so attaching
|
|
365
|
+
// unconditionally is safe.
|
|
366
|
+
disabledMessageTooltip.ref(el);
|
|
367
|
+
}}
|
|
214
368
|
role="radiogroup"
|
|
215
|
-
aria-
|
|
369
|
+
aria-labelledby={labelID}
|
|
370
|
+
onFocus={handleFocus}
|
|
216
371
|
aria-describedby={
|
|
217
372
|
[
|
|
218
373
|
description ? descriptionID : null,
|
|
219
374
|
status?.message ? statusMessageID : null,
|
|
375
|
+
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
220
376
|
]
|
|
221
377
|
.filter(Boolean)
|
|
222
378
|
.join(' ') || undefined
|
|
@@ -230,10 +386,10 @@ export function RadioList({
|
|
|
230
386
|
orientation === 'vertical' ? styles.vertical : styles.horizontal,
|
|
231
387
|
),
|
|
232
388
|
)}>
|
|
233
|
-
<RadioListContext value={contextValue}>
|
|
234
|
-
{children}
|
|
235
|
-
</RadioListContext>
|
|
389
|
+
<RadioListContext value={contextValue}>{children}</RadioListContext>
|
|
236
390
|
</div>
|
|
391
|
+
{showsDisabledMessage &&
|
|
392
|
+
disabledMessageTooltip.renderTooltip(disabledMessage)}
|
|
237
393
|
</Field>
|
|
238
394
|
);
|
|
239
395
|
}
|
|
@@ -230,6 +230,12 @@ export function RadioListItem({
|
|
|
230
230
|
const id = useId();
|
|
231
231
|
const descriptionID = useId();
|
|
232
232
|
const isDisabled = context.isDisabled || isItemDisabled;
|
|
233
|
+
// When the whole group is disabled with a disabledMessage, radios stay
|
|
234
|
+
// focusable via aria-disabled (instead of native `disabled`) so the group's
|
|
235
|
+
// reason tooltip is keyboard-discoverable. Per-item disabling is unaffected
|
|
236
|
+
// and always uses the native disabled attribute.
|
|
237
|
+
const keepsFocusableForMessage =
|
|
238
|
+
context.hasDisabledMessage && !isItemDisabled;
|
|
233
239
|
const isChecked = context.value === value;
|
|
234
240
|
const size = context.size;
|
|
235
241
|
|
|
@@ -246,9 +252,15 @@ export function RadioListItem({
|
|
|
246
252
|
name={context.name}
|
|
247
253
|
value={value}
|
|
248
254
|
checked={isChecked}
|
|
249
|
-
disabled={isDisabled}
|
|
255
|
+
disabled={isDisabled && !keepsFocusableForMessage}
|
|
256
|
+
aria-disabled={keepsFocusableForMessage ? 'true' : undefined}
|
|
250
257
|
required={context.isRequired}
|
|
251
|
-
onChange={() =>
|
|
258
|
+
onChange={() => {
|
|
259
|
+
if (isDisabled) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
context.onChange(value);
|
|
263
|
+
}}
|
|
252
264
|
aria-describedby={description ? descriptionID : undefined}
|
|
253
265
|
{...stylex.props(
|
|
254
266
|
styles.input,
|
|
@@ -65,6 +65,12 @@ export const docs = {
|
|
|
65
65
|
description: 'Whether the entire control is disabled.',
|
|
66
66
|
default: 'false',
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: 'disabledMessage',
|
|
70
|
+
type: 'string',
|
|
71
|
+
description:
|
|
72
|
+
'Explains why the control is disabled. Applies to the whole-group disabled state (isDisabled), not per segment. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the control focusable via aria-disabled (selection stays blocked). Use this instead of wrapping a disabled SegmentedControl in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
|
|
73
|
+
},
|
|
68
74
|
{
|
|
69
75
|
name: 'children',
|
|
70
76
|
type: 'ReactNode',
|
|
@@ -97,6 +103,7 @@ export const docs = {
|
|
|
97
103
|
{guidance: true, description: 'Provide a descriptive label for the control to ensure the group is accessible to screen readers.'},
|
|
98
104
|
{guidance: false, description: 'Use for page-level navigation; use TabList instead. TabList is a navigation component, while SegmentedControl is an input that always has exactly one selected option.'},
|
|
99
105
|
{guidance: false, description: 'Use for simple on/off states; use ToggleButton instead. ToggleButton can be toggled on or off independently, while SegmentedControl enforces a single selection from a group.'},
|
|
106
|
+
{guidance: false, description: 'Wrap a disabled SegmentedControl in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
100
107
|
],
|
|
101
108
|
},
|
|
102
109
|
};
|
|
@@ -111,6 +118,7 @@ export const docsZh = {
|
|
|
111
118
|
{guidance: true, description: 'Provide a descriptive label for the control to ensure the group is accessible to screen readers.'},
|
|
112
119
|
{guidance: false, description: 'Use for page-level navigation; use TabList instead. TabList is a navigation component, while SegmentedControl is an input that always has exactly one selected option.'},
|
|
113
120
|
{guidance: false, description: 'Use for simple on/off states; use ToggleButton instead. ToggleButton can be toggled on or off independently, while SegmentedControl enforces a single selection from a group.'},
|
|
121
|
+
{guidance: false, description: 'Wrap a disabled SegmentedControl in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
114
122
|
],
|
|
115
123
|
},
|
|
116
124
|
};
|
|
@@ -125,6 +133,7 @@ export const docsDense = {
|
|
|
125
133
|
{guidance: true, description: 'Provide a descriptive label for the control to ensure the group is accessible to screen readers.'},
|
|
126
134
|
{guidance: false, description: 'Use for page-level navigation; use TabList instead. TabList is a navigation component, while SegmentedControl is an input that always has exactly one selected option.'},
|
|
127
135
|
{guidance: false, description: 'Use for simple on/off states; use ToggleButton instead. ToggleButton can be toggled on or off independently, while SegmentedControl enforces a single selection from a group.'},
|
|
136
|
+
{guidance: false, description: 'Wrap a disabled SegmentedControl in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
128
137
|
],
|
|
129
138
|
},
|
|
130
139
|
propDescriptions: {
|