@astryxdesign/core 0.1.9 → 0.2.0-canary.0e01213
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/Center/Center.d.ts +20 -3
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +10 -2
- 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.d.ts +7 -0
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +16 -5
- 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 +18 -12
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +42 -64
- 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 +10 -3
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +435 -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 +24 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +34 -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 +27 -10
- 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 +51 -5
- 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/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +3 -2
- 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 +34 -15
- 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 +87 -45
- 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 +72 -19
- 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 +18 -3
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +58 -30
- 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 +11 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +65 -25
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Text/Text.d.ts +10 -1
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +25 -2
- package/dist/Text/index.d.ts +2 -2
- package/dist/Text/index.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 +119 -52
- 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.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +51 -45
- 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 +48 -77
- 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/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +9 -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 +115 -35
- package/dist/astryx.umd.js +51 -51
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/containerReveal.pool.stylex.d.ts +281 -0
- package/dist/hooks/containerReveal.pool.stylex.d.ts.map +1 -0
- package/dist/hooks/containerReveal.pool.stylex.js +281 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -1
- 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/useContainerReveal.d.ts +83 -0
- package/dist/hooks/useContainerReveal.d.ts.map +1 -0
- package/dist/hooks/useContainerReveal.js +123 -0
- 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 +169 -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/defineTheme.d.ts +6 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/theme/expandColorScale.d.ts +19 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +38 -10
- package/dist/theme/index.d.ts +3 -3
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/types.d.ts +39 -2
- package/dist/theme/types.d.ts.map +1 -1
- 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 +120 -0
- package/locales/pseudo.json +90 -0
- package/package.json +8 -3
- 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/Center/Center.doc.mjs +38 -0
- package/src/Center/Center.test.tsx +75 -0
- package/src/Center/Center.tsx +37 -2
- 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.test.tsx +57 -0
- package/src/Chat/ChatLayout.tsx +26 -5
- 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.doc.mjs +5 -2
- package/src/DropdownMenu/DropdownMenu.test.tsx +95 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +49 -66
- 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 +19 -4
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +84 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +680 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +569 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +40 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +40 -14
- package/src/Field/Field.doc.mjs +4 -0
- 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.doc.mjs +1 -0
- package/src/FieldStatus/FieldStatus.test.tsx +133 -0
- package/src/FieldStatus/FieldStatus.tsx +61 -4
- 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/Heading/Heading.tsx +2 -1
- 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.test.tsx +33 -0
- package/src/Lightbox/Lightbox.tsx +30 -10
- 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 +436 -0
- package/src/MultiSelector/MultiSelector.tsx +133 -67
- 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 +400 -0
- package/src/Selector/Selector.tsx +95 -23
- 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 +48 -21
- 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.test.tsx +161 -2
- package/src/Table/plugins/tree/useTableTreeData.tsx +71 -14
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowExpansion.doc.mjs +55 -2
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/Table/useTableTreeData.doc.mjs +9 -0
- package/src/Text/Heading.doc.mjs +3 -3
- package/src/Text/Text.doc.mjs +1 -1
- package/src/Text/Text.test.tsx +42 -0
- package/src/Text/Text.tsx +25 -1
- package/src/Text/index.ts +2 -6
- package/src/TextArea/TextArea.doc.mjs +23 -2
- package/src/TextArea/TextArea.test.tsx +132 -4
- package/src/TextArea/TextArea.tsx +161 -67
- 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.test.tsx +24 -1
- package/src/Thumbnail/Thumbnail.tsx +28 -35
- 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/TopNavMegaMenu.tsx +26 -2
- 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__/babelPluginAddExtensions.test.ts +190 -0
- 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/containerReveal.pool.stylex.ts +464 -0
- package/src/hooks/index.ts +13 -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/useContainerReveal.test.tsx +116 -0
- package/src/hooks/useContainerReveal.ts +180 -0
- 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 +221 -0
- package/src/hooks/useInputStatusIcon.tsx +235 -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/defineTheme.ts +6 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/expandColorScale.test.ts +144 -0
- package/src/theme/expandColorScale.ts +53 -15
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/theme/index.ts +4 -8
- package/src/theme/types.ts +43 -13
- 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
- package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +0 -9
- package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +0 -1
- package/dist/Thumbnail/thumbnail.markers.stylex.js +0 -14
- package/src/Thumbnail/thumbnail.markers.stylex.ts +0 -12
|
@@ -23,10 +23,12 @@ const NO_DISABLED: number[] = [];
|
|
|
23
23
|
function Grid({
|
|
24
24
|
disabled = NO_DISABLED,
|
|
25
25
|
seed = 0,
|
|
26
|
+
dir,
|
|
26
27
|
...opts
|
|
27
28
|
}: {
|
|
28
29
|
disabled?: number[];
|
|
29
30
|
seed?: number;
|
|
31
|
+
dir?: 'ltr' | 'rtl';
|
|
30
32
|
} & Partial<Parameters<typeof useGridFocus>[0]>) {
|
|
31
33
|
const {gridRef, handleKeyDown, handleFocus} = useGridFocus<HTMLDivElement>({
|
|
32
34
|
columns: 3,
|
|
@@ -41,6 +43,7 @@ function Grid({
|
|
|
41
43
|
<div
|
|
42
44
|
ref={gridRef}
|
|
43
45
|
role="grid"
|
|
46
|
+
dir={dir}
|
|
44
47
|
onKeyDown={handleKeyDown}
|
|
45
48
|
onFocus={handleFocus}>
|
|
46
49
|
{Array.from({length: 9}, (_, i) => (
|
|
@@ -130,3 +133,41 @@ describe('useGridFocus roving tabindex (hasRovingTabIndex)', () => {
|
|
|
130
133
|
expect(screen.getByTestId('cell-1')).toHaveAttribute('tabindex', '-1');
|
|
131
134
|
});
|
|
132
135
|
});
|
|
136
|
+
|
|
137
|
+
describe('useGridFocus RTL auto-detection (WCAG 1.3.2)', () => {
|
|
138
|
+
// jsdom reflects the `dir` attribute into computed style only on the element
|
|
139
|
+
// that carries it, so tests set dir="rtl" on the grid container itself — the
|
|
140
|
+
// element the hook reads via gridRef.
|
|
141
|
+
|
|
142
|
+
it('auto-detects dir="rtl": ArrowLeft moves forward (next cell)', () => {
|
|
143
|
+
render(<Grid seed={1} dir="rtl" />);
|
|
144
|
+
const grid = screen.getByRole('grid');
|
|
145
|
+
screen.getByTestId('cell-1').focus();
|
|
146
|
+
fireEvent.keyDown(grid, {key: 'ArrowLeft'});
|
|
147
|
+
expect(screen.getByTestId('cell-2')).toHaveFocus();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('auto-detects dir="rtl": ArrowRight moves backward (previous cell)', () => {
|
|
151
|
+
render(<Grid seed={1} dir="rtl" />);
|
|
152
|
+
const grid = screen.getByRole('grid');
|
|
153
|
+
screen.getByTestId('cell-1').focus();
|
|
154
|
+
fireEvent.keyDown(grid, {key: 'ArrowRight'});
|
|
155
|
+
expect(screen.getByTestId('cell-0')).toHaveFocus();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('leaves vertical navigation unaffected under dir="rtl"', () => {
|
|
159
|
+
render(<Grid seed={0} dir="rtl" />);
|
|
160
|
+
const grid = screen.getByRole('grid');
|
|
161
|
+
screen.getByTestId('cell-0').focus();
|
|
162
|
+
fireEvent.keyDown(grid, {key: 'ArrowDown'});
|
|
163
|
+
expect(screen.getByTestId('cell-3')).toHaveFocus();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('explicit isRtl={false} overrides a dir="rtl" container', () => {
|
|
167
|
+
render(<Grid seed={1} dir="rtl" isRtl={false} />);
|
|
168
|
+
const grid = screen.getByRole('grid');
|
|
169
|
+
screen.getByTestId('cell-1').focus();
|
|
170
|
+
fireEvent.keyDown(grid, {key: 'ArrowRight'});
|
|
171
|
+
expect(screen.getByTestId('cell-2')).toHaveFocus();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useGridFocus.ts
|
|
7
|
-
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
|
|
7
|
+
* @input Uses React useCallback, useRef, useIsomorphicLayoutEffect,
|
|
8
|
+
* isRtlElement
|
|
8
9
|
* @output Exports useGridFocus hook for grid keyboard navigation
|
|
9
|
-
* @position Core hook; used by Calendar for date grid navigation
|
|
10
|
+
* @position Core hook; used by Calendar for date grid navigation. Auto-detects
|
|
11
|
+
* RTL from the container's computed direction so horizontal arrow keys
|
|
12
|
+
* follow visual direction (WCAG 1.3.2).
|
|
10
13
|
*
|
|
11
14
|
* SYNC: When modified, update:
|
|
12
15
|
* - /packages/core/src/hooks/index.ts
|
|
@@ -14,6 +17,7 @@
|
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import {useCallback, useRef} from 'react';
|
|
20
|
+
import {isRtlElement} from './isRtlElement';
|
|
17
21
|
import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -88,7 +92,12 @@ export interface UseGridFocusOptions {
|
|
|
88
92
|
/**
|
|
89
93
|
* Whether the grid is in a right-to-left context. When true, ArrowLeft and
|
|
90
94
|
* ArrowRight are swapped so horizontal navigation follows visual direction.
|
|
91
|
-
*
|
|
95
|
+
*
|
|
96
|
+
* When omitted, the direction is auto-detected from the container's computed
|
|
97
|
+
* `direction` (read lazily on keydown, only for horizontal arrow keys), so
|
|
98
|
+
* grids inside `dir="rtl"` subtrees flip automatically. Pass an explicit
|
|
99
|
+
* boolean to override detection.
|
|
100
|
+
* @default undefined (auto-detect from the container)
|
|
92
101
|
*/
|
|
93
102
|
isRtl?: boolean;
|
|
94
103
|
|
|
@@ -217,7 +226,7 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
217
226
|
onNavigateAfter,
|
|
218
227
|
onPageUp,
|
|
219
228
|
onPageDown,
|
|
220
|
-
isRtl
|
|
229
|
+
isRtl,
|
|
221
230
|
hasRovingTabIndex = false,
|
|
222
231
|
} = options;
|
|
223
232
|
|
|
@@ -455,13 +464,15 @@ export function useGridFocus<T extends HTMLElement = HTMLElement>(
|
|
|
455
464
|
|
|
456
465
|
// In RTL, ArrowLeft/ArrowRight are swapped so horizontal navigation
|
|
457
466
|
// follows visual direction. Vertical keys (Up/Down) are unaffected.
|
|
467
|
+
// Direction is resolved lazily — getComputedStyle runs only when a
|
|
468
|
+
// horizontal arrow key is actually pressed (SSR-safe, no layout thrash
|
|
469
|
+
// on unrelated keys) — and an explicit `isRtl` always wins.
|
|
458
470
|
let key = e.key;
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
471
|
+
if (
|
|
472
|
+
(key === 'ArrowLeft' || key === 'ArrowRight') &&
|
|
473
|
+
(isRtl ?? isRtlElement(gridRef.current))
|
|
474
|
+
) {
|
|
475
|
+
key = key === 'ArrowLeft' ? 'ArrowRight' : 'ArrowLeft';
|
|
465
476
|
}
|
|
466
477
|
|
|
467
478
|
switch (key) {
|
package/src/hooks/useHotkeys.ts
CHANGED
|
@@ -155,6 +155,15 @@ function matchesCombo(
|
|
|
155
155
|
* definitions live in a ref, so re-renders update behavior without
|
|
156
156
|
* re-subscribing. First matching hotkey wins per event.
|
|
157
157
|
*
|
|
158
|
+
* Accessibility (WCAG 2.1.4 — Character Key Shortcuts): a shortcut whose
|
|
159
|
+
* combo is a single unmodified character (letter, digit, punctuation or
|
|
160
|
+
* symbol — e.g. 'c' or '/') is easily triggered by accident by speech-input
|
|
161
|
+
* users and keyboard users with motor impairments. If you register one, you
|
|
162
|
+
* MUST also provide at least one of: (a) a way to turn the shortcut off,
|
|
163
|
+
* (b) a way to remap it to include a modifier, or (c) scope it so it is only
|
|
164
|
+
* active while the relevant component has focus. Prefer `mod`-based combos
|
|
165
|
+
* where possible; the `isDisabled` flag can back a user-facing off switch.
|
|
166
|
+
*
|
|
158
167
|
* @param hotkeys - Shortcut registrations to listen for.
|
|
159
168
|
*
|
|
160
169
|
* @example
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import type {ReactElement} from 'react';
|
|
5
|
+
import {render, screen} from '@testing-library/react';
|
|
6
|
+
import {TextInput} from '../TextInput/TextInput';
|
|
7
|
+
import {TextArea} from '../TextArea/TextArea';
|
|
8
|
+
import {NumberInput} from '../NumberInput/NumberInput';
|
|
9
|
+
import {DateInput} from '../DateInput/DateInput';
|
|
10
|
+
import {DateRangeInput} from '../DateRangeInput/DateRangeInput';
|
|
11
|
+
import {TimeInput} from '../TimeInput/TimeInput';
|
|
12
|
+
import {FileInput} from '../FileInput/FileInput';
|
|
13
|
+
import type {FieldStatusVariant} from '../FieldStatus/FieldStatus';
|
|
14
|
+
|
|
15
|
+
// Every id referenced by an aria-describedby must resolve to an element in the
|
|
16
|
+
// document (WCAG 1.3.1) — a described-by pointing at a non-rendered node is a
|
|
17
|
+
// dangling reference. The tooltip variant is the interesting case: it swaps the
|
|
18
|
+
// message box for a tooltip layer, so the referenced ids change per variant.
|
|
19
|
+
function expectNoDanglingDescribedBy(root: HTMLElement) {
|
|
20
|
+
for (const el of Array.from(root.querySelectorAll('[aria-describedby]'))) {
|
|
21
|
+
const value = el.getAttribute('aria-describedby') ?? '';
|
|
22
|
+
for (const id of value.split(/\s+/).filter(Boolean)) {
|
|
23
|
+
expect(
|
|
24
|
+
document.getElementById(id),
|
|
25
|
+
`dangling aria-describedby id "${id}" on <${el.tagName.toLowerCase()}>`,
|
|
26
|
+
).not.toBeNull();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const VARIANTS: FieldStatusVariant[] = ['attached', 'detached', 'tooltip'];
|
|
32
|
+
|
|
33
|
+
const INPUTS: [string, (variant: FieldStatusVariant) => ReactElement][] = [
|
|
34
|
+
[
|
|
35
|
+
'TextInput',
|
|
36
|
+
v => (
|
|
37
|
+
<TextInput
|
|
38
|
+
label="Field"
|
|
39
|
+
value=""
|
|
40
|
+
onChange={() => {}}
|
|
41
|
+
status={{type: 'error', message: 'Message'}}
|
|
42
|
+
statusVariant={v}
|
|
43
|
+
/>
|
|
44
|
+
),
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
'TextArea',
|
|
48
|
+
v => (
|
|
49
|
+
<TextArea
|
|
50
|
+
label="Field"
|
|
51
|
+
value=""
|
|
52
|
+
onChange={() => {}}
|
|
53
|
+
status={{type: 'error', message: 'Message'}}
|
|
54
|
+
statusVariant={v}
|
|
55
|
+
/>
|
|
56
|
+
),
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
'NumberInput',
|
|
60
|
+
v => (
|
|
61
|
+
<NumberInput
|
|
62
|
+
label="Field"
|
|
63
|
+
value={undefined}
|
|
64
|
+
onChange={() => {}}
|
|
65
|
+
status={{type: 'error', message: 'Message'}}
|
|
66
|
+
statusVariant={v}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
'DateInput',
|
|
72
|
+
v => (
|
|
73
|
+
<DateInput
|
|
74
|
+
label="Field"
|
|
75
|
+
value={undefined}
|
|
76
|
+
onChange={() => {}}
|
|
77
|
+
status={{type: 'error', message: 'Message'}}
|
|
78
|
+
statusVariant={v}
|
|
79
|
+
/>
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
'DateRangeInput',
|
|
84
|
+
v => (
|
|
85
|
+
<DateRangeInput
|
|
86
|
+
label="Field"
|
|
87
|
+
value={null}
|
|
88
|
+
onChange={() => {}}
|
|
89
|
+
status={{type: 'error', message: 'Message'}}
|
|
90
|
+
statusVariant={v}
|
|
91
|
+
/>
|
|
92
|
+
),
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
'TimeInput',
|
|
96
|
+
v => (
|
|
97
|
+
<TimeInput
|
|
98
|
+
label="Field"
|
|
99
|
+
value={undefined}
|
|
100
|
+
onChange={() => {}}
|
|
101
|
+
status={{type: 'error', message: 'Message'}}
|
|
102
|
+
statusVariant={v}
|
|
103
|
+
/>
|
|
104
|
+
),
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
'FileInput',
|
|
108
|
+
v => (
|
|
109
|
+
<FileInput
|
|
110
|
+
label="Field"
|
|
111
|
+
value={null}
|
|
112
|
+
onChange={() => {}}
|
|
113
|
+
status={{type: 'error', message: 'Message'}}
|
|
114
|
+
statusVariant={v}
|
|
115
|
+
/>
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
describe('useInputStatusIcon — no dangling aria-describedby (WCAG 1.3.1)', () => {
|
|
121
|
+
for (const [name, make] of INPUTS) {
|
|
122
|
+
for (const variant of VARIANTS) {
|
|
123
|
+
it(`${name} / statusVariant="${variant}"`, () => {
|
|
124
|
+
const {container} = render(make(variant));
|
|
125
|
+
expectNoDanglingDescribedBy(container);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
it('tooltip variant links the input to the rendered tooltip element', () => {
|
|
131
|
+
render(
|
|
132
|
+
<TextInput
|
|
133
|
+
label="Field"
|
|
134
|
+
value=""
|
|
135
|
+
onChange={() => {}}
|
|
136
|
+
status={{type: 'error', message: 'Message'}}
|
|
137
|
+
statusVariant="tooltip"
|
|
138
|
+
/>,
|
|
139
|
+
);
|
|
140
|
+
const input = screen.getByRole('textbox');
|
|
141
|
+
const tooltip = screen.getByRole('tooltip', {hidden: true});
|
|
142
|
+
expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('does not render a tooltip (or reference one) when there is no message', () => {
|
|
146
|
+
// No message → the tooltip variant renders no tooltip layer and no
|
|
147
|
+
// described-by should point at a non-existent status element.
|
|
148
|
+
const {container} = render(
|
|
149
|
+
<TextInput
|
|
150
|
+
label="Field"
|
|
151
|
+
value=""
|
|
152
|
+
onChange={() => {}}
|
|
153
|
+
status={{type: 'error'}}
|
|
154
|
+
statusVariant="tooltip"
|
|
155
|
+
/>,
|
|
156
|
+
);
|
|
157
|
+
expect(
|
|
158
|
+
screen.queryByRole('tooltip', {hidden: true}),
|
|
159
|
+
).not.toBeInTheDocument();
|
|
160
|
+
expectNoDanglingDescribedBy(container);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('useInputStatusIcon — input-status-icon theme target', () => {
|
|
165
|
+
// The stable theme target lands on the on-field status glyph itself, so a
|
|
166
|
+
// theme can restyle (e.g. resize) just this icon via `defineTheme`. It is
|
|
167
|
+
// rendered by the attached and tooltip variants; detached suppresses the
|
|
168
|
+
// on-field icon in favour of the detached message box's leading glyph.
|
|
169
|
+
const getStatusIcon = (root: HTMLElement): HTMLElement => {
|
|
170
|
+
const icon = root.querySelector('.astryx-input-status-icon');
|
|
171
|
+
if (icon == null) {
|
|
172
|
+
throw new Error('status icon not found');
|
|
173
|
+
}
|
|
174
|
+
return icon as HTMLElement;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
for (const variant of ['attached', 'tooltip'] as const) {
|
|
178
|
+
it(`renders the target on the on-field icon (statusVariant="${variant}")`, () => {
|
|
179
|
+
const {container} = render(
|
|
180
|
+
<TextInput
|
|
181
|
+
label="Field"
|
|
182
|
+
value=""
|
|
183
|
+
onChange={() => {}}
|
|
184
|
+
status={{type: 'warning', message: 'Message'}}
|
|
185
|
+
statusVariant={variant}
|
|
186
|
+
/>,
|
|
187
|
+
);
|
|
188
|
+
const icon = getStatusIcon(container);
|
|
189
|
+
expect(icon).toHaveClass('astryx-input-status-icon');
|
|
190
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
191
|
+
expect(icon).toHaveAttribute('data-size', 'md');
|
|
192
|
+
expect(icon).toHaveAttribute('data-status', 'warning');
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
it('reflects the status type per status', () => {
|
|
197
|
+
const {container} = render(
|
|
198
|
+
<TextInput
|
|
199
|
+
label="Field"
|
|
200
|
+
value=""
|
|
201
|
+
onChange={() => {}}
|
|
202
|
+
status={{type: 'success', message: 'Message'}}
|
|
203
|
+
statusVariant="attached"
|
|
204
|
+
/>,
|
|
205
|
+
);
|
|
206
|
+
expect(getStatusIcon(container)).toHaveAttribute('data-status', 'success');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('does not render the on-field target for the detached variant', () => {
|
|
210
|
+
const {container} = render(
|
|
211
|
+
<TextInput
|
|
212
|
+
label="Field"
|
|
213
|
+
value=""
|
|
214
|
+
onChange={() => {}}
|
|
215
|
+
status={{type: 'error', message: 'Message'}}
|
|
216
|
+
statusVariant="detached"
|
|
217
|
+
/>,
|
|
218
|
+
);
|
|
219
|
+
expect(container.querySelector('.astryx-input-status-icon')).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
import {themeProps} from '../utils/themeProps';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Maps each status type to its glyph. Shared so every input shows the same icon
|
|
48
|
+
* for a given status, matching the detached message box's leading icon.
|
|
49
|
+
*/
|
|
50
|
+
const STATUS_ICON: Record<InputStatusType, IconName> = {
|
|
51
|
+
warning: 'warning',
|
|
52
|
+
error: 'error',
|
|
53
|
+
success: 'success',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Accessible-name i18n keys for the focusable status button, keyed by type.
|
|
58
|
+
*/
|
|
59
|
+
const STATUS_BUTTON_LABEL_KEY: Record<InputStatusType, string> = {
|
|
60
|
+
warning: '@astryx.input.statusButton.warning',
|
|
61
|
+
error: '@astryx.input.statusButton.error',
|
|
62
|
+
success: '@astryx.input.statusButton.success',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const styles = stylex.create({
|
|
66
|
+
// Contain the anchor so the tooltip positions against the icon, and keep the
|
|
67
|
+
// glyph vertically centered within the input control.
|
|
68
|
+
iconAnchor: {
|
|
69
|
+
display: 'inline-flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
},
|
|
72
|
+
// The tooltip-variant status affordance is a real button so it is keyboard
|
|
73
|
+
// focusable and tappable. Strip the native chrome and show only a focus ring.
|
|
74
|
+
statusButton: {
|
|
75
|
+
display: 'inline-flex',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
padding: 0,
|
|
79
|
+
margin: 0,
|
|
80
|
+
border: 'none',
|
|
81
|
+
background: 'none',
|
|
82
|
+
color: 'inherit',
|
|
83
|
+
cursor: 'pointer',
|
|
84
|
+
borderRadius: radiusVars['--radius-full'],
|
|
85
|
+
outlineWidth: {default: null, ':focus-visible': '2px'},
|
|
86
|
+
outlineStyle: {default: null, ':focus-visible': 'solid'},
|
|
87
|
+
outlineColor: {
|
|
88
|
+
default: null,
|
|
89
|
+
':focus-visible': colorVars['--color-accent'],
|
|
90
|
+
},
|
|
91
|
+
outlineOffset: {default: null, ':focus-visible': '2px'},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export interface UseInputStatusIconOptions {
|
|
96
|
+
/** The input's status, or undefined when there is none. */
|
|
97
|
+
status?: InputStatus;
|
|
98
|
+
/** How the status is presented relative to the input. */
|
|
99
|
+
statusVariant?: FieldStatusVariant;
|
|
100
|
+
/** Whether the input is inside an InputGroup (which owns status rendering). */
|
|
101
|
+
isInGroup?: boolean;
|
|
102
|
+
/** Size of the on-field icon. @default 'md' */
|
|
103
|
+
size?: IconSize;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface UseInputStatusIconReturn {
|
|
107
|
+
/**
|
|
108
|
+
* The on-field status affordance to render inside the input container —
|
|
109
|
+
* the status icon (plain, for attached) or a focusable info-tip button plus
|
|
110
|
+
* its tooltip layer (for the `tooltip` variant). `null` when no icon should
|
|
111
|
+
* render (no status, `detached` variant, or inside a group).
|
|
112
|
+
*/
|
|
113
|
+
statusIcon: ReactNode;
|
|
114
|
+
/**
|
|
115
|
+
* ID to add to the input's `aria-describedby` when — and only when — the
|
|
116
|
+
* status is surfaced through a rendered tooltip layer (the `tooltip` variant
|
|
117
|
+
* with a message). It is `undefined` in every other case (no status, no
|
|
118
|
+
* message, `attached`/`detached`, or inside a group), so call-sites can drop
|
|
119
|
+
* it straight into their described-by list without a dangling reference: the
|
|
120
|
+
* id is present exactly when its tooltip element is in the DOM.
|
|
121
|
+
*/
|
|
122
|
+
describedBy: string | undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Builds the on-field status icon and its accessibility wiring for a bordered
|
|
127
|
+
* input. See the file header for the per-variant behavior.
|
|
128
|
+
*/
|
|
129
|
+
export function useInputStatusIcon({
|
|
130
|
+
status,
|
|
131
|
+
statusVariant = 'attached',
|
|
132
|
+
isInGroup = false,
|
|
133
|
+
size = 'md',
|
|
134
|
+
}: UseInputStatusIconOptions): UseInputStatusIconReturn {
|
|
135
|
+
const t = useTranslator();
|
|
136
|
+
const isTooltipVariant = statusVariant === 'tooltip';
|
|
137
|
+
const hasTooltip = isTooltipVariant && !!status?.message;
|
|
138
|
+
|
|
139
|
+
// Touch tap-to-toggle. Hover is unavailable on touch, and useTooltip
|
|
140
|
+
// suppresses simulated hover there, so on touch a tap controls visibility.
|
|
141
|
+
// `undefined` leaves the tooltip uncontrolled (pointer + keyboard drive it);
|
|
142
|
+
// a boolean takes control for the current open/close cycle.
|
|
143
|
+
const [tapOpen, setTapOpen] = useState<boolean | undefined>(undefined);
|
|
144
|
+
|
|
145
|
+
const tooltip = useTooltip({
|
|
146
|
+
placement: 'above',
|
|
147
|
+
isEnabled: hasTooltip,
|
|
148
|
+
isOpen: tapOpen,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Re-hand control back to hover/focus once a tap-opened tooltip is dismissed
|
|
152
|
+
// (Escape/outside interaction), so subsequent pointer/keyboard use behaves
|
|
153
|
+
// normally.
|
|
154
|
+
const handleButtonBlur = useCallback(() => {
|
|
155
|
+
setTapOpen(undefined);
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
const handleButtonClick = useCallback(() => {
|
|
159
|
+
// Only take control on touch/hover-less devices. On hover-capable devices
|
|
160
|
+
// pointer hover and keyboard focus already drive the tooltip (uncontrolled),
|
|
161
|
+
// so a click-toggle would fight them; leave it uncontrolled there.
|
|
162
|
+
if (
|
|
163
|
+
typeof window === 'undefined' ||
|
|
164
|
+
typeof window.matchMedia !== 'function' ||
|
|
165
|
+
!window.matchMedia('(hover: none)').matches
|
|
166
|
+
) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
setTapOpen(prev => (prev === true ? false : true));
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
// Dismiss a tap-opened tooltip on Escape and return to the uncontrolled
|
|
173
|
+
// state (useTooltip owns Escape for the uncontrolled case, but while we hold
|
|
174
|
+
// control via isOpen it does not, so mirror it here).
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (tapOpen !== true) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
180
|
+
if (e.key === 'Escape') {
|
|
181
|
+
setTapOpen(undefined);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
document.addEventListener('keydown', onKeyDown);
|
|
185
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
186
|
+
}, [tapOpen]);
|
|
187
|
+
|
|
188
|
+
// Inside a group the group owns status rendering; the detached message box
|
|
189
|
+
// renders its own leading icon, so the on-field icon would duplicate it.
|
|
190
|
+
const shouldRenderIcon =
|
|
191
|
+
!!status && !isInGroup && statusVariant !== 'detached';
|
|
192
|
+
|
|
193
|
+
if (!shouldRenderIcon) {
|
|
194
|
+
return {statusIcon: null, describedBy: undefined};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const icon = (
|
|
198
|
+
<Icon
|
|
199
|
+
icon={STATUS_ICON[status.type]}
|
|
200
|
+
size={size}
|
|
201
|
+
color={status.type}
|
|
202
|
+
// Stable theme target on the status glyph itself, so a theme can restyle
|
|
203
|
+
// just this icon (color, size) — and each status — via `defineTheme`.
|
|
204
|
+
// Same-element rules in @layer astryx-theme win over the icon's own base
|
|
205
|
+
// width/height/fontSize, which a field-level target could not reach.
|
|
206
|
+
// Shared by the attached and tooltip variants across all bordered inputs.
|
|
207
|
+
{...themeProps('input-status-icon', {size, status: status.type})}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Attached (and tooltip-without-message) variants: plain, non-interactive
|
|
212
|
+
// icon. The message box (attached) already carries the text.
|
|
213
|
+
if (!hasTooltip) {
|
|
214
|
+
return {statusIcon: icon, describedBy: undefined};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
statusIcon: (
|
|
219
|
+
<>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
ref={tooltip.ref}
|
|
223
|
+
aria-label={t(STATUS_BUTTON_LABEL_KEY[status.type])}
|
|
224
|
+
aria-describedby={tooltip.describedBy}
|
|
225
|
+
onClick={handleButtonClick}
|
|
226
|
+
onBlur={handleButtonBlur}
|
|
227
|
+
{...stylex.props(styles.statusButton)}>
|
|
228
|
+
{icon}
|
|
229
|
+
</button>
|
|
230
|
+
{tooltip.renderTooltip(status.message)}
|
|
231
|
+
</>
|
|
232
|
+
),
|
|
233
|
+
describedBy: tooltip.describedBy,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -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',
|