@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,138 @@
|
|
|
1
1
|
# @xds/core
|
|
2
2
|
|
|
3
|
+
# 0.4.6
|
|
4
|
+
|
|
5
|
+
#### New Features
|
|
6
|
+
|
|
7
|
+
- DateInput fits the pointer: a touch picker on a finger, the text field
|
|
8
|
+
on a mouse (#5243)
|
|
9
|
+
|
|
10
|
+
`DateInput` has always been a control for a mouse — a field you type into with a calendar in a popover beside it. On a phone or a tablet that is the wrong shape: the popover is a desktop calendar operated by thumb, and focusing the field summons a keyboard that covers the thing it is meant to fill in.
|
|
11
|
+
|
|
12
|
+
The same component now renders a second surface where the primary pointer is a finger (`pointer: coarse`): a bottom sheet holding one month per screen, swiped sideways, with month and year wheels behind the header title for the far jumps swiping is bad at, arrows in the header corner for a single step, and every target floored at 44px. A day commits the moment it is tapped and leaves the sheet up, so a mistake can be corrected in place; Save closes the picker, and Reset puts it back to how it opened — no date, current month. The grid spills adjacent-month days, muted and unselectable, and the weekday header is three letters rather than two, both as the desktop calendar has them.
|
|
13
|
+
|
|
14
|
+
The month and year wheels are one layer that fades in and out on top of the calendar. The calendar itself never fades — it is covered and uncovered, so the only thing moving is the thing arriving. The layer carries an opaque background of its own, which is what makes the fade uniform: it renders as a finished image and the fade applies to the image, rather than the wheels' translucent selection band compositing against a live grid on its own terms.
|
|
15
|
+
|
|
16
|
+
Nothing changes at the call site. It is one component with two surfaces, not two components — same props, same values, no new import, no media query to write. Existing usage is untouched: with a mouse the rendered output is the control that was always there.
|
|
17
|
+
|
|
18
|
+
The switch is the pointer alone, deliberately with no width bound. `pointer` means the PRIMARY device, so a touchscreen laptop reports `fine` and keeps the typable field (its keyboard is right there), while a narrowed desktop window is still a mouse. Adding a width test would only re-exclude tablets, which are the clearest case for a thumb picker.
|
|
19
|
+
|
|
20
|
+
The public surface barely moves: six `@astryx.dateInput.*` catalog keys for the picker's header and footer, and nothing else. No new props, no new exports — `DateInputProps` is byte-identical at 25.
|
|
21
|
+
|
|
22
|
+
Nothing else is published, on purpose. There is no export that forces a surface: the touch picker is reachable by being on a touch device, which is the only place it is worth looking at. The media query the switch runs on is an internal constant, not an export — six other core components write `@media (pointer: coarse)` inline rather than sharing one, and nothing has asked to ask the same question. The picker's two sizes (the 44px day cell, the 28px wheel row) are compile-time constants rather than theme variables — the day size is an accessibility floor, and a variable a theme can quietly lower is not a floor. And the sheet's header button is addressed by a `data-` attribute rather than a theme target, because nothing has asked to restyle it. Each of those is additive later and awkward to withdraw once shipped.
|
|
23
|
+
|
|
24
|
+
The wheels also answer a mouse now. A wheel is a scroll container, so a finger pans it for free; a mouse got nothing, because browsers do not drag-scroll an overflow container — pressing and pulling on the one control shaped like a thing you spin did nothing at all. Dragging with a mouse works, and fixes a related bug on the way: BottomSheet begins its own drag from a `pointerdown` on its body and captures the pointer for it, so a click on a wheel row that wobbled more than a pixel or two used to select nothing.
|
|
25
|
+
- Export `useLocale` and `useCollator` for provider-backed formatting and comparison (#5194)
|
|
26
|
+
- RadioListItem: the `radio-list-item` row theme target now carries `size`, `selected`, and `disabled` state variants (matching `multi-selector-option`), so themes can style selected or disabled rows (#5143).
|
|
27
|
+
- RadioListItem: the `radio-list-item` theme target now rides the painting row element (converging with `list-item`), so a theme can style the row's hover background, padding, and border radius — previously it sat on a layout-only wrapper that painted nothing. The default (unthemed) row appearance is unchanged: it stays a bare surface with no row padding, radius, or hover/selected background (only the radio indicator tints on hover) (#5143).
|
|
28
|
+
- `Section`, `Stack` (with `HStack` / `VStack`) and `Center` accept a padding prop for each of the four edges — `paddingBlockStart`, `paddingBlockEnd`, `paddingInlineStart` and `paddingInlineEnd` — so an edge can take its own spacing step without an `xstyle` escape hatch. `Section` also gains the `paddingInline` axis prop it was missing, so all three components now expose the same seven-prop set (#5224).
|
|
29
|
+
Each prop takes the same spacing scale as `padding`, and resolution is most-specific-wins, per edge:
|
|
30
|
+
|
|
31
|
+
> edge prop → axis prop (`paddingInline` / `paddingBlock`) → `padding`
|
|
32
|
+
|
|
33
|
+
An edge prop changes its own edge and leaves the other three alone.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Section padding={6} paddingBlockStart={2}>…</Section> // tight top, 24px elsewhere
|
|
37
|
+
<Stack padding={4} paddingInlineEnd={0}>…</Stack> // flush trailing edge
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The inline props are logical, so `paddingInlineStart` is the left edge in LTR and the right edge in RTL.
|
|
41
|
+
|
|
42
|
+
On `Section` the matching `--container-padding-*` custom property moves with the prop, so bleed children (`Table`, `Divider`, a nested `Section`) keep compensating against the padding actually applied.
|
|
43
|
+
|
|
44
|
+
Existing code is unaffected: `padding`, `paddingInline` and `paddingBlock` behave exactly as before, and their generated class output is unchanged.
|
|
45
|
+
- Selector: add the `selector-option-row` theme target on the dropdown option row, carrying `size` plus `selected`/`disabled` state — so a theme can restyle row padding and density directly (mirroring `multi-selector-option`), instead of reaching the bare `role="option"` element with a structural selector (#5179).
|
|
46
|
+
- MediaTheme: add `mode="auto"` and `mode="off"` (#5299)
|
|
47
|
+
A theme is free to define `--color-background-inverted` as something that is not inverted — and a component that hardcodes `mode="dark"` then paints white text on pale grey at 1.25:1. The surface color is a runtime value and the mode was a compile-time guess, so no amount of care in the component could catch it.
|
|
48
|
+
|
|
49
|
+
`mode="auto"` measures the surface the browser actually painted and applies whichever of the theme's own `--color-on-dark` / `--color-on-light` reads better on it. There is no threshold and no contrast target: it picks between the theme's two answers, so a theme that wants a soft pairing still gets one. Deciding a surface needs _no_ media context stays an authoring choice — that is the new `mode="off"`, which renders the same element without the media attribute so children never remount.
|
|
50
|
+
|
|
51
|
+
When the backdrop is not knowable from CSS — during SSR, on the first client frame, and most often behind a `background-image`, whose pixels need sampling (see `useImageMode`) rather than a computed style — `auto` uses the new `fallback` prop instead of guessing.
|
|
52
|
+
|
|
53
|
+
Toast now uses `mode="auto"`, with its previous rule kept only as that fallback. Every stock Astryx surface renders exactly as before.
|
|
54
|
+
- Tooltip and HoverCard: tap to open where there is no hover (#5248)
|
|
55
|
+
Hover is the one trigger a touch screen cannot express, and both components were answering that badly. `Tooltip` suppressed itself on any device reporting `(hover: none)`, so its content — often the only label an icon button has — was simply unreachable on a phone. `HoverCard` did nothing at all: the `mouseenter` a tap synthesizes opened the card on every tap of its trigger, over the control the user was aiming at, with no gesture that closed it again.
|
|
56
|
+
|
|
57
|
+
Both now take a `touchTrigger` prop, and what the trigger DOES decides the default. A trigger that performs an action — a button, a link, a form control — keeps its tap under `auto`: the layer stays shut, because the tap already has somewhere to go and a hint about a control the user just operated is noise. A trigger that performs no action — an info icon, an abbreviation, a truncated label — has nothing to lose, so the tap opens the layer, with no show delay (a tap is a decision, not the hover intent the delay exists to filter) and a tap outside to dismiss it. `tap` and `none` state the choice outright; `tap` is what an info icon rendered as a button wants, since it looks like an action to the DOM while revealing the layer is the only thing it does.
|
|
58
|
+
|
|
59
|
+
Hover-capable devices are unaffected, hybrid ones included: the decision is made per interaction from the pointer type rather than once per device from a media query, so the same trigger opens on hover under a mouse and on tap under a finger. A stylus is a hover device by the same rule — a pen in detection range fires hover events with nothing in contact, so it opens the layer on hover, and only a pen that lands counts as a tap. Neither layer opens from the focus a tap leaves behind any more — the second way a tap could bury the control it activated, and on `Tooltip` it is the tapped text fields that were affected, since those match `:focus-visible` by design.
|
|
60
|
+
|
|
61
|
+
[fix] InfoTip (lab): opts into `touchTrigger="tap"`. Its trigger is a real button, so the `auto` rule would hand the tap to the control — but revealing the tooltip is that button's only purpose, and suppressing it left an InfoTip's content unreachable on a phone.
|
|
62
|
+
|
|
63
|
+
#### Fixes
|
|
64
|
+
|
|
65
|
+
- Avatar: a status element now reports its own accessible label to the avatar through context, so wrapping `AvatarStatusDot` in a component of your own keeps the status in the avatar's accessible name ("Jane Doe, Online") instead of silently dropping it — the `role="img"` root prunes descendant semantics, so composing it in is the only route to assistive tech (WCAG 4.1.2). Reading `label` off a directly-passed element still works and still resolves on the first render. An interactive avatar (`href`/`onClick`) with no `name`/`alt` warns in development, and a status label no longer counts as the control's identity: "Online" reads as a legitimate name while saying nothing about where the link goes. Derived `role`/`aria-label`/`aria-hidden` now spread before the passthrough props, following Icon, so a consumer's own values win. No API change (#5034)
|
|
66
|
+
- AvatarGroup: four defects out of the component audit, three of them in `AvatarGroupOverflow` (#5055).
|
|
67
|
+
The indicator was `display: flex` on a span, which is a block-level flex container, so the exported component rendered as a full-width bar instead of a circle anywhere outside an `AvatarGroup`: measured 1168px wide in a 1168px parent. It is `inline-flex` now; inside a group nothing changes, because a flex item is blockified either way.
|
|
68
|
+
|
|
69
|
+
Its label font size was a bare `size * 0.35`, which computes 7px at `xsm` and 8.4px at `sm`. That is under the 12px legibility floor, and the effect is worse than the number suggests: the glyph stroke ends up thinner than a pixel, so it never reaches its own text colour. Decoded from a screenshot, the darkest pixel at `xsm` is `#bebebe` on a `#f0f0f0` field, a contrast of 1.63:1 where 4.5:1 is required. The size now floors at the `--text-supporting-size` role token and scales proportionally above it, so `md` and larger are unchanged.
|
|
70
|
+
|
|
71
|
+
The indicator also kept its negative overlap margin when it was the first child of a group, hanging 12px outside the group's own box. It now carries the same `:not(:first-child)` guard `Avatar` already had. And a negative `count` rendered the string `+-3` and announced "-3 more"; since the documented shape for the prop is `total - visibleCount`, which goes negative whenever the list is shorter than the slice, it now clamps at zero.
|
|
72
|
+
|
|
73
|
+
Docs: the guidance told readers to "set max to limit visible avatars", and there is no `max` prop. The API is compositional on purpose, so the consumer slices; the guidance now says that. The keyboard behaviour names the APG roving tabindex technique it implements, and `size` now says that the group's value wins over each child avatar's own `size`, including when the group leaves it at the default.
|
|
74
|
+
- Blockquote: the `cite` attribution renders as a bare `<cite>` instead of being wrapped in a `<footer>`, which was becoming a `contentinfo` document landmark. Also guards the slot with `isRenderable`, so `cite={condition && author}` no longer emits an empty `<cite>`, and wraps long unbroken words instead of overflowing (#5144).
|
|
75
|
+
- Bottom Sheet: float the grab handle so content sits closer to the top (#5222)
|
|
76
|
+
The drag area above the sheet's content was a 48px row in the sheet's flex column, pushing everything below it down by its full height and reading as an empty band above the first line of content.
|
|
77
|
+
|
|
78
|
+
The bar is now 24px and floats over the content: the scrolling area starts at the sheet's top edge and rides up under the pill, so a heading sits 24px closer to the top. The pill is 4px tall centered in the band, so it occupies only 10-14px from the edge — inside the top padding a content wrapper already provides — and a surface gradient behind it keeps it legible over whatever sits or scrolls beneath.
|
|
79
|
+
- BottomSheet: give the sheet one uniform edge against the scrim. Two things were wrong in dark mode. The sheet drew no edge of its own — surface and scrim sit a few RGB steps apart and the `--shadow-high` drop shadow is black on near-black, so the left and right edges were invisible (measured 1.16:1 boundary contrast in dark against 2.89:1 in light); it now carries a `--border-width` / `--color-border` hairline on its three scrim-facing edges, the same treatment MobileNav gives its scrim-facing edge. And under a theme that packs an inset ring into `--shadow-high` (every bundled theme adds one in dark mode) that ring was painted over by an opaque content wrapper such as `Section`, so it showed only in the gap below where the content ended and the side edges appeared to change width partway down; the scrolling body now paints the surface across the sheet's whole inner box, hiding the ring evenly (#5305).
|
|
80
|
+
- Breadcrumbs: button crumbs keep the link's vertical padding, the variant reaches the item theme targets, and interactive crumbs paint the shared focus ring (#5332)
|
|
81
|
+
- DateInput's touch calendar no longer rests between two months (#5319)
|
|
82
|
+
Swiping the month calendar on iOS could leave it parked a couple of columns into a pane: the left of March and the right of April on screen at once, under one square Sun-to-Sat header, with the title still naming March. The grid was never skewed — the scrollport was simply at rest where no month begins.
|
|
83
|
+
|
|
84
|
+
`scroll-snap-type: mandatory` is supposed to make that impossible, and on a static list it does. This list is virtualized: seven panes exist out of twelve hundred, and the panes ARE the snap areas, so every month the finger crosses mounts one and unmounts another while the fling is still running. iOS scrolls off the main thread — it picks a landing place from the snap points it knows about at that moment, and a React re-render that lands after the decision moves them. The scroller stops where a snap point used to be and nothing re-snaps it. Chrome never showed it because it snaps again after the mutation.
|
|
85
|
+
|
|
86
|
+
The rest position is now corrected rather than trusted: once the gesture is genuinely over — touch released, the scroller quiet, AND its offset confirmed unchanged across a frame — a scroller that is off a pane boundary is moved to the nearest one. A scroller the browser snapped for itself is left alone, so nothing extra happens on Chrome, and sub-pixel drift on a fractional viewport is ignored.
|
|
87
|
+
|
|
88
|
+
That last condition is what keeps the fix from becoming a worse bug than the one it fixes. A quiet period is not proof of rest: iOS runs its own snap animation for a few hundred milliseconds after the finger lifts and fires scroll events irregularly while it does, so a correction that trusts quiet alone can land mid-animation, round an offset still travelling toward next month back to the month it came from, and reverse the swipe.
|
|
89
|
+
- A disabled element answers the pointer with `default`, never an interactive cursor. Every `cursor` in core and lab carries `':is(:disabled,[aria-disabled="true"])': 'default'`, and the reset gives the same cursor to any disabled element that declares none — `[aria-disabled]` included, which previously got nothing. A lint rule and a Chromium sweep over every story keep it that way. Disabled elements sealed behind `pointer-events: none` are unchanged: the pointer never reaches them, so their cursor comes from an ancestor — which is why the guarantee is `default` rather than a distinct disabled cursor the library could only paint on some of them (#5323).
|
|
90
|
+
- Disabled elements no longer paint a hover state: every self-`:hover` in core and lab, and every `:hover` a theme authors, now carries the zero-specificity guard `:hover:where(:not(:disabled,[aria-disabled="true"]))`, so existing overrides weigh exactly what they weighed before. A lint rule and a Chromium sweep over every story keep it that way (#5247).
|
|
91
|
+
- InputClearButton: the clear (✕) affordance now meets the WCAG 2.5.8 AA 24×24 minimum on touch. The shared button rendered a 20px glyph with a 20px tap target, so every input that clears through it — Typeahead, Tokenizer, FileInput and the rest of the family — was under the floor on a phone. An `::after` overlay now expands the tappable region to 24×24, gated behind `@media (pointer: coarse)`: on a fine pointer the overlay is not generated at all, because a mouse is precise enough, an unconditional overlay could overlap neighboring controls in dense desktop layouts, and an overlay covering the button would take hover away from the `astryx-input-clear-icon` theme target. The visual glyph is unchanged at every breakpoint, and the overlay stops at 24px so it stays clear of the 8px adornment gap and the input's own caret area (#4956).
|
|
92
|
+
- MobileNav: the drawer now slides in when it opens, instead of only sliding out when it closes. Two things were needed, and each is useless without the other. First, the dialog is `display: none` while closed, so the drawer's first rendered frame already holds the on-screen transform and a transition has no before-change value to run from — `@starting-style` supplies it, for the drawer's transform and for the `::backdrop`'s opacity. Second, the dialog clipped the off-screen drawer with `overflow: hidden`, which makes it a scroll container; a scroll container in the top layer whose subtree holds another scroller (the drawer's content area) does not paint a `@starting-style` entry transition for its descendants in Chromium — the transition ticks in the CSSOM while every painted frame shows the end value. `overflow: clip` clips identically without creating a scroll container. The drawer now slides in from its own edge (mirrored under RTL) and the scrim fades up, both on `--duration-medium` and both collapsing under `prefers-reduced-motion`, exactly as the close already did (#5218).
|
|
93
|
+
- NumberInput: the number-stepper column now tracks a themed padding and radius instead of assuming the defaults. Theming `number-input` padding left the steppers short of the field edges (a gap top and bottom), and a themed `borderRadius` rounded the field while the stepper corners kept the default radius. The wrapper's padding now goes through the shared container expansion, so it is picked up from any spelling a theme writes it in — `padding: 14px 20px`, `paddingBlock`, or a single `paddingBlockStart` — and both the wrapper and the column read the resulting per-side `--astryx-number-input-padding-*` tokens; an asymmetric `paddingBlock: 4px 12px` is cancelled correctly at each edge. A themed `number-input` borderRadius now also reaches `--_field-radius`, which the column's outer corners follow. Byte-identical by default, and inert for the no-stepper case and every other input (#5181).
|
|
94
|
+
- Stop the container padding system at every overlay boundary. Follow-up to #5209, which zeroed `--container-padding-*` on four overlay roots and closed the visible overflow in #5208 (#5231).
|
|
95
|
+
Two gaps remained. The variables descendants ADD (`--layout-padding-*`, and a Section's propagated padding) still crossed the boundary, so an unpadded `Section` inside an overlay took the page's padding instead of the theme default — 40px where 16px was meant. And `Lightbox`, `ContextMenu` and `HoverCard` were never covered.
|
|
96
|
+
|
|
97
|
+
The reset now lives in one place (`overlayPaddingReset`, exported from `@astryxdesign/core/Layout`) instead of being hand-copied per overlay, and moved onto the `useLayer` root, which covers every layer surface at once. Values descendants subtract are zeroed; values they add are cleared to `initial` so readers fall through to their own default rather than losing their padding.
|
|
98
|
+
|
|
99
|
+
Section's padding propagation moved from the public `--astryx-section-padding` token to a private `--_section-padding-propagated`. The two carried different authority under one name — a theme's value versus one ancestor's — and an overlay could not drop the inherited one without blanking the theme's. Propagated values still win over the theme for nested sections, so behavior is unchanged. Themes are unaffected: `--astryx-section-padding` remains the public token and still reaches inside overlays.
|
|
100
|
+
- Theming: a physical `paddingTop`/`paddingBottom` now reaches the container padding expansion, so `card`, `dialog`, `section` and `number-input` track it the way they already track the logical spellings. A physical block longhand matched none of the padding property names the expansion recognizes, so it landed raw on the element while the component's internals kept reading the default — the NumberInput stepper column came up ~10px short of the field edges, and container bleed compensated by the wrong amount. Mixing spellings was worse than either alone: `padding: '10px'` plus `paddingTop: '14px'` published 10px in the tokens while the element painted 14px on top. `padding-top` and `padding-bottom` ARE the block edges in every horizontal writing mode, so this normalization assumes no direction. `paddingLeft`/`paddingRight` are deliberately unchanged: they are direction-relative — left is inline-start in LTR and inline-end in RTL — and the tokens are consumed by logical properties, so routing them would silently move the padding to the opposite edge under RTL. They keep their physical meaning, exactly as before (#5244).
|
|
101
|
+
- `DateInput`, `DateTimeInput`, `DateRangeInput`, and `Calendar` now format and parse dates using the ambient `InternationalizationProvider` locale instead of the host/browser locale. `plainDateFormat` (backing `formatSharedDate`) previously called `Intl.DateTimeFormat(undefined, ...)`, and `dateParser`'s day/month disambiguation heuristic for ambiguous numeric input (e.g. `3/4/2026`) called `Intl.DateTimeFormat()` with no locale at all, so a tree wrapped in a non-English `locale` still formatted and parsed dates using the host locale (#5120).
|
|
102
|
+
- Use the InternationalizationProvider locale for sorting, formatting, and speech defaults (#5195)
|
|
103
|
+
- RadioListItem: the whole row is now a click target. Clicking the description — or the empty space in a row's hover area — selects the radio, matching CheckboxListItem. Previously only the radio and its label text responded, so the description and surrounding row were dead space. The row delegates surface clicks to the radio input (one tab stop per option preserved), and the radio keeps its accessible name via `aria-label` (#5143).
|
|
104
|
+
- Reset container padding custom properties on overlay elements (`BottomSheet`, `Dialog`, `MobileNav`, `Popover`) to prevent nested `Section` components from inheriting ancestor section padding (#5209).
|
|
105
|
+
- ResizeHandle: dragging the lower half of a tall handle works again. The invisible grab zone is stretched along the handle, but the offset that biases it onto the pill also carried the pill's own `-50%` centering shift — so the zone slid half the handle's length off the divider. On a full-height panel at 1440x900 the 16x900 hit box sat at y=-434, leaving everything below the pill's centre dead: a pointerdown on the visible grip's centre, or anywhere lower, started no drag at all. The dead region grew with the panel, and the same shift stranded the grab zone sideways on vertical handles. The bias now moves the zone along the pill's axis only (#5198).
|
|
106
|
+
- Opening a Dialog, BottomSheet, Lightbox, or MobileNav no longer shifts the page sideways when the browser has a classic scrollbar (#5219)
|
|
107
|
+
Locking background scroll hides the document's scrollbar. Where that scrollbar takes layout space — Windows/Linux desktop, and macOS set to always show scroll bars — hiding it widened the layout viewport by its width (~15px), so the whole page reflowed sideways behind the overlay and back again on close.
|
|
108
|
+
|
|
109
|
+
Both scroll locks now hold that gutter open with `scrollbar-gutter: stable` for the duration of the lock, which keeps `position: fixed` chrome (sticky headers, toast viewports) still as well as in-flow content. Pages with no space-taking scrollbar, and pages that already set `scrollbar-gutter` themselves, are left alone. Engines without `scrollbar-gutter` support fall back to padding the measured difference.
|
|
110
|
+
- SegmentedControl: keep item labels on a single line, truncating overflow with an ellipsis instead of wrapping to multiple lines (#5035)
|
|
111
|
+
- Selector: `SelectorOptionData` gains `description`, and the closed trigger now shows the selected option instead of just its label. The two-line option row `SelectorOption` draws was unreachable from the `options` prop — the data type carried only `value/label/disabled/icon` — so consumers kept a side map of descriptions keyed by value and re-rendered the row through `renderOption`. `description` now sits on the option data and `DefaultOption` forwards it. On the trigger, the selected option's own `icon` renders in the closed state (`startIcon` still wins when set, so a pinned field icon never doubles up), which retires the app-side `startIcon={value === 'x' ? … : …}` mirroring of state the component already knows. `renderValue` is the seam for drawing the selection yourself — the description included (#5202).
|
|
112
|
+
[feat] Item: new `layout` prop — `'stacked'` (default, unchanged) or `'inline'`, which keeps the description on the label's line with the description ellipsizing first. The inline row centers its two lines rather than sharing a baseline: two font sizes on one baseline make a line box taller than either line, which would push a fixed-height host off its size token. Every row built on `Item` gets the axis, `SelectorOption` included.
|
|
113
|
+
|
|
114
|
+
[fix] Selector: the trigger is sized by padding rather than a fixed height, so it is the `--size-element-*` token for a one-line value (28/32/36) and exactly one text line taller for a two-line one (48/52/56). The token and a text line are both multiples of 4, so every trigger lands on the 4px rhythm and lines up with the Buttons and inputs beside it — and no prop chooses the height, the content does. It previously swapped the fixed height for a minimum whenever `renderValue` was passed, keyed on the prop being present rather than on the content needing the room: a one-line value measured 39px and a two-line one 58px at _every_ size, so the `size` prop stopped affecting the trigger at all. Inside an `InputGroup`, where the group pins the row, the relaxed height did nothing and the content bled 4px through its own border. The group owns the row now and the trigger clamps its own value box to it, so nothing a caller draws can paint over the rows above and below: a `SelectorOption` folds onto one line and ellipsizes, and any other node is cut off at the row's edge. The trigger also stops asserting a height floor of its own there, so a control sized above its group — `<InputGroup size="md">` around a `size="lg"` control — sits in the group's row instead of growing it. The trigger's line box is pinned to that same token rather than a ratio, so the coarse-pointer font bump — and any theme that changes `--font-size-base` — grows the glyphs without moving the control off its size token.
|
|
115
|
+
- `Slider` with `orientation="vertical"` now gets the same 24px touch hit area the horizontal one got: its track was still only 20px wide on coarse pointers, under the WCAG 2.5.8 AA minimum. The whole track is the tap target for both orientations — the fix that floored the horizontal track's block size did nothing for vertical, whose short axis is the inline one — so the inline size is now floored to 24px on touch, with the same `@media (pointer: coarse)` gate. The rail, fill, marks and thumb all center on the inline 50%, so nothing visible moves and desktop is untouched (#5173).
|
|
116
|
+
- `@astryxdesign/core/theme/syntax` is importable from a server component again: the presets are data, not client references (#5076).
|
|
117
|
+
The subpath's barrel carried `'use client'`, and it is the only entry point for the syntax module. React therefore replaced _every_ export with a client reference for a server importer — including `dracula`, `oneLight`, `allSyntaxPresets`, `syntaxTokenDefaults` and `defineSyntaxTheme`, none of which need a boundary. Reading a preset in a Next.js server module (deriving a code-block ground, emitting theme CSS at build time) got a proxy instead of data, so `preset.tokens` was `undefined` and the failure surfaced far from its cause — the same import under plain Node worked perfectly.
|
|
118
|
+
|
|
119
|
+
The directive now sits only on `SyntaxTheme.tsx`, the provider that actually needs it, so `SyntaxTheme` and `useSyntaxTheme` keep their client boundary while the data exports resolve as data. No API change.
|
|
120
|
+
|
|
121
|
+
#### Contributors
|
|
122
|
+
|
|
123
|
+
Thanks to everyone who contributed to this release:
|
|
124
|
+
|
|
125
|
+
- @cixzhang
|
|
126
|
+
- @ernestt
|
|
127
|
+
- @freddymeta
|
|
128
|
+
- @Geervan
|
|
129
|
+
- @HelloOjasMutreja
|
|
130
|
+
- @imdreamrunner
|
|
131
|
+
- @nynexman4464
|
|
132
|
+
- @rubyycheung
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
3
136
|
# 0.4.5
|
|
4
137
|
|
|
5
138
|
#### New Features
|
|
@@ -10,6 +143,7 @@
|
|
|
10
143
|
Nothing changes unless you ask for it: `InternationalizationProvider` still defaults to English, and the catalogs are loaded through the existing `./locales/*.json` export. An app that already passes a `locale` now gets translated component strings where it previously fell back to English.
|
|
11
144
|
|
|
12
145
|
The catalogs come from Crowdin and are refreshed nightly (#5186), so a translation landing upstream reaches a release without anyone opening a PR by hand.
|
|
146
|
+
|
|
13
147
|
- DateRangeInput / Calendar: add `maxRangeSpan` and `minRangeSpan` to constrain the size of a selected range. Once a start date is picked, days outside the allowed window are disabled — e.g. `maxRangeSpan={7}` keeps the range within a 7-day window of the start (#5145).
|
|
14
148
|
- FormLayout: add `defaultOptionality` — set a form-wide default (`'optional'` or `'required'`) so only the exception carries a visible indicator. Under `'optional'` only `isRequired` fields show one; under `'required'` only `isOptional` fields do; a field that restates the default shows nothing. Under `'required'` the unmarked fields also expose `aria-required` so screen readers match what sighted users see — resolved on `aria-required` only, never the native `required` attribute. Unset keeps today's per-field behavior (#4791).
|
|
15
149
|
- Add `astryx-input-clear-button` theme target on the shared clear button wrapper. Themes can now control the clear button's height and hover independently of other ghost buttons — for example suppressing the hover fill or matching a different element size scale (#5093).
|
package/dist/Avatar/Avatar.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Avatar.tsx
|
|
3
|
-
* @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
|
|
3
|
+
* @input Uses React, HTMLAttributes, ReactNode, useState, useRef; useTooltip
|
|
4
4
|
* (Tooltip hook) for the optional name-on-hover tooltip; useTranslator (i18n)
|
|
5
5
|
* @output Exports Avatar component, AvatarProps, AvatarSize types
|
|
6
6
|
* @position Core implementation; consumed by index.ts
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* SYNC: When modified, update these files to stay in sync:
|
|
9
9
|
* - /packages/core/src/Avatar/Avatar.doc.mjs (props table, features, implementation notes)
|
|
10
10
|
* - /packages/core/src/Avatar/index.ts (exports if types change)
|
|
11
|
+
* - /packages/core/src/Avatar/AvatarStatusLabelContext.ts (the status label ref)
|
|
11
12
|
* - /apps/storybook/stories/Avatar.stories.tsx (storybook stories)
|
|
12
13
|
* - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
|
|
13
14
|
*
|
|
@@ -76,10 +77,14 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
|
|
|
76
77
|
* Content displayed in the corner of the avatar.
|
|
77
78
|
* Typically used for status indicators or badges.
|
|
78
79
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
80
|
+
* `AvatarStatusDot` reports its own `label` to the avatar, which composes
|
|
81
|
+
* it into the avatar's accessible name (e.g. "Jane Doe, Online") so
|
|
82
|
+
* assistive tech can reach the status: the `role="img"` root prunes
|
|
83
|
+
* descendant semantics (WCAG 4.1.2). Reporting goes through context, so it
|
|
84
|
+
* still works when the dot sits inside a wrapper component of your own. A
|
|
85
|
+
* custom status element that is not an `AvatarStatusDot` names itself the
|
|
86
|
+
* same way, by rendering an `AvatarStatusDot` or by carrying a string
|
|
87
|
+
* `label` prop.
|
|
83
88
|
*/
|
|
84
89
|
status?: ReactNode;
|
|
85
90
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAA4C,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAqB5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AA4BrD;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;GAEG;AACH,KAAK,iBAAiB,GACpB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAgBpD;AAgID,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CAChD;AAwHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EACrB,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,WAAW,EACX,IAAI,EACJ,IAAW,EACX,GAAG,EACH,MAAM,EACN,OAAc,EACd,IAAI,EACJ,EAAE,EACF,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAmUb;yBAtVe,MAAM"}
|
package/dist/Avatar/Avatar.js
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,16 +12,18 @@
|
|
|
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
|
-
import { isValidElement, useMemo, useState } from 'react';
|
|
21
|
+
import { isValidElement, useMemo, useRef, useState } from 'react';
|
|
21
22
|
import * as stylex from '@stylexjs/stylex';
|
|
22
23
|
import "../theme/tokens.stylex.js";
|
|
23
24
|
import { colorVars, typographyVars, fontWeightVars, radiusVars } from "../theme/tokens.stylex.js";
|
|
24
25
|
import { AvatarSizeContext } from "./AvatarSizeContext.js";
|
|
26
|
+
import { AvatarStatusLabelContext } from "./AvatarStatusLabelContext.js";
|
|
25
27
|
import { useAvatarGroup } from "../AvatarGroup/AvatarGroupContext.js";
|
|
26
28
|
import { mergeProps, mergeRefs } from "../utils/index.js";
|
|
27
29
|
import { themeProps } from "../utils/themeProps.js";
|
|
@@ -134,7 +136,7 @@ const styles = {
|
|
|
134
136
|
kMwMTN: "x1heor9g",
|
|
135
137
|
kVVagm: "xln7xf2",
|
|
136
138
|
kybGjl: "x1hl2dhg",
|
|
137
|
-
kkrTdU: "x1ypdohk",
|
|
139
|
+
kkrTdU: "x1ypdohk x16khyan",
|
|
138
140
|
kaIpWk: "xjspbzw",
|
|
139
141
|
$$css: true
|
|
140
142
|
}
|
|
@@ -217,13 +219,13 @@ function getInitials(name) {
|
|
|
217
219
|
|
|
218
220
|
/**
|
|
219
221
|
* Reads the accessible status label off the `status` element, when it
|
|
220
|
-
* exposes one.
|
|
221
|
-
* but any custom status element with a string `label` prop participates.
|
|
222
|
+
* exposes one.
|
|
222
223
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
224
|
+
* Only sees a literal `label` prop on the element the consumer passes, so a
|
|
225
|
+
* consumer's own wrapper around the dot hides it — that case comes in through
|
|
226
|
+
* `AvatarStatusLabelContext` instead. Kept because it answers the first
|
|
227
|
+
* render, before any report has landed, and because it tracks a label that
|
|
228
|
+
* changes on an element that does not report at all.
|
|
227
229
|
*/
|
|
228
230
|
function getStatusLabel(status) {
|
|
229
231
|
if (! /*#__PURE__*/isValidElement(status)) {
|
|
@@ -235,6 +237,60 @@ function getStatusLabel(status) {
|
|
|
235
237
|
return typeof label === 'string' && label !== '' ? label : undefined;
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
/**
|
|
241
|
+
* The string when it carries something, `undefined` when it is absent or
|
|
242
|
+
* blank. An empty accessible name is meaningless, so a blank reads as
|
|
243
|
+
* absent, the way Icon treats `label=""`.
|
|
244
|
+
*/
|
|
245
|
+
function meaningful(value) {
|
|
246
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Derives the root's ARIA attributes from the resolved accessible name,
|
|
251
|
+
* following Icon's `getIconA11yProps`.
|
|
252
|
+
*
|
|
253
|
+
* - A name → meaningful image: `role="img"` + `aria-label`.
|
|
254
|
+
* - No name → decorative: `role="presentation"` + `aria-hidden="true"`, rather
|
|
255
|
+
* than announcing a meaningless generic "Avatar" (obs-9).
|
|
256
|
+
*
|
|
257
|
+
* The result is spread BEFORE `{...props}` on every root so an explicit
|
|
258
|
+
* `aria-label` / `role` / `aria-hidden` from the consumer always wins.
|
|
259
|
+
*/
|
|
260
|
+
function getAvatarA11yProps(accessibleName) {
|
|
261
|
+
return accessibleName != null && accessibleName !== '' ? {
|
|
262
|
+
role: 'img',
|
|
263
|
+
'aria-label': accessibleName
|
|
264
|
+
} : {
|
|
265
|
+
role: 'presentation',
|
|
266
|
+
'aria-hidden': 'true'
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The avatar's accessible name: its own name, the status label, or the two
|
|
272
|
+
* composed ("Jane Doe, Online"). Used both by render and by the commit-phase
|
|
273
|
+
* update, so the two can never compose differently.
|
|
274
|
+
*/
|
|
275
|
+
function composeAccessibleName(t, nameLabel, statusLabel) {
|
|
276
|
+
if (nameLabel && statusLabel) {
|
|
277
|
+
return t('@astryx.avatar.nameWithStatus', {
|
|
278
|
+
name: nameLabel,
|
|
279
|
+
status: statusLabel
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return nameLabel || statusLabel;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Sets an attribute, or removes it when the value is absent. */
|
|
286
|
+
function setAttributeOrRemove(element, name, value) {
|
|
287
|
+
if (value == null) {
|
|
288
|
+
element.removeAttribute(name);
|
|
289
|
+
} else {
|
|
290
|
+
element.setAttribute(name, value);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
238
294
|
/**
|
|
239
295
|
* Default person icon SVG for when no image or name is provided
|
|
240
296
|
*/
|
|
@@ -299,8 +355,8 @@ export function Avatar({
|
|
|
299
355
|
// A whitespace-only string carries no identity. Without this it produces no
|
|
300
356
|
// initials (getInitials trims to nothing) and no default icon (a space is
|
|
301
357
|
// truthy), leaving an empty plate behind a blank accessible name.
|
|
302
|
-
const meaningfulName = name
|
|
303
|
-
const meaningfulAlt = alt
|
|
358
|
+
const meaningfulName = meaningful(name);
|
|
359
|
+
const meaningfulAlt = meaningful(alt);
|
|
304
360
|
const showInitials = !showImage && !showFallbackImage && meaningfulName;
|
|
305
361
|
const showIcon = !showImage && !showFallbackImage && !meaningfulName;
|
|
306
362
|
|
|
@@ -314,12 +370,17 @@ export function Avatar({
|
|
|
314
370
|
// generic "Avatar" (obs-9).
|
|
315
371
|
const t = useTranslator();
|
|
316
372
|
const nameLabel = meaningfulAlt || meaningfulName;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
373
|
+
// This render can only see a label introspected off a directly-passed
|
|
374
|
+
// element. A status inside a consumer's own wrapper reports through the ref
|
|
375
|
+
// below, in the commit phase, and lands on the root element from there.
|
|
376
|
+
const accessibleName = composeAccessibleName(t, nameLabel, getStatusLabel(status));
|
|
377
|
+
const a11yProps = getAvatarA11yProps(accessibleName);
|
|
378
|
+
// An `<a>`/`<button>` root carries its own role and must not be hidden, so
|
|
379
|
+
// only the name transfers. Spread before `{...props}` for the same
|
|
380
|
+
// consumer-wins precedence as the static root.
|
|
381
|
+
const a11yLabelProps = accessibleName ? {
|
|
382
|
+
'aria-label': accessibleName
|
|
383
|
+
} : undefined;
|
|
323
384
|
const avatarGroup = useAvatarGroup();
|
|
324
385
|
const resolvedSize = avatarGroup?.size ?? size;
|
|
325
386
|
const numericSize = useMemo(() => resolveSize(resolvedSize), [resolvedSize]);
|
|
@@ -351,8 +412,8 @@ export function Avatar({
|
|
|
351
412
|
isEnabled: showTooltip
|
|
352
413
|
});
|
|
353
414
|
// The tooltip ref attaches to whichever root element renders (static or
|
|
354
|
-
// interactive), so the tooltip works for link/button avatars too.
|
|
355
|
-
|
|
415
|
+
// interactive), so the tooltip works for link/button avatars too. The root
|
|
416
|
+
// ref itself is assembled below, once the element swap is resolved.
|
|
356
417
|
const describedByProp = showTooltip && isCustomTooltip ? {
|
|
357
418
|
'aria-describedby': [props['aria-describedby'], tooltipHook.describedBy].filter(Boolean).join(' ') || undefined
|
|
358
419
|
} : null;
|
|
@@ -365,10 +426,57 @@ export function Avatar({
|
|
|
365
426
|
const isInteractive = renderAsLink || renderAsButton;
|
|
366
427
|
const LinkComponent = useLinkComponent(as);
|
|
367
428
|
|
|
368
|
-
// An interactive control
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
|
|
429
|
+
// An interactive control needs an identity of its own. A status label is not
|
|
430
|
+
// one: it composes into the accessible name, so `<Avatar href status={<dot
|
|
431
|
+
// label="Online" />} />` resolves a name that reads as legitimate and says
|
|
432
|
+
// nothing about who the link points at. Only `alt`/`name` count, or a
|
|
433
|
+
// consumer's own `aria-label`/`aria-labelledby` escape hatch, which wins
|
|
434
|
+
// over the derived props below.
|
|
435
|
+
const consumerName = meaningful(props['aria-label']) ?? meaningful(props['aria-labelledby']);
|
|
436
|
+
useDevWarning('Avatar', 'an interactive avatar (with `href` or `onClick`) needs a meaningful ' + 'accessible name. Pass `alt` or `name`.', isInteractive && !nameLabel && consumerName == null);
|
|
437
|
+
|
|
438
|
+
// A status inside a consumer's own wrapper is invisible to render: the dot
|
|
439
|
+
// writes its label into this ref from its own callback ref, in the commit
|
|
440
|
+
// phase. A ref write cannot re-render, so the composed name is written
|
|
441
|
+
// straight onto the root element instead — still before paint, and with no
|
|
442
|
+
// second render.
|
|
443
|
+
const statusLabelRef = useRef({
|
|
444
|
+
label: undefined,
|
|
445
|
+
update: null
|
|
446
|
+
});
|
|
447
|
+
// A fresh callback ref every render, so React reattaches it on every commit
|
|
448
|
+
// and the name is recomposed after React has written this render's props.
|
|
449
|
+
// `update` covers the other direction: a label that changes while the avatar
|
|
450
|
+
// itself does not re-render.
|
|
451
|
+
const nameRef = element => {
|
|
452
|
+
if (element == null) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const target = statusLabelRef.current;
|
|
456
|
+
target.update = () => {
|
|
457
|
+
const composed = composeAccessibleName(t, nameLabel, meaningful(target.label) ?? getStatusLabel(status));
|
|
458
|
+
// A consumer's own ARIA wins here exactly as it does in render, where
|
|
459
|
+
// the derived props spread before `{...props}`.
|
|
460
|
+
if (props['aria-label'] == null) {
|
|
461
|
+
setAttributeOrRemove(element, 'aria-label', composed);
|
|
462
|
+
}
|
|
463
|
+
// An `<a>`/`<button>` root carries its own role and must not be hidden,
|
|
464
|
+
// so only the name transfers there.
|
|
465
|
+
if (!isInteractive) {
|
|
466
|
+
if (props.role == null) {
|
|
467
|
+
element.setAttribute('role', composed ? 'img' : 'presentation');
|
|
468
|
+
}
|
|
469
|
+
if (props['aria-hidden'] == null) {
|
|
470
|
+
setAttributeOrRemove(element, 'aria-hidden', composed ? undefined : 'true');
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
target.update();
|
|
475
|
+
return () => {
|
|
476
|
+
target.update = null;
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
const rootRef = mergeRefs(ref, showTooltip ? tooltipHook.ref : undefined, nameRef);
|
|
372
480
|
|
|
373
481
|
// The inner visuals are identical across the static and interactive variants.
|
|
374
482
|
const visualContent = /*#__PURE__*/_jsxs(_Fragment, {
|
|
@@ -430,13 +538,13 @@ export function Avatar({
|
|
|
430
538
|
// roving focus (which selects on `[data-avatar-item]`, not a tag/role) picks
|
|
431
539
|
// it up while ignoring nested buttons in a custom status/badge slot.
|
|
432
540
|
rootElement = /*#__PURE__*/_jsx(LinkComponent, {
|
|
541
|
+
...a11yLabelProps,
|
|
433
542
|
...interactivePassthrough,
|
|
434
543
|
...describedByProp,
|
|
435
544
|
ref: rootRef,
|
|
436
545
|
href: href,
|
|
437
546
|
target: target,
|
|
438
547
|
rel: rel,
|
|
439
|
-
"aria-label": accessibleName,
|
|
440
548
|
"data-avatar-item": "",
|
|
441
549
|
"data-testid": testId,
|
|
442
550
|
onClick: onClick,
|
|
@@ -445,11 +553,11 @@ export function Avatar({
|
|
|
445
553
|
});
|
|
446
554
|
} else if (renderAsButton) {
|
|
447
555
|
rootElement = /*#__PURE__*/_jsx("button", {
|
|
556
|
+
...a11yLabelProps,
|
|
448
557
|
...interactivePassthrough,
|
|
449
558
|
...describedByProp,
|
|
450
559
|
ref: rootRef,
|
|
451
560
|
type: "button",
|
|
452
|
-
"aria-label": accessibleName,
|
|
453
561
|
"data-avatar-item": "",
|
|
454
562
|
"data-testid": testId,
|
|
455
563
|
onClick: onClick,
|
|
@@ -458,11 +566,9 @@ export function Avatar({
|
|
|
458
566
|
});
|
|
459
567
|
} else {
|
|
460
568
|
rootElement = /*#__PURE__*/_jsx("div", {
|
|
569
|
+
...a11yProps,
|
|
461
570
|
...props,
|
|
462
|
-
ref: rootRef
|
|
463
|
-
role: isDecorative ? 'presentation' : 'img',
|
|
464
|
-
"aria-label": isDecorative ? undefined : accessibleName,
|
|
465
|
-
"aria-hidden": isDecorative || undefined
|
|
571
|
+
ref: rootRef
|
|
466
572
|
// The root is a div[role="img"], not natively focusable. When a name
|
|
467
573
|
// tooltip is active, add a tab stop so keyboard users can reveal it
|
|
468
574
|
// (WCAG 1.4.13 / 2.1.1) — matching Timestamp/Button. Suppressed inside
|
|
@@ -477,7 +583,10 @@ export function Avatar({
|
|
|
477
583
|
}
|
|
478
584
|
const avatarElement = /*#__PURE__*/_jsx(AvatarSizeContext, {
|
|
479
585
|
value: numericSize,
|
|
480
|
-
children:
|
|
586
|
+
children: /*#__PURE__*/_jsx(AvatarStatusLabelContext, {
|
|
587
|
+
value: statusLabelRef,
|
|
588
|
+
children: rootElement
|
|
589
|
+
})
|
|
481
590
|
});
|
|
482
591
|
|
|
483
592
|
// Always return the same structure so the avatar keeps its position in the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file AvatarStatusDot.tsx
|
|
3
|
-
* @input Uses React, StyleX, theme tokens, and
|
|
3
|
+
* @input Uses React, StyleX, theme tokens, AvatarSizeContext, and
|
|
4
|
+
* AvatarStatusLabelContext (label reporting)
|
|
4
5
|
* @output Exports AvatarStatusDot component and AvatarStatusDotProps type
|
|
5
6
|
* @position Sub-component of Avatar; renders a size-aware status indicator
|
|
6
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarStatusDot.d.ts","sourceRoot":"","sources":["../../src/Avatar/AvatarStatusDot.tsx"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"AvatarStatusDot.d.ts","sourceRoot":"","sources":["../../src/Avatar/AvatarStatusDot.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAA4B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AACvE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAO5C,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,SAAS,CAAC;AA4CvD;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,yBAAyB,CAAC;AAErE,MAAM,WAAW,oBAAqB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACrE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AA2JD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,OAAmB,EACnB,KAAK,EACL,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,oBAAoB,qBAiEtB;yBA1Ee,eAAe"}
|
|
@@ -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
|
*
|
|
@@ -14,12 +15,13 @@
|
|
|
14
15
|
* - /apps/storybook/stories/Avatar.stories.tsx (storybook stories)
|
|
15
16
|
* - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
|
|
16
17
|
*/
|
|
17
|
-
import React, { use } from 'react';
|
|
18
|
+
import React, { use, useCallback, useMemo } from 'react';
|
|
18
19
|
import * as stylex from '@stylexjs/stylex';
|
|
19
20
|
import "../theme/tokens.stylex.js";
|
|
20
21
|
import { colorVars, radiusVars } from "../theme/tokens.stylex.js";
|
|
21
22
|
import { AvatarSizeContext } from "./AvatarSizeContext.js";
|
|
22
|
-
import {
|
|
23
|
+
import { AvatarStatusLabelContext } from "./AvatarStatusLabelContext.js";
|
|
24
|
+
import { isRenderable, mergeProps, mergeRefs } from "../utils/index.js";
|
|
23
25
|
import { themeProps } from "../utils/themeProps.js";
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -264,6 +266,30 @@ export function AvatarStatusDot({
|
|
|
264
266
|
...props
|
|
265
267
|
}) {
|
|
266
268
|
const avatarSize = use(AvatarSizeContext);
|
|
269
|
+
const statusLabelRef = use(AvatarStatusLabelContext);
|
|
270
|
+
// Report the label through the avatar's ref from a callback ref rather than
|
|
271
|
+
// an Effect: the ref runs in the commit phase, so the avatar's accessible
|
|
272
|
+
// name is composed before paint, and a ref write costs no render.
|
|
273
|
+
/* eslint-disable react-compiler/react-compiler -- the avatar shares its own
|
|
274
|
+
ref through context so the dot can write into it; a context read looks
|
|
275
|
+
immutable to the compiler */
|
|
276
|
+
const reportRef = useCallback(() => {
|
|
277
|
+
const target = statusLabelRef?.current;
|
|
278
|
+
if (target == null) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
target.label = label;
|
|
282
|
+
target.update?.();
|
|
283
|
+
return () => {
|
|
284
|
+
target.label = undefined;
|
|
285
|
+
target.update?.();
|
|
286
|
+
};
|
|
287
|
+
}, [statusLabelRef, label]);
|
|
288
|
+
/* eslint-enable react-compiler/react-compiler */
|
|
289
|
+
// Memoized so React only detaches and reattaches when the reported label
|
|
290
|
+
// actually changes; an inline merge would withdraw and re-report on every
|
|
291
|
+
// render of the avatar.
|
|
292
|
+
const rootRef = useMemo(() => mergeRefs(ref, reportRef), [ref, reportRef]);
|
|
267
293
|
const {
|
|
268
294
|
dotSize,
|
|
269
295
|
borderWidth,
|
|
@@ -276,7 +302,7 @@ export function AvatarStatusDot({
|
|
|
276
302
|
const glyphShape = showsIcon ? undefined : glyphShapeMap[variant];
|
|
277
303
|
return /*#__PURE__*/_jsxs("div", {
|
|
278
304
|
...props,
|
|
279
|
-
ref:
|
|
305
|
+
ref: rootRef,
|
|
280
306
|
...(label ? {
|
|
281
307
|
role: 'img',
|
|
282
308
|
'aria-label': label
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file AvatarStatusLabelContext.ts
|
|
3
|
+
* @input Uses React createContext, RefObject
|
|
4
|
+
* @output Exports AvatarStatusLabelContext, AvatarStatusLabelTarget
|
|
5
|
+
* @position Internal context; provided by Avatar, consumed by AvatarStatusDot
|
|
6
|
+
*
|
|
7
|
+
* SYNC: When modified, update:
|
|
8
|
+
* - /packages/core/src/Avatar/Avatar.doc.mjs
|
|
9
|
+
*/
|
|
10
|
+
import { type RefObject } from 'react';
|
|
11
|
+
/**
|
|
12
|
+
* The box a status element writes its label into.
|
|
13
|
+
*
|
|
14
|
+
* `update` is the avatar's own recompose, set while its root element is
|
|
15
|
+
* attached and null otherwise. Writing `label` alone is enough on mount (the
|
|
16
|
+
* avatar reads the box when its root attaches, which happens after its
|
|
17
|
+
* children's refs); calling `update` covers a label that changes later
|
|
18
|
+
* without the avatar itself re-rendering.
|
|
19
|
+
*/
|
|
20
|
+
export interface AvatarStatusLabelTarget {
|
|
21
|
+
label: string | undefined;
|
|
22
|
+
update: (() => void) | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Lets a status element hand its accessible label to the enclosing Avatar,
|
|
26
|
+
* which composes it into its own accessible name ("Jane Doe, Online").
|
|
27
|
+
*
|
|
28
|
+
* The avatar root is `role="img"`, which prunes all descendant semantics, so
|
|
29
|
+
* composing the label in is the only way the status reaches assistive tech
|
|
30
|
+
* (WCAG 4.1.2). Reading `label` off the passed element only works when the
|
|
31
|
+
* consumer passes `AvatarStatusDot` directly; this route works through a
|
|
32
|
+
* consumer's own wrapper, at any depth.
|
|
33
|
+
*
|
|
34
|
+
* The value is a ref, written from the dot's own callback ref in the commit
|
|
35
|
+
* phase, so reporting a label costs no render and no state. `null` outside an
|
|
36
|
+
* Avatar, where a standalone dot names itself and has nobody to report to.
|
|
37
|
+
*/
|
|
38
|
+
export declare const AvatarStatusLabelContext: import("react").Context<RefObject<AvatarStatusLabelTarget> | null>;
|
|
39
|
+
//# sourceMappingURL=AvatarStatusLabelContext.d.ts.map
|