@astryxdesign/core 0.4.5 → 0.4.6-canary.cea9ecd
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 +134 -0
- package/dist/Avatar/Avatar.d.ts +10 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +139 -30
- package/dist/Avatar/AvatarStatusDot.d.ts +2 -1
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Avatar/AvatarStatusDot.js +30 -4
- package/dist/Avatar/AvatarStatusLabelContext.d.ts +39 -0
- package/dist/Avatar/AvatarStatusLabelContext.d.ts.map +1 -0
- package/dist/Avatar/AvatarStatusLabelContext.js +41 -0
- package/dist/AvatarGroup/AvatarGroup.d.ts +3 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +15 -9
- package/dist/Blockquote/Blockquote.d.ts +4 -3
- package/dist/Blockquote/Blockquote.d.ts.map +1 -1
- package/dist/Blockquote/Blockquote.js +9 -10
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +21 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +47 -34
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +7 -7
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +21 -12
- package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +5 -5
- package/dist/Center/Center.d.ts +23 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +12 -6
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +3 -3
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +2 -2
- package/dist/Chat/useChatDictation.d.ts +1 -1
- package/dist/Chat/useChatDictation.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.d.ts +1 -1
- package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
- package/dist/Chat/useSpeechRecognition.js +4 -3
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +2 -2
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +5 -5
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +4 -4
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +1 -1
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +2 -2
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +3 -3
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -6
- package/dist/DateInput/DateInput.d.ts +31 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +90 -22
- package/dist/DateInput/MonthScroller.d.ts +35 -0
- package/dist/DateInput/MonthScroller.d.ts.map +1 -0
- package/dist/DateInput/MonthScroller.js +658 -0
- package/dist/DateInput/MonthYearWheels.d.ts +24 -0
- package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
- package/dist/DateInput/MonthYearWheels.js +114 -0
- package/dist/DateInput/TouchDateField.d.ts +10 -0
- package/dist/DateInput/TouchDateField.d.ts.map +1 -0
- package/dist/DateInput/TouchDateField.js +810 -0
- package/dist/DateInput/Wheel.d.ts +41 -0
- package/dist/DateInput/Wheel.d.ts.map +1 -0
- package/dist/DateInput/Wheel.js +315 -0
- package/dist/DateInput/monthGeometry.d.ts +65 -0
- package/dist/DateInput/monthGeometry.d.ts.map +1 -0
- package/dist/DateInput/monthGeometry.js +98 -0
- package/dist/DateInput/tokens.stylex.d.ts +63 -0
- package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
- package/dist/DateInput/tokens.stylex.js +74 -0
- package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
- package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
- package/dist/DateInput/useOwnScrollGesture.js +202 -0
- package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
- package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
- package/dist/DateInput/usePointerDragScroll.js +216 -0
- package/dist/DateInput/useScrollSettle.d.ts +26 -0
- package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
- package/dist/DateInput/useScrollSettle.js +149 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +17 -14
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +17 -14
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +3 -3
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +4 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -0
- package/dist/Field/inputStyles.stylex.d.ts +1 -1
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +5 -5
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +6 -6
- package/dist/HoverCard/HoverCard.d.ts +13 -3
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/index.d.ts +1 -1
- package/dist/HoverCard/index.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.d.ts +17 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +78 -9
- package/dist/InputGroup/InputGroup.js +1 -1
- package/dist/Item/Item.d.ts +9 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +52 -14
- package/dist/Layer/index.d.ts +4 -2
- package/dist/Layer/index.d.ts.map +1 -1
- package/dist/Layer/index.js +2 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +3 -2
- package/dist/Layer/useTouchTrigger.d.ts +113 -0
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
- package/dist/Layer/useTouchTrigger.js +241 -0
- package/dist/Layout/container.stylex.d.ts +4 -2
- package/dist/Layout/container.stylex.d.ts.map +1 -1
- package/dist/Layout/container.stylex.js +19 -11
- package/dist/Layout/index.d.ts +1 -0
- package/dist/Layout/index.d.ts.map +1 -1
- package/dist/Layout/index.js +4 -0
- package/dist/Layout/padding.stylex.d.ts +315 -12
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +389 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +5 -4
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -6
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +1 -1
- package/dist/MobileNav/MobileNav.d.ts +1 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +24 -6
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +8 -8
- package/dist/NavItem/navItemStyles.stylex.d.ts +4 -4
- package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
- package/dist/NavItem/navItemStyles.stylex.js +4 -4
- package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenuItem.js +3 -3
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +36 -8
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +3 -3
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +2 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +11 -12
- package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchToken.js +3 -1
- package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +23 -8
- package/dist/RadioList/RadioListItem.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +70 -40
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +7 -7
- package/dist/Section/Section.d.ts +36 -1
- package/dist/Section/Section.d.ts.map +1 -1
- package/dist/Section/Section.js +12 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +7 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -3
- package/dist/Selector/Selector.d.ts +26 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +93 -20
- package/dist/Selector/SelectorOption.d.ts +14 -1
- package/dist/Selector/SelectorOption.d.ts.map +1 -1
- package/dist/Selector/SelectorOption.js +6 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
- package/dist/Selector/SelectorRowLayoutContext.js +27 -0
- package/dist/Selector/types.d.ts +1 -0
- package/dist/Selector/types.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +4 -4
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +3 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -7
- package/dist/Stack/Stack.d.ts +23 -1
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +12 -6
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +2 -2
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +1 -1
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +3 -3
- package/dist/Table/TableRow.js +4 -4
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
- package/dist/Table/plugins/filtering/useTableFiltering.js +2 -2
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +2 -2
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -2
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +3 -3
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.js +1 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +3 -3
- package/dist/TimeInput/TimeInput.js +2 -2
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +6 -4
- package/dist/Timestamp/formatInstant.d.ts +3 -2
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +8 -8
- package/dist/Timestamp/tooltipEntries.d.ts +2 -1
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.js +5 -3
- package/dist/Toast/Toast.d.ts +4 -3
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +10 -7
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +6 -6
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -1
- package/dist/Tooltip/Tooltip.d.ts +16 -3
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +2 -0
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +21 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +71 -13
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +4 -4
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +3 -3
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +2 -2
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +2 -2
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -4
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -7
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +2 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +1 -1
- package/dist/astryx.css +124 -62
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
- package/dist/hooks/containerReveal.stylex.js +18 -18
- package/dist/hooks/scrollbarGutter.d.ts +28 -0
- package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
- package/dist/hooks/scrollbarGutter.js +103 -0
- package/dist/hooks/useAutoMediaMode.d.ts +31 -0
- package/dist/hooks/useAutoMediaMode.d.ts.map +1 -0
- package/dist/hooks/useAutoMediaMode.js +201 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -1
- package/dist/hooks/useListFocus.d.ts +5 -2
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +12 -6
- package/dist/hooks/useScrollLock.d.ts +4 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.js +13 -1
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +2 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +6 -0
- package/dist/i18n/useCollator.d.ts +12 -0
- package/dist/i18n/useCollator.d.ts.map +1 -0
- package/dist/i18n/useCollator.js +40 -0
- package/dist/i18n/useLocale.d.ts +3 -0
- package/dist/i18n/useLocale.d.ts.map +1 -0
- package/dist/i18n/useLocale.js +31 -0
- package/dist/theme/MediaTheme.d.ts +25 -5
- package/dist/theme/MediaTheme.d.ts.map +1 -1
- package/dist/theme/MediaTheme.js +31 -6
- package/dist/theme/defineTheme.d.ts +5 -0
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +5 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +7 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +66 -5
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/syntax/index.d.ts +6 -0
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/index.js +7 -2
- package/dist/utils/dateParser.d.ts +4 -3
- package/dist/utils/dateParser.d.ts.map +1 -1
- package/dist/utils/dateParser.js +8 -8
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/plainDate.d.ts +5 -2
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +20 -4
- package/locales/en.json +28 -0
- package/locales/pseudo.json +21 -0
- package/package.json +3 -3
- package/src/Avatar/Avatar.doc.mjs +10 -6
- package/src/Avatar/Avatar.test.tsx +219 -1
- package/src/Avatar/Avatar.tsx +179 -37
- package/src/Avatar/AvatarStatusDot.tsx +30 -4
- package/src/Avatar/AvatarStatusLabelContext.ts +47 -0
- package/src/AvatarGroup/AvatarGroup.doc.mjs +10 -6
- package/src/AvatarGroup/AvatarGroup.test.tsx +62 -0
- package/src/AvatarGroup/AvatarGroup.tsx +3 -1
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +31 -8
- package/src/Banner/Banner.doc.mjs +2 -0
- package/src/Blockquote/Blockquote.doc.mjs +30 -7
- package/src/Blockquote/Blockquote.test.tsx +34 -3
- package/src/Blockquote/Blockquote.tsx +11 -9
- package/src/BottomSheet/BottomSheet.doc.mjs +3 -3
- package/src/BottomSheet/BottomSheet.test.tsx +24 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +58 -3
- package/src/Breadcrumbs/BreadcrumbItem.tsx +36 -23
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +26 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -0
- package/src/Button/Button.tsx +12 -9
- package/src/Calendar/Calendar.tsx +15 -9
- package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
- package/src/Calendar/styles.ts +8 -5
- package/src/Center/Center.doc.mjs +48 -0
- package/src/Center/Center.test.tsx +158 -0
- package/src/Center/Center.tsx +50 -9
- package/src/Chat/ChatComposer.tsx +11 -5
- package/src/Chat/ChatComposerDrawer.tsx +4 -1
- package/src/Chat/ChatToolCalls.tsx +13 -4
- package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
- package/src/Chat/useChatDictation.ts +1 -1
- package/src/Chat/useSpeechRecognition.ts +5 -3
- package/src/Chat/useTriggerMenu.tsx +4 -1
- package/src/CheckboxInput/CheckboxInput.tsx +5 -2
- package/src/Citation/Citation.test.tsx +6 -1
- package/src/Citation/Citation.tsx +11 -5
- package/src/ClickableCard/ClickableCard.tsx +7 -4
- package/src/CodeBlock/CodeBlock.tsx +4 -1
- package/src/Collapsible/Collapsible.tsx +5 -2
- package/src/CommandPalette/CommandPaletteItem.tsx +6 -3
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
- package/src/ComplexSelector/ComplexSelector.tsx +14 -7
- package/src/DateInput/DateInput.test.tsx +17 -1
- package/src/DateInput/DateInput.tsx +93 -21
- package/src/DateInput/DateInputTouch.test.tsx +2582 -0
- package/src/DateInput/MonthScroller.tsx +878 -0
- package/src/DateInput/MonthYearWheels.tsx +147 -0
- package/src/DateInput/TouchDateField.tsx +1223 -0
- package/src/DateInput/Wheel.tsx +473 -0
- package/src/DateInput/monthGeometry.ts +118 -0
- package/src/DateInput/tokens.stylex.ts +99 -0
- package/src/DateInput/useOwnScrollGesture.ts +227 -0
- package/src/DateInput/usePointerDragScroll.ts +220 -0
- package/src/DateInput/useScrollSettle.ts +164 -0
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.tsx +31 -13
- package/src/DateTimeInput/DateTimeInput.tsx +25 -9
- package/src/Dialog/Dialog.test.tsx +24 -0
- package/src/Dialog/Dialog.tsx +3 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -2
- package/src/Field/Field.doc.mjs +2 -0
- package/src/Field/FieldLabel.tsx +9 -3
- package/src/Field/InputClearButton.test.tsx +110 -3
- package/src/Field/InputClearButton.tsx +38 -0
- package/src/Field/inputStyles.stylex.ts +17 -13
- package/src/FileInput/FileInput.tsx +15 -8
- package/src/FormLayout/FormLayout.doc.mjs +1 -1
- package/src/HoverCard/HoverCard.doc.mjs +18 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -0
- package/src/HoverCard/HoverCard.tsx +22 -2
- package/src/HoverCard/index.ts +1 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +103 -7
- package/src/InputGroup/InputGroup.tsx +1 -1
- package/src/Item/Item.doc.mjs +1 -0
- package/src/Item/Item.test.tsx +50 -0
- package/src/Item/Item.tsx +54 -6
- package/src/Layer/index.ts +10 -0
- package/src/Layer/useLayer.tsx +13 -2
- package/src/Layer/useTouchTrigger.test.ts +70 -0
- package/src/Layer/useTouchTrigger.ts +366 -0
- package/src/Layout/Layout.doc.mjs +18 -0
- package/src/Layout/container.stylex.ts +11 -3
- package/src/Layout/index.ts +4 -0
- package/src/Layout/overlayPaddingReset.test.tsx +222 -0
- package/src/Layout/padding.stylex.ts +192 -12
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/Link/Link.tsx +9 -6
- package/src/MetadataList/MetadataList.tsx +4 -1
- package/src/MobileNav/MobileNav.tsx +61 -5
- package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
- package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
- package/src/MultiSelector/MultiSelector.test.tsx +1 -1
- package/src/MultiSelector/MultiSelector.tsx +26 -10
- package/src/NavItem/navItemStyles.stylex.ts +7 -4
- package/src/NavMenu/NavHeadingMenuItem.tsx +6 -3
- package/src/NumberInput/NumberInput.doc.mjs +24 -0
- package/src/NumberInput/NumberInput.test.tsx +138 -0
- package/src/NumberInput/NumberInput.tsx +47 -8
- package/src/Outline/Outline.tsx +5 -2
- package/src/Pagination/Pagination.tsx +5 -2
- package/src/Popover/Popover.test.tsx +27 -1
- package/src/PowerSearch/PowerSearch.tsx +17 -8
- package/src/PowerSearch/PowerSearchToken.tsx +3 -0
- package/src/PowerSearch/formatFilterValue.test.ts +22 -2
- package/src/PowerSearch/formatFilterValue.ts +31 -6
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/RadioList/RadioList.test.tsx +170 -2
- package/src/RadioList/RadioListItem.tsx +88 -53
- package/src/Resizable/ResizeHandle.test.tsx +22 -0
- package/src/Resizable/ResizeHandle.tsx +25 -7
- package/src/Section/Section.doc.mjs +55 -0
- package/src/Section/Section.test.tsx +201 -4
- package/src/Section/Section.tsx +69 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +15 -3
- package/src/SelectableCard/SelectableCard.tsx +6 -3
- package/src/Selector/Selector.doc.mjs +16 -2
- package/src/Selector/Selector.test.tsx +525 -1
- package/src/Selector/Selector.tsx +180 -25
- package/src/Selector/SelectorOption.doc.mjs +7 -0
- package/src/Selector/SelectorOption.tsx +21 -0
- package/src/Selector/SelectorRowLayoutContext.ts +32 -0
- package/src/Selector/types.ts +4 -4
- package/src/SideNav/SideNavHeading.tsx +14 -5
- package/src/SideNav/SideNavItem.tsx +9 -3
- package/src/Slider/Slider.tsx +25 -6
- package/src/Stack/Stack.doc.mjs +72 -0
- package/src/Stack/Stack.test.tsx +156 -0
- package/src/Stack/Stack.tsx +50 -8
- package/src/Switch/Switch.tsx +5 -2
- package/src/TabList/Tab.tsx +4 -1
- package/src/TabList/TabMenu.tsx +9 -3
- package/src/Table/TableRow.tsx +4 -4
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +6 -2
- package/src/Table/plugins/filtering/useTableFiltering.tsx +4 -1
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +10 -3
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +6 -3
- package/src/Table/plugins/sortable/useTableSortable.tsx +4 -1
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
- package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
- package/src/Table/plugins/tree/useTableTreeData.tsx +10 -4
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.tsx +1 -1
- package/src/TextInput/TextInput.tsx +1 -1
- package/src/Thumbnail/Thumbnail.tsx +9 -3
- package/src/TimeInput/TimeInput.tsx +1 -1
- package/src/Timestamp/Timestamp.test.tsx +27 -7
- package/src/Timestamp/Timestamp.tsx +8 -4
- package/src/Timestamp/formatInstant.ts +9 -7
- package/src/Timestamp/tooltipEntries.test.ts +11 -6
- package/src/Timestamp/tooltipEntries.ts +6 -2
- package/src/Toast/Toast.tsx +9 -7
- package/src/Token/Token.tsx +14 -5
- package/src/Tokenizer/Tokenizer.tsx +4 -1
- package/src/Tooltip/Tooltip.doc.mjs +18 -0
- package/src/Tooltip/Tooltip.test.tsx +254 -1
- package/src/Tooltip/Tooltip.tsx +22 -2
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/useTooltip.doc.mjs +2 -0
- package/src/Tooltip/useTooltip.tsx +99 -21
- package/src/TopNav/TopNavHeading.tsx +13 -4
- package/src/TopNav/TopNavItem.tsx +6 -3
- package/src/TopNav/TopNavMegaMenu.tsx +5 -2
- package/src/TopNav/TopNavMegaMenuItem.tsx +5 -2
- package/src/TopNav/TopNavMenu.tsx +10 -4
- package/src/TreeList/TreeListItem.tsx +22 -7
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.tsx +4 -1
- package/src/hooks/containerReveal.stylex.ts +54 -18
- package/src/hooks/scrollbarGutter.test.ts +167 -0
- package/src/hooks/scrollbarGutter.ts +121 -0
- package/src/hooks/useAutoMediaMode.test.ts +109 -0
- package/src/hooks/useAutoMediaMode.ts +233 -0
- package/src/hooks/useInputStatusIcon.tsx +4 -1
- package/src/hooks/useListFocus.doc.mjs +2 -2
- package/src/hooks/useListFocus.test.tsx +65 -3
- package/src/hooks/useListFocus.ts +15 -7
- package/src/hooks/useScrollLock.doc.mjs +4 -4
- package/src/hooks/useScrollLock.test.ts +73 -0
- package/src/hooks/useScrollLock.ts +14 -0
- package/src/i18n/InternationalizationContext.ts +2 -0
- package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
- package/src/i18n/__tests__/useCollator.test.tsx +115 -0
- package/src/i18n/__tests__/useLocale.test.tsx +52 -0
- package/src/i18n/index.ts +6 -0
- package/src/i18n/useCollator.doc.mjs +84 -0
- package/src/i18n/useCollator.ts +44 -0
- package/src/i18n/useLocale.doc.mjs +70 -0
- package/src/i18n/useLocale.ts +34 -0
- package/src/i18n/useTranslator.doc.mjs +1 -1
- package/src/reset.css +9 -2
- package/src/reset.test.ts +22 -0
- package/src/theme/MediaTheme.doc.mjs +23 -4
- package/src/theme/MediaTheme.dom.test.tsx +97 -0
- package/src/theme/MediaTheme.tsx +42 -7
- package/src/theme/defineTheme.test.ts +9 -3
- package/src/theme/defineTheme.ts +5 -0
- package/src/theme/derivedVarRegistry.test.ts +19 -5
- package/src/theme/derivedVarRegistry.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +118 -0
- package/src/theme/generateThemeRules.ts +83 -5
- package/src/theme/index.ts +1 -1
- package/src/theme/syntax/index.ts +6 -2
- package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
- package/src/utils/dateParser.test.ts +20 -0
- package/src/utils/dateParser.ts +14 -7
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +6 -0
- package/src/utils/plainDate.ts +21 -4
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Avatar.tsx
|
|
7
|
-
* @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
|
|
7
|
+
* @input Uses React, HTMLAttributes, ReactNode, useState, useRef; useTooltip
|
|
8
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
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
* SYNC: When modified, update these files to stay in sync:
|
|
13
13
|
* - /packages/core/src/Avatar/Avatar.doc.mjs (props table, features, implementation notes)
|
|
14
14
|
* - /packages/core/src/Avatar/index.ts (exports if types change)
|
|
15
|
+
* - /packages/core/src/Avatar/AvatarStatusLabelContext.ts (the status label ref)
|
|
15
16
|
* - /apps/storybook/stories/Avatar.stories.tsx (storybook stories)
|
|
16
17
|
* - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
|
|
17
18
|
*
|
|
18
19
|
* Last synced props: alt, fallbackSrc, name, size, src, status, href, as, target, rel, onClick
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
import {isValidElement, useMemo, useState, type ReactNode} from 'react';
|
|
22
|
+
import {isValidElement, useMemo, useRef, useState, type ReactNode} from 'react';
|
|
22
23
|
import type {BaseProps} from '../BaseProps';
|
|
23
24
|
import * as stylex from '@stylexjs/stylex';
|
|
24
25
|
import {
|
|
@@ -28,6 +29,10 @@ import {
|
|
|
28
29
|
radiusVars,
|
|
29
30
|
} from '../theme/tokens.stylex';
|
|
30
31
|
import {AvatarSizeContext} from './AvatarSizeContext';
|
|
32
|
+
import {
|
|
33
|
+
AvatarStatusLabelContext,
|
|
34
|
+
type AvatarStatusLabelTarget,
|
|
35
|
+
} from './AvatarStatusLabelContext';
|
|
31
36
|
import {useAvatarGroup} from '../AvatarGroup/AvatarGroupContext';
|
|
32
37
|
import {mergeProps, mergeRefs} from '../utils';
|
|
33
38
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -37,7 +42,7 @@ import {useTooltip} from '../Tooltip/useTooltip';
|
|
|
37
42
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
38
43
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
39
44
|
import type {LinkComponentType} from '../Link/types';
|
|
40
|
-
import {useTranslator} from '../i18n';
|
|
45
|
+
import {useTranslator, type TranslatorFn} from '../i18n';
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* The offset ratio for positioning elements on a circle's edge at 45°.
|
|
@@ -170,7 +175,10 @@ const styles = stylex.create({
|
|
|
170
175
|
color: 'inherit',
|
|
171
176
|
font: 'inherit',
|
|
172
177
|
textDecoration: 'none',
|
|
173
|
-
cursor:
|
|
178
|
+
cursor: {
|
|
179
|
+
default: 'pointer',
|
|
180
|
+
':is(:disabled,[aria-disabled="true"])': 'default',
|
|
181
|
+
},
|
|
174
182
|
// Match the avatar's circular shape so the focus ring hugs it.
|
|
175
183
|
borderRadius: radiusVars['--radius-full'],
|
|
176
184
|
},
|
|
@@ -268,10 +276,14 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
|
|
|
268
276
|
* Content displayed in the corner of the avatar.
|
|
269
277
|
* Typically used for status indicators or badges.
|
|
270
278
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
279
|
+
* `AvatarStatusDot` reports its own `label` to the avatar, which composes
|
|
280
|
+
* it into the avatar's accessible name (e.g. "Jane Doe, Online") so
|
|
281
|
+
* assistive tech can reach the status: the `role="img"` root prunes
|
|
282
|
+
* descendant semantics (WCAG 4.1.2). Reporting goes through context, so it
|
|
283
|
+
* still works when the dot sits inside a wrapper component of your own. A
|
|
284
|
+
* custom status element that is not an `AvatarStatusDot` names itself the
|
|
285
|
+
* same way, by rendering an `AvatarStatusDot` or by carrying a string
|
|
286
|
+
* `label` prop.
|
|
275
287
|
*/
|
|
276
288
|
status?: ReactNode;
|
|
277
289
|
/**
|
|
@@ -342,13 +354,13 @@ function getInitials(name: string): string {
|
|
|
342
354
|
|
|
343
355
|
/**
|
|
344
356
|
* Reads the accessible status label off the `status` element, when it
|
|
345
|
-
* exposes one.
|
|
346
|
-
* but any custom status element with a string `label` prop participates.
|
|
357
|
+
* exposes one.
|
|
347
358
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
359
|
+
* Only sees a literal `label` prop on the element the consumer passes, so a
|
|
360
|
+
* consumer's own wrapper around the dot hides it — that case comes in through
|
|
361
|
+
* `AvatarStatusLabelContext` instead. Kept because it answers the first
|
|
362
|
+
* render, before any report has landed, and because it tracks a label that
|
|
363
|
+
* changes on an element that does not report at all.
|
|
352
364
|
*/
|
|
353
365
|
function getStatusLabel(status: ReactNode): string | undefined {
|
|
354
366
|
if (!isValidElement(status)) {
|
|
@@ -358,6 +370,68 @@ function getStatusLabel(status: ReactNode): string | undefined {
|
|
|
358
370
|
return typeof label === 'string' && label !== '' ? label : undefined;
|
|
359
371
|
}
|
|
360
372
|
|
|
373
|
+
/**
|
|
374
|
+
* The string when it carries something, `undefined` when it is absent or
|
|
375
|
+
* blank. An empty accessible name is meaningless, so a blank reads as
|
|
376
|
+
* absent, the way Icon treats `label=""`.
|
|
377
|
+
*/
|
|
378
|
+
function meaningful(value: unknown): string | undefined {
|
|
379
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Derives the root's ARIA attributes from the resolved accessible name,
|
|
384
|
+
* following Icon's `getIconA11yProps`.
|
|
385
|
+
*
|
|
386
|
+
* - A name → meaningful image: `role="img"` + `aria-label`.
|
|
387
|
+
* - No name → decorative: `role="presentation"` + `aria-hidden="true"`, rather
|
|
388
|
+
* than announcing a meaningless generic "Avatar" (obs-9).
|
|
389
|
+
*
|
|
390
|
+
* The result is spread BEFORE `{...props}` on every root so an explicit
|
|
391
|
+
* `aria-label` / `role` / `aria-hidden` from the consumer always wins.
|
|
392
|
+
*/
|
|
393
|
+
function getAvatarA11yProps(
|
|
394
|
+
accessibleName: string | undefined,
|
|
395
|
+
):
|
|
396
|
+
| {role: 'img'; 'aria-label': string}
|
|
397
|
+
| {role: 'presentation'; 'aria-hidden': 'true'} {
|
|
398
|
+
return accessibleName != null && accessibleName !== ''
|
|
399
|
+
? {role: 'img', 'aria-label': accessibleName}
|
|
400
|
+
: {role: 'presentation', 'aria-hidden': 'true'};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The avatar's accessible name: its own name, the status label, or the two
|
|
405
|
+
* composed ("Jane Doe, Online"). Used both by render and by the commit-phase
|
|
406
|
+
* update, so the two can never compose differently.
|
|
407
|
+
*/
|
|
408
|
+
function composeAccessibleName(
|
|
409
|
+
t: TranslatorFn,
|
|
410
|
+
nameLabel: string | undefined,
|
|
411
|
+
statusLabel: string | undefined,
|
|
412
|
+
): string | undefined {
|
|
413
|
+
if (nameLabel && statusLabel) {
|
|
414
|
+
return t('@astryx.avatar.nameWithStatus', {
|
|
415
|
+
name: nameLabel,
|
|
416
|
+
status: statusLabel,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return nameLabel || statusLabel;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Sets an attribute, or removes it when the value is absent. */
|
|
423
|
+
function setAttributeOrRemove(
|
|
424
|
+
element: HTMLElement,
|
|
425
|
+
name: string,
|
|
426
|
+
value: string | undefined,
|
|
427
|
+
): void {
|
|
428
|
+
if (value == null) {
|
|
429
|
+
element.removeAttribute(name);
|
|
430
|
+
} else {
|
|
431
|
+
element.setAttribute(name, value);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
361
435
|
/**
|
|
362
436
|
* Default person icon SVG for when no image or name is provided
|
|
363
437
|
*/
|
|
@@ -424,8 +498,8 @@ export function Avatar({
|
|
|
424
498
|
// A whitespace-only string carries no identity. Without this it produces no
|
|
425
499
|
// initials (getInitials trims to nothing) and no default icon (a space is
|
|
426
500
|
// truthy), leaving an empty plate behind a blank accessible name.
|
|
427
|
-
const meaningfulName = name
|
|
428
|
-
const meaningfulAlt = alt
|
|
501
|
+
const meaningfulName = meaningful(name);
|
|
502
|
+
const meaningfulAlt = meaningful(alt);
|
|
429
503
|
const showInitials = !showImage && !showFallbackImage && meaningfulName;
|
|
430
504
|
const showIcon = !showImage && !showFallbackImage && !meaningfulName;
|
|
431
505
|
|
|
@@ -439,15 +513,21 @@ export function Avatar({
|
|
|
439
513
|
// generic "Avatar" (obs-9).
|
|
440
514
|
const t = useTranslator();
|
|
441
515
|
const nameLabel = meaningfulAlt || meaningfulName;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
516
|
+
// This render can only see a label introspected off a directly-passed
|
|
517
|
+
// element. A status inside a consumer's own wrapper reports through the ref
|
|
518
|
+
// below, in the commit phase, and lands on the root element from there.
|
|
519
|
+
const accessibleName = composeAccessibleName(
|
|
520
|
+
t,
|
|
521
|
+
nameLabel,
|
|
522
|
+
getStatusLabel(status),
|
|
523
|
+
);
|
|
524
|
+
const a11yProps = getAvatarA11yProps(accessibleName);
|
|
525
|
+
// An `<a>`/`<button>` root carries its own role and must not be hidden, so
|
|
526
|
+
// only the name transfers. Spread before `{...props}` for the same
|
|
527
|
+
// consumer-wins precedence as the static root.
|
|
528
|
+
const a11yLabelProps = accessibleName
|
|
529
|
+
? {'aria-label': accessibleName}
|
|
530
|
+
: undefined;
|
|
451
531
|
const avatarGroup = useAvatarGroup();
|
|
452
532
|
const resolvedSize = avatarGroup?.size ?? size;
|
|
453
533
|
const numericSize = useMemo(() => resolveSize(resolvedSize), [resolvedSize]);
|
|
@@ -484,8 +564,8 @@ export function Avatar({
|
|
|
484
564
|
isEnabled: showTooltip,
|
|
485
565
|
});
|
|
486
566
|
// The tooltip ref attaches to whichever root element renders (static or
|
|
487
|
-
// interactive), so the tooltip works for link/button avatars too.
|
|
488
|
-
|
|
567
|
+
// interactive), so the tooltip works for link/button avatars too. The root
|
|
568
|
+
// ref itself is assembled below, once the element swap is resolved.
|
|
489
569
|
const describedByProp =
|
|
490
570
|
showTooltip && isCustomTooltip
|
|
491
571
|
? {
|
|
@@ -504,14 +584,74 @@ export function Avatar({
|
|
|
504
584
|
const isInteractive = renderAsLink || renderAsButton;
|
|
505
585
|
const LinkComponent = useLinkComponent(as);
|
|
506
586
|
|
|
507
|
-
// An interactive control
|
|
508
|
-
//
|
|
509
|
-
//
|
|
587
|
+
// An interactive control needs an identity of its own. A status label is not
|
|
588
|
+
// one: it composes into the accessible name, so `<Avatar href status={<dot
|
|
589
|
+
// label="Online" />} />` resolves a name that reads as legitimate and says
|
|
590
|
+
// nothing about who the link points at. Only `alt`/`name` count, or a
|
|
591
|
+
// consumer's own `aria-label`/`aria-labelledby` escape hatch, which wins
|
|
592
|
+
// over the derived props below.
|
|
593
|
+
const consumerName =
|
|
594
|
+
meaningful(props['aria-label']) ?? meaningful(props['aria-labelledby']);
|
|
510
595
|
useDevWarning(
|
|
511
596
|
'Avatar',
|
|
512
597
|
'an interactive avatar (with `href` or `onClick`) needs a meaningful ' +
|
|
513
598
|
'accessible name. Pass `alt` or `name`.',
|
|
514
|
-
isInteractive && !
|
|
599
|
+
isInteractive && !nameLabel && consumerName == null,
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
// A status inside a consumer's own wrapper is invisible to render: the dot
|
|
603
|
+
// writes its label into this ref from its own callback ref, in the commit
|
|
604
|
+
// phase. A ref write cannot re-render, so the composed name is written
|
|
605
|
+
// straight onto the root element instead — still before paint, and with no
|
|
606
|
+
// second render.
|
|
607
|
+
const statusLabelRef = useRef<AvatarStatusLabelTarget>({
|
|
608
|
+
label: undefined,
|
|
609
|
+
update: null,
|
|
610
|
+
});
|
|
611
|
+
// A fresh callback ref every render, so React reattaches it on every commit
|
|
612
|
+
// and the name is recomposed after React has written this render's props.
|
|
613
|
+
// `update` covers the other direction: a label that changes while the avatar
|
|
614
|
+
// itself does not re-render.
|
|
615
|
+
const nameRef = (element: HTMLElement | null) => {
|
|
616
|
+
if (element == null) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const target = statusLabelRef.current;
|
|
620
|
+
target.update = () => {
|
|
621
|
+
const composed = composeAccessibleName(
|
|
622
|
+
t,
|
|
623
|
+
nameLabel,
|
|
624
|
+
meaningful(target.label) ?? getStatusLabel(status),
|
|
625
|
+
);
|
|
626
|
+
// A consumer's own ARIA wins here exactly as it does in render, where
|
|
627
|
+
// the derived props spread before `{...props}`.
|
|
628
|
+
if (props['aria-label'] == null) {
|
|
629
|
+
setAttributeOrRemove(element, 'aria-label', composed);
|
|
630
|
+
}
|
|
631
|
+
// An `<a>`/`<button>` root carries its own role and must not be hidden,
|
|
632
|
+
// so only the name transfers there.
|
|
633
|
+
if (!isInteractive) {
|
|
634
|
+
if (props.role == null) {
|
|
635
|
+
element.setAttribute('role', composed ? 'img' : 'presentation');
|
|
636
|
+
}
|
|
637
|
+
if (props['aria-hidden'] == null) {
|
|
638
|
+
setAttributeOrRemove(
|
|
639
|
+
element,
|
|
640
|
+
'aria-hidden',
|
|
641
|
+
composed ? undefined : 'true',
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
target.update();
|
|
647
|
+
return () => {
|
|
648
|
+
target.update = null;
|
|
649
|
+
};
|
|
650
|
+
};
|
|
651
|
+
const rootRef = mergeRefs(
|
|
652
|
+
ref,
|
|
653
|
+
showTooltip ? tooltipHook.ref : undefined,
|
|
654
|
+
nameRef,
|
|
515
655
|
);
|
|
516
656
|
|
|
517
657
|
// The inner visuals are identical across the static and interactive variants.
|
|
@@ -601,13 +741,13 @@ export function Avatar({
|
|
|
601
741
|
// it up while ignoring nested buttons in a custom status/badge slot.
|
|
602
742
|
rootElement = (
|
|
603
743
|
<LinkComponent
|
|
744
|
+
{...a11yLabelProps}
|
|
604
745
|
{...interactivePassthrough}
|
|
605
746
|
{...describedByProp}
|
|
606
747
|
ref={rootRef as React.Ref<HTMLAnchorElement>}
|
|
607
748
|
href={href}
|
|
608
749
|
target={target}
|
|
609
750
|
rel={rel}
|
|
610
|
-
aria-label={accessibleName}
|
|
611
751
|
data-avatar-item=""
|
|
612
752
|
data-testid={testId}
|
|
613
753
|
onClick={onClick}
|
|
@@ -618,11 +758,11 @@ export function Avatar({
|
|
|
618
758
|
} else if (renderAsButton) {
|
|
619
759
|
rootElement = (
|
|
620
760
|
<button
|
|
761
|
+
{...a11yLabelProps}
|
|
621
762
|
{...interactivePassthrough}
|
|
622
763
|
{...describedByProp}
|
|
623
764
|
ref={rootRef}
|
|
624
765
|
type="button"
|
|
625
|
-
aria-label={accessibleName}
|
|
626
766
|
data-avatar-item=""
|
|
627
767
|
data-testid={testId}
|
|
628
768
|
onClick={onClick}
|
|
@@ -633,11 +773,9 @@ export function Avatar({
|
|
|
633
773
|
} else {
|
|
634
774
|
rootElement = (
|
|
635
775
|
<div
|
|
776
|
+
{...a11yProps}
|
|
636
777
|
{...props}
|
|
637
778
|
ref={rootRef}
|
|
638
|
-
role={isDecorative ? 'presentation' : 'img'}
|
|
639
|
-
aria-label={isDecorative ? undefined : accessibleName}
|
|
640
|
-
aria-hidden={isDecorative || undefined}
|
|
641
779
|
// The root is a div[role="img"], not natively focusable. When a name
|
|
642
780
|
// tooltip is active, add a tab stop so keyboard users can reveal it
|
|
643
781
|
// (WCAG 1.4.13 / 2.1.1) — matching Timestamp/Button. Suppressed inside
|
|
@@ -652,7 +790,11 @@ export function Avatar({
|
|
|
652
790
|
}
|
|
653
791
|
|
|
654
792
|
const avatarElement = (
|
|
655
|
-
<AvatarSizeContext value={numericSize}>
|
|
793
|
+
<AvatarSizeContext value={numericSize}>
|
|
794
|
+
<AvatarStatusLabelContext value={statusLabelRef}>
|
|
795
|
+
{rootElement}
|
|
796
|
+
</AvatarStatusLabelContext>
|
|
797
|
+
</AvatarSizeContext>
|
|
656
798
|
);
|
|
657
799
|
|
|
658
800
|
// Always return the same structure so the avatar keeps its position in the
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file AvatarStatusDot.tsx
|
|
7
|
-
* @input Uses React, StyleX, theme tokens, and
|
|
7
|
+
* @input Uses React, StyleX, theme tokens, AvatarSizeContext, and
|
|
8
|
+
* AvatarStatusLabelContext (label reporting)
|
|
8
9
|
* @output Exports AvatarStatusDot component and AvatarStatusDotProps type
|
|
9
10
|
* @position Sub-component of Avatar; renders a size-aware status indicator
|
|
10
11
|
*
|
|
@@ -15,12 +16,13 @@
|
|
|
15
16
|
* - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
import React, {use, type ReactNode} from 'react';
|
|
19
|
+
import React, {use, useCallback, useMemo, type ReactNode} from 'react';
|
|
19
20
|
import type {BaseProps} from '../BaseProps';
|
|
20
21
|
import * as stylex from '@stylexjs/stylex';
|
|
21
22
|
import {colorVars, radiusVars} from '../theme/tokens.stylex';
|
|
22
23
|
import {AvatarSizeContext} from './AvatarSizeContext';
|
|
23
|
-
import {
|
|
24
|
+
import {AvatarStatusLabelContext} from './AvatarStatusLabelContext';
|
|
25
|
+
import {isRenderable, mergeProps, mergeRefs} from '../utils';
|
|
24
26
|
import {themeProps} from '../utils/themeProps';
|
|
25
27
|
import type {AvatarStatusDotVariantMap} from './index';
|
|
26
28
|
|
|
@@ -311,6 +313,30 @@ export function AvatarStatusDot({
|
|
|
311
313
|
...props
|
|
312
314
|
}: AvatarStatusDotProps) {
|
|
313
315
|
const avatarSize = use(AvatarSizeContext);
|
|
316
|
+
const statusLabelRef = use(AvatarStatusLabelContext);
|
|
317
|
+
// Report the label through the avatar's ref from a callback ref rather than
|
|
318
|
+
// an Effect: the ref runs in the commit phase, so the avatar's accessible
|
|
319
|
+
// name is composed before paint, and a ref write costs no render.
|
|
320
|
+
/* eslint-disable react-compiler/react-compiler -- the avatar shares its own
|
|
321
|
+
ref through context so the dot can write into it; a context read looks
|
|
322
|
+
immutable to the compiler */
|
|
323
|
+
const reportRef = useCallback(() => {
|
|
324
|
+
const target = statusLabelRef?.current;
|
|
325
|
+
if (target == null) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
target.label = label;
|
|
329
|
+
target.update?.();
|
|
330
|
+
return () => {
|
|
331
|
+
target.label = undefined;
|
|
332
|
+
target.update?.();
|
|
333
|
+
};
|
|
334
|
+
}, [statusLabelRef, label]);
|
|
335
|
+
/* eslint-enable react-compiler/react-compiler */
|
|
336
|
+
// Memoized so React only detaches and reattaches when the reported label
|
|
337
|
+
// actually changes; an inline merge would withdraw and re-report on every
|
|
338
|
+
// render of the avatar.
|
|
339
|
+
const rootRef = useMemo(() => mergeRefs(ref, reportRef), [ref, reportRef]);
|
|
314
340
|
const {dotSize, borderWidth, iconSize, tier} =
|
|
315
341
|
resolveStatusDotSize(avatarSize);
|
|
316
342
|
const showsIcon = isRenderable(icon) && iconSize > 0;
|
|
@@ -321,7 +347,7 @@ export function AvatarStatusDot({
|
|
|
321
347
|
return (
|
|
322
348
|
<div
|
|
323
349
|
{...props}
|
|
324
|
-
ref={
|
|
350
|
+
ref={rootRef}
|
|
325
351
|
{...(label ? {role: 'img', 'aria-label': label} : undefined)}
|
|
326
352
|
{...mergeProps(
|
|
327
353
|
themeProps('avatar-status-dot', {variant}),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file AvatarStatusLabelContext.ts
|
|
7
|
+
* @input Uses React createContext, RefObject
|
|
8
|
+
* @output Exports AvatarStatusLabelContext, AvatarStatusLabelTarget
|
|
9
|
+
* @position Internal context; provided by Avatar, consumed by AvatarStatusDot
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update:
|
|
12
|
+
* - /packages/core/src/Avatar/Avatar.doc.mjs
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {createContext, type RefObject} from 'react';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The box a status element writes its label into.
|
|
19
|
+
*
|
|
20
|
+
* `update` is the avatar's own recompose, set while its root element is
|
|
21
|
+
* attached and null otherwise. Writing `label` alone is enough on mount (the
|
|
22
|
+
* avatar reads the box when its root attaches, which happens after its
|
|
23
|
+
* children's refs); calling `update` covers a label that changes later
|
|
24
|
+
* without the avatar itself re-rendering.
|
|
25
|
+
*/
|
|
26
|
+
export interface AvatarStatusLabelTarget {
|
|
27
|
+
label: string | undefined;
|
|
28
|
+
update: (() => void) | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lets a status element hand its accessible label to the enclosing Avatar,
|
|
33
|
+
* which composes it into its own accessible name ("Jane Doe, Online").
|
|
34
|
+
*
|
|
35
|
+
* The avatar root is `role="img"`, which prunes all descendant semantics, so
|
|
36
|
+
* composing the label in is the only way the status reaches assistive tech
|
|
37
|
+
* (WCAG 4.1.2). Reading `label` off the passed element only works when the
|
|
38
|
+
* consumer passes `AvatarStatusDot` directly; this route works through a
|
|
39
|
+
* consumer's own wrapper, at any depth.
|
|
40
|
+
*
|
|
41
|
+
* The value is a ref, written from the dot's own callback ref in the commit
|
|
42
|
+
* phase, so reporting a label costs no render and no state. `null` outside an
|
|
43
|
+
* Avatar, where a standalone dot names itself and has nobody to report to.
|
|
44
|
+
*/
|
|
45
|
+
export const AvatarStatusLabelContext =
|
|
46
|
+
createContext<RefObject<AvatarStatusLabelTarget> | null>(null);
|
|
47
|
+
AvatarStatusLabelContext.displayName = 'AvatarStatusLabelContext';
|
|
@@ -11,11 +11,12 @@ export const docs = {
|
|
|
11
11
|
description:
|
|
12
12
|
'AvatarGroup displays multiple avatars in an overlapping row with an optional overflow indicator. Uses a compositional API: pass Avatar children directly so each avatar can carry its own props (status dots, click handlers, etc.).',
|
|
13
13
|
bestPractices: [
|
|
14
|
-
{guidance: true, description: '
|
|
14
|
+
{guidance: true, description: 'Slice the list yourself and pass only the avatars you want visible; 3-5 is typical. The group renders exactly the children it is given and never slices for you.'},
|
|
15
15
|
{guidance: true, description: 'Use AvatarGroupOverflow for custom overflow content like a popover trigger or "add member" button.'},
|
|
16
16
|
{guidance: true, description: 'Pass status dots, click handlers, or tooltips directly on each Avatar child.'},
|
|
17
|
-
{guidance: true, description: 'Make avatars interactive with href or onClick to link to profiles. Interactive avatars share a single Tab stop; arrow keys move between them, and the group announces a keyboard hint to assistive tech.'},
|
|
17
|
+
{guidance: true, description: 'Make avatars interactive with href or onClick to link to profiles. Interactive avatars share a single Tab stop; arrow keys move between them, and the group announces a keyboard hint to assistive tech. This follows the WAI-ARIA APG roving tabindex technique for managing focus in a composite: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex'},
|
|
18
18
|
{guidance: false, description: "Don't nest AvatarGroups; use a single group with all avatars."},
|
|
19
|
+
{guidance: false, description: "Don't set size on the child avatars. The group's size wins over each child's own size prop, including when the group leaves size at its default, so a child's size is silently ignored inside a group."},
|
|
19
20
|
],
|
|
20
21
|
anatomy: [
|
|
21
22
|
{name: 'Avatar children', required: true, description: 'Avatar elements that form the overlapping row. Each can have its own props.'},
|
|
@@ -47,7 +48,8 @@ export const docs = {
|
|
|
47
48
|
{
|
|
48
49
|
name: 'size',
|
|
49
50
|
type: 'AvatarSize',
|
|
50
|
-
description:
|
|
51
|
+
description:
|
|
52
|
+
"Size applied to all avatars via context. This wins over each child Avatar's own size prop, including when it is left at the default, so set the size here rather than on the children.",
|
|
51
53
|
default: "'md'",
|
|
52
54
|
},
|
|
53
55
|
{
|
|
@@ -77,10 +79,11 @@ export const docsZh = {
|
|
|
77
79
|
description:
|
|
78
80
|
'AvatarGroup 以重叠排列方式显示多个头像,并可选择显示溢出指示器。使用组合式 API:直接传入 Avatar 子元素,每个头像可携带自己的属性。',
|
|
79
81
|
bestPractices: [
|
|
80
|
-
{guidance: true, description: '
|
|
82
|
+
{guidance: true, description: '自行切分列表,只传入需要显示的头像,通常 3-5 个为佳。组件只渲染传入的子元素,不会自动切分。'},
|
|
81
83
|
{guidance: true, description: '使用 AvatarGroupOverflow 自定义溢出内容,如弹出触发器或"添加成员"按钮。'},
|
|
82
84
|
{guidance: true, description: '直接在每个 Avatar 子元素上传递状态点、点击处理器或工具提示。'},
|
|
83
85
|
{guidance: false, description: '不要嵌套 AvatarGroup,使用单个组包含所有头像。'},
|
|
86
|
+
{guidance: false, description: '不要在子头像上设置 size。分组的 size 会覆盖每个子 Avatar 自身的 size,即使分组使用默认值也是如此。'},
|
|
84
87
|
],
|
|
85
88
|
},
|
|
86
89
|
};
|
|
@@ -92,11 +95,12 @@ export const docsDense = {
|
|
|
92
95
|
description:
|
|
93
96
|
'Stacked avatar display with overlapping layout. Compositional API: Avatar children with per-avatar props (status dots, clicks). Optional AvatarGroupOverflow slot for custom overflow.',
|
|
94
97
|
bestPractices: [
|
|
95
|
-
{guidance: true, description: '
|
|
98
|
+
{guidance: true, description: 'Slice the list yourself; pass only visible avatars (3-5 typical). No auto-slicing.'},
|
|
96
99
|
{guidance: true, description: 'Use AvatarGroupOverflow for custom overflow (popover, add button).'},
|
|
97
100
|
{guidance: true, description: 'Pass status dots / click handlers directly on each Avatar.'},
|
|
98
|
-
{guidance: true, description: 'Interactive avatars (href/onClick) share one Tab stop; arrows rove between them + the overflow button.'},
|
|
101
|
+
{guidance: true, description: 'Interactive avatars (href/onClick) share one Tab stop; arrows rove between them + the overflow button (APG roving tabindex).'},
|
|
99
102
|
{guidance: false, description: "Don't nest AvatarGroups."},
|
|
103
|
+
{guidance: false, description: "Don't set size on children; the group's size always wins, default included."},
|
|
100
104
|
],
|
|
101
105
|
},
|
|
102
106
|
};
|
|
@@ -231,6 +231,31 @@ describe('AvatarGroupOverflow — hardening', () => {
|
|
|
231
231
|
expect(screen.getByLabelText('0 more')).toBeInTheDocument();
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
+
it('clamps a negative count rather than rendering "+-3"', () => {
|
|
235
|
+
// `count={total - visibleCount}` is the documented shape, and it goes
|
|
236
|
+
// negative whenever the list is shorter than the slice.
|
|
237
|
+
render(
|
|
238
|
+
<AvatarGroup>
|
|
239
|
+
<Avatar name="Alice" />
|
|
240
|
+
<AvatarGroupOverflow count={-3} />
|
|
241
|
+
</AvatarGroup>,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(screen.queryByText('+-3')).not.toBeInTheDocument();
|
|
245
|
+
expect(screen.getByText('+0')).toBeInTheDocument();
|
|
246
|
+
expect(screen.getByLabelText('0 more')).toBeInTheDocument();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('renders outside an AvatarGroup at the md fallback size', () => {
|
|
250
|
+
render(<AvatarGroupOverflow count={3} data-testid="loose" />);
|
|
251
|
+
|
|
252
|
+
const overflow = screen.getByTestId('loose');
|
|
253
|
+
expect(overflow).toHaveTextContent('+3');
|
|
254
|
+
// inline-flex, so a standalone indicator stays a circle instead of
|
|
255
|
+
// stretching to its parent's width.
|
|
256
|
+
expect(overflow.tagName).toBe('SPAN');
|
|
257
|
+
});
|
|
258
|
+
|
|
234
259
|
it('handles very large count', () => {
|
|
235
260
|
render(
|
|
236
261
|
<AvatarGroup>
|
|
@@ -400,3 +425,40 @@ describe('AvatarGroup — roving focus + keyboard hint', () => {
|
|
|
400
425
|
expect(screen.getByTestId('bob')).not.toHaveAttribute('tabindex');
|
|
401
426
|
});
|
|
402
427
|
});
|
|
428
|
+
|
|
429
|
+
describe('AvatarGroup — size cascade', () => {
|
|
430
|
+
const sizeClasses = (el: HTMLElement) => el.className.split(/\s+/);
|
|
431
|
+
|
|
432
|
+
it("the group's size overrides a child's own size prop", () => {
|
|
433
|
+
render(
|
|
434
|
+
<AvatarGroup size="lg">
|
|
435
|
+
<Avatar name="Alice" size="xsm" data-testid="alice" />
|
|
436
|
+
</AvatarGroup>,
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const classes = sizeClasses(screen.getByTestId('alice'));
|
|
440
|
+
expect(classes).toContain('lg');
|
|
441
|
+
expect(classes).not.toContain('xsm');
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("the group's default size also overrides a child's own size prop", () => {
|
|
445
|
+
// Known open finding: the group always publishes a size on its context, so
|
|
446
|
+
// a group that never set one still imposes md on children that did set one.
|
|
447
|
+
// Pinned here so the behaviour cannot change without a deliberate edit.
|
|
448
|
+
render(
|
|
449
|
+
<AvatarGroup>
|
|
450
|
+
<Avatar name="Alice" size="xl" data-testid="alice" />
|
|
451
|
+
</AvatarGroup>,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const classes = sizeClasses(screen.getByTestId('alice'));
|
|
455
|
+
expect(classes).toContain('md');
|
|
456
|
+
expect(classes).not.toContain('xl');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("outside a group the avatar's own size applies", () => {
|
|
460
|
+
render(<Avatar name="Alice" size="xl" data-testid="alice" />);
|
|
461
|
+
|
|
462
|
+
expect(sizeClasses(screen.getByTestId('alice'))).toContain('xl');
|
|
463
|
+
});
|
|
464
|
+
});
|
|
@@ -48,7 +48,9 @@ export interface AvatarGroupProps extends BaseProps<HTMLDivElement> {
|
|
|
48
48
|
*/
|
|
49
49
|
children: ReactNode;
|
|
50
50
|
/**
|
|
51
|
-
* Size applied to all avatars via context.
|
|
51
|
+
* Size applied to all avatars via context. This wins over each child
|
|
52
|
+
* Avatar's own `size` prop, including when it is left at the default, so
|
|
53
|
+
* set the size here rather than on the children.
|
|
52
54
|
* @default 'md'
|
|
53
55
|
*/
|
|
54
56
|
size?: AvatarSize;
|