@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Avatar/Avatar.d.ts +6 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +46 -12
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
- package/dist/Breadcrumbs/index.d.ts +1 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +2 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +8 -8
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +10 -3
- package/dist/CheckboxInput/CheckboxInput.js +4 -4
- package/dist/Citation/Citation.d.ts +22 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -6
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +13 -7
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +3 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +42 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +14 -5
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +3 -1
- package/dist/DateInput/DateInput.d.ts +1 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +31 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -19
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/inputStyles.stylex.d.ts +1 -0
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +1 -0
- package/dist/FieldStatus/FieldStatus.d.ts +4 -1
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/FileInput/FileInput.d.ts +1 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +91 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +15 -9
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +65 -40
- package/dist/NumberInput/NumberInput.d.ts +1 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +20 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.js +4 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +49 -19
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +58 -16
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +5 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +13 -10
- package/dist/SideNav/SideNavItem.d.ts +1 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +12 -10
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +87 -34
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +11 -2
- package/dist/Switch/Switch.d.ts +8 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +160 -91
- package/dist/TabList/TabList.d.ts +2 -11
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +5 -9
- package/dist/TabList/TabListContext.d.ts +0 -5
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +0 -5
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +1 -0
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -19
- package/dist/TextInput/TextInput.d.ts +1 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +18 -17
- package/dist/TimeInput/TimeInput.d.ts +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Token/Token.d.ts +30 -3
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +17 -2
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -10
- package/dist/Typeahead/Typeahead.js +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +20 -10
- package/dist/astryx.css +38 -14
- package/dist/astryx.umd.js +52 -52
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +76 -0
- package/locales/pseudo.json +57 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +18 -4
- package/src/Avatar/Avatar.test.tsx +106 -0
- package/src/Avatar/Avatar.tsx +62 -10
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.tsx +4 -4
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/Citation/Citation.doc.mjs +4 -3
- package/src/Citation/Citation.test.tsx +97 -0
- package/src/Citation/Citation.tsx +43 -11
- package/src/CodeBlock/CodeBlock.tsx +35 -8
- package/src/Collapsible/Collapsible.doc.mjs +1 -0
- package/src/Collapsible/Collapsible.test.tsx +7 -0
- package/src/Collapsible/Collapsible.tsx +16 -6
- package/src/CommandPalette/CommandPalette.test.tsx +95 -1
- package/src/CommandPalette/CommandPalette.tsx +41 -2
- package/src/ContextMenu/ContextMenu.test.tsx +42 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +15 -5
- package/src/DateInput/DateInput.test.tsx +210 -3
- package/src/DateInput/DateInput.tsx +41 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
- package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
- package/src/DateRangeInput/DateRangeInput.tsx +29 -27
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.tsx +10 -4
- package/src/FileInput/FileInput.doc.mjs +13 -7
- package/src/FileInput/FileInput.test.tsx +84 -16
- package/src/FileInput/FileInput.tsx +96 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
- package/src/MultiSelector/MultiSelector.test.tsx +306 -3
- package/src/MultiSelector/MultiSelector.tsx +88 -57
- package/src/NumberInput/NumberInput.doc.mjs +11 -5
- package/src/NumberInput/NumberInput.test.tsx +48 -0
- package/src/NumberInput/NumberInput.tsx +25 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +4 -4
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
- package/src/Selector/Selector.doc.mjs +8 -0
- package/src/Selector/Selector.test.tsx +328 -3
- package/src/Selector/Selector.tsx +70 -19
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Slider/Slider.doc.mjs +6 -0
- package/src/Slider/Slider.test.tsx +240 -12
- package/src/Slider/Slider.tsx +96 -23
- package/src/Spinner/Spinner.test.tsx +10 -8
- package/src/Spinner/Spinner.tsx +15 -5
- package/src/Switch/Switch.doc.mjs +142 -42
- package/src/Switch/Switch.test.tsx +22 -0
- package/src/Switch/Switch.tsx +90 -33
- package/src/TabList/TabList.doc.mjs +0 -6
- package/src/TabList/TabList.test.tsx +3 -15
- package/src/TabList/TabList.tsx +6 -17
- package/src/TabList/TabListContext.ts +0 -6
- package/src/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
- package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
- package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +11 -5
- package/src/TextArea/TextArea.tsx +17 -26
- package/src/TextInput/TextInput.doc.mjs +11 -5
- package/src/TextInput/TextInput.test.tsx +130 -3
- package/src/TextInput/TextInput.tsx +18 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +11 -5
- package/src/TimeInput/TimeInput.tsx +14 -25
- package/src/Timestamp/Timestamp.doc.mjs +32 -6
- package/src/Timestamp/Timestamp.test.tsx +371 -0
- package/src/Timestamp/Timestamp.tsx +96 -93
- package/src/Timestamp/formatInstant.ts +218 -0
- package/src/Timestamp/index.ts +4 -0
- package/src/Timestamp/tooltipEntries.test.ts +552 -0
- package/src/Timestamp/tooltipEntries.ts +210 -0
- package/src/Token/Token.test.tsx +21 -15
- package/src/Token/Token.tsx +31 -14
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
- package/src/Tokenizer/Tokenizer.tsx +4 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +29 -14
- package/src/Typeahead/Typeahead.doc.mjs +6 -0
- package/src/Typeahead/Typeahead.test.tsx +27 -0
- package/src/Typeahead/Typeahead.tsx +1 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file FileInput.tsx
|
|
7
|
-
* @input Uses React, useId, Field, Icon, Spinner
|
|
7
|
+
* @input Uses React, useId, Field, Icon, Spinner, VisuallyHidden
|
|
8
8
|
* @output Exports FileInput component, FileInputProps, FileInputStatus
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by FileInput.test.tsx
|
|
10
10
|
*
|
|
@@ -40,12 +40,14 @@ import {
|
|
|
40
40
|
Field,
|
|
41
41
|
InputClearButton,
|
|
42
42
|
type InputStatus,
|
|
43
|
-
type InputStatusType,
|
|
44
43
|
type FieldStatusVariant,
|
|
45
44
|
} from '../Field';
|
|
46
|
-
import {Icon
|
|
45
|
+
import {Icon} from '../Icon';
|
|
47
46
|
import {Spinner} from '../Spinner';
|
|
47
|
+
import {VisuallyHidden} from '../VisuallyHidden';
|
|
48
48
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
49
|
+
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
50
|
+
import {useClickableContainer} from '../hooks/useClickableContainer';
|
|
49
51
|
import {useTooltip} from '../Tooltip';
|
|
50
52
|
|
|
51
53
|
export type {
|
|
@@ -247,17 +249,6 @@ const styles = stylex.create({
|
|
|
247
249
|
flex: 1,
|
|
248
250
|
minWidth: 0,
|
|
249
251
|
},
|
|
250
|
-
liveRegion: {
|
|
251
|
-
position: 'absolute',
|
|
252
|
-
width: 1,
|
|
253
|
-
height: 1,
|
|
254
|
-
padding: 0,
|
|
255
|
-
margin: -1,
|
|
256
|
-
overflow: 'hidden',
|
|
257
|
-
clip: 'rect(0, 0, 0, 0)',
|
|
258
|
-
whiteSpace: 'nowrap',
|
|
259
|
-
borderWidth: 0,
|
|
260
|
-
},
|
|
261
252
|
});
|
|
262
253
|
|
|
263
254
|
const statusBorderStyles = stylex.create({
|
|
@@ -363,6 +354,7 @@ export interface FileInputProps extends Omit<
|
|
|
363
354
|
* How the status message is placed relative to the input.
|
|
364
355
|
* - 'attached': message overlaps directly below the input (bordered treatment)
|
|
365
356
|
* - 'detached': message floats below as a separate element with spacing
|
|
357
|
+
* - 'tooltip': no message box; the status icon becomes a focusable info-tip button that reveals the message on hover, keyboard focus, or tap
|
|
366
358
|
* @default 'attached'
|
|
367
359
|
*/
|
|
368
360
|
statusVariant?: FieldStatusVariant;
|
|
@@ -439,22 +431,24 @@ export function FileInput({
|
|
|
439
431
|
const id = useId();
|
|
440
432
|
const descriptionID = useId();
|
|
441
433
|
const statusMessageID = useId();
|
|
442
|
-
const
|
|
434
|
+
const requiredID = useId();
|
|
443
435
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
436
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
444
437
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
445
438
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
446
439
|
const [, startTransition] = useTransition();
|
|
447
440
|
|
|
448
441
|
// Announce successful file selection to screen readers via a persistent
|
|
449
|
-
// live region (forms-17).
|
|
450
|
-
//
|
|
442
|
+
// live region (forms-17). Validation errors are announced (assertively) by
|
|
443
|
+
// the FieldStatus that the derived error status mounts, so only the
|
|
444
|
+
// successful attach needs an explicit announcement here.
|
|
451
445
|
const announce = useAnnounce();
|
|
452
446
|
|
|
453
447
|
// Disabled-reason tooltip. Disabled controls swallow pointer events, so the
|
|
454
|
-
// tooltip listeners attach to the
|
|
455
|
-
//
|
|
456
|
-
//
|
|
457
|
-
//
|
|
448
|
+
// tooltip listeners attach to the visible container (which stays hoverable)
|
|
449
|
+
// and the trigger stays perceivable via aria-disabled instead of the native
|
|
450
|
+
// disabled attribute. Opening the picker is blocked by the isDisabled guards
|
|
451
|
+
// in the handlers.
|
|
458
452
|
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
459
453
|
const disabledMessageTooltip = useTooltip({
|
|
460
454
|
placement: 'above',
|
|
@@ -468,25 +462,27 @@ export function FileInput({
|
|
|
468
462
|
? {type: 'error' as const, message: validationError}
|
|
469
463
|
: undefined);
|
|
470
464
|
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
465
|
+
const {statusIcon, describedBy: statusTooltipDescribedBy} =
|
|
466
|
+
useInputStatusIcon({
|
|
467
|
+
status,
|
|
468
|
+
statusVariant,
|
|
469
|
+
});
|
|
476
470
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
success: 'success',
|
|
484
|
-
};
|
|
471
|
+
// Required state. The trigger is a real button, but AT does not reliably
|
|
472
|
+
// announce aria-required, so the trigger instead points its aria-describedby
|
|
473
|
+
// at a visually hidden "Required" span — mirroring the Field label's visible
|
|
474
|
+
// indicator (where isOptional takes precedence) and the pattern Slider uses
|
|
475
|
+
// for its thumbs.
|
|
476
|
+
const conveysRequired = isRequired && !isOptional;
|
|
485
477
|
|
|
486
478
|
const ariaDescribedBy =
|
|
487
479
|
[
|
|
488
480
|
description ? descriptionID : null,
|
|
489
|
-
status?.message ? statusMessageID : null,
|
|
481
|
+
statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
|
|
482
|
+
// The tooltip variant renders no message box; describe the input by the
|
|
483
|
+
// tooltip's content instead so the status is still announced.
|
|
484
|
+
statusTooltipDescribedBy,
|
|
485
|
+
conveysRequired ? requiredID : null,
|
|
490
486
|
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
491
487
|
]
|
|
492
488
|
.filter(Boolean)
|
|
@@ -524,8 +520,9 @@ export function FileInput({
|
|
|
524
520
|
onChange(result);
|
|
525
521
|
|
|
526
522
|
// Announce the successful selection politely. Validation errors are
|
|
527
|
-
//
|
|
528
|
-
// attach here (do not
|
|
523
|
+
// announced assertively by the FieldStatus that the derived error
|
|
524
|
+
// status mounts, so only announce the attach here (do not
|
|
525
|
+
// double-announce errors).
|
|
529
526
|
if (errors.length === 0) {
|
|
530
527
|
announce(
|
|
531
528
|
valid.length === 1
|
|
@@ -583,6 +580,9 @@ export function FileInput({
|
|
|
583
580
|
}
|
|
584
581
|
}, [isDisabled]);
|
|
585
582
|
|
|
583
|
+
// Explicit keyboard activation so Enter/Space open the picker even where a
|
|
584
|
+
// synthetic keydown does not raise a native click. The isDisabled guard
|
|
585
|
+
// blocks activation while focusable-disabled.
|
|
586
586
|
const handleKeyDown = useCallback(
|
|
587
587
|
(e: React.KeyboardEvent) => {
|
|
588
588
|
if ((e.key === 'Enter' || e.key === ' ') && !isDisabled) {
|
|
@@ -593,6 +593,16 @@ export function FileInput({
|
|
|
593
593
|
[isDisabled],
|
|
594
594
|
);
|
|
595
595
|
|
|
596
|
+
// Make the surface clickable without giving it an interactive role. Clicks on
|
|
597
|
+
// the nested clear/status controls are ignored by the hook, so those buttons
|
|
598
|
+
// are plain siblings — no nested-interactive violation.
|
|
599
|
+
const {onClick: onContainerClick, onMouseUp: onContainerMouseUp} =
|
|
600
|
+
useClickableContainer({
|
|
601
|
+
containerRef,
|
|
602
|
+
onClick: handleClick,
|
|
603
|
+
disabled: isDisabled,
|
|
604
|
+
});
|
|
605
|
+
|
|
596
606
|
const handleDragEnter = useCallback(
|
|
597
607
|
(e: DragEvent) => {
|
|
598
608
|
e.preventDefault();
|
|
@@ -696,13 +706,7 @@ export function FileInput({
|
|
|
696
706
|
)}>
|
|
697
707
|
{fileNames ?? displayPlaceholder}
|
|
698
708
|
</span>
|
|
699
|
-
{
|
|
700
|
-
<Icon
|
|
701
|
-
icon={statusIconMap[status.type]}
|
|
702
|
-
size="md"
|
|
703
|
-
color={statusIconColorMap[status.type]}
|
|
704
|
-
/>
|
|
705
|
-
)}
|
|
709
|
+
{statusIcon}
|
|
706
710
|
</>
|
|
707
711
|
);
|
|
708
712
|
};
|
|
@@ -742,35 +746,16 @@ export function FileInput({
|
|
|
742
746
|
width={width}>
|
|
743
747
|
<div
|
|
744
748
|
ref={el => {
|
|
745
|
-
|
|
746
|
-
//
|
|
747
|
-
//
|
|
749
|
+
containerRef.current = el;
|
|
750
|
+
// Anchor + hover/focus listeners for the disabled-message tooltip on
|
|
751
|
+
// the visible container (the focusable trigger is visually hidden, so
|
|
752
|
+
// anchoring there would place the tooltip at a 1px box). focusin
|
|
753
|
+
// bubbles from the trigger button, so the tooltip still opens on
|
|
754
|
+
// keyboard focus. Handlers are gated internally by isEnabled.
|
|
748
755
|
disabledMessageTooltip.ref(el);
|
|
749
756
|
}}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
// aria-disabled so the reason is focus-discoverable; opening the picker
|
|
753
|
-
// is still blocked by the isDisabled guards in the handlers.
|
|
754
|
-
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
755
|
-
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
756
|
-
onClick={handleClick}
|
|
757
|
-
onKeyDown={handleKeyDown}
|
|
758
|
-
// aria-label suppresses child content from the accessible name, so
|
|
759
|
-
// compose the selected filenames into it — otherwise a screen-reader
|
|
760
|
-
// user refocusing the control hears only the field label and cannot
|
|
761
|
-
// tell what (if anything) is attached.
|
|
762
|
-
aria-label={
|
|
763
|
-
hasFiles && fileNames
|
|
764
|
-
? t('@astryx.fileInput.triggerWithFiles', {label, fileNames})
|
|
765
|
-
: label
|
|
766
|
-
}
|
|
767
|
-
aria-busy={isLoading || undefined}
|
|
768
|
-
// These describe the operable control, so they belong on the focusable
|
|
769
|
-
// role="button" wrapper — not the hidden tabIndex={-1} file input the
|
|
770
|
-
// user never focuses (forms-6).
|
|
771
|
-
aria-describedby={ariaDescribedBy}
|
|
772
|
-
aria-required={isRequired ? 'true' : undefined}
|
|
773
|
-
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
757
|
+
onClick={!isDisabled ? onContainerClick : undefined}
|
|
758
|
+
onMouseUp={!isDisabled ? onContainerMouseUp : undefined}
|
|
774
759
|
{...dragDropProps}
|
|
775
760
|
{...mergeProps(
|
|
776
761
|
themeProps('file-input', {mode, status: status?.type ?? null}),
|
|
@@ -786,6 +771,40 @@ export function FileInput({
|
|
|
786
771
|
className,
|
|
787
772
|
style,
|
|
788
773
|
)}>
|
|
774
|
+
{/* Visually hidden real button carrying the accessible role, name, and
|
|
775
|
+
ARIA. Kept as a sibling of the clear/status controls inside a
|
|
776
|
+
non-interactive container, so no interactive element nests inside
|
|
777
|
+
another (WCAG 4.1.2). The visible focus feedback is the container's
|
|
778
|
+
:focus-within border; the surface click is handled by the container
|
|
779
|
+
(VisuallyHidden disables the button's own pointer events), and
|
|
780
|
+
keyboard activation still fires here. */}
|
|
781
|
+
<VisuallyHidden>
|
|
782
|
+
<button
|
|
783
|
+
type="button"
|
|
784
|
+
// With a disabledMessage the trigger keeps focusability via
|
|
785
|
+
// aria-disabled so the reason is focus-discoverable; opening the
|
|
786
|
+
// picker is still blocked by the isDisabled guards in the handlers.
|
|
787
|
+
// Otherwise the native disabled attribute removes it from the tab
|
|
788
|
+
// order.
|
|
789
|
+
disabled={isDisabled && !showsDisabledMessage}
|
|
790
|
+
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
791
|
+
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
792
|
+
onClick={handleClick}
|
|
793
|
+
onKeyDown={handleKeyDown}
|
|
794
|
+
// aria-label suppresses child content from the accessible name, so
|
|
795
|
+
// compose the selected filenames into it — otherwise a
|
|
796
|
+
// screen-reader user refocusing the control hears only the field
|
|
797
|
+
// label and cannot tell what (if anything) is attached.
|
|
798
|
+
aria-label={
|
|
799
|
+
hasFiles && fileNames
|
|
800
|
+
? t('@astryx.fileInput.triggerWithFiles', {label, fileNames})
|
|
801
|
+
: label
|
|
802
|
+
}
|
|
803
|
+
aria-busy={isLoading || undefined}
|
|
804
|
+
aria-describedby={ariaDescribedBy}
|
|
805
|
+
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
806
|
+
/>
|
|
807
|
+
</VisuallyHidden>
|
|
789
808
|
<input
|
|
790
809
|
{...rest}
|
|
791
810
|
ref={mergeRefs(ref, inputRef)}
|
|
@@ -796,7 +815,7 @@ export function FileInput({
|
|
|
796
815
|
disabled={isDisabled}
|
|
797
816
|
onChange={handleInputChange}
|
|
798
817
|
// The visually-hidden native input is never focused (tabIndex={-1});
|
|
799
|
-
// its describedby/required/invalid live on the
|
|
818
|
+
// its describedby/required/invalid live on the trigger button.
|
|
800
819
|
aria-hidden="true"
|
|
801
820
|
tabIndex={-1}
|
|
802
821
|
{...stylex.props(styles.hiddenInput)}
|
|
@@ -809,13 +828,11 @@ export function FileInput({
|
|
|
809
828
|
/>
|
|
810
829
|
)}
|
|
811
830
|
</div>
|
|
812
|
-
|
|
813
|
-
id={
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
{validationError}
|
|
818
|
-
</div>
|
|
831
|
+
{conveysRequired && (
|
|
832
|
+
<VisuallyHidden id={requiredID}>
|
|
833
|
+
{t('@astryx.fileInput.required')}
|
|
834
|
+
</VisuallyHidden>
|
|
835
|
+
)}
|
|
819
836
|
{showsDisabledMessage &&
|
|
820
837
|
disabledMessageTooltip.renderTooltip(disabledMessage)}
|
|
821
838
|
</Field>
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
radiusVars,
|
|
35
35
|
spacingVars,
|
|
36
36
|
} from '../theme/tokens.stylex';
|
|
37
|
-
import {mergeProps} from '../utils';
|
|
38
37
|
import {themeProps} from '../utils/themeProps';
|
|
39
38
|
|
|
40
39
|
const styles = stylex.create({
|
|
@@ -469,6 +468,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
469
468
|
props?: Omit<ContextRenderProps, 'positioning'>,
|
|
470
469
|
): ReactNode => {
|
|
471
470
|
const renderPlacement = props?.placement ?? placement;
|
|
471
|
+
const themeClassName = themeProps('hovercard').className;
|
|
472
472
|
const renderProps = {
|
|
473
473
|
placement: renderPlacement,
|
|
474
474
|
alignment: props?.alignment ?? alignment,
|
|
@@ -477,7 +477,19 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
477
477
|
// are non-modal, so group is honest semantics without a name.
|
|
478
478
|
role: label ? 'dialog' : 'group',
|
|
479
479
|
'aria-label': label || undefined,
|
|
480
|
-
|
|
480
|
+
// Consumer surface style props land on the layer container — the
|
|
481
|
+
// themed surface (bg/radius/shadow) where the theme class lives — so
|
|
482
|
+
// customizing the card targets the same element as the theme. The inner
|
|
483
|
+
// span keeps `styles.content` for padding.
|
|
484
|
+
xstyle: [
|
|
485
|
+
popoverXstyle,
|
|
486
|
+
layerAnimations[renderPlacement],
|
|
487
|
+
props?.xstyle,
|
|
488
|
+
],
|
|
489
|
+
className: props?.className
|
|
490
|
+
? `${themeClassName} ${props.className}`
|
|
491
|
+
: themeClassName,
|
|
492
|
+
style: props?.style,
|
|
481
493
|
// Render the layer as inline-safe phrasing markup so HoverCard stays
|
|
482
494
|
// valid (and hydration-stable) inside inline contexts like a `<p>`.
|
|
483
495
|
as: 'span' as const,
|
|
@@ -485,7 +497,7 @@ export function useHoverCard(options: HoverCardOptions = {}): HoverCardReturn {
|
|
|
485
497
|
|
|
486
498
|
return layer.render(
|
|
487
499
|
<span
|
|
488
|
-
{...
|
|
500
|
+
{...stylex.props(styles.content)}
|
|
489
501
|
onMouseEnter={() => {
|
|
490
502
|
isHoveringContentRef.current = true;
|
|
491
503
|
clearTimeouts();
|
package/src/Icon/Icon.doc.mjs
CHANGED
|
@@ -39,6 +39,12 @@ export const docs = {
|
|
|
39
39
|
type: 'string',
|
|
40
40
|
description: 'Accessible name for a MEANINGFUL, standalone icon (a status glyph or icon-only indicator with no adjacent text). Setting it exposes the icon to screen readers as role="img" with this text as the accessible name (aria-label) and removes the default aria-hidden. Omit it (default) for decorative icons and the icon stays hidden from assistive tech (aria-hidden="true"). This is the accessible-name / alt-text prop for icons: one prop instead of manually setting aria-label + role + aria-hidden. An empty string is treated as decorative. Do not set it when an interactive parent (Button, IconButton, link) already names the control.',
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
name: 'xstyle',
|
|
44
|
+
type: 'StyleXStyles',
|
|
45
|
+
description:
|
|
46
|
+
'StyleX styles for customization (color, size, opacity). Folded into the icon\'s own stylex.props() call so it composes with the base color/size styles. Must be a stylex.create() value, not an inline style object like style={{}}.',
|
|
47
|
+
},
|
|
42
48
|
],
|
|
43
49
|
theming: {
|
|
44
50
|
targets: [
|
|
@@ -90,6 +96,12 @@ export const docsZh = {
|
|
|
90
96
|
type: 'string',
|
|
91
97
|
description: '有含义的独立图标的可访问名称(无相邻文字的状态图标或纯图标指示器)。设置后会将图标以 role="img" 暴露给辅助技术,并以该文本作为可访问名称(aria-label),同时移除默认的 aria-hidden。省略(默认)用于装饰性图标,图标对辅助技术保持隐藏(aria-hidden="true")。空字符串按装饰性处理。当交互式父元素(Button、IconButton、链接)已命名该控件时请勿设置。',
|
|
92
98
|
},
|
|
99
|
+
{
|
|
100
|
+
name: 'xstyle',
|
|
101
|
+
type: 'StyleXStyles',
|
|
102
|
+
description:
|
|
103
|
+
'用于自定义的 StyleX 样式(颜色、尺寸、不透明度)。会并入图标自身的 stylex.props() 调用,从而与基础的颜色/尺寸样式组合。必须是 stylex.create() 的值,而不是像 style={{}} 这样的内联样式对象。',
|
|
104
|
+
},
|
|
93
105
|
],
|
|
94
106
|
theming: {
|
|
95
107
|
targets: [
|
|
@@ -136,5 +148,6 @@ export const docsDense = {
|
|
|
136
148
|
color: 'Color variant mapped to Astryx icon color tokens.',
|
|
137
149
|
size: 'Icon size.',
|
|
138
150
|
label: 'Accessible name for a meaningful, standalone icon. Sets role="img" + aria-label and drops the default aria-hidden. Omit (default) for decorative icons (stays aria-hidden). Empty string = decorative. The accessible-name/alt-text prop for icons.',
|
|
151
|
+
xstyle: 'StyleX styles (color, size, opacity) via stylex.create(); composes with the base color/size styles.',
|
|
139
152
|
},
|
|
140
153
|
};
|
package/src/Icon/Icon.test.tsx
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen} from '@testing-library/react';
|
|
14
|
+
import * as stylex from '@stylexjs/stylex';
|
|
14
15
|
import {TestIcon} from '../__tests__/TestIcon';
|
|
15
16
|
import {Icon} from './Icon';
|
|
16
17
|
|
|
@@ -279,4 +280,92 @@ describe('Icon', () => {
|
|
|
279
280
|
);
|
|
280
281
|
});
|
|
281
282
|
});
|
|
283
|
+
|
|
284
|
+
describe('styling prop handling', () => {
|
|
285
|
+
it('composes a consumer className with the internal classes (string mode)', () => {
|
|
286
|
+
render(
|
|
287
|
+
<Icon icon="check" className="consumer-target" data-testid="icon" />,
|
|
288
|
+
);
|
|
289
|
+
const icon = screen.getByTestId('icon');
|
|
290
|
+
// Consumer className must survive alongside the stable astryx-icon class
|
|
291
|
+
// and the StyleX classes — previously it was clobbered by the later
|
|
292
|
+
// internal spread.
|
|
293
|
+
expect(icon).toHaveClass('consumer-target');
|
|
294
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
295
|
+
// At least one StyleX-generated class is still present.
|
|
296
|
+
expect(icon.className.split(' ').length).toBeGreaterThan(2);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('forwards a consumer className in component (SVG) mode', () => {
|
|
300
|
+
render(
|
|
301
|
+
<Icon icon={TestIcon} className="consumer-target" data-testid="icon" />,
|
|
302
|
+
);
|
|
303
|
+
const icon = screen.getByTestId('icon');
|
|
304
|
+
expect(icon).toHaveClass('consumer-target');
|
|
305
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('merges a consumer style onto the rendered element (string mode)', () => {
|
|
309
|
+
render(<Icon icon="check" style={{opacity: 0.5}} data-testid="icon" />);
|
|
310
|
+
expect(screen.getByTestId('icon')).toHaveStyle({opacity: '0.5'});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('merges a consumer style onto the rendered element (component mode)', () => {
|
|
314
|
+
render(
|
|
315
|
+
<Icon icon={TestIcon} style={{opacity: 0.5}} data-testid="icon" />,
|
|
316
|
+
);
|
|
317
|
+
expect(screen.getByTestId('icon')).toHaveStyle({opacity: '0.5'});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('applies xstyle to the rendered element (string mode)', () => {
|
|
321
|
+
const overrides = stylex.create({root: {opacity: 0.25}});
|
|
322
|
+
render(<Icon icon="check" xstyle={overrides.root} data-testid="icon" />);
|
|
323
|
+
const icon = screen.getByTestId('icon');
|
|
324
|
+
// xstyle is folded into stylex.props, so it contributes a StyleX class
|
|
325
|
+
// (and, in jsdom's stylex runtime, an inline style) alongside the base
|
|
326
|
+
// color/size classes rather than clobbering them.
|
|
327
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
328
|
+
expect(icon).toHaveStyle({opacity: '0.25'});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('applies xstyle to the rendered element (component mode)', () => {
|
|
332
|
+
const overrides = stylex.create({root: {opacity: 0.25}});
|
|
333
|
+
render(
|
|
334
|
+
<Icon icon={TestIcon} xstyle={overrides.root} data-testid="icon" />,
|
|
335
|
+
);
|
|
336
|
+
const icon = screen.getByTestId('icon');
|
|
337
|
+
expect(icon).toHaveClass('astryx-icon');
|
|
338
|
+
expect(icon).toHaveStyle({opacity: '0.25'});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('leaves default rendering unchanged when no styling props are passed (string mode)', () => {
|
|
342
|
+
const {container} = render(
|
|
343
|
+
<Icon icon="check" size="sm" color="secondary" />,
|
|
344
|
+
);
|
|
345
|
+
const icon = container.querySelector('.astryx-icon') as HTMLElement;
|
|
346
|
+
const {container: refContainer} = render(
|
|
347
|
+
<Icon icon="check" size="sm" color="secondary" />,
|
|
348
|
+
);
|
|
349
|
+
const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
|
|
350
|
+
// Default output is identical to itself — the styling-prop handling adds
|
|
351
|
+
// nothing (no extra class, no inline style) unless a prop is passed.
|
|
352
|
+
expect(icon.className).toBe(refIcon.className);
|
|
353
|
+
expect(icon.getAttribute('style')).toBe(refIcon.getAttribute('style'));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('leaves default rendering unchanged when no styling props are passed (component mode)', () => {
|
|
357
|
+
const {container} = render(
|
|
358
|
+
<Icon icon={TestIcon} size="sm" color="secondary" />,
|
|
359
|
+
);
|
|
360
|
+
const icon = container.querySelector('.astryx-icon') as HTMLElement;
|
|
361
|
+
const {container: refContainer} = render(
|
|
362
|
+
<Icon icon={TestIcon} size="sm" color="secondary" />,
|
|
363
|
+
);
|
|
364
|
+
const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
|
|
365
|
+
// SVGElement.className is an SVGAnimatedString, so compare the class
|
|
366
|
+
// attribute string rather than the property object.
|
|
367
|
+
expect(icon.getAttribute('class')).toBe(refIcon.getAttribute('class'));
|
|
368
|
+
expect(icon.getAttribute('style')).toBe(refIcon.getAttribute('style'));
|
|
369
|
+
});
|
|
370
|
+
});
|
|
282
371
|
});
|
package/src/Icon/Icon.tsx
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import React, {type ComponentType, type SVGProps} from 'react';
|
|
26
26
|
import * as stylex from '@stylexjs/stylex';
|
|
27
|
+
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
27
28
|
import {colorVars} from '../theme/tokens.stylex';
|
|
28
29
|
import {getIcon} from './globalIconRegistry';
|
|
29
30
|
import type {IconName} from './globalIconRegistry';
|
|
@@ -244,6 +245,19 @@ export interface IconProps extends Omit<
|
|
|
244
245
|
* ```
|
|
245
246
|
*/
|
|
246
247
|
label?: string;
|
|
248
|
+
/**
|
|
249
|
+
* StyleX styles created via `stylex.create()`. Folded into the icon's own
|
|
250
|
+
* `stylex.props()` call (as the last argument) so it merges with the base
|
|
251
|
+
* color/size styles for optimal deduplication, matching how other Astryx
|
|
252
|
+
* components accept `xstyle`.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```
|
|
256
|
+
* const overrides = stylex.create({ root: { opacity: 0.5 } });
|
|
257
|
+
* <Icon icon="search" xstyle={overrides.root} />
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
xstyle?: StyleXStyles;
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
/**
|
|
@@ -283,6 +297,9 @@ export function Icon({
|
|
|
283
297
|
size = 'md',
|
|
284
298
|
label,
|
|
285
299
|
ref,
|
|
300
|
+
className,
|
|
301
|
+
style,
|
|
302
|
+
xstyle,
|
|
286
303
|
...props
|
|
287
304
|
}: IconProps) {
|
|
288
305
|
// Derive ARIA from `label`: decorative (aria-hidden) by default, or a
|
|
@@ -297,6 +314,9 @@ export function Icon({
|
|
|
297
314
|
color={color}
|
|
298
315
|
size={size}
|
|
299
316
|
a11yProps={a11yProps}
|
|
317
|
+
className={className}
|
|
318
|
+
style={style}
|
|
319
|
+
xstyle={xstyle}
|
|
300
320
|
spanProps={props}
|
|
301
321
|
/>
|
|
302
322
|
);
|
|
@@ -311,9 +331,16 @@ export function Icon({
|
|
|
311
331
|
// BEFORE {...props} so an explicit aria-hidden/role/aria-label from the
|
|
312
332
|
// consumer still wins as an escape hatch.
|
|
313
333
|
{...a11yProps}
|
|
334
|
+
// The styling props (className, style, xstyle) are handled here so they
|
|
335
|
+
// COMPOSE with the internal classes/styles instead of clobbering them:
|
|
336
|
+
// xstyle folds into stylex.props, and className/style merge via
|
|
337
|
+
// mergeProps. The remaining rest props keep their prior last-spread
|
|
338
|
+
// precedence as escape hatches.
|
|
314
339
|
{...mergeProps(
|
|
315
340
|
themeProps('icon', {size, color}),
|
|
316
|
-
stylex.props(styles.root, colorStyles[color], sizeStyles[size]),
|
|
341
|
+
stylex.props(styles.root, colorStyles[color], sizeStyles[size], xstyle),
|
|
342
|
+
className ?? undefined,
|
|
343
|
+
style,
|
|
317
344
|
)}
|
|
318
345
|
{...props}
|
|
319
346
|
/>
|
|
@@ -338,12 +365,18 @@ function IconFromRegistry({
|
|
|
338
365
|
color,
|
|
339
366
|
size,
|
|
340
367
|
a11yProps,
|
|
368
|
+
className,
|
|
369
|
+
style,
|
|
370
|
+
xstyle,
|
|
341
371
|
spanProps,
|
|
342
372
|
}: {
|
|
343
373
|
name: IconName;
|
|
344
374
|
color: IconColor;
|
|
345
375
|
size: IconSize;
|
|
346
376
|
a11yProps: {role: 'img'; 'aria-label': string} | {'aria-hidden': 'true'};
|
|
377
|
+
className?: string;
|
|
378
|
+
style?: React.CSSProperties;
|
|
379
|
+
xstyle?: StyleXStyles;
|
|
347
380
|
spanProps?: Omit<SVGProps<SVGSVGElement>, 'ref' | 'color'>;
|
|
348
381
|
}) {
|
|
349
382
|
const resolvedIcon = getIcon(name);
|
|
@@ -352,6 +385,14 @@ function IconFromRegistry({
|
|
|
352
385
|
return null;
|
|
353
386
|
}
|
|
354
387
|
|
|
388
|
+
// The styling props (className, style, xstyle) are handled here so they
|
|
389
|
+
// COMPOSE with the internal astryx-icon + StyleX classes/styles instead of
|
|
390
|
+
// being shadowed by the later spread: xstyle folds into stylex.props, and
|
|
391
|
+
// className/style merge via mergeProps. Other span props keep their prior
|
|
392
|
+
// precedence (spread before the internal merge).
|
|
393
|
+
const restSpanProps =
|
|
394
|
+
(spanProps as React.HTMLAttributes<HTMLSpanElement>) ?? {};
|
|
395
|
+
|
|
355
396
|
return (
|
|
356
397
|
<span
|
|
357
398
|
// Derived a11y — decorative (aria-hidden) by default, or a meaningful
|
|
@@ -359,10 +400,17 @@ function IconFromRegistry({
|
|
|
359
400
|
// prop spread so consumers can still override it with explicit
|
|
360
401
|
// aria-hidden/role/aria-label. This mirrors component-mode Icon.
|
|
361
402
|
{...a11yProps}
|
|
362
|
-
{...
|
|
403
|
+
{...restSpanProps}
|
|
363
404
|
{...mergeProps(
|
|
364
405
|
themeProps('icon', {size, color}),
|
|
365
|
-
stylex.props(
|
|
406
|
+
stylex.props(
|
|
407
|
+
styles.span,
|
|
408
|
+
colorStyles[color],
|
|
409
|
+
spanSizeStyles[size],
|
|
410
|
+
xstyle,
|
|
411
|
+
),
|
|
412
|
+
className ?? undefined,
|
|
413
|
+
style,
|
|
366
414
|
)}>
|
|
367
415
|
{resolvedIcon}
|
|
368
416
|
</span>
|
|
@@ -32,7 +32,7 @@ import {IconButton} from '../IconButton';
|
|
|
32
32
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
33
33
|
import {useScrollLock} from '../hooks/useScrollLock';
|
|
34
34
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
35
|
-
import {mergeProps, mergeRefs} from '../utils';
|
|
35
|
+
import {mergeProps, mergeRefs, rtlStyles} from '../utils';
|
|
36
36
|
import type {BaseProps} from '../BaseProps';
|
|
37
37
|
import {themeProps} from '../utils/themeProps';
|
|
38
38
|
import {useTranslator} from '../i18n';
|
|
@@ -532,7 +532,11 @@ export function Lightbox({
|
|
|
532
532
|
{isGallery && (
|
|
533
533
|
<div {...stylex.props(styles.navButton, styles.navPrev)}>
|
|
534
534
|
<IconButton
|
|
535
|
-
icon={
|
|
535
|
+
icon={
|
|
536
|
+
<span {...stylex.props(rtlStyles.mirror)}>
|
|
537
|
+
<Icon icon="chevronLeft" size="sm" color="inherit" />
|
|
538
|
+
</span>
|
|
539
|
+
}
|
|
536
540
|
label={t('@astryx.lightbox.previous')}
|
|
537
541
|
variant="ghost"
|
|
538
542
|
isDisabled={!canPrev}
|
|
@@ -587,7 +591,11 @@ export function Lightbox({
|
|
|
587
591
|
{isGallery && (
|
|
588
592
|
<div {...stylex.props(styles.navButton, styles.navNext)}>
|
|
589
593
|
<IconButton
|
|
590
|
-
icon={
|
|
594
|
+
icon={
|
|
595
|
+
<span {...stylex.props(rtlStyles.mirror)}>
|
|
596
|
+
<Icon icon="chevronRight" size="sm" color="inherit" />
|
|
597
|
+
</span>
|
|
598
|
+
}
|
|
591
599
|
label={t('@astryx.lightbox.next')}
|
|
592
600
|
variant="ghost"
|
|
593
601
|
isDisabled={!canNext}
|