@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241
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/dist/Avatar/Avatar.d.ts +6 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +46 -12
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- 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/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- 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/CheckboxInput/CheckboxInput.js +4 -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 +13 -7
- 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/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +14 -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 +1 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +31 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -19
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/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 +4 -1
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/FileInput/FileInput.d.ts +1 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +91 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +15 -9
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +65 -40
- package/dist/NumberInput/NumberInput.d.ts +1 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +20 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.js +4 -4
- 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 +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +58 -16
- 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/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 +160 -91
- 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/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
- 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 +14 -6
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +1 -0
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -19
- package/dist/TextInput/TextInput.d.ts +1 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +18 -17
- package/dist/TimeInput/TimeInput.d.ts +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Token/Token.d.ts +30 -3
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +17 -2
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -10
- package/dist/Typeahead/Typeahead.js +1 -1
- 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 +38 -14
- package/dist/astryx.umd.js +52 -52
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +76 -0
- package/locales/pseudo.json +57 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +18 -4
- package/src/Avatar/Avatar.test.tsx +106 -0
- package/src/Avatar/Avatar.tsx +62 -10
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.tsx +4 -4
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- 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.tsx +35 -8
- 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/ContextMenu/ContextMenu.test.tsx +42 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +15 -5
- package/src/DateInput/DateInput.test.tsx +210 -3
- package/src/DateInput/DateInput.tsx +41 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
- package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
- package/src/DateRangeInput/DateRangeInput.tsx +29 -27
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.tsx +10 -4
- package/src/FileInput/FileInput.doc.mjs +13 -7
- package/src/FileInput/FileInput.test.tsx +84 -16
- package/src/FileInput/FileInput.tsx +96 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
- package/src/MultiSelector/MultiSelector.test.tsx +306 -3
- package/src/MultiSelector/MultiSelector.tsx +88 -57
- package/src/NumberInput/NumberInput.doc.mjs +11 -5
- package/src/NumberInput/NumberInput.test.tsx +48 -0
- package/src/NumberInput/NumberInput.tsx +25 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +4 -4
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
- package/src/Selector/Selector.doc.mjs +8 -0
- package/src/Selector/Selector.test.tsx +328 -3
- package/src/Selector/Selector.tsx +70 -19
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- 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 +22 -0
- package/src/Switch/Switch.tsx +90 -33
- 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/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
- 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 +37 -8
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +11 -5
- package/src/TextArea/TextArea.tsx +17 -26
- package/src/TextInput/TextInput.doc.mjs +11 -5
- package/src/TextInput/TextInput.test.tsx +130 -3
- package/src/TextInput/TextInput.tsx +18 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +11 -5
- package/src/TimeInput/TimeInput.tsx +14 -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/Token/Token.test.tsx +21 -15
- package/src/Token/Token.tsx +31 -14
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
- package/src/Tokenizer/Tokenizer.tsx +4 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +29 -14
- package/src/Typeahead/Typeahead.doc.mjs +6 -0
- package/src/Typeahead/Typeahead.test.tsx +27 -0
- package/src/Typeahead/Typeahead.tsx +1 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -32,6 +32,18 @@ export const docs = {
|
|
|
32
32
|
{className: 'astryx-avatar-status-dot', visualProps: ['variant']},
|
|
33
33
|
{className: 'astryx-avatar-status-dot-glyph', visualProps: ['shape']},
|
|
34
34
|
],
|
|
35
|
+
vars: [
|
|
36
|
+
{name: '--_avatar-fallback-font-size', description: 'Initials font size; default is proportional to the avatar size (size × 0.4). Override per size tier (e.g. size:sm) for a custom type scale.', default: 'calc(avatar-size × 0.4)', private: true},
|
|
37
|
+
{name: '--_avatar-fallback-font-weight', description: 'Initials font weight', default: 'var(--font-weight-medium)', private: true},
|
|
38
|
+
{name: '--_avatar-fallback-color', description: 'Fallback text and default-icon color', default: 'var(--color-text-secondary)', private: true},
|
|
39
|
+
{name: '--_avatar-fallback-background', description: 'Fallback wash background fill', default: 'var(--color-neutral)', private: true},
|
|
40
|
+
],
|
|
41
|
+
derived: [
|
|
42
|
+
{property: 'fontSize', vars: ['--_avatar-fallback-font-size']},
|
|
43
|
+
{property: 'fontWeight', vars: ['--_avatar-fallback-font-weight']},
|
|
44
|
+
{property: 'color', vars: ['--_avatar-fallback-color']},
|
|
45
|
+
{property: 'backgroundColor', vars: ['--_avatar-fallback-background']},
|
|
46
|
+
],
|
|
35
47
|
},
|
|
36
48
|
description: 'Displays a user avatar with image, initials fallback, and optional status indicator.',
|
|
37
49
|
props: [
|
|
@@ -64,7 +76,7 @@ export const docs = {
|
|
|
64
76
|
{
|
|
65
77
|
name: 'status',
|
|
66
78
|
type: 'ReactNode',
|
|
67
|
-
description: 'Corner content for status indicators.',
|
|
79
|
+
description: 'Corner content for status indicators. A string `label` on the element (as on AvatarStatusDot) is composed into the avatar\'s accessible name (e.g. "Jane Doe, Online") so screen readers announce the status.',
|
|
68
80
|
slotElements: [
|
|
69
81
|
{
|
|
70
82
|
__element: 'AvatarStatusDot',
|
|
@@ -79,14 +91,14 @@ export const docs = {
|
|
|
79
91
|
name: 'tooltip',
|
|
80
92
|
type: 'string | boolean',
|
|
81
93
|
description:
|
|
82
|
-
"Tooltip shown on hover and keyboard focus. Omitted or true shows the avatar's name; a string shows that text instead; false shows no tooltip. Not auto-disabled when wrapped in your own Tooltip/HoverCard
|
|
94
|
+
"Tooltip shown on hover and keyboard focus. Omitted or true shows the avatar's name; a string shows that text instead; false shows no tooltip. Not auto-disabled when wrapped in your own Tooltip/HoverCard. Set tooltip={false} if you supply your own overlay. No tooltip is shown when tooltip is true/omitted and there is no name.",
|
|
83
95
|
default: 'true',
|
|
84
96
|
},
|
|
85
97
|
{
|
|
86
98
|
name: 'href',
|
|
87
99
|
type: 'string',
|
|
88
100
|
description:
|
|
89
|
-
'When set, the avatar renders as an interactive link (`<a>` or a custom link component) pointing here
|
|
101
|
+
'When set, the avatar renders as an interactive link (`<a>` or a custom link component) pointing here. This follows the same element-swap rule as Button. Requires a meaningful accessible name via `alt` or `name`. Inside an AvatarGroup, interactive avatars share a single Tab stop and are reached with arrow keys.',
|
|
90
102
|
},
|
|
91
103
|
{
|
|
92
104
|
name: 'as',
|
|
@@ -140,6 +152,7 @@ export const docsDense = {
|
|
|
140
152
|
{guidance: true, description: 'Always pass a name for initials fallback and screen reader alt text.'},
|
|
141
153
|
{guidance: true, description: 'Match size to context: xsm/sm inline, md/lg in lists, xl for profiles.'},
|
|
142
154
|
{guidance: true, description: 'Add a status dot in chat or team views where availability matters.'},
|
|
155
|
+
{guidance: true, description: 'When wrapping Avatar in your own Tooltip or HoverCard, set tooltip={false} so the built-in name tooltip does not overlap yours.'},
|
|
143
156
|
{guidance: false, description: 'Use for logos or product images. Use an image or icon instead.'},
|
|
144
157
|
{guidance: false, description: 'Force a square or custom shape. Avatars are always circular.'},
|
|
145
158
|
],
|
|
@@ -150,7 +163,8 @@ export const docsDense = {
|
|
|
150
163
|
name: 'user name for initials and alt text',
|
|
151
164
|
alt: 'alt text; falls back to name',
|
|
152
165
|
size: "avatar size. Named ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or numeric px.",
|
|
153
|
-
status:
|
|
166
|
+
status:
|
|
167
|
+
'corner content for status indicators; its `label` is composed into the avatar accessible name ("Jane Doe, Online")',
|
|
154
168
|
tooltip:
|
|
155
169
|
"hover/focus tooltip. true/omitted → name; string → that text; false → none. Owns its tooltip; set false when wrapping in your own Tooltip/HoverCard. Default true.",
|
|
156
170
|
href: 'renders avatar as a link (<a>/custom). Needs alt/name. Button-style element swap.',
|
|
@@ -4,6 +4,7 @@ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
|
|
4
4
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import {Avatar} from './Avatar';
|
|
7
|
+
import {AvatarStatusDot} from './AvatarStatusDot';
|
|
7
8
|
|
|
8
9
|
describe('Avatar', () => {
|
|
9
10
|
it('exposes role="img" with the name as accessible name', () => {
|
|
@@ -36,6 +37,19 @@ describe('Avatar', () => {
|
|
|
36
37
|
expect(innerImg).toHaveAttribute('alt', '');
|
|
37
38
|
});
|
|
38
39
|
|
|
40
|
+
it('renders fallback initials through the themeable font-size var, not a bare px literal', () => {
|
|
41
|
+
render(<Avatar name="Ada Lovelace" size="sm" data-testid="a" />);
|
|
42
|
+
const initials = screen.getByText('AL');
|
|
43
|
+
// The seam: the dynamic font size resolves to the Avatar-scoped var (with
|
|
44
|
+
// the proportional `size × 0.4` default baked in as the fallback), so a
|
|
45
|
+
// theme can re-scope it per size. A regression to a bare px literal would
|
|
46
|
+
// break theming.
|
|
47
|
+
const style = initials.getAttribute('style') ?? '';
|
|
48
|
+
expect(style).toContain('var(--_avatar-fallback-font-size,');
|
|
49
|
+
// Default still reproduces the proportional scale (sm = 24 × 0.4 = 9.6px).
|
|
50
|
+
expect(style).toMatch(/var\(--_avatar-fallback-font-size,\s*9\.6\d*px\)/);
|
|
51
|
+
});
|
|
52
|
+
|
|
39
53
|
it('retries a new src after a previous src failed to load', () => {
|
|
40
54
|
const {rerender} = render(
|
|
41
55
|
<Avatar name="Ada" src="https://example.com/broken.jpg" />,
|
|
@@ -53,6 +67,98 @@ describe('Avatar', () => {
|
|
|
53
67
|
expect(img).toHaveAttribute('src', 'https://example.com/ada.jpg');
|
|
54
68
|
});
|
|
55
69
|
|
|
70
|
+
describe('status in the accessible name (WCAG 4.1.2)', () => {
|
|
71
|
+
// The avatar root is role="img", which prunes descendant semantics —
|
|
72
|
+
// a label inside the status subtree is never announced on its own.
|
|
73
|
+
// Avatar composes the status element's `label` into its own name.
|
|
74
|
+
it('composes the status label into the accessible name for image avatars', () => {
|
|
75
|
+
render(
|
|
76
|
+
<Avatar
|
|
77
|
+
name="Ada Lovelace"
|
|
78
|
+
src="https://example.com/ada.jpg"
|
|
79
|
+
status={<AvatarStatusDot variant="success" label="Online" />}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
expect(
|
|
83
|
+
screen.getByRole('img', {name: 'Ada Lovelace, Online'}),
|
|
84
|
+
).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('composes the status label into the accessible name for initials avatars', () => {
|
|
88
|
+
render(
|
|
89
|
+
<Avatar
|
|
90
|
+
name="Ada Lovelace"
|
|
91
|
+
status={<AvatarStatusDot variant="error" label="Busy" />}
|
|
92
|
+
/>,
|
|
93
|
+
);
|
|
94
|
+
const avatar = screen.getByRole('img', {name: 'Ada Lovelace, Busy'});
|
|
95
|
+
expect(avatar).toBeInTheDocument();
|
|
96
|
+
expect(avatar).toHaveTextContent('AL');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('composes the status label with alt when alt overrides name', () => {
|
|
100
|
+
render(
|
|
101
|
+
<Avatar
|
|
102
|
+
name="Ada"
|
|
103
|
+
alt="Ada Lovelace, profile photo"
|
|
104
|
+
status={<AvatarStatusDot label="Online" />}
|
|
105
|
+
/>,
|
|
106
|
+
);
|
|
107
|
+
expect(
|
|
108
|
+
screen.getByRole('img', {name: 'Ada Lovelace, profile photo, Online'}),
|
|
109
|
+
).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('keeps the plain name when there is no status', () => {
|
|
113
|
+
render(<Avatar name="Ada Lovelace" data-testid="a" />);
|
|
114
|
+
expect(screen.getByTestId('a')).toHaveAttribute(
|
|
115
|
+
'aria-label',
|
|
116
|
+
'Ada Lovelace',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('keeps the plain name when the status dot has no label', () => {
|
|
121
|
+
render(
|
|
122
|
+
<Avatar
|
|
123
|
+
name="Ada Lovelace"
|
|
124
|
+
data-testid="a"
|
|
125
|
+
status={<AvatarStatusDot variant="success" />}
|
|
126
|
+
/>,
|
|
127
|
+
);
|
|
128
|
+
expect(screen.getByTestId('a')).toHaveAttribute(
|
|
129
|
+
'aria-label',
|
|
130
|
+
'Ada Lovelace',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('keeps the plain name for custom status nodes without a label prop', () => {
|
|
135
|
+
render(<Avatar name="Ada Lovelace" data-testid="a" status={<span />} />);
|
|
136
|
+
expect(screen.getByTestId('a')).toHaveAttribute(
|
|
137
|
+
'aria-label',
|
|
138
|
+
'Ada Lovelace',
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('announces a labelled status even on an otherwise decorative avatar', () => {
|
|
143
|
+
// A labelled status is meaningful information on its own — the avatar
|
|
144
|
+
// must not stay aria-hidden and swallow it.
|
|
145
|
+
render(
|
|
146
|
+
<Avatar data-testid="a" status={<AvatarStatusDot label="Online" />} />,
|
|
147
|
+
);
|
|
148
|
+
const el = screen.getByTestId('a');
|
|
149
|
+
expect(el).toHaveAttribute('role', 'img');
|
|
150
|
+
expect(el).toHaveAttribute('aria-label', 'Online');
|
|
151
|
+
expect(el).not.toHaveAttribute('aria-hidden');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('stays decorative with an unlabelled status and no name', () => {
|
|
155
|
+
render(<Avatar data-testid="a" status={<AvatarStatusDot />} />);
|
|
156
|
+
const el = screen.getByTestId('a');
|
|
157
|
+
expect(el).toHaveAttribute('aria-hidden', 'true');
|
|
158
|
+
expect(el).not.toHaveAttribute('aria-label');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
56
162
|
it('retries a new fallbackSrc after a previous fallbackSrc failed to load', () => {
|
|
57
163
|
const {rerender} = render(
|
|
58
164
|
<Avatar name="Ada" fallbackSrc="https://example.com/broken.jpg" />,
|
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Avatar.tsx
|
|
7
7
|
* @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
|
|
8
|
-
* (Tooltip hook) for the optional name-on-hover tooltip
|
|
8
|
+
* (Tooltip hook) for the optional name-on-hover tooltip; useTranslator (i18n)
|
|
9
9
|
* @output Exports Avatar component, AvatarProps, AvatarSize types
|
|
10
10
|
* @position Core implementation; consumed by index.ts
|
|
11
11
|
*
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Last synced props: alt, fallbackSrc, name, size, src, status, href, as, target, rel, onClick
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {useMemo, useState, type ReactNode} from 'react';
|
|
21
|
+
import {isValidElement, useMemo, useState, type ReactNode} from 'react';
|
|
22
22
|
import type {BaseProps} from '../BaseProps';
|
|
23
23
|
import * as stylex from '@stylexjs/stylex';
|
|
24
24
|
import {
|
|
@@ -34,6 +34,7 @@ import {themeProps} from '../utils/themeProps';
|
|
|
34
34
|
import {useTooltip} from '../Tooltip/useTooltip';
|
|
35
35
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
36
36
|
import type {LinkComponentType} from '../Link/types';
|
|
37
|
+
import {useTranslator} from '../i18n';
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* The offset ratio for positioning elements on a circle's edge at 45°.
|
|
@@ -110,6 +111,11 @@ const styles = stylex.create({
|
|
|
110
111
|
position: 'relative',
|
|
111
112
|
display: 'inline-flex',
|
|
112
113
|
flexShrink: 0,
|
|
114
|
+
// The wrapper is not clipped (so the status dot can overflow), so it must be
|
|
115
|
+
// rounded itself: a themed fallback background lands on `.astryx-avatar` (the
|
|
116
|
+
// class-bearing wrapper) as well as the internal var, and an unrounded
|
|
117
|
+
// wrapper would show that fill as square corners behind the circular content.
|
|
118
|
+
borderRadius: radiusVars['--radius-full'],
|
|
113
119
|
},
|
|
114
120
|
content: {
|
|
115
121
|
display: 'flex',
|
|
@@ -130,10 +136,14 @@ const styles = stylex.create({
|
|
|
130
136
|
justifyContent: 'center',
|
|
131
137
|
width: '100%',
|
|
132
138
|
height: '100%',
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
// Fallback surface (initials + default icon). Each property reads an
|
|
140
|
+
// Avatar-scoped internal var so a theme can re-scope the fallback wash and
|
|
141
|
+
// initials weight/color without forking; the defaults reproduce today's
|
|
142
|
+
// exact output. See derivedVarRegistry (avatar) + Avatar.doc.mjs theming.
|
|
143
|
+
backgroundColor: `var(--_avatar-fallback-background, ${colorVars['--color-neutral']})`,
|
|
144
|
+
color: `var(--_avatar-fallback-color, ${colorVars['--color-text-secondary']})`,
|
|
135
145
|
fontFamily: typographyVars['--font-family-body'],
|
|
136
|
-
fontWeight: fontWeightVars['--font-weight-medium']
|
|
146
|
+
fontWeight: `var(--_avatar-fallback-font-weight, ${fontWeightVars['--font-weight-medium']})`,
|
|
137
147
|
textTransform: 'uppercase',
|
|
138
148
|
},
|
|
139
149
|
status: {
|
|
@@ -195,8 +205,13 @@ const dynamicStyles = stylex.create({
|
|
|
195
205
|
width: size,
|
|
196
206
|
height: size,
|
|
197
207
|
}),
|
|
208
|
+
// Initials font size defaults to the proportional `size × ratio` scale but is
|
|
209
|
+
// reachable via the `--_avatar-fallback-font-size` derived var, so a theme can
|
|
210
|
+
// set a per-size type scale (e.g. `components.avatar['size:sm']`).
|
|
198
211
|
fontSize: (size: number) => ({
|
|
199
|
-
fontSize: size
|
|
212
|
+
fontSize: `var(--_avatar-fallback-font-size, ${
|
|
213
|
+
size * INITIALS_FONT_SIZE_RATIO
|
|
214
|
+
}px)`,
|
|
200
215
|
}),
|
|
201
216
|
statusPosition: (size: number) => ({
|
|
202
217
|
bottom: size * CIRCLE_EDGE_OFFSET_RATIO,
|
|
@@ -266,6 +281,11 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
|
|
|
266
281
|
/**
|
|
267
282
|
* Content displayed in the corner of the avatar.
|
|
268
283
|
* Typically used for status indicators or badges.
|
|
284
|
+
*
|
|
285
|
+
* When the element carries a string `label` prop (as `AvatarStatusDot`
|
|
286
|
+
* does), the label is composed into the avatar's accessible name
|
|
287
|
+
* (e.g. "Jane Doe, Online") so assistive tech can reach the status —
|
|
288
|
+
* the `role="img"` root prunes descendant semantics (WCAG 4.1.2).
|
|
269
289
|
*/
|
|
270
290
|
status?: ReactNode;
|
|
271
291
|
/**
|
|
@@ -332,6 +352,24 @@ function getInitials(name: string): string {
|
|
|
332
352
|
return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase();
|
|
333
353
|
}
|
|
334
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Reads the accessible status label off the `status` element, when it
|
|
357
|
+
* exposes one. `AvatarStatusDot`'s `label` prop is the canonical source,
|
|
358
|
+
* but any custom status element with a string `label` prop participates.
|
|
359
|
+
*
|
|
360
|
+
* The avatar root is `role="img"`, which prunes ALL descendant semantics
|
|
361
|
+
* from the accessibility tree — a label inside the status subtree is never
|
|
362
|
+
* announced on its own. Composing it into the avatar's own accessible name
|
|
363
|
+
* is the only way the status reaches assistive tech (WCAG 4.1.2).
|
|
364
|
+
*/
|
|
365
|
+
function getStatusLabel(status: ReactNode): string | undefined {
|
|
366
|
+
if (!isValidElement(status)) {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
const {label} = status.props as {label?: unknown};
|
|
370
|
+
return typeof label === 'string' && label !== '' ? label : undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
335
373
|
/**
|
|
336
374
|
* Default person icon SVG for when no image or name is provided
|
|
337
375
|
*/
|
|
@@ -398,10 +436,24 @@ export function Avatar({
|
|
|
398
436
|
const showInitials = !showImage && !showFallbackImage && name;
|
|
399
437
|
const showIcon = !showImage && !showFallbackImage && !name;
|
|
400
438
|
|
|
401
|
-
// A meaningful accessible name comes from `alt
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
|
|
439
|
+
// A meaningful accessible name comes from `alt`/`name`, composed with the
|
|
440
|
+
// status element's `label` when one is present ("Jane Doe, Online") — the
|
|
441
|
+
// `role="img"` root prunes descendant semantics, so surfacing the label in
|
|
442
|
+
// the avatar's own name is the only way assistive tech can reach the
|
|
443
|
+
// status (WCAG 4.1.2). A labelled status alone is also meaningful. With
|
|
444
|
+
// neither a name nor a labelled status, the avatar is decorative — expose
|
|
445
|
+
// it as `presentation`/`aria-hidden` rather than announcing a meaningless
|
|
446
|
+
// generic "Avatar" (obs-9).
|
|
447
|
+
const t = useTranslator();
|
|
448
|
+
const nameLabel = alt || name;
|
|
449
|
+
const statusLabel = getStatusLabel(status);
|
|
450
|
+
const accessibleName =
|
|
451
|
+
nameLabel && statusLabel
|
|
452
|
+
? t('@astryx.avatar.nameWithStatus', {
|
|
453
|
+
name: nameLabel,
|
|
454
|
+
status: statusLabel,
|
|
455
|
+
})
|
|
456
|
+
: nameLabel || statusLabel;
|
|
405
457
|
const isDecorative = !accessibleName;
|
|
406
458
|
const avatarGroup = useAvatarGroup();
|
|
407
459
|
const resolvedSize = avatarGroup?.size ?? size;
|
|
@@ -21,7 +21,7 @@ export const docs = {
|
|
|
21
21
|
name: 'label',
|
|
22
22
|
type: 'string',
|
|
23
23
|
description:
|
|
24
|
-
'Accessible label describing the status.
|
|
24
|
+
'Accessible label describing the status. Inside an Avatar it is composed into the avatar\'s accessible name (e.g. "Jane Doe, Online"); the Avatar root is role="img", which prunes child semantics, so the composed name is how the status reaches assistive tech. Standalone dots expose role="img" with this label directly.',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
name: 'icon',
|
|
@@ -59,7 +59,7 @@ export const docsZh = {
|
|
|
59
59
|
name: 'label',
|
|
60
60
|
type: 'string',
|
|
61
61
|
description:
|
|
62
|
-
'
|
|
62
|
+
'描述状态的无障碍标签。在 Avatar 内会被合成进头像自身的无障碍名称(如 "Jane Doe, Online")——Avatar 根元素为 role="img",会裁剪子元素语义,因此合成名称是辅助技术获知状态的途径。独立使用时状态点直接以 role="img" 暴露该标签。',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
name: 'icon',
|
|
@@ -80,7 +80,7 @@ export const docsDense = {
|
|
|
80
80
|
variant:
|
|
81
81
|
'colour + shape variant: success filled, neutral ring, error minus',
|
|
82
82
|
label:
|
|
83
|
-
'accessible status label (
|
|
84
|
-
icon: 'icon centered in dot (hidden at tiny sizes); replaces the built-in shape glyph
|
|
83
|
+
'accessible status label; composed into the Avatar accessible name ("Jane Doe, Online"); standalone dots expose role="img" with it directly',
|
|
84
|
+
icon: 'icon centered in dot (hidden at tiny sizes); replaces the built-in shape glyph, so differ it per status',
|
|
85
85
|
},
|
|
86
86
|
};
|
|
@@ -115,10 +115,12 @@ export interface AvatarStatusDotProps extends BaseProps<HTMLDivElement> {
|
|
|
115
115
|
* Describes the meaning of the indicator for screen readers
|
|
116
116
|
* (e.g. "Online", "Accepted", "John Doe is busy").
|
|
117
117
|
*
|
|
118
|
-
* Note: inside an Avatar the
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
118
|
+
* Note: inside an Avatar the dot sits in the avatar's `role="img"`
|
|
119
|
+
* subtree, where descendant semantics are pruned — the dot is never its
|
|
120
|
+
* own stop there. Instead, Avatar reads this `label` and composes it into
|
|
121
|
+
* its own accessible name (e.g. "Jane Doe, Online"), which is how the
|
|
122
|
+
* status reaches assistive tech (WCAG 4.1.2). Standalone dots (outside an
|
|
123
|
+
* Avatar) expose `role="img"` with this label directly.
|
|
122
124
|
*/
|
|
123
125
|
label?: string;
|
|
124
126
|
/**
|
|
@@ -4,7 +4,7 @@ import {render, screen} from '@testing-library/react';
|
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import {AvatarGroup} from './AvatarGroup';
|
|
6
6
|
import {AvatarGroupOverflow} from './AvatarGroupOverflow';
|
|
7
|
-
import {Avatar} from '../Avatar';
|
|
7
|
+
import {Avatar, AvatarStatusDot} from '../Avatar';
|
|
8
8
|
|
|
9
9
|
describe('AvatarGroup', () => {
|
|
10
10
|
it('renders all avatar children', () => {
|
|
@@ -21,6 +21,21 @@ describe('AvatarGroup', () => {
|
|
|
21
21
|
expect(screen.getByLabelText('Charlie')).toBeInTheDocument();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
it('composes a labelled status into a grouped avatar accessible name (WCAG 4.1.2)', () => {
|
|
25
|
+
render(
|
|
26
|
+
<AvatarGroup>
|
|
27
|
+
<Avatar
|
|
28
|
+
name="Alice"
|
|
29
|
+
status={<AvatarStatusDot variant="success" label="Online" />}
|
|
30
|
+
/>
|
|
31
|
+
<Avatar name="Bob" />
|
|
32
|
+
</AvatarGroup>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByLabelText('Alice, Online')).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByLabelText('Bob')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
24
39
|
it('renders with role="group" and default aria-label', () => {
|
|
25
40
|
render(
|
|
26
41
|
<AvatarGroup>
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
import {
|
|
60
60
|
MENU_ITEM_ROLES,
|
|
61
61
|
MENU_ITEM_SELECTOR,
|
|
62
|
+
MENU_BOUNDARY_SELECTOR,
|
|
62
63
|
} from '../DropdownMenu/menuItemRoles';
|
|
63
64
|
import type {DropdownMenuOption} from '../DropdownMenu/DropdownMenu';
|
|
64
65
|
|
|
@@ -515,21 +516,15 @@ function BreadcrumbMenuTrigger({
|
|
|
515
516
|
handleKeyDown: listNavKeyDown,
|
|
516
517
|
focusFirst,
|
|
517
518
|
focusItem,
|
|
519
|
+
ownsEvent,
|
|
520
|
+
getItems: getMenuItems,
|
|
518
521
|
} = useListFocus<HTMLDivElement>({
|
|
519
522
|
itemSelector: MENU_ITEM_SELECTOR,
|
|
523
|
+
boundarySelector: MENU_BOUNDARY_SELECTOR,
|
|
520
524
|
wrap: false,
|
|
521
525
|
onEscape: closeMenu,
|
|
522
526
|
});
|
|
523
527
|
|
|
524
|
-
const getMenuItems = useCallback(
|
|
525
|
-
(): HTMLElement[] =>
|
|
526
|
-
listRef.current
|
|
527
|
-
? Array.from(
|
|
528
|
-
listRef.current.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR),
|
|
529
|
-
)
|
|
530
|
-
: [],
|
|
531
|
-
[listRef],
|
|
532
|
-
);
|
|
533
528
|
const typeahead = useTypeahead({
|
|
534
529
|
getItemLabels: () => getMenuItems().map(el => el.textContent),
|
|
535
530
|
onMatch: focusItem,
|
|
@@ -542,6 +537,11 @@ function BreadcrumbMenuTrigger({
|
|
|
542
537
|
|
|
543
538
|
const listKeyDown = useCallback(
|
|
544
539
|
(e: React.KeyboardEvent) => {
|
|
540
|
+
// A submenu flyout renders inline inside this menu; its key events bubble
|
|
541
|
+
// up here. Let that level own them — only handle events from this level.
|
|
542
|
+
if (!ownsEvent(e)) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
545
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
546
546
|
e.preventDefault();
|
|
547
547
|
const focused = document.activeElement as HTMLElement | null;
|
|
@@ -564,7 +564,7 @@ function BreadcrumbMenuTrigger({
|
|
|
564
564
|
}
|
|
565
565
|
listNavKeyDown(e);
|
|
566
566
|
},
|
|
567
|
-
[listNavKeyDown, closeMenu, typeahead],
|
|
567
|
+
[listNavKeyDown, closeMenu, typeahead, ownsEvent],
|
|
568
568
|
);
|
|
569
569
|
|
|
570
570
|
const openAndFocus = useCallback(() => {
|
|
@@ -589,6 +589,57 @@ describe('BreadcrumbItem menu', () => {
|
|
|
589
589
|
).toHaveFocus();
|
|
590
590
|
});
|
|
591
591
|
|
|
592
|
+
it('renders a submenu from a nested items array and keyboard-reaches an item after it', async () => {
|
|
593
|
+
// Breadcrumb menus reuse the DropdownMenu item pipeline, so a nested
|
|
594
|
+
// `items` array becomes a submenu. The inline flyout must not pollute the
|
|
595
|
+
// breadcrumb menu's roving order — an item after the submenu row stays
|
|
596
|
+
// keyboard-reachable.
|
|
597
|
+
const onDelete = vi.fn();
|
|
598
|
+
render(
|
|
599
|
+
<Breadcrumbs>
|
|
600
|
+
<BreadcrumbItem
|
|
601
|
+
menu={[
|
|
602
|
+
{label: 'Rename', onClick: vi.fn()},
|
|
603
|
+
{
|
|
604
|
+
label: 'Move to',
|
|
605
|
+
items: [
|
|
606
|
+
{label: 'Folder A', onClick: vi.fn()},
|
|
607
|
+
{label: 'Folder B', onClick: vi.fn()},
|
|
608
|
+
],
|
|
609
|
+
},
|
|
610
|
+
{type: 'divider'},
|
|
611
|
+
{label: 'Delete', onClick: onDelete},
|
|
612
|
+
]}>
|
|
613
|
+
Teams
|
|
614
|
+
</BreadcrumbItem>
|
|
615
|
+
<BreadcrumbItem isCurrent>Overview</BreadcrumbItem>
|
|
616
|
+
</Breadcrumbs>,
|
|
617
|
+
);
|
|
618
|
+
const trigger = screen.getByRole('button', {name: 'Teams'});
|
|
619
|
+
trigger.focus();
|
|
620
|
+
fireEvent.keyDown(trigger, {key: 'ArrowDown'});
|
|
621
|
+
// Two role="menu" exist (the breadcrumb menu + the inline submenu flyout);
|
|
622
|
+
// the breadcrumb menu is the first in DOM order.
|
|
623
|
+
const menu = screen.getAllByRole('menu', {hidden: true})[0];
|
|
624
|
+
const submenuTrigger = screen.getByRole('menuitem', {
|
|
625
|
+
name: /Move to/,
|
|
626
|
+
hidden: true,
|
|
627
|
+
});
|
|
628
|
+
expect(submenuTrigger).toHaveAttribute('aria-haspopup', 'menu');
|
|
629
|
+
await waitFor(() => {
|
|
630
|
+
expect(
|
|
631
|
+
screen.getByRole('menuitem', {name: 'Rename', hidden: true}),
|
|
632
|
+
).toHaveFocus();
|
|
633
|
+
});
|
|
634
|
+
// Rename → Move to → Delete, one step per press.
|
|
635
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
636
|
+
expect(submenuTrigger).toHaveFocus();
|
|
637
|
+
fireEvent.keyDown(menu, {key: 'ArrowDown'});
|
|
638
|
+
expect(
|
|
639
|
+
screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
|
|
640
|
+
).toHaveFocus();
|
|
641
|
+
});
|
|
642
|
+
|
|
592
643
|
it('allows menu together with isCurrent (both aria-current and aria-haspopup)', () => {
|
|
593
644
|
render(
|
|
594
645
|
<Breadcrumbs>
|
package/src/Breadcrumbs/index.ts
CHANGED
|
@@ -35,6 +35,10 @@ export {
|
|
|
35
35
|
DropdownMenuRadioItem as BreadcrumbMenuRadioItem,
|
|
36
36
|
type DropdownMenuRadioItemProps as BreadcrumbMenuRadioItemProps,
|
|
37
37
|
} from '../DropdownMenu/DropdownMenuRadioItem';
|
|
38
|
+
export {
|
|
39
|
+
DropdownMenuSubMenu as BreadcrumbMenuSubMenu,
|
|
40
|
+
type DropdownMenuSubMenuProps as BreadcrumbMenuSubMenuProps,
|
|
41
|
+
} from '../DropdownMenu/DropdownMenuSubMenu';
|
|
38
42
|
export type {
|
|
39
43
|
DropdownMenuOption as BreadcrumbMenuOption,
|
|
40
44
|
DropdownMenuItemData as BreadcrumbMenuItemData,
|
|
@@ -163,8 +163,6 @@ export const docs = {
|
|
|
163
163
|
],
|
|
164
164
|
vars: [
|
|
165
165
|
{name: '--_button-radius', description: 'Border radius', default: 'var(--radius-element)', private: true},
|
|
166
|
-
{name: '--button-press-scale', description: 'Active press transform', default: 'scale(0.98)'},
|
|
167
|
-
{name: '--button-disabled-opacity', description: 'Opacity when disabled', default: '0.5'},
|
|
168
166
|
{name: '--button-focus-offset', description: 'Focus ring outline offset', default: '3px'},
|
|
169
167
|
{name: '--button-icon-only-aspect', description: 'Aspect ratio for icon-only buttons', default: '1 / 1'},
|
|
170
168
|
],
|
|
@@ -247,8 +245,6 @@ export const docsZh = {
|
|
|
247
245
|
],
|
|
248
246
|
vars: [
|
|
249
247
|
{name: '--_button-radius', description: '圆角半径', default: 'var(--radius-element)', private: true},
|
|
250
|
-
{name: '--button-press-scale', description: '按下时的变换', default: 'scale(0.98)'},
|
|
251
|
-
{name: '--button-disabled-opacity', description: '禁用时的不透明度', default: '0.5'},
|
|
252
248
|
{name: '--button-focus-offset', description: '焦点环轮廓偏移', default: '3px'},
|
|
253
249
|
{name: '--button-icon-only-aspect', description: '纯图标按钮的宽高比', default: '1 / 1'},
|
|
254
250
|
],
|