@astryxdesign/core 0.1.8 → 0.1.9
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 +100 -0
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +6 -2
- package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
- package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts +18 -2
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +63 -7
- package/dist/Avatar/AvatarStatusDot.d.ts +33 -5
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Avatar/AvatarStatusDot.js +144 -19
- package/dist/Banner/Banner.d.ts +9 -1
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +31 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
- package/dist/Breadcrumbs/index.d.ts +5 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +9 -1
- package/dist/Button/Button.d.ts +8 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +31 -2
- package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +30 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +30 -2
- package/dist/Calendar/dayCellUtils.js +1 -1
- package/dist/Card/Card.d.ts +8 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +28 -2
- package/dist/Carousel/Carousel.d.ts +4 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +24 -4
- package/dist/Chat/ChatComposer.d.ts +8 -0
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +46 -7
- package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +11 -1
- package/dist/Chat/ChatComposerInput.d.ts +20 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +35 -3
- package/dist/Chat/ChatContext.d.ts +33 -0
- package/dist/Chat/ChatContext.d.ts.map +1 -1
- package/dist/Chat/ChatContext.js +16 -2
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +1 -1
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +4 -4
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +7 -1
- package/dist/Chat/ChatToolCalls.d.ts +5 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +18 -9
- package/dist/Chat/index.d.ts +2 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/Chat/index.js +1 -1
- package/dist/Chat/useChatStreamScroll.d.ts +2 -1
- package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
- package/dist/Chat/useChatStreamScroll.js +22 -8
- package/dist/Chat/useSpeechRecognition.d.ts +0 -1
- package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.js +1 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +11 -1
- package/dist/ClickableCard/ClickableCard.d.ts +9 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +5 -2
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +8 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +3 -1
- package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
- package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +6 -1
- package/dist/DateInput/DateInput.d.ts +47 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +26 -2
- package/dist/DateInput/index.d.ts +1 -1
- package/dist/DateInput/index.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +10 -1
- package/dist/Dialog/Dialog.d.ts +3 -0
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +57 -6
- package/dist/Dialog/DialogContext.d.ts +7 -0
- package/dist/Dialog/DialogContext.d.ts.map +1 -1
- package/dist/Dialog/DialogContext.js +2 -2
- package/dist/Dialog/DialogHeader.d.ts +5 -2
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +6 -1
- package/dist/Divider/Divider.js +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -2
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +1 -2
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -2
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +2 -3
- package/dist/Field/FieldLabel.d.ts +1 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +42 -15
- package/dist/FieldStatus/FieldStatus.d.ts +8 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +22 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +10 -2
- package/dist/HoverCard/HoverCard.d.ts +9 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/useHoverCard.d.ts +8 -0
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -2
- package/dist/Icon/Icon.d.ts +7 -2
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Item/Item.d.ts +6 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +22 -1
- package/dist/Layer/useLayer.d.ts +6 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +2 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +6 -1
- package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
- package/dist/MobileNav/MobileNavToggle.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +39 -12
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +16 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/Outline/Outline.d.ts +0 -1
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +0 -1
- package/dist/OverflowList/OverflowList.d.ts +20 -3
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +29 -4
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +3 -9
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts +9 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +16 -13
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +4 -2
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +16 -0
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +21 -8
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +19 -2
- package/dist/Stack/stack.stylex.d.ts +9 -9
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +3 -2
- package/dist/Table/index.d.ts +2 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/index.js +3 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts +13 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +4 -1
- package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +5 -3
- package/dist/Thumbnail/Thumbnail.d.ts +26 -5
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +97 -54
- package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
- package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
- package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -4
- package/dist/Timestamp/Timestamp.d.ts +3 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +8 -6
- package/dist/Toast/useToast.d.ts.map +1 -1
- package/dist/Toast/useToast.js +2 -5
- package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
- package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +18 -8
- package/dist/TopNav/TopNavMegaMenu.js +10 -4
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
- package/dist/TopNav/TopNavMenu.d.ts +2 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +68 -6
- package/dist/TreeList/TreeList.d.ts +12 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +2 -0
- package/dist/TreeList/TreeListBranches.d.ts +5 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +10 -4
- package/dist/TreeList/TreeListItem.d.ts +8 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +27 -7
- package/dist/TreeList/TreeListTypes.d.ts +25 -1
- package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
- package/dist/TreeList/index.d.ts +2 -2
- package/dist/TreeList/index.d.ts.map +1 -1
- package/dist/astryx.css +28 -18
- package/dist/astryx.umd.js +51 -51
- package/dist/astryx.umd.js.map +4 -4
- package/dist/authoring/doc.d.ts.map +1 -1
- package/dist/docs-types.d.ts +8 -0
- package/dist/docs-types.d.ts.map +1 -1
- package/dist/hooks/computeOverflow.d.ts +47 -0
- package/dist/hooks/computeOverflow.d.ts.map +1 -0
- package/dist/hooks/computeOverflow.js +213 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -1
- package/dist/hooks/useDevWarning.d.ts +18 -0
- package/dist/hooks/useDevWarning.d.ts.map +1 -0
- package/dist/hooks/useDevWarning.js +49 -0
- package/dist/hooks/useOverflow.d.ts +19 -0
- package/dist/hooks/useOverflow.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +36 -25
- package/dist/i18n/resolve.d.ts.map +1 -1
- package/dist/i18n/resolve.js +7 -18
- package/dist/theme/Theme.d.ts.map +1 -1
- package/dist/theme/Theme.js +2 -7
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +51 -0
- package/dist/theme/hct.d.ts +0 -1
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
- package/dist/utils/devWarning.d.ts +42 -0
- package/dist/utils/devWarning.d.ts.map +1 -0
- package/dist/utils/devWarning.js +88 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/plainDate.d.ts +30 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +53 -0
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/locales/en.json +24 -0
- package/locales/fr-FR.json +14 -0
- package/locales/pseudo.json +18 -0
- package/package.json +1 -1
- package/src/AppShell/AppShell.tsx +6 -0
- package/src/AppShell/AppShellMobileContext.tsx +8 -0
- package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
- package/src/Avatar/Avatar.doc.mjs +15 -3
- package/src/Avatar/Avatar.test.tsx +156 -1
- package/src/Avatar/Avatar.tsx +96 -4
- package/src/Avatar/AvatarStatusDot.doc.mjs +23 -12
- package/src/Avatar/AvatarStatusDot.test.tsx +284 -0
- package/src/Avatar/AvatarStatusDot.tsx +178 -21
- package/src/Banner/Banner.doc.mjs +9 -0
- package/src/Banner/Banner.test.tsx +30 -0
- package/src/Banner/Banner.tsx +47 -3
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
- package/src/Breadcrumbs/index.ts +27 -0
- package/src/Button/Button.doc.mjs +9 -0
- package/src/Button/Button.test.tsx +49 -0
- package/src/Button/Button.tsx +25 -1
- package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
- package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
- package/src/ButtonGroup/ButtonGroup.tsx +32 -0
- package/src/Calendar/Calendar.doc.mjs +12 -1
- package/src/Calendar/Calendar.test.tsx +193 -0
- package/src/Calendar/Calendar.tsx +38 -16
- package/src/Calendar/dayCellUtils.test.ts +5 -0
- package/src/Calendar/dayCellUtils.ts +1 -1
- package/src/Card/Card.doc.mjs +9 -0
- package/src/Card/Card.test.tsx +21 -3
- package/src/Card/Card.tsx +35 -2
- package/src/Carousel/Carousel.doc.mjs +1 -0
- package/src/Carousel/Carousel.test.tsx +53 -0
- package/src/Carousel/Carousel.tsx +34 -4
- package/src/Chat/ChatComposer.doc.mjs +12 -3
- package/src/Chat/ChatComposer.test.tsx +115 -0
- package/src/Chat/ChatComposer.tsx +73 -9
- package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
- package/src/Chat/ChatComposerDrawer.tsx +11 -1
- package/src/Chat/ChatComposerInput.doc.mjs +7 -0
- package/src/Chat/ChatComposerInput.test.tsx +103 -0
- package/src/Chat/ChatComposerInput.tsx +49 -3
- package/src/Chat/ChatContext.tsx +37 -10
- package/src/Chat/ChatDictationButton.tsx +4 -1
- package/src/Chat/ChatLayout.tsx +3 -1
- package/src/Chat/ChatLayoutScrollButton.tsx +7 -2
- package/src/Chat/ChatMessageMetadata.tsx +1 -2
- package/src/Chat/ChatSendButton.tsx +11 -1
- package/src/Chat/ChatToolCalls.test.tsx +64 -0
- package/src/Chat/ChatToolCalls.tsx +25 -4
- package/src/Chat/index.ts +5 -1
- package/src/Chat/useChatStreamScroll.test.tsx +49 -0
- package/src/Chat/useChatStreamScroll.ts +27 -10
- package/src/Chat/useSpeechRecognition.ts +1 -1
- package/src/Chat/useTriggerMenu.tsx +3 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
- package/src/CheckboxInput/CheckboxInput.tsx +1 -1
- package/src/CheckboxList/CheckboxList.test.tsx +81 -3
- package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
- package/src/CheckboxList/CheckboxListItem.tsx +41 -5
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
- package/src/ClickableCard/ClickableCard.test.tsx +34 -0
- package/src/ClickableCard/ClickableCard.tsx +13 -3
- package/src/CodeBlock/CodeBlock.test.tsx +56 -0
- package/src/CodeBlock/CodeBlock.tsx +9 -1
- package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
- package/src/CommandPalette/CommandPalette.tsx +3 -1
- package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
- package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
- package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
- package/src/ContextMenu/ContextMenu.test.tsx +193 -0
- package/src/DateInput/DateInput.doc.mjs +16 -0
- package/src/DateInput/DateInput.test.tsx +108 -0
- package/src/DateInput/DateInput.tsx +68 -3
- package/src/DateInput/index.ts +1 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
- package/src/DateTimeInput/DateTimeInput.tsx +14 -2
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/Dialog/Dialog.test.tsx +77 -4
- package/src/Dialog/Dialog.tsx +89 -5
- package/src/Dialog/DialogContext.ts +9 -2
- package/src/Dialog/DialogHeader.doc.mjs +2 -2
- package/src/Dialog/DialogHeader.tsx +9 -2
- package/src/Divider/Divider.tsx +2 -2
- package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -2
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +1 -2
- package/src/Field/Field.test.tsx +57 -12
- package/src/Field/Field.tsx +6 -5
- package/src/Field/FieldLabel.tsx +8 -0
- package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
- package/src/FieldStatus/FieldStatus.test.tsx +78 -40
- package/src/FieldStatus/FieldStatus.tsx +22 -4
- package/src/FileInput/FileInput.test.tsx +52 -3
- package/src/FileInput/FileInput.tsx +13 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -0
- package/src/HoverCard/HoverCard.test.tsx +21 -2
- package/src/HoverCard/HoverCard.tsx +11 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +24 -2
- package/src/Icon/Icon.doc.mjs +3 -2
- package/src/Icon/Icon.tsx +7 -2
- package/src/IconButton/IconButton.doc.mjs +8 -0
- package/src/IconButton/IconButton.test.tsx +15 -18
- package/src/Item/Item.test.tsx +40 -18
- package/src/Item/Item.tsx +38 -2
- package/src/Layer/useLayer.tsx +8 -0
- package/src/List/List.test.tsx +9 -8
- package/src/MobileNav/MobileNav.tsx +12 -1
- package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
- package/src/MobileNav/MobileNavToggle.tsx +9 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
- package/src/MultiSelector/MultiSelector.test.tsx +172 -3
- package/src/MultiSelector/MultiSelector.tsx +55 -12
- package/src/MultiSelector/hooks.ts +17 -0
- package/src/NumberInput/NumberInput.test.tsx +11 -7
- package/src/NumberInput/NumberInput.tsx +1 -2
- package/src/Outline/Outline.tsx +0 -1
- package/src/OverflowList/OverflowList.doc.mjs +39 -6
- package/src/OverflowList/OverflowList.test.tsx +111 -0
- package/src/OverflowList/OverflowList.tsx +48 -8
- package/src/Popover/Popover.tsx +7 -4
- package/src/Popover/usePopover.tsx +9 -13
- package/src/PowerSearch/PowerSearch.doc.mjs +14 -0
- package/src/PowerSearch/PowerSearch.test.tsx +20 -0
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/usePowerSearchSource.ts +1 -2
- package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
- package/src/SelectableCard/SelectableCard.test.tsx +46 -4
- package/src/SelectableCard/SelectableCard.tsx +29 -16
- package/src/Selector/Selector.test.tsx +69 -0
- package/src/Selector/Selector.tsx +5 -1
- package/src/Selector/hooks.ts +17 -0
- package/src/SideNav/SideNav.test.tsx +119 -3
- package/src/SideNav/SideNavCollapseButton.tsx +6 -1
- package/src/SideNav/SideNavHeading.tsx +24 -8
- package/src/SideNav/SideNavItem.tsx +5 -1
- package/src/Slider/Slider.test.tsx +75 -2
- package/src/Slider/Slider.tsx +21 -5
- package/src/Switch/Switch.test.tsx +32 -1
- package/src/Table/BaseTable.tsx +3 -5
- package/src/Table/index.ts +5 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
- package/src/Table/plugins/rowStatus/index.ts +7 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +19 -1
- package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
- package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
- package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
- package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
- package/src/Table/useBaseTablePlugins.ts +10 -6
- package/src/Table/useTableRowStatus.doc.mjs +30 -0
- package/src/Table/useTableSelection.doc.mjs +65 -14
- package/src/Table/useTableTreeData.doc.mjs +29 -0
- package/src/Table/useTableTreeState.doc.mjs +2 -2
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +19 -10
- package/src/Thumbnail/Thumbnail.test.tsx +98 -7
- package/src/Thumbnail/Thumbnail.tsx +141 -41
- package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
- package/src/TimeInput/TimeInput.test.tsx +76 -1
- package/src/TimeInput/TimeInput.tsx +19 -6
- package/src/Timestamp/Timestamp.doc.mjs +4 -4
- package/src/Timestamp/Timestamp.test.tsx +32 -0
- package/src/Timestamp/Timestamp.tsx +28 -8
- package/src/Toast/useToast.tsx +7 -8
- package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +15 -1
- package/src/Tokenizer/Tokenizer.test.tsx +183 -2
- package/src/Tokenizer/Tokenizer.tsx +16 -4
- package/src/TopNav/TopNav.test.tsx +125 -1
- package/src/TopNav/TopNavHeading.tsx +24 -8
- package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
- package/src/TopNav/TopNavMegaMenu.tsx +10 -4
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
- package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
- package/src/TopNav/TopNavMenu.test.tsx +118 -4
- package/src/TopNav/TopNavMenu.tsx +83 -5
- package/src/TreeList/TreeList.doc.mjs +22 -0
- package/src/TreeList/TreeList.test.tsx +232 -0
- package/src/TreeList/TreeList.tsx +18 -2
- package/src/TreeList/TreeListBranches.tsx +16 -2
- package/src/TreeList/TreeListItem.tsx +51 -11
- package/src/TreeList/TreeListTypes.ts +27 -1
- package/src/TreeList/index.ts +2 -2
- package/src/authoring/doc.ts +1 -3
- package/src/docs-types.ts +5 -0
- package/src/hooks/computeOverflow.test.ts +422 -0
- package/src/hooks/computeOverflow.ts +290 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useDevWarning.test.tsx +45 -0
- package/src/hooks/useDevWarning.ts +54 -0
- package/src/hooks/useOverflow.test.ts +96 -4
- package/src/hooks/useOverflow.ts +74 -33
- package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
- package/src/i18n/resolve.ts +9 -21
- package/src/theme/Theme.tsx +11 -14
- package/src/theme/generateThemeRules.test.ts +65 -0
- package/src/theme/generateThemeRules.ts +56 -0
- package/src/theme/hct.ts +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
- package/src/utils/devWarning.test.ts +56 -0
- package/src/utils/devWarning.ts +102 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/plainDate.test.ts +22 -0
- package/src/utils/plainDate.ts +61 -0
- package/src/utils/types.ts +14 -0
|
@@ -332,6 +332,10 @@ export function TopNavHeading({
|
|
|
332
332
|
|
|
333
333
|
const popover = usePopover({
|
|
334
334
|
dialogLabel: t('@astryx.topNav.heading.dialogLabel'),
|
|
335
|
+
// The popup exposes its own role="menu" semantics; a role="dialog"
|
|
336
|
+
// aria-modal wrapper would announce "dialog, Navigation menu" around a
|
|
337
|
+
// menu (the anti-pattern removed in a478a3dcf).
|
|
338
|
+
role: 'none',
|
|
335
339
|
hasCloseButton: false,
|
|
336
340
|
});
|
|
337
341
|
|
|
@@ -511,13 +515,19 @@ export function TopNavHeading({
|
|
|
511
515
|
{popover.render(
|
|
512
516
|
<div
|
|
513
517
|
ref={menuRef}
|
|
514
|
-
role="menu"
|
|
515
518
|
{...stylex.props(styles.popoverContent)}
|
|
516
519
|
{...contentProps}>
|
|
517
520
|
{popoverHeadingContent}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
+
{/* The menu role is scoped to the actual menu items so the
|
|
522
|
+
heading button above stays a valid sibling, not an invalid
|
|
523
|
+
child of a role="menu" element. */}
|
|
524
|
+
<div
|
|
525
|
+
role="menu"
|
|
526
|
+
aria-label={heading ?? t('@astryx.topNav.heading.dialogLabel')}>
|
|
527
|
+
<NavHeadingCloseContext value={closeMenuCtx}>
|
|
528
|
+
{menu}
|
|
529
|
+
</NavHeadingCloseContext>
|
|
530
|
+
</div>
|
|
521
531
|
</div>,
|
|
522
532
|
{
|
|
523
533
|
placement: 'below',
|
|
@@ -575,13 +585,19 @@ export function TopNavHeading({
|
|
|
575
585
|
{popover.render(
|
|
576
586
|
<div
|
|
577
587
|
ref={menuRef}
|
|
578
|
-
role="menu"
|
|
579
588
|
{...stylex.props(styles.popoverContent)}
|
|
580
589
|
{...contentProps}>
|
|
581
590
|
{popoverHeadingContent}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
591
|
+
{/* The menu role is scoped to the actual menu items so the
|
|
592
|
+
heading button above stays a valid sibling, not an invalid
|
|
593
|
+
child of a role="menu" element. */}
|
|
594
|
+
<div
|
|
595
|
+
role="menu"
|
|
596
|
+
aria-label={heading ?? t('@astryx.topNav.heading.dialogLabel')}>
|
|
597
|
+
<NavHeadingCloseContext value={closeMenuCtx}>
|
|
598
|
+
{menu}
|
|
599
|
+
</NavHeadingCloseContext>
|
|
600
|
+
</div>
|
|
585
601
|
</div>,
|
|
586
602
|
{
|
|
587
603
|
placement: 'below',
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* SYNC: When TopNavMegaMenu changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
12
|
+
import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
|
|
13
13
|
import {render, screen} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {TopNavMegaMenu} from './TopNavMegaMenu';
|
|
@@ -75,6 +75,154 @@ describe('TopNavMegaMenu — default mode', () => {
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// Popup semantics (default mode)
|
|
80
|
+
// =============================================================================
|
|
81
|
+
|
|
82
|
+
describe('TopNavMegaMenu — popup semantics', () => {
|
|
83
|
+
// Mock the Popover API (not implemented in jsdom).
|
|
84
|
+
const originalMatches = HTMLElement.prototype.matches;
|
|
85
|
+
|
|
86
|
+
beforeAll(() => {
|
|
87
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
88
|
+
this.setAttribute('popover-open', '');
|
|
89
|
+
const event = new Event('toggle', {bubbles: false});
|
|
90
|
+
Object.defineProperty(event, 'newState', {value: 'open'});
|
|
91
|
+
this.dispatchEvent(event);
|
|
92
|
+
});
|
|
93
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
94
|
+
this.removeAttribute('popover-open');
|
|
95
|
+
const event = new Event('toggle', {bubbles: false});
|
|
96
|
+
Object.defineProperty(event, 'newState', {value: 'closed'});
|
|
97
|
+
this.dispatchEvent(event);
|
|
98
|
+
});
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
(HTMLElement.prototype as any).matches = function (
|
|
101
|
+
selector: string,
|
|
102
|
+
): boolean {
|
|
103
|
+
if (selector === ':popover-open') {
|
|
104
|
+
return this.hasAttribute('popover-open');
|
|
105
|
+
}
|
|
106
|
+
return originalMatches.call(this, selector);
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
afterAll(() => {
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
+
(HTMLElement.prototype as any).matches = originalMatches;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('trigger aria-controls resolves to the popup element', async () => {
|
|
116
|
+
const user = userEvent.setup();
|
|
117
|
+
render(
|
|
118
|
+
<TopNavMegaMenu
|
|
119
|
+
label="Products"
|
|
120
|
+
items={<TopNavMegaMenuItem title="Analytics" href="/analytics" />}
|
|
121
|
+
/>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const trigger = screen.getByRole('button', {name: 'Products'});
|
|
125
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
126
|
+
|
|
127
|
+
// aria-controls must be present and point at the real popup element.
|
|
128
|
+
const controlsId = trigger.getAttribute('aria-controls');
|
|
129
|
+
expect(controlsId).toBeTruthy();
|
|
130
|
+
const popup = document.getElementById(controlsId as string);
|
|
131
|
+
expect(popup).not.toBeNull();
|
|
132
|
+
|
|
133
|
+
await user.click(trigger);
|
|
134
|
+
|
|
135
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
136
|
+
// The referenced element is the popup that contains the panel content.
|
|
137
|
+
expect(popup).toContainElement(
|
|
138
|
+
screen.getByRole('group', {name: 'Products', hidden: true}),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('does not wrap the panel in a role="dialog" aria-modal element', async () => {
|
|
143
|
+
const user = userEvent.setup();
|
|
144
|
+
render(
|
|
145
|
+
<TopNavMegaMenu
|
|
146
|
+
label="Products"
|
|
147
|
+
items={<TopNavMegaMenuItem title="Analytics" href="/analytics" />}
|
|
148
|
+
/>,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
152
|
+
|
|
153
|
+
// Focus stays on the trigger while the panel is open, so a modal dialog
|
|
154
|
+
// wrapper would tell assistive tech the focused control is inert.
|
|
155
|
+
expect(
|
|
156
|
+
screen.queryByRole('dialog', {hidden: true}),
|
|
157
|
+
).not.toBeInTheDocument();
|
|
158
|
+
expect(document.querySelector('[aria-modal="true"]')).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not expose role="menu" — a link grid is not an ARIA menu', async () => {
|
|
162
|
+
const user = userEvent.setup();
|
|
163
|
+
render(
|
|
164
|
+
<TopNavMegaMenu
|
|
165
|
+
label="Products"
|
|
166
|
+
items={
|
|
167
|
+
<>
|
|
168
|
+
<TopNavMegaMenuItem title="Analytics" href="/analytics" />
|
|
169
|
+
<TopNavMegaMenuItem title="Reports" href="/reports" />
|
|
170
|
+
</>
|
|
171
|
+
}
|
|
172
|
+
/>,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
176
|
+
|
|
177
|
+
// Per the WAI-ARIA APG, mega menu panels of navigation links must not use
|
|
178
|
+
// the menu role (reserved for action menus with menuitem children).
|
|
179
|
+
expect(screen.queryByRole('menu', {hidden: true})).not.toBeInTheDocument();
|
|
180
|
+
expect(screen.queryAllByRole('menuitem', {hidden: true})).toHaveLength(0);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('exposes the panel as a labeled group', async () => {
|
|
184
|
+
const user = userEvent.setup();
|
|
185
|
+
render(
|
|
186
|
+
<TopNavMegaMenu
|
|
187
|
+
label="Products"
|
|
188
|
+
items={<TopNavMegaMenuItem title="Analytics" href="/analytics" />}
|
|
189
|
+
/>,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
screen.getByRole('group', {name: 'Products', hidden: true}),
|
|
196
|
+
).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('keeps item links with accessible names inside the panel', async () => {
|
|
200
|
+
const user = userEvent.setup();
|
|
201
|
+
render(
|
|
202
|
+
<TopNavMegaMenu
|
|
203
|
+
label="Products"
|
|
204
|
+
items={
|
|
205
|
+
<>
|
|
206
|
+
<TopNavMegaMenuItem title="Analytics" href="/analytics" />
|
|
207
|
+
<TopNavMegaMenuItem title="Reports" href="/reports" />
|
|
208
|
+
</>
|
|
209
|
+
}
|
|
210
|
+
/>,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
214
|
+
|
|
215
|
+
const analytics = screen.getByRole('link', {
|
|
216
|
+
name: /Analytics/,
|
|
217
|
+
hidden: true,
|
|
218
|
+
});
|
|
219
|
+
expect(analytics).toHaveAttribute('href', '/analytics');
|
|
220
|
+
expect(
|
|
221
|
+
screen.getByRole('link', {name: /Reports/, hidden: true}),
|
|
222
|
+
).toHaveAttribute('href', '/reports');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
78
226
|
// =============================================================================
|
|
79
227
|
// Mobile-bar mode — should be hidden
|
|
80
228
|
// =============================================================================
|
|
@@ -354,7 +354,11 @@ function DefaultMegaMenu({
|
|
|
354
354
|
}, [onOpenChange]);
|
|
355
355
|
|
|
356
356
|
const popover = usePopover({
|
|
357
|
-
|
|
357
|
+
// role: 'none' — the panel exposes its own role="group" labeled by
|
|
358
|
+
// `label`. Focus stays on the trigger while the panel is open, so a
|
|
359
|
+
// role="dialog" aria-modal="true" wrapper would announce an unnamed
|
|
360
|
+
// modal dialog around a grid of links.
|
|
361
|
+
role: 'none',
|
|
358
362
|
// hasSurface: false — mega menu provides its own surface (panelContainer)
|
|
359
363
|
// with border-top and custom overflow. Animation is applied via the
|
|
360
364
|
// render() call's xstyle prop (panelAnimation), not the hook options.
|
|
@@ -434,8 +438,7 @@ function DefaultMegaMenu({
|
|
|
434
438
|
<button
|
|
435
439
|
ref={mergeRefs(triggerButtonRef, ref)}
|
|
436
440
|
type="button"
|
|
437
|
-
|
|
438
|
-
aria-expanded={popover.isOpen}
|
|
441
|
+
{...popover.triggerProps}
|
|
439
442
|
onClick={handleClick}
|
|
440
443
|
onMouseEnter={handleMouseEnter}
|
|
441
444
|
onMouseLeave={handleMouseLeave}
|
|
@@ -454,7 +457,10 @@ function DefaultMegaMenu({
|
|
|
454
457
|
</button>
|
|
455
458
|
{popover.render(
|
|
456
459
|
<div
|
|
457
|
-
role="menu
|
|
460
|
+
// role="group" — a mega menu is a browsing grid of links, not an
|
|
461
|
+
// ARIA menu of menuitems (per the WAI-ARIA APG, the menu role is
|
|
462
|
+
// for action menus; link mega menus are the documented anti-case).
|
|
463
|
+
role="group"
|
|
458
464
|
aria-label={label}
|
|
459
465
|
onMouseEnter={handleMouseEnter}
|
|
460
466
|
onMouseLeave={handleMouseLeave}
|
|
@@ -27,7 +27,7 @@ export const docs = {
|
|
|
27
27
|
{
|
|
28
28
|
name: 'imageAlt',
|
|
29
29
|
type: 'string',
|
|
30
|
-
description: 'Alt text for the image.',
|
|
30
|
+
description: 'Alt text for the image. When omitted, the image is explicitly decorative (alt="", role="presentation", aria-hidden) — usually correct since the card already has a visible title. Provide it only when the image conveys information beyond the title and description.',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
name: 'linkLabel',
|
|
@@ -71,7 +71,7 @@ export const docsZh = {
|
|
|
71
71
|
{
|
|
72
72
|
name: 'imageAlt',
|
|
73
73
|
type: 'string',
|
|
74
|
-
description: '
|
|
74
|
+
description: '图片的替代文本。省略时图片会被显式标记为装饰性(alt=""、role="presentation"、aria-hidden)——由于卡片已有可见标题,这通常是正确的。仅当图片传达标题和描述之外的信息时才提供。',
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
name: 'linkLabel',
|
|
@@ -99,7 +99,7 @@ export const docsDense = {
|
|
|
99
99
|
title: 'Card title.',
|
|
100
100
|
description: 'Description below title.',
|
|
101
101
|
image: 'Image URL above body.',
|
|
102
|
-
imageAlt: 'Image alt text.',
|
|
102
|
+
imageAlt: 'Image alt text. Omitted → explicitly decorative (hidden from screen readers); usually correct since card has visible title.',
|
|
103
103
|
linkLabel: 'CTA link text.',
|
|
104
104
|
linkHref: 'CTA link URL.',
|
|
105
105
|
children: 'Custom content below standard body.',
|
|
@@ -84,7 +84,15 @@ export interface TopNavMegaMenuFeaturedCardProps extends BaseProps<HTMLDivElemen
|
|
|
84
84
|
description?: string;
|
|
85
85
|
/** Optional image URL displayed above the body. */
|
|
86
86
|
image?: string;
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Alt text for the image.
|
|
89
|
+
*
|
|
90
|
+
* When omitted, the image is explicitly decorative (`alt=""` +
|
|
91
|
+
* `role="presentation"` + `aria-hidden`, matching Avatar) and hidden from
|
|
92
|
+
* assistive technology. Since the card already has a visible `title`, a
|
|
93
|
+
* decorative image is usually correct; pass `imageAlt` only when the image
|
|
94
|
+
* conveys information beyond the title and description.
|
|
95
|
+
*/
|
|
88
96
|
imageAlt?: string;
|
|
89
97
|
/** CTA link text. */
|
|
90
98
|
linkLabel?: string;
|
|
@@ -149,7 +157,16 @@ export function TopNavMegaMenuFeaturedCard({
|
|
|
149
157
|
)}
|
|
150
158
|
{...rest}>
|
|
151
159
|
{image && (
|
|
152
|
-
|
|
160
|
+
// Without `imageAlt`, the image is explicitly decorative rather than
|
|
161
|
+
// silently empty-alt, matching Avatar's handling of unnamed images.
|
|
162
|
+
// Usually correct here: the card already has a visible title.
|
|
163
|
+
<img
|
|
164
|
+
src={image}
|
|
165
|
+
alt={imageAlt ?? ''}
|
|
166
|
+
role={imageAlt ? undefined : 'presentation'}
|
|
167
|
+
aria-hidden={imageAlt ? undefined : true}
|
|
168
|
+
{...stylex.props(styles.image)}
|
|
169
|
+
/>
|
|
153
170
|
)}
|
|
154
171
|
<div {...stylex.props(styles.body)}>
|
|
155
172
|
<span {...stylex.props(styles.title)}>{title}</span>
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* SYNC: When TopNavMenu changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
12
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
+
import {render, screen, fireEvent} from '@testing-library/react';
|
|
14
|
+
import userEvent from '@testing-library/user-event';
|
|
14
15
|
import {TopNavMenu} from './TopNavMenu';
|
|
15
16
|
|
|
16
17
|
const mockItems = [
|
|
@@ -32,10 +33,13 @@ describe('TopNavMenu', () => {
|
|
|
32
33
|
expect(screen.getByRole('button', {name: 'Products'})).toBeInTheDocument();
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
it('trigger
|
|
36
|
+
it('trigger announces a menu popup, not a dialog', () => {
|
|
36
37
|
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
37
38
|
const trigger = screen.getByRole('button', {name: 'Products'});
|
|
38
|
-
|
|
39
|
+
// usePopover with role:'none' emits aria-haspopup="true" (the ARIA
|
|
40
|
+
// synonym for "menu") because the exposed semantics of the popup are its
|
|
41
|
+
// child role="menu", not a dialog.
|
|
42
|
+
expect(trigger).toHaveAttribute('aria-haspopup', 'true');
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
it('renders with custom items', () => {
|
|
@@ -57,3 +61,113 @@ describe('TopNavMenu', () => {
|
|
|
57
61
|
expect(screen.getByRole('button', {name: 'Menu'})).toBeInTheDocument();
|
|
58
62
|
});
|
|
59
63
|
});
|
|
64
|
+
|
|
65
|
+
describe('menu semantics (APG)', () => {
|
|
66
|
+
it('does not wrap the popup in a role="dialog" / aria-modal shell', () => {
|
|
67
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
68
|
+
// The popup's exposed semantics are the role="menu" container itself —
|
|
69
|
+
// no dialog wrapper, no aria-modal.
|
|
70
|
+
expect(
|
|
71
|
+
screen.queryByRole('dialog', {hidden: true}),
|
|
72
|
+
).not.toBeInTheDocument();
|
|
73
|
+
expect(document.querySelector('[aria-modal]')).toBeNull();
|
|
74
|
+
expect(
|
|
75
|
+
screen.getByRole('menu', {name: 'Products', hidden: true}),
|
|
76
|
+
).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('keyboard navigation (APG menu pattern)', () => {
|
|
81
|
+
it('exposes exactly one tab stop among menu items (roving tabindex)', async () => {
|
|
82
|
+
const user = userEvent.setup();
|
|
83
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
84
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
85
|
+
|
|
86
|
+
const items = screen.getAllByRole('menuitem', {hidden: true});
|
|
87
|
+
expect(items).toHaveLength(2);
|
|
88
|
+
const tabbable = items.filter(el => el.getAttribute('tabindex') === '0');
|
|
89
|
+
expect(tabbable).toHaveLength(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('moves focus with ArrowDown/ArrowUp and the tab stop follows', async () => {
|
|
93
|
+
const user = userEvent.setup();
|
|
94
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
95
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
96
|
+
|
|
97
|
+
const menu = screen.getByRole('menu', {hidden: true});
|
|
98
|
+
const items = screen.getAllByRole('menuitem', {hidden: true});
|
|
99
|
+
items[0].focus();
|
|
100
|
+
|
|
101
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
102
|
+
expect(items[1]).toHaveFocus();
|
|
103
|
+
expect(items[1]).toHaveAttribute('tabindex', '0');
|
|
104
|
+
expect(items[0]).toHaveAttribute('tabindex', '-1');
|
|
105
|
+
|
|
106
|
+
fireEvent.keyDown(menu, {key: 'ArrowUp'});
|
|
107
|
+
expect(items[0]).toHaveFocus();
|
|
108
|
+
expect(items[0]).toHaveAttribute('tabindex', '0');
|
|
109
|
+
expect(items[1]).toHaveAttribute('tabindex', '-1');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('wraps focus at both ends', async () => {
|
|
113
|
+
const user = userEvent.setup();
|
|
114
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
115
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
116
|
+
|
|
117
|
+
const menu = screen.getByRole('menu', {hidden: true});
|
|
118
|
+
const items = screen.getAllByRole('menuitem', {hidden: true});
|
|
119
|
+
|
|
120
|
+
items[1].focus();
|
|
121
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
122
|
+
expect(items[0]).toHaveFocus();
|
|
123
|
+
|
|
124
|
+
fireEvent.keyDown(menu, {key: 'ArrowUp'});
|
|
125
|
+
expect(items[1]).toHaveFocus();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('typeahead moves focus to the item matching the typed character', async () => {
|
|
129
|
+
const user = userEvent.setup();
|
|
130
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
131
|
+
await user.click(screen.getByRole('button', {name: 'Products'}));
|
|
132
|
+
|
|
133
|
+
const menu = screen.getByRole('menu', {hidden: true});
|
|
134
|
+
fireEvent.keyDown(menu, {key: 'm'});
|
|
135
|
+
expect(
|
|
136
|
+
screen.getByRole('menuitem', {name: /Messaging/, hidden: true}),
|
|
137
|
+
).toHaveFocus();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('activates a focused onClick-only item with Enter', async () => {
|
|
141
|
+
const user = userEvent.setup();
|
|
142
|
+
const onClick = vi.fn();
|
|
143
|
+
render(<TopNavMenu label="Menu" items={[{title: 'Action', onClick}]} />);
|
|
144
|
+
await user.click(screen.getByRole('button', {name: 'Menu'}));
|
|
145
|
+
|
|
146
|
+
const item = screen.getByRole('menuitem', {hidden: true});
|
|
147
|
+
item.focus();
|
|
148
|
+
fireEvent.keyDown(item, {key: 'Enter'});
|
|
149
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('still activates an item on click', async () => {
|
|
153
|
+
const user = userEvent.setup();
|
|
154
|
+
const onClick = vi.fn();
|
|
155
|
+
render(<TopNavMenu label="Menu" items={[{title: 'Action', onClick}]} />);
|
|
156
|
+
await user.click(screen.getByRole('button', {name: 'Menu'}));
|
|
157
|
+
|
|
158
|
+
await user.click(screen.getByRole('menuitem', {hidden: true}));
|
|
159
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('closes the menu with Escape', async () => {
|
|
163
|
+
const user = userEvent.setup();
|
|
164
|
+
render(<TopNavMenu label="Products" items={mockItems} />);
|
|
165
|
+
const trigger = screen.getByRole('button', {name: 'Products'});
|
|
166
|
+
await user.click(trigger);
|
|
167
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
168
|
+
|
|
169
|
+
const menu = screen.getByRole('menu', {hidden: true});
|
|
170
|
+
fireEvent.keyDown(menu, {key: 'Escape'});
|
|
171
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file TopNavMenu.tsx
|
|
7
|
-
* @input Uses React, StyleX,
|
|
7
|
+
* @input Uses React, StyleX, usePopover, useMenuHover, useListFocus,
|
|
8
|
+
* useTypeahead, TopNavItem tokens
|
|
8
9
|
* @output Exports TopNavMenu component and related types
|
|
9
10
|
* @position Navigation item with hover-triggered overflow menu for TopNav
|
|
10
11
|
*
|
|
@@ -15,10 +16,18 @@
|
|
|
15
16
|
* - /packages/cli/templates/blocks/components/TopNav/ (showcase blocks)
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
import React, {
|
|
19
|
+
import React, {
|
|
20
|
+
useCallback,
|
|
21
|
+
useId,
|
|
22
|
+
useRef,
|
|
23
|
+
useState,
|
|
24
|
+
type ReactNode,
|
|
25
|
+
} from 'react';
|
|
19
26
|
import * as stylex from '@stylexjs/stylex';
|
|
20
27
|
import {usePopover} from '../Popover/usePopover';
|
|
21
28
|
import {useMenuHover} from '../hooks/useMenuHover';
|
|
29
|
+
import {useListFocus} from '../hooks/useListFocus';
|
|
30
|
+
import {useTypeahead} from '../hooks/useTypeahead';
|
|
22
31
|
import {getIcon} from '../Icon/globalIconRegistry';
|
|
23
32
|
import {mergeProps, mergeRefs} from '../utils';
|
|
24
33
|
import type {BaseProps} from '../BaseProps';
|
|
@@ -332,7 +341,10 @@ export function TopNavMenu({
|
|
|
332
341
|
const triggerButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
333
342
|
|
|
334
343
|
const popover = usePopover({
|
|
335
|
-
|
|
344
|
+
// The popup's own role="menu" is the exposed semantics; a modal dialog
|
|
345
|
+
// wrapper would announce an unnamed dialog around the menu and make the
|
|
346
|
+
// trigger claim aria-haspopup="dialog" for menu content (see TabMenu).
|
|
347
|
+
role: 'none',
|
|
336
348
|
xstyle: styles.menuOffset,
|
|
337
349
|
});
|
|
338
350
|
|
|
@@ -353,6 +365,65 @@ export function TopNavMenu({
|
|
|
353
365
|
ref,
|
|
354
366
|
);
|
|
355
367
|
|
|
368
|
+
// The desktop popup is a composite menu widget per the APG menu pattern:
|
|
369
|
+
// a single roving tab stop with ArrowUp/ArrowDown traversal (wrapping),
|
|
370
|
+
// Home/End, and first-character typeahead. The hook owns item tabindex —
|
|
371
|
+
// items render tabIndex={-1} and exactly one is promoted to 0. The
|
|
372
|
+
// composition mirrors NavHeadingMenu.
|
|
373
|
+
const {listRef, handleKeyDown, handleFocus, focusItem} =
|
|
374
|
+
useListFocus<HTMLDivElement>({
|
|
375
|
+
itemSelector: '[role="menuitem"]',
|
|
376
|
+
hasRovingTabIndex: true,
|
|
377
|
+
onEscape: popover.hide,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// First-character typeahead over the menu items (menus-11).
|
|
381
|
+
const getMenuItems = useCallback(
|
|
382
|
+
(): HTMLElement[] =>
|
|
383
|
+
listRef.current
|
|
384
|
+
? Array.from(
|
|
385
|
+
listRef.current.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
386
|
+
)
|
|
387
|
+
: [],
|
|
388
|
+
[listRef],
|
|
389
|
+
);
|
|
390
|
+
const typeahead = useTypeahead({
|
|
391
|
+
getItemLabels: () => getMenuItems().map(el => el.textContent),
|
|
392
|
+
onMatch: focusItem,
|
|
393
|
+
getCurrentIndex: () =>
|
|
394
|
+
getMenuItems().findIndex(
|
|
395
|
+
el =>
|
|
396
|
+
el === document.activeElement || el.contains(document.activeElement),
|
|
397
|
+
),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Extend useListFocus with Enter/Space activation. Items rendered without an
|
|
401
|
+
// `href` are `<div role="menuitem">` elements, which have no native keyboard
|
|
402
|
+
// activation — without this, Enter/Space on a focused onClick-only item does
|
|
403
|
+
// nothing. Anchor items (with `href`) already activate on Enter natively.
|
|
404
|
+
const menuKeyDown = useCallback(
|
|
405
|
+
(e: React.KeyboardEvent) => {
|
|
406
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
407
|
+
const focused = document.activeElement as HTMLElement | null;
|
|
408
|
+
if (focused?.getAttribute('role') === 'menuitem') {
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
focused.click();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (typeahead.onKeyDown(e)) {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
handleKeyDown(e);
|
|
419
|
+
},
|
|
420
|
+
[handleKeyDown, typeahead],
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Menu container carries both the hover hook's ref (for its open/close
|
|
424
|
+
// focus management) and the list-focus ref (for roving tabindex/typeahead).
|
|
425
|
+
const setMenuRef = mergeRefs<HTMLDivElement>(menuRef, listRef);
|
|
426
|
+
|
|
356
427
|
// Mobile bar: hide menus entirely
|
|
357
428
|
if (renderMode === 'mobile-bar') {
|
|
358
429
|
return null;
|
|
@@ -437,10 +508,15 @@ export function TopNavMenu({
|
|
|
437
508
|
</button>
|
|
438
509
|
{popover.render(
|
|
439
510
|
<div
|
|
440
|
-
ref={
|
|
511
|
+
ref={setMenuRef}
|
|
441
512
|
role="menu"
|
|
442
513
|
aria-label={label}
|
|
443
514
|
{...contentProps}
|
|
515
|
+
// After the contentProps spread so the full APG composition (roving
|
|
516
|
+
// tabindex + typeahead + Enter/Space activation) replaces the hover
|
|
517
|
+
// hook's basic arrow-key handler.
|
|
518
|
+
onKeyDown={menuKeyDown}
|
|
519
|
+
onFocus={handleFocus}
|
|
444
520
|
{...stylex.props(styles.menuContainer)}>
|
|
445
521
|
{items.map(item => {
|
|
446
522
|
const Element = item.href ? 'a' : 'div';
|
|
@@ -448,7 +524,9 @@ export function TopNavMenu({
|
|
|
448
524
|
<Element
|
|
449
525
|
key={getMenuItemKey(item)}
|
|
450
526
|
role="menuitem"
|
|
451
|
-
|
|
527
|
+
// Single tab stop: useListFocus owns the roving tabindex and
|
|
528
|
+
// promotes exactly one item to 0.
|
|
529
|
+
tabIndex={-1}
|
|
452
530
|
href={item.href}
|
|
453
531
|
onClick={item.onClick}
|
|
454
532
|
{...stylex.props(styles.menuItem)}>
|
|
@@ -26,6 +26,9 @@ export const docs = {
|
|
|
26
26
|
targets: [
|
|
27
27
|
{className: 'astryx-tree-list', visualProps: ['density']},
|
|
28
28
|
{className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
|
|
29
|
+
{className: 'astryx-tree-list-chevron', states: ['state']},
|
|
30
|
+
{className: 'astryx-tree-list-item-label', states: ['selected']},
|
|
31
|
+
{className: 'astryx-tree-list-guide'},
|
|
29
32
|
],
|
|
30
33
|
},
|
|
31
34
|
components: [
|
|
@@ -47,6 +50,13 @@ export const docs = {
|
|
|
47
50
|
description: 'Spacing density for items.',
|
|
48
51
|
default: "'balanced'",
|
|
49
52
|
},
|
|
53
|
+
{
|
|
54
|
+
name: 'variant',
|
|
55
|
+
type: "'lineGuides' | 'noGuides'",
|
|
56
|
+
description:
|
|
57
|
+
'Visual treatment of the hierarchy guide lines. lineGuides shows connector lines; noGuides hides them, keeping indentation. Orthogonal to density.',
|
|
58
|
+
default: "'lineGuides'",
|
|
59
|
+
},
|
|
50
60
|
{
|
|
51
61
|
name: 'header',
|
|
52
62
|
type: 'ReactNode',
|
|
@@ -90,6 +100,9 @@ export const docsZh = {
|
|
|
90
100
|
targets: [
|
|
91
101
|
{className: 'astryx-tree-list', visualProps: ['density']},
|
|
92
102
|
{className: 'astryx-tree-list-item', visualProps: ['density'], states: ['selected', 'disabled']},
|
|
103
|
+
{className: 'astryx-tree-list-chevron', states: ['state']},
|
|
104
|
+
{className: 'astryx-tree-list-item-label', states: ['selected']},
|
|
105
|
+
{className: 'astryx-tree-list-guide'},
|
|
93
106
|
],
|
|
94
107
|
},
|
|
95
108
|
components: [
|
|
@@ -112,6 +125,13 @@ export const docsZh = {
|
|
|
112
125
|
description: '项目的间距密度。',
|
|
113
126
|
default: "'balanced'",
|
|
114
127
|
},
|
|
128
|
+
{
|
|
129
|
+
name: 'variant',
|
|
130
|
+
type: "'lineGuides' | 'noGuides'",
|
|
131
|
+
description:
|
|
132
|
+
'层级引导线的视觉呈现。lineGuides 显示连接线;noGuides 隐藏连接线并保留缩进。与 density 正交。',
|
|
133
|
+
default: "'lineGuides'",
|
|
134
|
+
},
|
|
115
135
|
{
|
|
116
136
|
name: 'header',
|
|
117
137
|
type: 'ReactNode',
|
|
@@ -156,6 +176,7 @@ export const docsDense = {
|
|
|
156
176
|
propDescriptions: {
|
|
157
177
|
items: 'Recursive tree item data w/ id, label, optional children + isExpanded.',
|
|
158
178
|
density: 'Spacing density for items.',
|
|
179
|
+
variant: 'Guide-line treatment: lineGuides shows connectors, noGuides hides them (indent kept). Orthogonal to density.',
|
|
159
180
|
header: 'Header content, linked to tree via aria-labelledby.',
|
|
160
181
|
xstyle: 'StyleX styles for layout. Must be stylex.create() value.',
|
|
161
182
|
},
|
|
@@ -167,6 +188,7 @@ export const docsDense = {
|
|
|
167
188
|
propDescriptions: {
|
|
168
189
|
items: 'Recursive tree item data w/ id, label, optional children + isExpanded.',
|
|
169
190
|
density: 'Spacing density for items.',
|
|
191
|
+
variant: 'Guide-line treatment: lineGuides shows connectors, noGuides hides them (indent kept). Orthogonal to density.',
|
|
170
192
|
header: 'Header content, linked to tree via aria-labelledby.',
|
|
171
193
|
xstyle: 'StyleX styles for layout. Must be stylex.create() value.',
|
|
172
194
|
},
|