@astryxdesign/core 0.1.9 → 0.2.0-canary.136a00f
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 +107 -0
- package/dist/Avatar/Avatar.d.ts +42 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +190 -59
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +58 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
- package/dist/Banner/Banner.js +2 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
- package/dist/Breadcrumbs/index.d.ts +1 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +2 -1
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +6 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatComposerInput.js +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatLayout.js +4 -4
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +8 -8
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +10 -3
- package/dist/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -6
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +19 -4
- package/dist/Citation/Citation.d.ts +22 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -6
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +16 -10
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +3 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +42 -3
- package/dist/CommandPalette/CommandPaletteItem.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +23 -5
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +3 -1
- package/dist/DateInput/DateInput.d.ts +10 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +33 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +23 -20
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +5 -2
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +5 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +9 -5
- package/dist/Field/inputStyles.stylex.d.ts +1 -0
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +1 -0
- package/dist/FieldStatus/FieldStatus.d.ts +10 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +39 -3
- package/dist/FileInput/FileInput.d.ts +10 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +93 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Item/Item.d.ts +16 -3
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +31 -6
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +10 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +19 -13
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/List/ListItem.d.ts +11 -1
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +2 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MetadataList/MetadataList.d.ts +1 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +4 -2
- package/dist/MultiSelector/MultiSelector.d.ts +9 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +80 -42
- package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
- package/dist/NumberInput/NumberInput.d.ts +10 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +22 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +11 -3
- package/dist/PowerSearch/PowerSearch.d.ts +9 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +2 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +6 -1
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -5
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +49 -19
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
- package/dist/Selector/Selector.d.ts +8 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +66 -17
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +5 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +13 -10
- package/dist/SideNav/SideNavItem.d.ts +1 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +12 -10
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +2 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +87 -34
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +11 -2
- package/dist/Switch/Switch.d.ts +8 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +171 -91
- package/dist/TabList/Tab.js +2 -2
- package/dist/TabList/TabList.d.ts +2 -11
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +5 -9
- package/dist/TabList/TabListContext.d.ts +0 -5
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +0 -5
- package/dist/TabList/TabMenu.js +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableHeaderCell.js +3 -3
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +37 -27
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +10 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +18 -19
- package/dist/TextInput/TextInput.d.ts +10 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +20 -17
- package/dist/Thumbnail/Thumbnail.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts +10 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +17 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +61 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +3 -1
- package/dist/Token/Token.d.ts +34 -4
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +85 -45
- package/dist/Token/TokenLink.d.ts +62 -0
- package/dist/Token/TokenLink.d.ts.map +1 -0
- package/dist/Token/TokenLink.js +76 -0
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts +9 -2
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +4 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +22 -11
- package/dist/Typeahead/Typeahead.d.ts +9 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +11 -2
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +20 -10
- package/dist/astryx.css +70 -30
- package/dist/astryx.umd.js +51 -51
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useKeyboardHint.d.ts +1 -1
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
- package/dist/hooks/useKeyboardHint.js +4 -2
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/i18n/InternationalizationContext.d.ts +1 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -0
- package/dist/i18n/InternationalizationProvider.d.ts +8 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +5 -1
- package/dist/i18n/getLocaleDirection.d.ts +18 -0
- package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
- package/dist/i18n/getLocaleDirection.js +29 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +7 -1
- package/dist/i18n/useDirection.d.ts +2 -0
- package/dist/i18n/useDirection.d.ts.map +1 -0
- package/dist/i18n/useDirection.js +27 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +112 -0
- package/locales/pseudo.json +84 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +50 -3
- package/src/Avatar/Avatar.test.tsx +218 -0
- package/src/Avatar/Avatar.tsx +285 -79
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
- package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
- package/src/AvatarGroup/AvatarGroup.tsx +58 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
- package/src/Banner/Banner.tsx +6 -6
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Button/Button.test.tsx +16 -0
- package/src/Button/Button.tsx +6 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.tsx +2 -2
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatLayout.tsx +4 -4
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/Chat/useTriggerMenu.tsx +1 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/CheckboxList/CheckboxList.test.tsx +94 -3
- package/src/CheckboxList/CheckboxListItem.tsx +17 -10
- package/src/Citation/Citation.doc.mjs +4 -3
- package/src/Citation/Citation.test.tsx +97 -0
- package/src/Citation/Citation.tsx +43 -11
- package/src/CodeBlock/CodeBlock.test.tsx +17 -0
- package/src/CodeBlock/CodeBlock.tsx +38 -11
- package/src/Collapsible/Collapsible.doc.mjs +1 -0
- package/src/Collapsible/Collapsible.test.tsx +7 -0
- package/src/Collapsible/Collapsible.tsx +16 -6
- package/src/CommandPalette/CommandPalette.test.tsx +95 -1
- package/src/CommandPalette/CommandPalette.tsx +41 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
- package/src/ContextMenu/ContextMenu.test.tsx +59 -2
- package/src/ContextMenu/ContextMenu.tsx +22 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +25 -0
- package/src/DateInput/DateInput.test.tsx +230 -0
- package/src/DateInput/DateInput.tsx +51 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
- package/src/DateRangeInput/DateRangeInput.tsx +42 -30
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/FieldLabel.test.tsx +25 -0
- package/src/Field/FieldLabel.tsx +12 -3
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.test.tsx +72 -0
- package/src/FieldStatus/FieldStatus.tsx +48 -2
- package/src/FileInput/FileInput.doc.mjs +23 -2
- package/src/FileInput/FileInput.test.tsx +104 -13
- package/src/FileInput/FileInput.tsx +106 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Item/Item.doc.mjs +5 -0
- package/src/Item/Item.test.tsx +88 -0
- package/src/Item/Item.tsx +57 -11
- package/src/Layer/layerAnimations.stylex.ts +23 -0
- package/src/Lightbox/Lightbox.tsx +15 -7
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/List/ListItem.doc.mjs +12 -0
- package/src/List/ListItem.tsx +13 -0
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MetadataList/MetadataList.test.tsx +24 -0
- package/src/MetadataList/MetadataList.tsx +6 -2
- package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
- package/src/MultiSelector/MultiSelector.test.tsx +402 -0
- package/src/MultiSelector/MultiSelector.tsx +120 -59
- package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
- package/src/NumberInput/NumberInput.doc.mjs +21 -0
- package/src/NumberInput/NumberInput.test.tsx +71 -0
- package/src/NumberInput/NumberInput.tsx +35 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/Pagination/Pagination.tsx +11 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +23 -0
- package/src/PowerSearch/PowerSearch.tsx +10 -1
- package/src/ProgressBar/ProgressBar.test.tsx +34 -0
- package/src/ProgressBar/ProgressBar.tsx +16 -1
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.test.tsx +26 -4
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +12 -5
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
- package/src/Selector/Selector.doc.mjs +15 -0
- package/src/Selector/Selector.test.tsx +370 -0
- package/src/Selector/Selector.tsx +88 -20
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Skeleton/Skeleton.test.tsx +16 -0
- package/src/Skeleton/Skeleton.tsx +13 -1
- package/src/Slider/Slider.doc.mjs +6 -0
- package/src/Slider/Slider.test.tsx +240 -12
- package/src/Slider/Slider.tsx +96 -23
- package/src/Spinner/Spinner.test.tsx +10 -8
- package/src/Spinner/Spinner.tsx +15 -5
- package/src/Switch/Switch.doc.mjs +142 -42
- package/src/Switch/Switch.test.tsx +111 -0
- package/src/Switch/Switch.tsx +149 -32
- package/src/TabList/Tab.tsx +2 -2
- package/src/TabList/TabList.doc.mjs +0 -6
- package/src/TabList/TabList.test.tsx +3 -15
- package/src/TabList/TabList.tsx +6 -17
- package/src/TabList/TabListContext.ts +0 -6
- package/src/TabList/TabMenu.tsx +2 -2
- package/src/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/TableCell.tsx +3 -3
- package/src/Table/TableHeaderCell.tsx +3 -3
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
- package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
- package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +21 -0
- package/src/TextArea/TextArea.test.tsx +23 -0
- package/src/TextArea/TextArea.tsx +27 -26
- package/src/TextInput/TextInput.doc.mjs +21 -0
- package/src/TextInput/TextInput.test.tsx +150 -0
- package/src/TextInput/TextInput.tsx +28 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.tsx +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +21 -0
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +24 -25
- package/src/Timestamp/Timestamp.doc.mjs +32 -6
- package/src/Timestamp/Timestamp.test.tsx +371 -0
- package/src/Timestamp/Timestamp.tsx +96 -93
- package/src/Timestamp/formatInstant.ts +218 -0
- package/src/Timestamp/index.ts +4 -0
- package/src/Timestamp/tooltipEntries.test.ts +552 -0
- package/src/Timestamp/tooltipEntries.ts +210 -0
- package/src/Toast/ToastViewport.test.tsx +218 -8
- package/src/Toast/ToastViewport.tsx +71 -12
- package/src/ToggleButton/ToggleButton.test.tsx +24 -0
- package/src/ToggleButton/ToggleButton.tsx +19 -1
- package/src/Token/Token.test.tsx +166 -15
- package/src/Token/Token.tsx +104 -47
- package/src/Token/TokenLink.tsx +107 -0
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
- package/src/Tokenizer/Tokenizer.test.tsx +23 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +42 -15
- package/src/Typeahead/Typeahead.doc.mjs +13 -0
- package/src/Typeahead/Typeahead.test.tsx +112 -0
- package/src/Typeahead/Typeahead.tsx +17 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/forcedColors.ts +60 -0
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useKeyboardHint.test.tsx +22 -0
- package/src/hooks/useKeyboardHint.tsx +6 -2
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/i18n/InternationalizationContext.ts +4 -0
- package/src/i18n/InternationalizationProvider.tsx +12 -2
- package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
- package/src/i18n/__tests__/useDirection.test.tsx +66 -0
- package/src/i18n/getLocaleDirection.ts +29 -0
- package/src/i18n/index.ts +6 -0
- package/src/i18n/useDirection.ts +29 -0
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useInputStatusIcon.tsx
|
|
7
|
+
* @input Input status, statusVariant, and InputGroup awareness
|
|
8
|
+
* @output The on-field status icon element (with tooltip) and aria-describedby wiring
|
|
9
|
+
* @position Shared hook for bordered inputs that render a status affordance inside the control
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/hooks/index.ts (exports)
|
|
13
|
+
*
|
|
14
|
+
* Centralizes the on-field status icon across the bordered inputs (TextInput,
|
|
15
|
+
* TextArea, NumberInput, DateInput, DateTimeInput, DateRangeInput, TimeInput,
|
|
16
|
+
* FileInput) so the three statusVariants behave consistently:
|
|
17
|
+
*
|
|
18
|
+
* - `attached` → icon sits inside the control; the message box below carries
|
|
19
|
+
* the text.
|
|
20
|
+
* - `detached` → the detached message box renders its OWN leading icon, so the
|
|
21
|
+
* on-field icon is suppressed here to avoid a duplicate glyph.
|
|
22
|
+
* - `tooltip` → no message box renders; the status is surfaced through an
|
|
23
|
+
* info-tip on the on-field icon. The icon is a real focusable `<button>` so
|
|
24
|
+
* the status is reachable by every user, not just those with hover or a
|
|
25
|
+
* screen reader:
|
|
26
|
+
* - Keyboard (no AT): the button is in the tab order (WCAG 2.1.1) with a
|
|
27
|
+
* visible focus ring (WCAG 2.4.7); focusing it opens the tooltip.
|
|
28
|
+
* - Pointer: hover opens it (guarded to fine pointers).
|
|
29
|
+
* - Touch: hover is unavailable, so a tap toggles the tooltip open/closed.
|
|
30
|
+
* - Assistive tech: the button has an accessible name (its status type,
|
|
31
|
+
* WCAG 4.1.2) and is described by the message via `aria-describedby`.
|
|
32
|
+
* - Dismissible with Escape and hoverable (WCAG 1.4.13) via useTooltip.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import {useCallback, useEffect, useState} from 'react';
|
|
36
|
+
import type {ReactNode} from 'react';
|
|
37
|
+
import * as stylex from '@stylexjs/stylex';
|
|
38
|
+
import {Icon, type IconName, type IconSize} from '../Icon';
|
|
39
|
+
import type {FieldStatusVariant} from '../FieldStatus/FieldStatus';
|
|
40
|
+
import type {InputStatus, InputStatusType} from '../Field/types';
|
|
41
|
+
import {useTooltip} from '../Tooltip';
|
|
42
|
+
import {useTranslator} from '../i18n';
|
|
43
|
+
import {colorVars, radiusVars} from '../theme/tokens.stylex';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maps each status type to its glyph. Shared so every input shows the same icon
|
|
47
|
+
* for a given status, matching the detached message box's leading icon.
|
|
48
|
+
*/
|
|
49
|
+
const STATUS_ICON: Record<InputStatusType, IconName> = {
|
|
50
|
+
warning: 'warning',
|
|
51
|
+
error: 'error',
|
|
52
|
+
success: 'success',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Accessible-name i18n keys for the focusable status button, keyed by type.
|
|
57
|
+
*/
|
|
58
|
+
const STATUS_BUTTON_LABEL_KEY: Record<InputStatusType, string> = {
|
|
59
|
+
warning: '@astryx.input.statusButton.warning',
|
|
60
|
+
error: '@astryx.input.statusButton.error',
|
|
61
|
+
success: '@astryx.input.statusButton.success',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const styles = stylex.create({
|
|
65
|
+
// Contain the anchor so the tooltip positions against the icon, and keep the
|
|
66
|
+
// glyph vertically centered within the input control.
|
|
67
|
+
iconAnchor: {
|
|
68
|
+
display: 'inline-flex',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
},
|
|
71
|
+
// The tooltip-variant status affordance is a real button so it is keyboard
|
|
72
|
+
// focusable and tappable. Strip the native chrome and show only a focus ring.
|
|
73
|
+
statusButton: {
|
|
74
|
+
display: 'inline-flex',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
justifyContent: 'center',
|
|
77
|
+
padding: 0,
|
|
78
|
+
margin: 0,
|
|
79
|
+
border: 'none',
|
|
80
|
+
background: 'none',
|
|
81
|
+
color: 'inherit',
|
|
82
|
+
cursor: 'pointer',
|
|
83
|
+
borderRadius: radiusVars['--radius-full'],
|
|
84
|
+
outlineWidth: {default: null, ':focus-visible': '2px'},
|
|
85
|
+
outlineStyle: {default: null, ':focus-visible': 'solid'},
|
|
86
|
+
outlineColor: {
|
|
87
|
+
default: null,
|
|
88
|
+
':focus-visible': colorVars['--color-accent'],
|
|
89
|
+
},
|
|
90
|
+
outlineOffset: {default: null, ':focus-visible': '2px'},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export interface UseInputStatusIconOptions {
|
|
95
|
+
/** The input's status, or undefined when there is none. */
|
|
96
|
+
status?: InputStatus;
|
|
97
|
+
/** How the status is presented relative to the input. */
|
|
98
|
+
statusVariant?: FieldStatusVariant;
|
|
99
|
+
/** Whether the input is inside an InputGroup (which owns status rendering). */
|
|
100
|
+
isInGroup?: boolean;
|
|
101
|
+
/** Size of the on-field icon. @default 'md' */
|
|
102
|
+
size?: IconSize;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface UseInputStatusIconReturn {
|
|
106
|
+
/**
|
|
107
|
+
* The on-field status affordance to render inside the input container —
|
|
108
|
+
* the status icon (plain, for attached) or a focusable info-tip button plus
|
|
109
|
+
* its tooltip layer (for the `tooltip` variant). `null` when no icon should
|
|
110
|
+
* render (no status, `detached` variant, or inside a group).
|
|
111
|
+
*/
|
|
112
|
+
statusIcon: ReactNode;
|
|
113
|
+
/**
|
|
114
|
+
* ID to add to the input's `aria-describedby` when — and only when — the
|
|
115
|
+
* status is surfaced through a rendered tooltip layer (the `tooltip` variant
|
|
116
|
+
* with a message). It is `undefined` in every other case (no status, no
|
|
117
|
+
* message, `attached`/`detached`, or inside a group), so call-sites can drop
|
|
118
|
+
* it straight into their described-by list without a dangling reference: the
|
|
119
|
+
* id is present exactly when its tooltip element is in the DOM.
|
|
120
|
+
*/
|
|
121
|
+
describedBy: string | undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Builds the on-field status icon and its accessibility wiring for a bordered
|
|
126
|
+
* input. See the file header for the per-variant behavior.
|
|
127
|
+
*/
|
|
128
|
+
export function useInputStatusIcon({
|
|
129
|
+
status,
|
|
130
|
+
statusVariant = 'attached',
|
|
131
|
+
isInGroup = false,
|
|
132
|
+
size = 'md',
|
|
133
|
+
}: UseInputStatusIconOptions): UseInputStatusIconReturn {
|
|
134
|
+
const t = useTranslator();
|
|
135
|
+
const isTooltipVariant = statusVariant === 'tooltip';
|
|
136
|
+
const hasTooltip = isTooltipVariant && !!status?.message;
|
|
137
|
+
|
|
138
|
+
// Touch tap-to-toggle. Hover is unavailable on touch, and useTooltip
|
|
139
|
+
// suppresses simulated hover there, so on touch a tap controls visibility.
|
|
140
|
+
// `undefined` leaves the tooltip uncontrolled (pointer + keyboard drive it);
|
|
141
|
+
// a boolean takes control for the current open/close cycle.
|
|
142
|
+
const [tapOpen, setTapOpen] = useState<boolean | undefined>(undefined);
|
|
143
|
+
|
|
144
|
+
const tooltip = useTooltip({
|
|
145
|
+
placement: 'above',
|
|
146
|
+
isEnabled: hasTooltip,
|
|
147
|
+
isOpen: tapOpen,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Re-hand control back to hover/focus once a tap-opened tooltip is dismissed
|
|
151
|
+
// (Escape/outside interaction), so subsequent pointer/keyboard use behaves
|
|
152
|
+
// normally.
|
|
153
|
+
const handleButtonBlur = useCallback(() => {
|
|
154
|
+
setTapOpen(undefined);
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const handleButtonClick = useCallback(() => {
|
|
158
|
+
// Only take control on touch/hover-less devices. On hover-capable devices
|
|
159
|
+
// pointer hover and keyboard focus already drive the tooltip (uncontrolled),
|
|
160
|
+
// so a click-toggle would fight them; leave it uncontrolled there.
|
|
161
|
+
if (
|
|
162
|
+
typeof window === 'undefined' ||
|
|
163
|
+
typeof window.matchMedia !== 'function' ||
|
|
164
|
+
!window.matchMedia('(hover: none)').matches
|
|
165
|
+
) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
setTapOpen(prev => (prev === true ? false : true));
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
// Dismiss a tap-opened tooltip on Escape and return to the uncontrolled
|
|
172
|
+
// state (useTooltip owns Escape for the uncontrolled case, but while we hold
|
|
173
|
+
// control via isOpen it does not, so mirror it here).
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (tapOpen !== true) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
179
|
+
if (e.key === 'Escape') {
|
|
180
|
+
setTapOpen(undefined);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
document.addEventListener('keydown', onKeyDown);
|
|
184
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
185
|
+
}, [tapOpen]);
|
|
186
|
+
|
|
187
|
+
// Inside a group the group owns status rendering; the detached message box
|
|
188
|
+
// renders its own leading icon, so the on-field icon would duplicate it.
|
|
189
|
+
const shouldRenderIcon =
|
|
190
|
+
!!status && !isInGroup && statusVariant !== 'detached';
|
|
191
|
+
|
|
192
|
+
if (!shouldRenderIcon) {
|
|
193
|
+
return {statusIcon: null, describedBy: undefined};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const icon = (
|
|
197
|
+
<Icon icon={STATUS_ICON[status.type]} size={size} color={status.type} />
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Attached (and tooltip-without-message) variants: plain, non-interactive
|
|
201
|
+
// icon. The message box (attached) already carries the text.
|
|
202
|
+
if (!hasTooltip) {
|
|
203
|
+
return {statusIcon: icon, describedBy: undefined};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
statusIcon: (
|
|
208
|
+
<>
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
ref={tooltip.ref}
|
|
212
|
+
aria-label={t(STATUS_BUTTON_LABEL_KEY[status.type])}
|
|
213
|
+
aria-describedby={tooltip.describedBy}
|
|
214
|
+
onClick={handleButtonClick}
|
|
215
|
+
onBlur={handleButtonBlur}
|
|
216
|
+
{...stylex.props(styles.statusButton)}>
|
|
217
|
+
{icon}
|
|
218
|
+
</button>
|
|
219
|
+
{tooltip.renderTooltip(status.message)}
|
|
220
|
+
</>
|
|
221
|
+
),
|
|
222
|
+
describedBy: tooltip.describedBy,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import {render} from '@testing-library/react';
|
|
10
10
|
import {describe, expect, it} from 'vitest';
|
|
11
11
|
import {useKeyboardHint, type KeyboardHintOrientation} from './useKeyboardHint';
|
|
12
|
+
import {InternationalizationProvider} from '../i18n';
|
|
12
13
|
|
|
13
14
|
function TestHint({orientation}: {orientation?: KeyboardHintOrientation}) {
|
|
14
15
|
const {hintElement} = useKeyboardHint({orientation});
|
|
@@ -67,4 +68,25 @@ describe('useKeyboardHint', () => {
|
|
|
67
68
|
|
|
68
69
|
expect(hint.className).toContain('useKeyboardHint__styles.hint');
|
|
69
70
|
});
|
|
71
|
+
|
|
72
|
+
it('renders the "to navigate" label from the i18n catalog', () => {
|
|
73
|
+
const {container} = render(<TestHint />);
|
|
74
|
+
const hint = container.querySelector('[popover="manual"]') as HTMLElement;
|
|
75
|
+
|
|
76
|
+
expect(hint.textContent).toContain('to navigate');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('localizes the "to navigate" label through the i18n catalog', () => {
|
|
80
|
+
const {container} = render(
|
|
81
|
+
<InternationalizationProvider
|
|
82
|
+
locale="fr"
|
|
83
|
+
overrides={{fr: {'@astryx.keyboardHint.toNavigate': 'pour naviguer'}}}>
|
|
84
|
+
<TestHint />
|
|
85
|
+
</InternationalizationProvider>,
|
|
86
|
+
);
|
|
87
|
+
const hint = container.querySelector('[popover="manual"]') as HTMLElement;
|
|
88
|
+
|
|
89
|
+
expect(hint.textContent).toContain('pour naviguer');
|
|
90
|
+
expect(hint.textContent).not.toContain('to navigate');
|
|
91
|
+
});
|
|
70
92
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useKeyboardHint.tsx
|
|
7
|
-
* @input Uses React, StyleX, Kbd, useLayer
|
|
7
|
+
* @input Uses React, StyleX, Kbd, useLayer, i18n (useTranslator)
|
|
8
8
|
* @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
|
|
9
9
|
* @position Core hook; shows sighted keyboard users how to navigate composite
|
|
10
10
|
* widgets that use roving tabindex (single Tab stop, arrows inside)
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from '../theme/tokens.stylex';
|
|
28
28
|
import {Kbd} from '../Kbd';
|
|
29
29
|
import {useLayer} from '../Layer/useLayer';
|
|
30
|
+
import {useTranslator} from '../i18n';
|
|
30
31
|
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Types
|
|
@@ -179,6 +180,7 @@ export function useKeyboardHint(
|
|
|
179
180
|
isEnabled = true,
|
|
180
181
|
} = options;
|
|
181
182
|
|
|
183
|
+
const t = useTranslator();
|
|
182
184
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
183
185
|
const dismissedRef = useRef(false);
|
|
184
186
|
const isVisibleRef = useRef(false);
|
|
@@ -316,7 +318,9 @@ export function useKeyboardHint(
|
|
|
316
318
|
const hintElement = layer.render(
|
|
317
319
|
<span aria-hidden="true">
|
|
318
320
|
{arrowContent}
|
|
319
|
-
<span {...stylex.props(styles.label)}>
|
|
321
|
+
<span {...stylex.props(styles.label)}>
|
|
322
|
+
{t('@astryx.keyboardHint.toNavigate')}
|
|
323
|
+
</span>
|
|
320
324
|
</span>,
|
|
321
325
|
{
|
|
322
326
|
placement: 'below',
|
|
@@ -19,6 +19,13 @@ export const docs = {
|
|
|
19
19
|
default: "'[role=\"menuitem\"]'",
|
|
20
20
|
required: false,
|
|
21
21
|
},
|
|
22
|
+
{
|
|
23
|
+
name: 'options.boundarySelector',
|
|
24
|
+
type: 'string',
|
|
25
|
+
description:
|
|
26
|
+
"Selector identifying a list boundary, for lists that contain nested lists of the same kind (e.g. a menu with submenu flyouts). When set, item collection and key handling are scoped to this level's own container. Typically '[role=\"menu\"]'.",
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
22
29
|
{
|
|
23
30
|
name: 'options.wrap',
|
|
24
31
|
type: 'boolean',
|
|
@@ -49,8 +56,8 @@ export const docs = {
|
|
|
49
56
|
{
|
|
50
57
|
name: 'options.isRtl',
|
|
51
58
|
type: 'boolean',
|
|
52
|
-
description: 'Whether the list is in a right-to-left context. When true, ArrowLeft/ArrowRight are swapped for horizontal navigation so it follows visual direction.',
|
|
53
|
-
default: '
|
|
59
|
+
description: 'Whether the list is in a right-to-left context. When true, ArrowLeft/ArrowRight are swapped for horizontal navigation so it follows visual direction. When omitted, auto-detected from the container computed direction on keydown.',
|
|
60
|
+
default: "undefined (auto-detect from the container's computed direction)",
|
|
54
61
|
required: false,
|
|
55
62
|
},
|
|
56
63
|
{
|
|
@@ -99,6 +106,18 @@ export const docs = {
|
|
|
99
106
|
type: '() => boolean',
|
|
100
107
|
description: 'Focus the last enabled item. Returns true when an item was focused.',
|
|
101
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'ownsEvent',
|
|
111
|
+
type: '(e: React.KeyboardEvent) => boolean',
|
|
112
|
+
description:
|
|
113
|
+
"Whether a key event belongs to this list level rather than a nested list sharing the same boundarySelector. Always true when no boundarySelector is set. Use to guard consumer-added key handling (Enter/Space, typeahead).",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'getItems',
|
|
117
|
+
type: '() => HTMLElement[]',
|
|
118
|
+
description:
|
|
119
|
+
"This level's focusable items in DOM order (already scoped by boundarySelector). Build typeahead targets from this instead of re-querying.",
|
|
120
|
+
},
|
|
102
121
|
],
|
|
103
122
|
usage: {
|
|
104
123
|
description:
|
|
@@ -123,11 +142,12 @@ export const docsDense = {
|
|
|
123
142
|
paramDescriptions: {
|
|
124
143
|
options: 'config for list focus behavior. All fields optional.',
|
|
125
144
|
'options.itemSelector': 'selector for focusable items in list.',
|
|
145
|
+
'options.boundarySelector': "boundary selector for lists that contain nested lists (e.g. submenu flyouts); scopes items + key handling to this level.",
|
|
126
146
|
'options.wrap': 'whether arrow navigation wraps around at ends.',
|
|
127
147
|
'options.onEscape': 'callback when Escape key pressed (e.g. close menu).',
|
|
128
148
|
'options.orientation': "navigation orientation. 'horizontal' uses ArrowLeft/ArrowRight, 'vertical' uses ArrowUp/ArrowDown, 'both' accepts all four arrows.",
|
|
129
149
|
'options.hasHomeEnd': 'whether Home/End jump to first/last enabled item.',
|
|
130
|
-
'options.isRtl': '
|
|
150
|
+
'options.isRtl': 'ArrowLeft/ArrowRight swap for horizontal nav (RTL). default: auto-detect from container computed direction; explicit boolean wins.',
|
|
131
151
|
'options.hasRovingTabIndex': 'opt into roving-tabindex ownership: hook stamps + repairs a single tab stop across items.',
|
|
132
152
|
'options.hasCaretGuard': "when true, don't steal arrow keys from a nested text input/textarea mid-line or from a contenteditable.",
|
|
133
153
|
},
|
|
@@ -138,6 +158,8 @@ export const docsDense = {
|
|
|
138
158
|
focusItem: 'focus specific item by index (clamped to valid range).',
|
|
139
159
|
focusFirst: 'focus first enabled item; returns true when focused.',
|
|
140
160
|
focusLast: 'focus last enabled item; returns true when focused.',
|
|
161
|
+
ownsEvent: 'whether a key event is this level\'s vs a nested list\'s (boundarySelector); always true without one.',
|
|
162
|
+
getItems: "this level's focusable items in DOM order, scoped by boundarySelector.",
|
|
141
163
|
},
|
|
142
164
|
usage: {
|
|
143
165
|
description:
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file useListFocus.test.tsx
|
|
5
5
|
* @input Uses vitest, @testing-library/react, useListFocus hook
|
|
6
|
-
* @output Unit tests for useListFocus disabled-item skipping
|
|
6
|
+
* @output Unit tests for useListFocus disabled-item skipping, navigation, and
|
|
7
|
+
* RTL auto-detection
|
|
7
8
|
* @position Testing; validates useListFocus.ts keyboard navigation
|
|
8
9
|
*
|
|
9
10
|
* SYNC: When useListFocus.ts changes, update tests to match new behavior
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import {describe, it, expect} from 'vitest';
|
|
14
|
+
import type {KeyboardEvent as ReactKeyboardEvent} from 'react';
|
|
13
15
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
16
|
import {useListFocus} from './useListFocus';
|
|
15
17
|
|
|
@@ -373,3 +375,153 @@ describe('useListFocus shortcut passthrough', () => {
|
|
|
373
375
|
expect(screen.getByTestId('A')).toHaveFocus();
|
|
374
376
|
});
|
|
375
377
|
});
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* A horizontal list (menubar-like) for RTL direction tests. jsdom reflects the
|
|
381
|
+
* `dir` attribute into computed style only on the element that carries it, so
|
|
382
|
+
* `dir` is set on the list container itself — the element the hook reads via
|
|
383
|
+
* listRef.
|
|
384
|
+
*/
|
|
385
|
+
function HorizontalMenu({dir, isRtl}: {dir?: 'ltr' | 'rtl'; isRtl?: boolean}) {
|
|
386
|
+
const {listRef, handleKeyDown} = useListFocus<HTMLDivElement>({
|
|
387
|
+
orientation: 'horizontal',
|
|
388
|
+
isRtl,
|
|
389
|
+
});
|
|
390
|
+
const items = ['One', 'Two', 'Three'];
|
|
391
|
+
return (
|
|
392
|
+
<div ref={listRef} role="menu" dir={dir} onKeyDown={handleKeyDown}>
|
|
393
|
+
{items.map(label => (
|
|
394
|
+
<div key={label} role="menuitem" tabIndex={-1} data-testid={label}>
|
|
395
|
+
{label}
|
|
396
|
+
</div>
|
|
397
|
+
))}
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
describe('useListFocus RTL auto-detection (WCAG 1.3.2)', () => {
|
|
403
|
+
it('auto-detects dir="rtl": ArrowLeft moves to the next item', () => {
|
|
404
|
+
render(<HorizontalMenu dir="rtl" />);
|
|
405
|
+
const menu = screen.getByRole('menu');
|
|
406
|
+
screen.getByTestId('One').focus();
|
|
407
|
+
fireEvent.keyDown(menu, {key: 'ArrowLeft'});
|
|
408
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('auto-detects dir="rtl": ArrowRight moves to the previous item', () => {
|
|
412
|
+
render(<HorizontalMenu dir="rtl" />);
|
|
413
|
+
const menu = screen.getByRole('menu');
|
|
414
|
+
screen.getByTestId('Two').focus();
|
|
415
|
+
fireEvent.keyDown(menu, {key: 'ArrowRight'});
|
|
416
|
+
expect(screen.getByTestId('One')).toHaveFocus();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('stays LTR without a direction: ArrowRight moves to the next item', () => {
|
|
420
|
+
render(<HorizontalMenu />);
|
|
421
|
+
const menu = screen.getByRole('menu');
|
|
422
|
+
screen.getByTestId('One').focus();
|
|
423
|
+
fireEvent.keyDown(menu, {key: 'ArrowRight'});
|
|
424
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('explicit isRtl={false} overrides a dir="rtl" container', () => {
|
|
428
|
+
render(<HorizontalMenu dir="rtl" isRtl={false} />);
|
|
429
|
+
const menu = screen.getByRole('menu');
|
|
430
|
+
screen.getByTestId('One').focus();
|
|
431
|
+
fireEvent.keyDown(menu, {key: 'ArrowRight'});
|
|
432
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('explicit isRtl={true} flips arrows without a dir attribute', () => {
|
|
436
|
+
render(<HorizontalMenu isRtl />);
|
|
437
|
+
const menu = screen.getByRole('menu');
|
|
438
|
+
screen.getByTestId('One').focus();
|
|
439
|
+
fireEvent.keyDown(menu, {key: 'ArrowLeft'});
|
|
440
|
+
expect(screen.getByTestId('Two')).toHaveFocus();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// A menu whose second item contains a NESTED role="menu" (mirrors an inline
|
|
445
|
+
// submenu flyout). Exercises boundarySelector: item scoping + event ownership.
|
|
446
|
+
function NestedMenu() {
|
|
447
|
+
const {listRef, handleKeyDown, ownsEvent} = useListFocus<HTMLDivElement>({
|
|
448
|
+
boundarySelector: '[role="menu"]',
|
|
449
|
+
wrap: false,
|
|
450
|
+
});
|
|
451
|
+
return (
|
|
452
|
+
<div ref={listRef} role="menu" onKeyDown={handleKeyDown}>
|
|
453
|
+
<div role="menuitem" tabIndex={-1} data-testid="Outer1">
|
|
454
|
+
Outer1
|
|
455
|
+
</div>
|
|
456
|
+
<div role="menuitem" tabIndex={-1} data-testid="Outer2">
|
|
457
|
+
Outer2
|
|
458
|
+
{/* Nested submenu rendered inline (like a popover flyout). */}
|
|
459
|
+
<div role="menu" data-testid="inner-menu">
|
|
460
|
+
<div role="menuitem" tabIndex={-1} data-testid="Inner1">
|
|
461
|
+
Inner1
|
|
462
|
+
</div>
|
|
463
|
+
<div role="menuitem" tabIndex={-1} data-testid="Inner2">
|
|
464
|
+
Inner2
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
<div role="menuitem" tabIndex={-1} data-testid="Outer3">
|
|
469
|
+
Outer3
|
|
470
|
+
</div>
|
|
471
|
+
{/* Surface ownership for assertions. */}
|
|
472
|
+
<input
|
|
473
|
+
data-testid="probe-owns"
|
|
474
|
+
onKeyDown={e => {
|
|
475
|
+
e.currentTarget.setAttribute('data-owns', String(ownsEvent(e)));
|
|
476
|
+
}}
|
|
477
|
+
/>
|
|
478
|
+
</div>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Variant with the ownsEvent probe placed INSIDE the nested menu, so a key
|
|
483
|
+
// event from it reports as not-owned by the outer list.
|
|
484
|
+
function NestedMenuWithInnerProbe() {
|
|
485
|
+
const {listRef, handleKeyDown, ownsEvent} = useListFocus<HTMLDivElement>({
|
|
486
|
+
boundarySelector: '[role="menu"]',
|
|
487
|
+
wrap: false,
|
|
488
|
+
});
|
|
489
|
+
const report = (e: ReactKeyboardEvent<HTMLInputElement>) => {
|
|
490
|
+
e.currentTarget.setAttribute('data-owns', String(ownsEvent(e)));
|
|
491
|
+
};
|
|
492
|
+
return (
|
|
493
|
+
<div ref={listRef} role="menu" onKeyDown={handleKeyDown}>
|
|
494
|
+
<div role="menuitem" tabIndex={-1} data-testid="Outer1">
|
|
495
|
+
Outer1
|
|
496
|
+
</div>
|
|
497
|
+
<div role="menu" data-testid="inner-menu">
|
|
498
|
+
<input data-testid="inner-probe" onKeyDown={report} />
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
describe('useListFocus boundarySelector (nested lists)', () => {
|
|
505
|
+
it("ArrowDown skips over a nested list's items to the next own item", () => {
|
|
506
|
+
render(<NestedMenu />);
|
|
507
|
+
const menu = screen.getAllByRole('menu')[0];
|
|
508
|
+
screen.getByTestId('Outer2').focus();
|
|
509
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
510
|
+
// Lands on Outer3, not Inner1 (which is inside the nested menu).
|
|
511
|
+
expect(screen.getByTestId('Outer3')).toHaveFocus();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('ownsEvent is true for an event originating at this level', () => {
|
|
515
|
+
render(<NestedMenu />);
|
|
516
|
+
const ownProbe = screen.getByTestId('probe-owns');
|
|
517
|
+
fireEvent.keyDown(ownProbe, {key: 'ArrowDown'});
|
|
518
|
+
expect(ownProbe).toHaveAttribute('data-owns', 'true');
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('ownsEvent is false for an event from inside a nested list', () => {
|
|
522
|
+
render(<NestedMenuWithInnerProbe />);
|
|
523
|
+
const innerProbe = screen.getByTestId('inner-probe');
|
|
524
|
+
fireEvent.keyDown(innerProbe, {key: 'ArrowDown'});
|
|
525
|
+
expect(innerProbe).toHaveAttribute('data-owns', 'false');
|
|
526
|
+
});
|
|
527
|
+
});
|