@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
|
@@ -89,18 +89,18 @@ export const docs = {
|
|
|
89
89
|
},
|
|
90
90
|
usage: {
|
|
91
91
|
description:
|
|
92
|
-
'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled
|
|
92
|
+
'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled; picking relative for recency, absolute for precision, or auto to let the component decide.',
|
|
93
93
|
bestPractices: [
|
|
94
|
-
{guidance: true, description: 'Use the auto format in feeds and lists so recent items show
|
|
95
|
-
{guidance: true, description: 'Keep formatting consistent within the same list or table
|
|
94
|
+
{guidance: true, description: 'Use the auto format in feeds and lists so recent items show "2 hours ago" and older items show the full date automatically.'},
|
|
95
|
+
{guidance: true, description: 'Keep formatting consistent within the same list or table; mixing relative and absolute timestamps in the same column confuses scanning.'},
|
|
96
96
|
{guidance: true, description: 'Enable isTimezoneShown when the audience spans multiple time zones, like a global team calendar or audit log.'},
|
|
97
97
|
{guidance: true, description: 'Use isLive for active dashboards or real-time feeds so the relative time stays accurate without a page refresh.'},
|
|
98
|
-
{guidance: false, description: 'Don\
|
|
99
|
-
{guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI
|
|
100
|
-
{guidance: false, description: 'Don\
|
|
98
|
+
{guidance: false, description: 'Don\'t display raw Unix timestamps or ISO strings to users; always pass them through Timestamp to get a human-readable format.'},
|
|
99
|
+
{guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI; they are meant for developer tools, logs, and machine-readable contexts.'},
|
|
100
|
+
{guidance: false, description: 'Don\'t disable the tooltip on relative timestamps; users expect to hover for the full date when they see "3 hours ago".'},
|
|
101
101
|
],
|
|
102
102
|
anatomy: [
|
|
103
|
-
{name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like
|
|
103
|
+
{name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like "2 hours ago" or "Mar 21, 2025".'},
|
|
104
104
|
{name: 'Tooltip', required: false, description: 'A hover card showing the full absolute date and time when the display is relative.'},
|
|
105
105
|
],
|
|
106
106
|
},
|
|
@@ -122,18 +122,18 @@ export const docsZh = {
|
|
|
122
122
|
},
|
|
123
123
|
usage: {
|
|
124
124
|
description:
|
|
125
|
-
'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled
|
|
125
|
+
'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled; picking relative for recency, absolute for precision, or auto to let the component decide.',
|
|
126
126
|
bestPractices: [
|
|
127
|
-
{guidance: true, description: 'Use the auto format in feeds and lists so recent items show
|
|
128
|
-
{guidance: true, description: 'Keep formatting consistent within the same list or table
|
|
127
|
+
{guidance: true, description: 'Use the auto format in feeds and lists so recent items show "2 hours ago" and older items show the full date automatically.'},
|
|
128
|
+
{guidance: true, description: 'Keep formatting consistent within the same list or table; mixing relative and absolute timestamps in the same column confuses scanning.'},
|
|
129
129
|
{guidance: true, description: 'Enable isTimezoneShown when the audience spans multiple time zones, like a global team calendar or audit log.'},
|
|
130
130
|
{guidance: true, description: 'Use isLive for active dashboards or real-time feeds so the relative time stays accurate without a page refresh.'},
|
|
131
|
-
{guidance: false, description: 'Don\
|
|
132
|
-
{guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI
|
|
133
|
-
{guidance: false, description: 'Don\
|
|
131
|
+
{guidance: false, description: 'Don\'t display raw Unix timestamps or ISO strings to users; always pass them through Timestamp to get a human-readable format.'},
|
|
132
|
+
{guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI; they are meant for developer tools, logs, and machine-readable contexts.'},
|
|
133
|
+
{guidance: false, description: 'Don\'t disable the tooltip on relative timestamps; users expect to hover for the full date when they see "3 hours ago".'},
|
|
134
134
|
],
|
|
135
135
|
anatomy: [
|
|
136
|
-
{name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like
|
|
136
|
+
{name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like "2 hours ago" or "Mar 21, 2025".'},
|
|
137
137
|
{name: 'Tooltip', required: false, description: 'A hover card showing the full absolute date and time when the display is relative.'},
|
|
138
138
|
],
|
|
139
139
|
},
|
|
@@ -144,15 +144,15 @@ export const docsDense = {
|
|
|
144
144
|
description: 'formatted timestamp with relative/absolute/auto modes, live updates, timezone display',
|
|
145
145
|
usage: {
|
|
146
146
|
description:
|
|
147
|
-
'Timestamp formats a date or time into readable text. Use for creation dates, update times, or schedules
|
|
147
|
+
'Timestamp formats a date or time into readable text. Use for creation dates, update times, or schedules; relative for recency, absolute for precision, auto to switch automatically.',
|
|
148
148
|
bestPractices: [
|
|
149
|
-
{guidance: true, description: 'Auto format in feeds and lists
|
|
149
|
+
{guidance: true, description: 'Auto format in feeds and lists; recent shows relative, older shows date_time.'},
|
|
150
150
|
{guidance: true, description: 'Consistent formatting within the same list or table column.'},
|
|
151
151
|
{guidance: true, description: 'isTimezoneShown for multi-timezone audiences.'},
|
|
152
152
|
{guidance: true, description: 'isLive for dashboards so relative time stays current.'},
|
|
153
|
-
{guidance: false, description: 'Don\
|
|
154
|
-
{guidance: false, description: 'Avoid system_* formats in user-facing UI
|
|
155
|
-
{guidance: false, description: 'Don\
|
|
153
|
+
{guidance: false, description: 'Don\'t show raw Unix timestamps or ISO strings; always use Timestamp.'},
|
|
154
|
+
{guidance: false, description: 'Avoid system_* formats in user-facing UI; those are for dev tools and logs.'},
|
|
155
|
+
{guidance: false, description: 'Don\'t disable tooltip on relative timestamps; users expect the full date on hover.'},
|
|
156
156
|
],
|
|
157
157
|
},
|
|
158
158
|
propDescriptions: {
|
package/src/Toast/Toast.tsx
CHANGED
|
@@ -167,6 +167,20 @@ export function Toast({
|
|
|
167
167
|
};
|
|
168
168
|
}, [autoHideDuration, startTimer]);
|
|
169
169
|
|
|
170
|
+
// Pause the auto-hide timer while the window is not focused, so a toast
|
|
171
|
+
// doesn't silently expire while the user is in another window or tab.
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!isAutoHide) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
window.addEventListener('blur', pauseTimer);
|
|
177
|
+
window.addEventListener('focus', resumeTimer);
|
|
178
|
+
return () => {
|
|
179
|
+
window.removeEventListener('blur', pauseTimer);
|
|
180
|
+
window.removeEventListener('focus', resumeTimer);
|
|
181
|
+
};
|
|
182
|
+
}, [isAutoHide, pauseTimer, resumeTimer]);
|
|
183
|
+
|
|
170
184
|
const handleDismiss = useCallback(() => {
|
|
171
185
|
onDismiss('manual');
|
|
172
186
|
}, [onDismiss]);
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ToastViewport.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, ToastViewport + useToast
|
|
6
|
+
* @output Unit tests for toast keyboard reach and focus management
|
|
7
|
+
* @position Testing; validates ToastViewport.tsx + Toast.tsx focus behavior
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When ToastViewport.tsx or Toast.tsx focus handling changes, update these tests
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect, vi, beforeAll} from 'vitest';
|
|
13
|
+
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import {ToastViewport} from './ToastViewport';
|
|
16
|
+
import {useToast} from './useToast';
|
|
17
|
+
import type {ToastOptions} from './types';
|
|
18
|
+
|
|
19
|
+
// Popover API is not implemented in jsdom.
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
if (typeof HTMLElement.prototype.showPopover === 'undefined') {
|
|
22
|
+
HTMLElement.prototype.showPopover = vi.fn();
|
|
23
|
+
HTMLElement.prototype.hidePopover = vi.fn();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Module-level constant default props (avoids unstable-default-props lint).
|
|
28
|
+
const EMPTY_OPTIONS: ToastOptions = {body: 'placeholder'};
|
|
29
|
+
|
|
30
|
+
function ShowToastButton({
|
|
31
|
+
options = EMPTY_OPTIONS,
|
|
32
|
+
triggerLabel = 'Trigger',
|
|
33
|
+
}: {
|
|
34
|
+
options?: ToastOptions;
|
|
35
|
+
triggerLabel?: string;
|
|
36
|
+
}) {
|
|
37
|
+
const toast = useToast();
|
|
38
|
+
return (
|
|
39
|
+
<button type="button" onClick={() => toast(options)}>
|
|
40
|
+
{triggerLabel}
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const INFO_A: ToastOptions = {body: 'Toast A'};
|
|
46
|
+
const INFO_B: ToastOptions = {body: 'Toast B'};
|
|
47
|
+
const AUTO_TOAST: ToastOptions = {body: 'Auto toast', autoHideDuration: 3000};
|
|
48
|
+
|
|
49
|
+
function renderViewport(children: React.ReactNode) {
|
|
50
|
+
return render(<ToastViewport isTopLayer={false}>{children}</ToastViewport>);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fire the transition-end that ToastViewport listens for to unmount an
|
|
54
|
+
// exiting toast (jsdom does not run CSS transitions).
|
|
55
|
+
function completeExit(toastId: string) {
|
|
56
|
+
const node = document.querySelector<HTMLElement>(
|
|
57
|
+
`[data-toast-id="${toastId}"]`,
|
|
58
|
+
);
|
|
59
|
+
if (node) {
|
|
60
|
+
fireEvent.transitionEnd(node, {propertyName: 'grid-template-rows'});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('ToastViewport keyboard reach + focus', () => {
|
|
65
|
+
it('F6 moves focus into the newest toast', () => {
|
|
66
|
+
renderViewport(
|
|
67
|
+
<ShowToastButton options={INFO_A} triggerLabel="Trigger A" />,
|
|
68
|
+
);
|
|
69
|
+
const trigger = screen.getByText('Trigger A');
|
|
70
|
+
trigger.focus();
|
|
71
|
+
act(() => {
|
|
72
|
+
fireEvent.click(trigger);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText('Toast A')).toBeInTheDocument();
|
|
76
|
+
expect(document.activeElement).toBe(trigger);
|
|
77
|
+
|
|
78
|
+
act(() => {
|
|
79
|
+
fireEvent.keyDown(document, {key: 'F6'});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Focus lands on the dismiss button of the newest toast.
|
|
83
|
+
const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
|
|
84
|
+
expect(document.activeElement).toBe(dismiss);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('dismissing a focused toast moves focus to a remaining toast, not body', () => {
|
|
88
|
+
renderViewport(
|
|
89
|
+
<>
|
|
90
|
+
<ShowToastButton options={INFO_A} triggerLabel="Trigger A" />
|
|
91
|
+
<ShowToastButton options={INFO_B} triggerLabel="Trigger B" />
|
|
92
|
+
</>,
|
|
93
|
+
);
|
|
94
|
+
act(() => {
|
|
95
|
+
fireEvent.click(screen.getByText('Trigger A'));
|
|
96
|
+
});
|
|
97
|
+
act(() => {
|
|
98
|
+
fireEvent.click(screen.getByText('Trigger B'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const dismissButtons = screen.getAllByRole('button', {
|
|
102
|
+
name: 'Dismiss notification',
|
|
103
|
+
});
|
|
104
|
+
expect(dismissButtons).toHaveLength(2);
|
|
105
|
+
|
|
106
|
+
// Focus the first toast's dismiss button, then dismiss it.
|
|
107
|
+
const firstToast = document.querySelectorAll('[data-toast-id]')[0];
|
|
108
|
+
const firstToastId = firstToast.getAttribute('data-toast-id')!;
|
|
109
|
+
const firstDismiss = firstToast.querySelector<HTMLElement>(
|
|
110
|
+
'button[aria-label="Dismiss notification"]',
|
|
111
|
+
)!;
|
|
112
|
+
firstDismiss.focus();
|
|
113
|
+
expect(document.activeElement).toBe(firstDismiss);
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
fireEvent.click(firstDismiss);
|
|
117
|
+
});
|
|
118
|
+
act(() => {
|
|
119
|
+
completeExit(firstToastId);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Focus must NOT drop to <body>; it moves to the remaining toast.
|
|
123
|
+
expect(document.activeElement).not.toBe(document.body);
|
|
124
|
+
expect(document.activeElement?.getAttribute('aria-label')).toBe(
|
|
125
|
+
'Dismiss notification',
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('dismissing the last focused toast restores the previously-focused element', () => {
|
|
130
|
+
renderViewport(
|
|
131
|
+
<ShowToastButton options={INFO_A} triggerLabel="Trigger A" />,
|
|
132
|
+
);
|
|
133
|
+
const trigger = screen.getByText('Trigger A');
|
|
134
|
+
trigger.focus();
|
|
135
|
+
act(() => {
|
|
136
|
+
fireEvent.click(trigger);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// F6 into the toast, remembering the trigger as the prior focus.
|
|
140
|
+
act(() => {
|
|
141
|
+
fireEvent.keyDown(document, {key: 'F6'});
|
|
142
|
+
});
|
|
143
|
+
const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
|
|
144
|
+
expect(document.activeElement).toBe(dismiss);
|
|
145
|
+
|
|
146
|
+
const toastId = document
|
|
147
|
+
.querySelector('[data-toast-id]')!
|
|
148
|
+
.getAttribute('data-toast-id')!;
|
|
149
|
+
|
|
150
|
+
act(() => {
|
|
151
|
+
fireEvent.click(dismiss);
|
|
152
|
+
});
|
|
153
|
+
act(() => {
|
|
154
|
+
completeExit(toastId);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// No toasts left — focus returns to the element focused before F6.
|
|
158
|
+
expect(document.activeElement).toBe(trigger);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('Toast blur timer pause', () => {
|
|
163
|
+
it('pauses the auto-hide timer while the window is blurred', () => {
|
|
164
|
+
vi.useFakeTimers();
|
|
165
|
+
try {
|
|
166
|
+
renderViewport(
|
|
167
|
+
<ShowToastButton options={AUTO_TOAST} triggerLabel="Trigger Auto" />,
|
|
168
|
+
);
|
|
169
|
+
act(() => {
|
|
170
|
+
fireEvent.click(screen.getByText('Trigger Auto'));
|
|
171
|
+
});
|
|
172
|
+
expect(screen.getByText('Auto toast')).toBeInTheDocument();
|
|
173
|
+
|
|
174
|
+
// Window loses focus — timer should pause.
|
|
175
|
+
act(() => {
|
|
176
|
+
window.dispatchEvent(new Event('blur'));
|
|
177
|
+
});
|
|
178
|
+
act(() => {
|
|
179
|
+
vi.advanceTimersByTime(5000);
|
|
180
|
+
});
|
|
181
|
+
// Still present because the timer was paused while blurred.
|
|
182
|
+
expect(screen.getByText('Auto toast')).toBeInTheDocument();
|
|
183
|
+
|
|
184
|
+
// Window regains focus — timer resumes and the toast dismisses.
|
|
185
|
+
act(() => {
|
|
186
|
+
window.dispatchEvent(new Event('focus'));
|
|
187
|
+
});
|
|
188
|
+
act(() => {
|
|
189
|
+
vi.advanceTimersByTime(5000);
|
|
190
|
+
});
|
|
191
|
+
const toastId = document
|
|
192
|
+
.querySelector('[data-toast-id]')
|
|
193
|
+
?.getAttribute('data-toast-id');
|
|
194
|
+
if (toastId) {
|
|
195
|
+
act(() => {
|
|
196
|
+
completeExit(toastId);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
expect(screen.queryByText('Auto toast')).not.toBeInTheDocument();
|
|
200
|
+
} finally {
|
|
201
|
+
vi.useRealTimers();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('ToastViewport region ARIA', () => {
|
|
207
|
+
it('exposes the notifications region without a prohibited aria-modal', () => {
|
|
208
|
+
renderViewport(<ShowToastButton />);
|
|
209
|
+
const region = screen.getByRole('region', {name: 'Notifications'});
|
|
210
|
+
// aria-modal is only valid on role="dialog"/"alertdialog"; a region must
|
|
211
|
+
// not declare it (axe: aria-allowed-attr).
|
|
212
|
+
expect(region).not.toHaveAttribute('aria-modal');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
useLayoutEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react';
|
|
6
13
|
import * as stylex from '@stylexjs/stylex';
|
|
7
14
|
import {spacingVars, durationVars, easeVars} from '../theme/tokens.stylex';
|
|
8
15
|
import {mergeProps} from '../utils';
|
|
16
|
+
import {INTERACTIVE_SELECTORS} from '../hooks/useClickableContainer';
|
|
9
17
|
import {Toast} from './Toast';
|
|
10
18
|
import {ToastContext, type ToastContextValue} from './ToastContext';
|
|
11
|
-
import type {
|
|
12
|
-
ToastEntry,
|
|
13
|
-
ToastPosition,
|
|
14
|
-
ToastDismissReason,
|
|
15
|
-
} from './types';
|
|
19
|
+
import type {ToastEntry, ToastPosition, ToastDismissReason} from './types';
|
|
16
20
|
|
|
17
21
|
const styles = stylex.create({
|
|
18
22
|
viewport: {
|
|
@@ -108,6 +112,48 @@ export function ToastViewport({
|
|
|
108
112
|
const toastsRef = useRef(toasts);
|
|
109
113
|
toastsRef.current = toasts;
|
|
110
114
|
|
|
115
|
+
// Show the popover on mount so it enters the top layer.
|
|
116
|
+
const viewportRef = useRef<HTMLDivElement>(null);
|
|
117
|
+
// The element that was focused before the user jumped into the viewport
|
|
118
|
+
// (via F6). Used to restore focus once all toasts are dismissed so focus
|
|
119
|
+
// never falls to <body>.
|
|
120
|
+
const prevFocusRef = useRef<HTMLElement | null>(null);
|
|
121
|
+
// When a toast is dismissed while focus lives inside it, we need to move
|
|
122
|
+
// focus to a sensible neighbor after that toast unmounts. This holds the
|
|
123
|
+
// id of the toast whose removal should trigger a focus handoff.
|
|
124
|
+
const focusHandoffIdRef = useRef<string | null>(null);
|
|
125
|
+
// The next toast id that should receive focus once the dismissed toast has
|
|
126
|
+
// unmounted, or 'restore' to fall back to the previously-focused element.
|
|
127
|
+
const pendingFocusRef = useRef<string | 'restore' | null>(null);
|
|
128
|
+
|
|
129
|
+
// Collect a focusable control within a toast node, if any.
|
|
130
|
+
// Reuses the canonical INTERACTIVE_SELECTORS list (native controls plus
|
|
131
|
+
// role-based interactive elements) instead of a hand-rolled subset, then
|
|
132
|
+
// narrows to the first candidate that can actually receive focus —
|
|
133
|
+
// excluding elements opted out with `tabindex="-1"` and disabled controls.
|
|
134
|
+
const getFocusable = useCallback(
|
|
135
|
+
(container: HTMLElement | null): HTMLElement | null => {
|
|
136
|
+
if (!container) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const candidates = container.querySelectorAll<HTMLElement>(
|
|
140
|
+
INTERACTIVE_SELECTORS,
|
|
141
|
+
);
|
|
142
|
+
for (const candidate of candidates) {
|
|
143
|
+
if (
|
|
144
|
+
candidate.getAttribute('tabindex') === '-1' ||
|
|
145
|
+
candidate.hasAttribute('disabled') ||
|
|
146
|
+
candidate.getAttribute('aria-disabled') === 'true'
|
|
147
|
+
) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
return candidate;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
},
|
|
154
|
+
[],
|
|
155
|
+
);
|
|
156
|
+
|
|
111
157
|
const addToast = useCallback((entry: ToastEntry) => {
|
|
112
158
|
setToasts(prev => {
|
|
113
159
|
const {uniqueID, collisionBehavior = 'overwrite'} = entry.options;
|
|
@@ -124,21 +170,44 @@ export function ToastViewport({
|
|
|
124
170
|
});
|
|
125
171
|
}, []);
|
|
126
172
|
|
|
127
|
-
const removeToast = useCallback(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
|
|
174
|
+
const entry = toastsRef.current.find(t => t.id === id);
|
|
175
|
+
if (entry) {
|
|
176
|
+
entry.options.onHide?.(reason);
|
|
177
|
+
}
|
|
178
|
+
// If focus currently lives inside the toast being dismissed, remember
|
|
179
|
+
// that its removal must hand focus off to a neighbor (or the element
|
|
180
|
+
// focused before the user entered the viewport) rather than <body>.
|
|
181
|
+
const el = viewportRef.current;
|
|
182
|
+
const active = document.activeElement;
|
|
183
|
+
const dismissedNode =
|
|
184
|
+
el?.querySelector<HTMLElement>(`[data-toast-id="${id}"]`) ?? null;
|
|
185
|
+
if (
|
|
186
|
+
dismissedNode &&
|
|
187
|
+
active instanceof Node &&
|
|
188
|
+
dismissedNode.contains(active)
|
|
189
|
+
) {
|
|
190
|
+
focusHandoffIdRef.current = id;
|
|
191
|
+
// Pick the neighbor to receive focus while the DOM is still intact:
|
|
192
|
+
// prefer the next toast, then the previous, else restore.
|
|
193
|
+
const remaining = toastsRef.current.filter(t => t.id !== id);
|
|
194
|
+
if (remaining.length > 0) {
|
|
195
|
+
const dismissedIndex = toastsRef.current.findIndex(t => t.id === id);
|
|
196
|
+
const next =
|
|
197
|
+
toastsRef.current[dismissedIndex + 1] ??
|
|
198
|
+
toastsRef.current[dismissedIndex - 1];
|
|
199
|
+
pendingFocusRef.current = next ? next.id : 'restore';
|
|
200
|
+
} else {
|
|
201
|
+
pendingFocusRef.current = 'restore';
|
|
132
202
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
);
|
|
203
|
+
}
|
|
204
|
+
setExitingIds(prev => {
|
|
205
|
+
if (prev.has(id)) {
|
|
206
|
+
return prev;
|
|
207
|
+
}
|
|
208
|
+
return new Set(prev).add(id);
|
|
209
|
+
});
|
|
210
|
+
}, []);
|
|
142
211
|
|
|
143
212
|
const handleExited = useCallback((id: string) => {
|
|
144
213
|
setExitingIds(prev => {
|
|
@@ -152,6 +221,43 @@ export function ToastViewport({
|
|
|
152
221
|
setToasts(prev => prev.filter(t => t.id !== id));
|
|
153
222
|
}, []);
|
|
154
223
|
|
|
224
|
+
// After a dismissed toast unmounts, hand focus off so it never falls to
|
|
225
|
+
// <body>. Runs in a layout effect once the toast list no longer contains
|
|
226
|
+
// the dismissed toast.
|
|
227
|
+
useLayoutEffect(() => {
|
|
228
|
+
const handoffId = focusHandoffIdRef.current;
|
|
229
|
+
const target = pendingFocusRef.current;
|
|
230
|
+
if (handoffId == null || target == null) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// Wait until the dismissed toast is actually gone from the list.
|
|
234
|
+
if (toasts.some(t => t.id === handoffId)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
focusHandoffIdRef.current = null;
|
|
238
|
+
pendingFocusRef.current = null;
|
|
239
|
+
const el = viewportRef.current;
|
|
240
|
+
if (target !== 'restore' && el) {
|
|
241
|
+
const nextNode = el.querySelector<HTMLElement>(
|
|
242
|
+
`[data-toast-id="${target}"]`,
|
|
243
|
+
);
|
|
244
|
+
const focusable = getFocusable(nextNode) ?? nextNode;
|
|
245
|
+
if (focusable) {
|
|
246
|
+
focusable.focus();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// No remaining toast to receive focus — restore the previously-focused
|
|
251
|
+
// element if it's still connected, else fall back to the container.
|
|
252
|
+
const prev = prevFocusRef.current;
|
|
253
|
+
prevFocusRef.current = null;
|
|
254
|
+
if (prev && prev.isConnected) {
|
|
255
|
+
prev.focus();
|
|
256
|
+
} else if (el) {
|
|
257
|
+
el.focus();
|
|
258
|
+
}
|
|
259
|
+
}, [toasts, getFocusable]);
|
|
260
|
+
|
|
155
261
|
const findByUniqueID = useCallback((uid: string) => {
|
|
156
262
|
return toastsRef.current.find(t => t.options.uniqueID === uid);
|
|
157
263
|
}, []);
|
|
@@ -177,7 +283,6 @@ export function ToastViewport({
|
|
|
177
283
|
}
|
|
178
284
|
|
|
179
285
|
// Show the popover on mount so it enters the top layer
|
|
180
|
-
const viewportRef = useRef<HTMLDivElement>(null);
|
|
181
286
|
useEffect(() => {
|
|
182
287
|
if (!isTopLayer) {
|
|
183
288
|
return;
|
|
@@ -192,6 +297,45 @@ export function ToastViewport({
|
|
|
192
297
|
}
|
|
193
298
|
}, [isTopLayer]);
|
|
194
299
|
|
|
300
|
+
// F6 jumps focus into the toast viewport — the standard "go to
|
|
301
|
+
// notifications" affordance. Focus the first control in the newest toast,
|
|
302
|
+
// or the viewport container if none. Toasts are non-modal, so this only
|
|
303
|
+
// moves focus in; Shift+Tab / Escape let focus leave naturally.
|
|
304
|
+
const hasToasts = toasts.length > 0;
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
if (!hasToasts) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
310
|
+
if (e.key !== 'F6') {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const el = viewportRef.current;
|
|
314
|
+
if (!el) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Already inside the viewport — nothing to do.
|
|
318
|
+
const active = document.activeElement;
|
|
319
|
+
if (active instanceof Node && el.contains(active)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
// Remember where focus was so it can be restored on dismiss.
|
|
324
|
+
if (active instanceof HTMLElement) {
|
|
325
|
+
prevFocusRef.current = active;
|
|
326
|
+
}
|
|
327
|
+
// Newest toast is the last one rendered in the DOM.
|
|
328
|
+
const toastNodes = el.querySelectorAll<HTMLElement>('[data-toast-id]');
|
|
329
|
+
const newest = toastNodes[toastNodes.length - 1] ?? null;
|
|
330
|
+
const focusable = getFocusable(newest) ?? newest ?? el;
|
|
331
|
+
focusable.focus();
|
|
332
|
+
};
|
|
333
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
334
|
+
return () => {
|
|
335
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
336
|
+
};
|
|
337
|
+
}, [hasToasts, getFocusable]);
|
|
338
|
+
|
|
195
339
|
const posStyle =
|
|
196
340
|
position === 'topEnd'
|
|
197
341
|
? styles.topEnd
|
|
@@ -208,6 +352,7 @@ export function ToastViewport({
|
|
|
208
352
|
ref={viewportRef}
|
|
209
353
|
role="region"
|
|
210
354
|
aria-label="Notifications"
|
|
355
|
+
tabIndex={-1}
|
|
211
356
|
// popover="manual" promotes to the top layer (above dialogs).
|
|
212
357
|
// Omitted inside dialogs where the viewport is already in a top layer.
|
|
213
358
|
popover={isTopLayer ? 'manual' : undefined}
|
|
@@ -223,6 +368,7 @@ export function ToastViewport({
|
|
|
223
368
|
return (
|
|
224
369
|
<div
|
|
225
370
|
key={entry.id}
|
|
371
|
+
data-toast-id={entry.id}
|
|
226
372
|
{...stylex.props(
|
|
227
373
|
styles.toastWrapper,
|
|
228
374
|
isExiting && styles.toastWrapperExiting,
|
|
@@ -70,6 +70,12 @@ export const docs = {
|
|
|
70
70
|
description: 'Disables the input and all token interactions.',
|
|
71
71
|
default: 'false',
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
name: 'disabledMessage',
|
|
75
|
+
type: 'string',
|
|
76
|
+
description:
|
|
77
|
+
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
|
|
78
|
+
},
|
|
73
79
|
{
|
|
74
80
|
name: 'status',
|
|
75
81
|
type: 'InputStatus',
|
|
@@ -172,7 +178,7 @@ export const docs = {
|
|
|
172
178
|
name: 'xstyle',
|
|
173
179
|
type: 'StyleXStyles',
|
|
174
180
|
description:
|
|
175
|
-
'StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value
|
|
181
|
+
'StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value; not an inline style object like style={{}}.',
|
|
176
182
|
},
|
|
177
183
|
],
|
|
178
184
|
theming: {
|
|
@@ -184,13 +190,14 @@ export const docs = {
|
|
|
184
190
|
description:
|
|
185
191
|
'Tokenizer is a multi-select input that lets users search, select, and manage multiple items displayed as removable chips. Use it when users need to build a set of selections from a searchable data source, like adding team members, applying tags, or choosing filters.',
|
|
186
192
|
bestPractices: [
|
|
187
|
-
{guidance: true, description: 'Write a placeholder that tells users what they can search for
|
|
193
|
+
{guidance: true, description: 'Write a placeholder that tells users what they can search for, such as "Search people..." or "Add tags...", so the input is not a blank mystery.'},
|
|
188
194
|
{guidance: true, description: 'Set maxEntries when the number of selections should be bounded, like limiting a review to 5 approvers.'},
|
|
189
195
|
{guidance: true, description: 'Use hasCreate for free-form tagging where users need to enter values that do not exist in the search source.'},
|
|
190
196
|
{guidance: true, description: 'Show validation status with the status prop so users know immediately when a selection is missing or invalid.'},
|
|
191
|
-
{guidance: false, description: 'Don\
|
|
192
|
-
{guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer
|
|
193
|
-
{guidance: false, description: 'Don\
|
|
197
|
+
{guidance: false, description: 'Don\'t use Tokenizer for single-item selection; use Typeahead instead. Tokenizer is for building sets of two or more items.'},
|
|
198
|
+
{guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer; use the default token style for visual consistency across the set.'},
|
|
199
|
+
{guidance: false, description: 'Don\'t hide the label; every Tokenizer needs a visible label so users understand what they are selecting. Use isLabelHidden only when surrounding context makes the purpose obvious.'},
|
|
200
|
+
{guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
194
201
|
],
|
|
195
202
|
anatomy: [
|
|
196
203
|
{name: 'Label', required: true, description: 'The visible text above the input describing what the user is selecting. Also used as the accessible name.'},
|
|
@@ -270,6 +277,12 @@ export const docsZh = {
|
|
|
270
277
|
description: '\u7981\u7528\u8f93\u5165\u6846\u548c\u6240\u6709\u6807\u8bb0\u4ea4\u4e92\u3002',
|
|
271
278
|
default: 'false',
|
|
272
279
|
},
|
|
280
|
+
{
|
|
281
|
+
name: 'disabledMessage',
|
|
282
|
+
type: 'string',
|
|
283
|
+
description:
|
|
284
|
+
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
|
|
285
|
+
},
|
|
273
286
|
{
|
|
274
287
|
name: 'status',
|
|
275
288
|
type: 'InputStatus',
|
|
@@ -361,7 +374,7 @@ export const docsZh = {
|
|
|
361
374
|
name: 'xstyle',
|
|
362
375
|
type: 'StyleXStyles',
|
|
363
376
|
description:
|
|
364
|
-
'\u7528\u4e8e\u5e03\u5c40\u81ea\u5b9a\u4e49\u7684 StyleX \u6837\u5f0f\uff08\u5916\u8fb9\u8ddd\u3001\u5b9a\u4f4d\u3001\u5c3a\u5bf8\uff09\u3002\u5fc5\u987b\u662f stylex.create() \u7684\u503c \
|
|
377
|
+
'\u7528\u4e8e\u5e03\u5c40\u81ea\u5b9a\u4e49\u7684 StyleX \u6837\u5f0f\uff08\u5916\u8fb9\u8ddd\u3001\u5b9a\u4f4d\u3001\u5c3a\u5bf8\uff09\u3002\u5fc5\u987b\u662f stylex.create() \u7684\u503c; \u4e0d\u80fd\u662f\u5185\u8054\u6837\u5f0f\u5bf9\u8c61\u5982 style={{}}\u3002',
|
|
365
378
|
},
|
|
366
379
|
],
|
|
367
380
|
theming: {
|
|
@@ -373,13 +386,14 @@ export const docsZh = {
|
|
|
373
386
|
description:
|
|
374
387
|
'Tokenizer is a multi-select input that lets users search, select, and manage multiple items displayed as removable chips. Use it when users need to build a set of selections from a searchable data source, like adding team members, applying tags, or choosing filters.',
|
|
375
388
|
bestPractices: [
|
|
376
|
-
{guidance: true, description: 'Write a placeholder that tells users what they can search for
|
|
389
|
+
{guidance: true, description: 'Write a placeholder that tells users what they can search for, such as "Search people..." or "Add tags...", so the input is not a blank mystery.'},
|
|
377
390
|
{guidance: true, description: 'Set maxEntries when the number of selections should be bounded, like limiting a review to 5 approvers.'},
|
|
378
391
|
{guidance: true, description: 'Use hasCreate for free-form tagging where users need to enter values that do not exist in the search source.'},
|
|
379
392
|
{guidance: true, description: 'Show validation status with the status prop so users know immediately when a selection is missing or invalid.'},
|
|
380
|
-
{guidance: false, description: 'Don\
|
|
381
|
-
{guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer
|
|
382
|
-
{guidance: false, description: 'Don\
|
|
393
|
+
{guidance: false, description: 'Don\'t use Tokenizer for single-item selection; use Typeahead instead. Tokenizer is for building sets of two or more items.'},
|
|
394
|
+
{guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer; use the default token style for visual consistency across the set.'},
|
|
395
|
+
{guidance: false, description: 'Don\'t hide the label; every Tokenizer needs a visible label so users understand what they are selecting. Use isLabelHidden only when surrounding context makes the purpose obvious.'},
|
|
396
|
+
{guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
383
397
|
],
|
|
384
398
|
anatomy: [
|
|
385
399
|
{name: 'Label', required: true, description: 'The visible text above the input describing what the user is selecting. Also used as the accessible name.'},
|
|
@@ -399,13 +413,14 @@ export const docsDense = {
|
|
|
399
413
|
description:
|
|
400
414
|
'Multi-select input for searching and selecting multiple items as removable chips. Use for team members, tags, filters, or any set built from a searchable source.',
|
|
401
415
|
bestPractices: [
|
|
402
|
-
{guidance: true, description: 'Placeholder that communicates what to search
|
|
416
|
+
{guidance: true, description: 'Placeholder that communicates what to search, such as "Search people..." rather than blank.'},
|
|
403
417
|
{guidance: true, description: 'maxEntries when selections are bounded (e.g. 5 approvers max).'},
|
|
404
418
|
{guidance: true, description: 'hasCreate for free-form tagging with values not in the source.'},
|
|
405
419
|
{guidance: true, description: 'status prop for immediate validation feedback.'},
|
|
406
|
-
{guidance: false, description: 'Don\
|
|
407
|
-
{guidance: false, description: 'Avoid custom token colors
|
|
408
|
-
{guidance: false, description: 'Don\
|
|
420
|
+
{guidance: false, description: 'Don\'t use for single-item selection; use Typeahead instead.'},
|
|
421
|
+
{guidance: false, description: 'Avoid custom token colors; default style for consistency.'},
|
|
422
|
+
{guidance: false, description: 'Don\'t hide the label unless context makes purpose obvious.'},
|
|
423
|
+
{guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
|
|
409
424
|
],
|
|
410
425
|
},
|
|
411
426
|
propDescriptions: {
|