@astryxdesign/core 0.1.9 → 0.2.0-canary.136a00f
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 +107 -0
- package/dist/Avatar/Avatar.d.ts +42 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +190 -59
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +58 -6
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
- package/dist/Banner/Banner.js +2 -2
- 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/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +6 -4
- 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/ChatComposerInput.js +1 -1
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatLayout.js +4 -4
- 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/Chat/useTriggerMenu.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +6 -6
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +19 -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 +16 -10
- 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/CommandPalette/CommandPaletteItem.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +23 -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 +10 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +33 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +23 -20
- 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 +5 -2
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +5 -2
- 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/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +9 -5
- 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 +10 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +39 -3
- package/dist/FileInput/FileInput.d.ts +10 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +93 -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/Item/Item.d.ts +16 -3
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +31 -6
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +10 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +19 -13
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/List/ListItem.d.ts +11 -1
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +2 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MetadataList/MetadataList.d.ts +1 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +4 -2
- package/dist/MultiSelector/MultiSelector.d.ts +9 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +80 -42
- package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
- package/dist/NumberInput/NumberInput.d.ts +10 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +22 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +11 -3
- package/dist/PowerSearch/PowerSearch.d.ts +9 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +2 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +6 -1
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -5
- 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 +5 -3
- package/dist/Selector/Selector.d.ts +8 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +66 -17
- 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/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +2 -2
- 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 +171 -91
- package/dist/TabList/Tab.js +2 -2
- 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/TabList/TabMenu.js +1 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/TableCell.js +3 -3
- package/dist/Table/TableHeaderCell.js +3 -3
- 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 +37 -27
- 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 +15 -7
- 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 +10 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +18 -19
- package/dist/TextInput/TextInput.d.ts +10 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +20 -17
- package/dist/Thumbnail/Thumbnail.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts +10 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +17 -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/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +61 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +3 -1
- package/dist/Token/Token.d.ts +34 -4
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +85 -45
- package/dist/Token/TokenLink.d.ts +62 -0
- package/dist/Token/TokenLink.d.ts.map +1 -0
- package/dist/Token/TokenLink.js +76 -0
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts +9 -2
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +4 -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 +9 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +22 -11
- package/dist/Typeahead/Typeahead.d.ts +9 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +11 -2
- 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 +70 -30
- package/dist/astryx.umd.js +51 -51
- 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/useKeyboardHint.d.ts +1 -1
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
- package/dist/hooks/useKeyboardHint.js +4 -2
- 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/i18n/InternationalizationContext.d.ts +1 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -0
- package/dist/i18n/InternationalizationProvider.d.ts +8 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +5 -1
- package/dist/i18n/getLocaleDirection.d.ts +18 -0
- package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
- package/dist/i18n/getLocaleDirection.js +29 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +7 -1
- package/dist/i18n/useDirection.d.ts +2 -0
- package/dist/i18n/useDirection.d.ts.map +1 -0
- package/dist/i18n/useDirection.js +27 -0
- 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 +112 -0
- package/locales/pseudo.json +84 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +50 -3
- package/src/Avatar/Avatar.test.tsx +218 -0
- package/src/Avatar/Avatar.tsx +285 -79
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
- package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
- package/src/AvatarGroup/AvatarGroup.tsx +58 -3
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
- package/src/Banner/Banner.tsx +6 -6
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Button/Button.test.tsx +16 -0
- package/src/Button/Button.tsx +6 -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/ChatComposerInput.tsx +2 -2
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatLayout.tsx +4 -4
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/Chat/useTriggerMenu.tsx +1 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/CheckboxList/CheckboxList.test.tsx +94 -3
- package/src/CheckboxList/CheckboxListItem.tsx +17 -10
- 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.test.tsx +17 -0
- package/src/CodeBlock/CodeBlock.tsx +38 -11
- 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/CommandPalette/CommandPaletteItem.tsx +1 -1
- package/src/ContextMenu/ContextMenu.test.tsx +59 -2
- package/src/ContextMenu/ContextMenu.tsx +22 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +25 -0
- package/src/DateInput/DateInput.test.tsx +230 -0
- package/src/DateInput/DateInput.tsx +51 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
- package/src/DateRangeInput/DateRangeInput.tsx +42 -30
- 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 +9 -3
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -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/FieldLabel.test.tsx +25 -0
- package/src/Field/FieldLabel.tsx +12 -3
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.test.tsx +72 -0
- package/src/FieldStatus/FieldStatus.tsx +48 -2
- package/src/FileInput/FileInput.doc.mjs +23 -2
- package/src/FileInput/FileInput.test.tsx +104 -13
- package/src/FileInput/FileInput.tsx +106 -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/Item/Item.doc.mjs +5 -0
- package/src/Item/Item.test.tsx +88 -0
- package/src/Item/Item.tsx +57 -11
- package/src/Layer/layerAnimations.stylex.ts +23 -0
- package/src/Lightbox/Lightbox.tsx +15 -7
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/List/ListItem.doc.mjs +12 -0
- package/src/List/ListItem.tsx +13 -0
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MetadataList/MetadataList.test.tsx +24 -0
- package/src/MetadataList/MetadataList.tsx +6 -2
- package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
- package/src/MultiSelector/MultiSelector.test.tsx +402 -0
- package/src/MultiSelector/MultiSelector.tsx +120 -59
- package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
- package/src/NumberInput/NumberInput.doc.mjs +21 -0
- package/src/NumberInput/NumberInput.test.tsx +71 -0
- package/src/NumberInput/NumberInput.tsx +35 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/Pagination/Pagination.tsx +11 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +23 -0
- package/src/PowerSearch/PowerSearch.tsx +10 -1
- package/src/ProgressBar/ProgressBar.test.tsx +34 -0
- package/src/ProgressBar/ProgressBar.tsx +16 -1
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.test.tsx +26 -4
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +12 -5
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
- package/src/Selector/Selector.doc.mjs +15 -0
- package/src/Selector/Selector.test.tsx +370 -0
- package/src/Selector/Selector.tsx +88 -20
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Skeleton/Skeleton.test.tsx +16 -0
- package/src/Skeleton/Skeleton.tsx +13 -1
- 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 +111 -0
- package/src/Switch/Switch.tsx +149 -32
- package/src/TabList/Tab.tsx +2 -2
- 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/TabList/TabMenu.tsx +2 -2
- 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/TableCell.tsx +3 -3
- package/src/Table/TableHeaderCell.tsx +3 -3
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
- 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 +38 -9
- 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 +21 -0
- package/src/TextArea/TextArea.test.tsx +23 -0
- package/src/TextArea/TextArea.tsx +27 -26
- package/src/TextInput/TextInput.doc.mjs +21 -0
- package/src/TextInput/TextInput.test.tsx +150 -0
- package/src/TextInput/TextInput.tsx +28 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.tsx +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +21 -0
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +24 -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/Toast/ToastViewport.test.tsx +218 -8
- package/src/Toast/ToastViewport.tsx +71 -12
- package/src/ToggleButton/ToggleButton.test.tsx +24 -0
- package/src/ToggleButton/ToggleButton.tsx +19 -1
- package/src/Token/Token.test.tsx +166 -15
- package/src/Token/Token.tsx +104 -47
- package/src/Token/TokenLink.tsx +107 -0
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
- package/src/Tokenizer/Tokenizer.test.tsx +23 -0
- package/src/Tokenizer/Tokenizer.tsx +14 -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 +42 -15
- package/src/Typeahead/Typeahead.doc.mjs +13 -0
- package/src/Typeahead/Typeahead.test.tsx +112 -0
- package/src/Typeahead/Typeahead.tsx +17 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/forcedColors.ts +60 -0
- 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/useKeyboardHint.test.tsx +22 -0
- package/src/hooks/useKeyboardHint.tsx +6 -2
- 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/i18n/InternationalizationContext.ts +4 -0
- package/src/i18n/InternationalizationProvider.tsx +12 -2
- package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
- package/src/i18n/__tests__/useDirection.test.tsx +66 -0
- package/src/i18n/getLocaleDirection.ts +29 -0
- package/src/i18n/index.ts +6 -0
- package/src/i18n/useDirection.ts +29 -0
- 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
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file DropdownMenuSubMenu.tsx
|
|
7
|
+
* @input React, stylex, useLayer (context mode), useListFocus, useMenuHover,
|
|
8
|
+
* useTypeahead, Item, Icon, Spinner, DropdownMenu context + item roles.
|
|
9
|
+
* @output Exports DropdownMenuSubMenu — a single menu row that reveals a nested
|
|
10
|
+
* flyout menu of its own children/items.
|
|
11
|
+
* @position Sub-component; place inside a DropdownMenu (or ContextMenu)
|
|
12
|
+
* alongside plain items.
|
|
13
|
+
*
|
|
14
|
+
* One component, not three. The row itself adopts DropdownMenuItem semantics
|
|
15
|
+
* (label / icon / description / isDisabled) and its children become the
|
|
16
|
+
* flyout's content. This mirrors how SideNavItem / TreeListItem promote a
|
|
17
|
+
* normal row into a nested surface when given children, rather than the Radix
|
|
18
|
+
* Sub / SubTrigger / SubContent split. Data-driven menus never touch this
|
|
19
|
+
* component directly — renderDropdownItems renders it from a nested `items`
|
|
20
|
+
* array and passes the rendered children in.
|
|
21
|
+
*
|
|
22
|
+
* Built on existing primitives — no bespoke floating code:
|
|
23
|
+
* - Positioning: useLayer context mode opens the flyout inline-end with
|
|
24
|
+
* viewport auto-flip via CSS anchor positioning (RTL-correct by default).
|
|
25
|
+
* - Pointer: useMenuHover for open/close intent.
|
|
26
|
+
* - Keyboard: a per-level useListFocus + useTypeahead. Right (Left in RTL) /
|
|
27
|
+
* Enter / Space opens the flyout and focuses its first item; Left (Right in
|
|
28
|
+
* RTL) / Escape closes it and returns focus to the trigger row.
|
|
29
|
+
*
|
|
30
|
+
* Prior art: legacy internal XDS `XDSDropdownSubMenuItem` (APG menubar-
|
|
31
|
+
* navigation submenu). This re-expresses the same contract on Astryx primitives.
|
|
32
|
+
*
|
|
33
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
34
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs
|
|
35
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuSubMenu.test.tsx
|
|
36
|
+
* - /packages/core/src/DropdownMenu/index.ts
|
|
37
|
+
* - /apps/storybook/stories/DropdownMenu.stories.tsx
|
|
38
|
+
* - /packages/cli/templates/blocks/components/DropdownMenu/ (showcase blocks)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import React, {
|
|
42
|
+
useCallback,
|
|
43
|
+
useId,
|
|
44
|
+
useMemo,
|
|
45
|
+
useRef,
|
|
46
|
+
useState,
|
|
47
|
+
type ReactNode,
|
|
48
|
+
} from 'react';
|
|
49
|
+
import * as stylex from '@stylexjs/stylex';
|
|
50
|
+
import {Icon, renderIconSlot, type IconType} from '../Icon';
|
|
51
|
+
import {Spinner} from '../Spinner';
|
|
52
|
+
import {Item} from '../Item';
|
|
53
|
+
import {useLayer} from '../Layer/useLayer';
|
|
54
|
+
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
55
|
+
import {useListFocus} from '../hooks/useListFocus';
|
|
56
|
+
import {useMenuHover} from '../hooks/useMenuHover';
|
|
57
|
+
import {useTypeahead} from '../hooks/useTypeahead';
|
|
58
|
+
import {
|
|
59
|
+
colorVars,
|
|
60
|
+
spacingVars,
|
|
61
|
+
radiusVars,
|
|
62
|
+
durationVars,
|
|
63
|
+
easeVars,
|
|
64
|
+
shadowVars,
|
|
65
|
+
typographyVars,
|
|
66
|
+
typeScaleVars,
|
|
67
|
+
} from '../theme/tokens.stylex';
|
|
68
|
+
import {mergeProps} from '../utils';
|
|
69
|
+
import {themeProps} from '../utils/themeProps';
|
|
70
|
+
import type {BaseProps} from '../BaseProps';
|
|
71
|
+
import {
|
|
72
|
+
MENU_ITEM_ROLES,
|
|
73
|
+
MENU_ITEM_SELECTOR,
|
|
74
|
+
MENU_BOUNDARY_SELECTOR,
|
|
75
|
+
} from './menuItemRoles';
|
|
76
|
+
import {
|
|
77
|
+
DropdownMenuContext,
|
|
78
|
+
useDropdownMenuContext,
|
|
79
|
+
type DropdownMenuContextValue,
|
|
80
|
+
} from './DropdownMenuContext';
|
|
81
|
+
|
|
82
|
+
const triggerStyles = stylex.create({
|
|
83
|
+
root: {
|
|
84
|
+
boxSizing: 'border-box',
|
|
85
|
+
width: '100%',
|
|
86
|
+
paddingBlock: spacingVars['--spacing-2'],
|
|
87
|
+
paddingInline: spacingVars['--spacing-2'],
|
|
88
|
+
borderRadius: `max(0px, calc(var(--_dropdown-menu-radius, ${spacingVars['--spacing-2']}) - var(--_dropdown-menu-padding, ${spacingVars['--spacing-1']})))`,
|
|
89
|
+
fontFamily: typographyVars['--font-family-body'],
|
|
90
|
+
fontSize: typeScaleVars['--text-label-size'],
|
|
91
|
+
color: colorVars['--color-text-primary'],
|
|
92
|
+
backgroundColor: {
|
|
93
|
+
default: 'transparent',
|
|
94
|
+
':focus': colorVars['--color-overlay-hover'],
|
|
95
|
+
},
|
|
96
|
+
':hover': {
|
|
97
|
+
'@media (hover: hover)': {
|
|
98
|
+
backgroundColor: colorVars['--color-overlay-hover'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
border: 'none',
|
|
102
|
+
cursor: 'pointer',
|
|
103
|
+
textAlign: 'start',
|
|
104
|
+
outline: 'none',
|
|
105
|
+
},
|
|
106
|
+
// While the flyout is open, keep the trigger visibly active so the open
|
|
107
|
+
// branch reads as the current path even when focus has moved into the child.
|
|
108
|
+
open: {
|
|
109
|
+
backgroundColor: colorVars['--color-overlay-hover'],
|
|
110
|
+
},
|
|
111
|
+
disabled: {
|
|
112
|
+
opacity: 0.5,
|
|
113
|
+
cursor: 'not-allowed',
|
|
114
|
+
},
|
|
115
|
+
caret: {
|
|
116
|
+
display: 'flex',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const triggerSizeStyles = stylex.create({
|
|
122
|
+
sm: {
|
|
123
|
+
paddingBlock: spacingVars['--spacing-1'],
|
|
124
|
+
paddingInline: spacingVars['--spacing-2'],
|
|
125
|
+
},
|
|
126
|
+
md: {
|
|
127
|
+
paddingBlock: spacingVars['--spacing-1-5'],
|
|
128
|
+
},
|
|
129
|
+
lg: {},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const flyoutStyles = stylex.create({
|
|
133
|
+
menu: {
|
|
134
|
+
boxSizing: 'border-box',
|
|
135
|
+
display: 'flex',
|
|
136
|
+
flexDirection: 'column',
|
|
137
|
+
gap: spacingVars['--spacing-0-5'],
|
|
138
|
+
maxHeight: '300px',
|
|
139
|
+
overflowY: 'auto',
|
|
140
|
+
'--_dropdown-menu-radius': radiusVars['--radius-container'],
|
|
141
|
+
'--_dropdown-menu-padding': spacingVars['--spacing-1'],
|
|
142
|
+
padding: spacingVars['--spacing-1'],
|
|
143
|
+
borderRadius: 'var(--_dropdown-menu-radius)',
|
|
144
|
+
backgroundColor: colorVars['--color-background-popover'],
|
|
145
|
+
boxShadow: shadowVars['--shadow-low'],
|
|
146
|
+
opacity: 1,
|
|
147
|
+
transitionProperty: 'opacity',
|
|
148
|
+
transitionDuration: durationVars['--duration-fast'],
|
|
149
|
+
transitionTimingFunction: easeVars['--ease-standard'],
|
|
150
|
+
},
|
|
151
|
+
popover: {
|
|
152
|
+
minWidth: '160px',
|
|
153
|
+
// Small inline gap so the flyout doesn't sit flush against the parent menu.
|
|
154
|
+
marginInlineStart: spacingVars['--spacing-1'],
|
|
155
|
+
marginInlineEnd: spacingVars['--spacing-1'],
|
|
156
|
+
},
|
|
157
|
+
popoverCustomWidth: (width: string | number) => ({
|
|
158
|
+
minWidth: typeof width === 'number' ? `${width}px` : width,
|
|
159
|
+
marginInlineStart: spacingVars['--spacing-1'],
|
|
160
|
+
marginInlineEnd: spacingVars['--spacing-1'],
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
interface DropdownMenuSubMenuBaseProps extends Pick<
|
|
165
|
+
BaseProps,
|
|
166
|
+
'xstyle' | 'className' | 'style'
|
|
167
|
+
> {
|
|
168
|
+
/** Icon to display before the label on the trigger row. */
|
|
169
|
+
icon?: ReactNode | IconType;
|
|
170
|
+
/** Primary label text for the trigger row. */
|
|
171
|
+
label: ReactNode;
|
|
172
|
+
/** Secondary description text displayed below the label. */
|
|
173
|
+
description?: ReactNode;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the submenu is disabled. A disabled submenu renders its trigger
|
|
176
|
+
* row but never opens the flyout.
|
|
177
|
+
* @default false
|
|
178
|
+
*/
|
|
179
|
+
isDisabled?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Show a spinner in place of the caret, e.g. while a lazy submenu's children
|
|
182
|
+
* are loading. Ported from the legacy `hasSpinner` async-submenu affordance.
|
|
183
|
+
* @default false
|
|
184
|
+
*/
|
|
185
|
+
hasSpinner?: boolean;
|
|
186
|
+
/** Fixed flyout width. Defaults to sizing to its content (min 160px). */
|
|
187
|
+
menuWidth?: number | string;
|
|
188
|
+
/** Called when the flyout opens or closes. */
|
|
189
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
190
|
+
/** Test id for the trigger row. */
|
|
191
|
+
'data-testid'?: string;
|
|
192
|
+
/** Test id for the flyout menu. */
|
|
193
|
+
menuDataTestId?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface DropdownMenuSubMenuProps extends DropdownMenuSubMenuBaseProps {
|
|
197
|
+
/**
|
|
198
|
+
* The flyout's menu items — the same components used at the top level
|
|
199
|
+
* (`DropdownMenuItem`, `DropdownMenuSubMenu`, selectable items, etc.).
|
|
200
|
+
*
|
|
201
|
+
* Data-mode parity lives one level up: give a `DropdownMenu`/`ContextMenu`
|
|
202
|
+
* item a nested `items` array and it renders a `DropdownMenuSubMenu` with
|
|
203
|
+
* these children automatically — so the data path never has to reach into
|
|
204
|
+
* this component.
|
|
205
|
+
*/
|
|
206
|
+
children: ReactNode;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A single menu row that reveals a nested flyout of its own items. The row
|
|
211
|
+
* adopts DropdownMenuItem semantics (label / icon / description / isDisabled);
|
|
212
|
+
* its `children` become the flyout content. Place inside a DropdownMenu (or
|
|
213
|
+
* ContextMenu) alongside plain items.
|
|
214
|
+
*
|
|
215
|
+
* For data-driven menus, don't use this directly — give a menu item a nested
|
|
216
|
+
* `items` array and DropdownMenu/ContextMenu renders the submenu for you.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```
|
|
220
|
+
* <DropdownMenu button={{label: 'Actions'}}>
|
|
221
|
+
* <DropdownMenuItem label="Rename" onClick={rename} />
|
|
222
|
+
* <DropdownMenuSubMenu label="Move to" icon="folder">
|
|
223
|
+
* <DropdownMenuItem label="Folder A" onClick={() => move('a')} />
|
|
224
|
+
* <DropdownMenuItem label="Folder B" onClick={() => move('b')} />
|
|
225
|
+
* </DropdownMenuSubMenu>
|
|
226
|
+
* </DropdownMenu>
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
export function DropdownMenuSubMenu(
|
|
230
|
+
props: DropdownMenuSubMenuProps,
|
|
231
|
+
): ReactNode {
|
|
232
|
+
const {
|
|
233
|
+
icon,
|
|
234
|
+
label,
|
|
235
|
+
description,
|
|
236
|
+
isDisabled = false,
|
|
237
|
+
hasSpinner = false,
|
|
238
|
+
menuWidth,
|
|
239
|
+
onOpenChange,
|
|
240
|
+
children,
|
|
241
|
+
xstyle,
|
|
242
|
+
className,
|
|
243
|
+
style,
|
|
244
|
+
'data-testid': testId,
|
|
245
|
+
menuDataTestId,
|
|
246
|
+
} = props;
|
|
247
|
+
|
|
248
|
+
const menuCtx = useDropdownMenuContext();
|
|
249
|
+
const menuSize = menuCtx?.menuSize ?? 'md';
|
|
250
|
+
const canOpen = !isDisabled;
|
|
251
|
+
|
|
252
|
+
const contentId = useId();
|
|
253
|
+
const triggerId = useId();
|
|
254
|
+
const triggerRef = useRef<HTMLDivElement | null>(null);
|
|
255
|
+
|
|
256
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
257
|
+
|
|
258
|
+
const layer = useLayer({
|
|
259
|
+
mode: 'context',
|
|
260
|
+
lightDismiss: false,
|
|
261
|
+
onShow: useCallback(() => {
|
|
262
|
+
setIsOpen(true);
|
|
263
|
+
onOpenChange?.(true);
|
|
264
|
+
}, [onOpenChange]),
|
|
265
|
+
onHide: useCallback(() => {
|
|
266
|
+
setIsOpen(false);
|
|
267
|
+
onOpenChange?.(false);
|
|
268
|
+
}, [onOpenChange]),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const showLayer = useCallback(() => {
|
|
272
|
+
if (canOpen) {
|
|
273
|
+
layer.show();
|
|
274
|
+
}
|
|
275
|
+
}, [canOpen, layer]);
|
|
276
|
+
const hideLayer = useCallback(() => {
|
|
277
|
+
layer.hide();
|
|
278
|
+
}, [layer]);
|
|
279
|
+
|
|
280
|
+
// Dedicated roving-focus + typeahead for this flyout level. The boundary
|
|
281
|
+
// selector scopes item collection and key handling to this flyout's own
|
|
282
|
+
// `role="menu"` — so a submenu nested inside this one (also inline, also
|
|
283
|
+
// `role="menu"`) doesn't pollute this level's items or double-handle keys.
|
|
284
|
+
const {
|
|
285
|
+
listRef: menuRef,
|
|
286
|
+
handleKeyDown: listNavKeyDown,
|
|
287
|
+
focusFirst,
|
|
288
|
+
focusItem,
|
|
289
|
+
ownsEvent,
|
|
290
|
+
getItems,
|
|
291
|
+
} = useListFocus<HTMLDivElement>({
|
|
292
|
+
itemSelector: MENU_ITEM_SELECTOR,
|
|
293
|
+
boundarySelector: MENU_BOUNDARY_SELECTOR,
|
|
294
|
+
wrap: false,
|
|
295
|
+
onEscape: () => close({focusTrigger: true}),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const typeahead = useTypeahead({
|
|
299
|
+
getItemLabels: () => getItems().map(el => el.textContent),
|
|
300
|
+
onMatch: focusItem,
|
|
301
|
+
getCurrentIndex: () =>
|
|
302
|
+
getItems().findIndex(
|
|
303
|
+
el =>
|
|
304
|
+
el === document.activeElement || el.contains(document.activeElement),
|
|
305
|
+
),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Hover-intent: entering the trigger opens after a short delay; leaving
|
|
309
|
+
// either surface closes after a delay. Hover-open does not steal focus.
|
|
310
|
+
const {triggerProps, contentProps} = useMenuHover<HTMLDivElement>({
|
|
311
|
+
show: showLayer,
|
|
312
|
+
hide: hideLayer,
|
|
313
|
+
isOpen,
|
|
314
|
+
isEnabled: canOpen,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const open = useCallback(
|
|
318
|
+
(options?: {focusFirst?: boolean}) => {
|
|
319
|
+
if (!canOpen) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
layer.show();
|
|
323
|
+
if (options?.focusFirst) {
|
|
324
|
+
requestAnimationFrame(() => {
|
|
325
|
+
// Move focus into the flyout. When it has no focusable items yet
|
|
326
|
+
// (e.g. an async submenu showing only a disabled "Loading…" row via
|
|
327
|
+
// hasSpinner), focusFirst() finds nothing — fall back to focusing the
|
|
328
|
+
// flyout container itself so keyboard ownership still transfers off
|
|
329
|
+
// the parent list. Otherwise the parent would keep focus, letting
|
|
330
|
+
// arrow keys rove the parent while the empty flyout stays open.
|
|
331
|
+
const focusedItem = focusFirst();
|
|
332
|
+
if (!focusedItem) {
|
|
333
|
+
menuRef.current?.focus();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
[canOpen, layer, focusFirst, menuRef],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const close = useCallback(
|
|
342
|
+
(options?: {focusTrigger?: boolean}) => {
|
|
343
|
+
layer.hide();
|
|
344
|
+
if (options?.focusTrigger !== false) {
|
|
345
|
+
triggerRef.current?.focus();
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
[layer],
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Single ref for the trigger row: store it for focus management AND wire it
|
|
352
|
+
// as the flyout's positioning anchor (CSS anchor positioning).
|
|
353
|
+
const setTriggerEl = useCallback(
|
|
354
|
+
(el: HTMLDivElement | null) => {
|
|
355
|
+
triggerRef.current = el;
|
|
356
|
+
layer.ref(el);
|
|
357
|
+
},
|
|
358
|
+
[layer],
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const handleTriggerClick = useCallback(() => {
|
|
362
|
+
if (isDisabled) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Click toggles the flyout, moving focus into it on open.
|
|
366
|
+
if (isOpen) {
|
|
367
|
+
close({focusTrigger: true});
|
|
368
|
+
} else {
|
|
369
|
+
open({focusFirst: true});
|
|
370
|
+
}
|
|
371
|
+
}, [isDisabled, isOpen, open, close]);
|
|
372
|
+
|
|
373
|
+
const handleTriggerKeyDown = useCallback(
|
|
374
|
+
(e: React.KeyboardEvent) => {
|
|
375
|
+
if (isDisabled) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const isRtl =
|
|
379
|
+
typeof window !== 'undefined' && triggerRef.current
|
|
380
|
+
? window.getComputedStyle(triggerRef.current).direction === 'rtl'
|
|
381
|
+
: false;
|
|
382
|
+
const openKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
|
|
383
|
+
if (e.key === openKey || e.key === 'Enter' || e.key === ' ') {
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
// The trigger row is a direct child of the parent menu (not a nested
|
|
386
|
+
// role="menu"), so the parent's key handler would otherwise also act on
|
|
387
|
+
// this event (e.g. Enter clicking the focused row). Stop it here — this
|
|
388
|
+
// level fully handles opening the flyout.
|
|
389
|
+
e.stopPropagation();
|
|
390
|
+
open({focusFirst: true});
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
[isDisabled, open],
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Enter/Space activate the focused row; typeahead jumps by first character;
|
|
397
|
+
// the close key (Left, or Right in RTL) returns focus to the trigger;
|
|
398
|
+
// arrows/Home/End defer to useListFocus (RTL-aware).
|
|
399
|
+
//
|
|
400
|
+
// The flyout renders inline (useLayer context mode is a native popover, not a
|
|
401
|
+
// portal), so a submenu nested inside this one bubbles its key events up to
|
|
402
|
+
// this handler. `ownsEvent` (from useListFocus, scoped by boundarySelector)
|
|
403
|
+
// tells us whether the event originated in THIS flyout or a deeper one; we
|
|
404
|
+
// only act on our own, letting the deeper level keep ownership. No manual
|
|
405
|
+
// stopPropagation needed — each level self-filters.
|
|
406
|
+
const handleContentKeyDown = useCallback(
|
|
407
|
+
(e: React.KeyboardEvent) => {
|
|
408
|
+
if (!ownsEvent(e)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// Escape closes just this submenu and returns focus to its trigger (APG:
|
|
412
|
+
// Escape collapses the current level, not the whole stack). The root
|
|
413
|
+
// popover's focus-trap listens for Escape at the document level and bails
|
|
414
|
+
// when the event is already handled, so we mark it handled here (stop
|
|
415
|
+
// propagation + preventDefault) to keep the parent menu open.
|
|
416
|
+
if (e.key === 'Escape') {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
e.stopPropagation();
|
|
419
|
+
close({focusTrigger: true});
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
423
|
+
e.preventDefault();
|
|
424
|
+
const focused = document.activeElement as HTMLElement | null;
|
|
425
|
+
if (
|
|
426
|
+
focused &&
|
|
427
|
+
MENU_ITEM_ROLES.has(focused.getAttribute('role') ?? '')
|
|
428
|
+
) {
|
|
429
|
+
focused.click();
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const isRtl =
|
|
434
|
+
typeof window !== 'undefined' && menuRef.current
|
|
435
|
+
? window.getComputedStyle(menuRef.current).direction === 'rtl'
|
|
436
|
+
: false;
|
|
437
|
+
const closeKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
|
|
438
|
+
if (e.key === closeKey) {
|
|
439
|
+
e.preventDefault();
|
|
440
|
+
close({focusTrigger: true});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (typeahead.onKeyDown(e)) {
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
listNavKeyDown(e);
|
|
448
|
+
},
|
|
449
|
+
[ownsEvent, close, listNavKeyDown, typeahead, menuRef],
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Re-provide the menu context so nested items behave exactly like top-level
|
|
453
|
+
// ones. Selecting a leaf item must dismiss the WHOLE stack, not just this
|
|
454
|
+
// flyout: close this level (without stealing focus back to the trigger) and
|
|
455
|
+
// propagate up via the parent menu's closeMenu. Because every level
|
|
456
|
+
// re-provides the context, this chains from the deepest flyout all the way to
|
|
457
|
+
// the root DropdownMenu (whose closeMenu hides the popover).
|
|
458
|
+
const nestedMenuContext = useMemo<DropdownMenuContextValue>(
|
|
459
|
+
() => ({
|
|
460
|
+
menuSize,
|
|
461
|
+
closeMenu: () => {
|
|
462
|
+
close({focusTrigger: false});
|
|
463
|
+
menuCtx?.closeMenu();
|
|
464
|
+
},
|
|
465
|
+
}),
|
|
466
|
+
[menuSize, close, menuCtx],
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const endAffordance = hasSpinner ? (
|
|
470
|
+
<span {...stylex.props(triggerStyles.caret)}>
|
|
471
|
+
<Spinner size="sm" />
|
|
472
|
+
</span>
|
|
473
|
+
) : (
|
|
474
|
+
<span {...stylex.props(triggerStyles.caret)}>
|
|
475
|
+
<Icon icon="chevronRight" size="sm" color="secondary" />
|
|
476
|
+
</span>
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const popoverXstyle = menuWidth
|
|
480
|
+
? flyoutStyles.popoverCustomWidth(menuWidth)
|
|
481
|
+
: flyoutStyles.popover;
|
|
482
|
+
|
|
483
|
+
return (
|
|
484
|
+
<>
|
|
485
|
+
<Item
|
|
486
|
+
ref={el => setTriggerEl(el as HTMLDivElement | null)}
|
|
487
|
+
id={triggerId}
|
|
488
|
+
role="menuitem"
|
|
489
|
+
tabIndex={isDisabled ? undefined : -1}
|
|
490
|
+
aria-haspopup="menu"
|
|
491
|
+
aria-expanded={isOpen}
|
|
492
|
+
aria-controls={isOpen ? contentId : undefined}
|
|
493
|
+
aria-disabled={isDisabled || undefined}
|
|
494
|
+
data-testid={testId}
|
|
495
|
+
onMouseEnter={triggerProps.onMouseEnter}
|
|
496
|
+
onMouseLeave={triggerProps.onMouseLeave}
|
|
497
|
+
startContent={
|
|
498
|
+
icon
|
|
499
|
+
? renderIconSlot(icon, {size: 'sm', color: 'secondary'})
|
|
500
|
+
: undefined
|
|
501
|
+
}
|
|
502
|
+
label={label}
|
|
503
|
+
description={description}
|
|
504
|
+
endContent={endAffordance}
|
|
505
|
+
onClick={handleTriggerClick}
|
|
506
|
+
onKeyDown={handleTriggerKeyDown}
|
|
507
|
+
isDisabled={isDisabled}
|
|
508
|
+
xstyle={[
|
|
509
|
+
triggerStyles.root,
|
|
510
|
+
triggerSizeStyles[menuSize],
|
|
511
|
+
isOpen && triggerStyles.open,
|
|
512
|
+
isDisabled && triggerStyles.disabled,
|
|
513
|
+
xstyle,
|
|
514
|
+
]}
|
|
515
|
+
{...mergeProps(themeProps('dropdown-menu-item', {size: menuSize}), {
|
|
516
|
+
className,
|
|
517
|
+
style,
|
|
518
|
+
})}
|
|
519
|
+
/>
|
|
520
|
+
{layer.render(
|
|
521
|
+
<div
|
|
522
|
+
ref={menuRef}
|
|
523
|
+
id={contentId}
|
|
524
|
+
role="menu"
|
|
525
|
+
// Focusable as a fallback target so an empty/loading flyout (e.g.
|
|
526
|
+
// hasSpinner with only a disabled row) can still receive keyboard
|
|
527
|
+
// focus and own arrow/Escape keys instead of leaving focus on the
|
|
528
|
+
// parent list.
|
|
529
|
+
tabIndex={-1}
|
|
530
|
+
aria-labelledby={triggerId}
|
|
531
|
+
onKeyDown={handleContentKeyDown}
|
|
532
|
+
onMouseEnter={contentProps.onMouseEnter}
|
|
533
|
+
onMouseLeave={contentProps.onMouseLeave}
|
|
534
|
+
data-testid={menuDataTestId}
|
|
535
|
+
{...mergeProps(
|
|
536
|
+
themeProps('dropdown-menu'),
|
|
537
|
+
stylex.props(flyoutStyles.menu),
|
|
538
|
+
)}>
|
|
539
|
+
<DropdownMenuContext value={nestedMenuContext}>
|
|
540
|
+
{children}
|
|
541
|
+
</DropdownMenuContext>
|
|
542
|
+
</div>,
|
|
543
|
+
{
|
|
544
|
+
placement: 'end',
|
|
545
|
+
alignment: 'start',
|
|
546
|
+
xstyle: [popoverXstyle, layerAnimations.end],
|
|
547
|
+
},
|
|
548
|
+
)}
|
|
549
|
+
</>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
DropdownMenuSubMenu.displayName = 'DropdownMenuSubMenu';
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
* @file index.ts
|
|
7
7
|
* @output Exports DropdownMenu, DropdownMenuItem and related types
|
|
8
8
|
* @position Public API entry point
|
|
9
|
+
*
|
|
10
|
+
* SYNC: The item components below are re-exported (aliased) by the other menu
|
|
11
|
+
* surfaces that reuse the DropdownMenu item pipeline. When you add or remove an
|
|
12
|
+
* item component here, mirror it in:
|
|
13
|
+
* - /packages/core/src/ContextMenu/index.ts (ContextMenu* aliases)
|
|
14
|
+
* - /packages/core/src/Breadcrumbs/index.ts (BreadcrumbMenu* aliases)
|
|
15
|
+
* Those surfaces render menu items through the same renderDropdownItems +
|
|
16
|
+
* DropdownMenuContext + useListFocus path, so a new item type (e.g. a submenu)
|
|
17
|
+
* only shows up in their public API once it's aliased there too.
|
|
9
18
|
*/
|
|
10
19
|
|
|
11
20
|
export {
|
|
@@ -34,6 +43,13 @@ export {
|
|
|
34
43
|
type DropdownMenuRadioItemProps,
|
|
35
44
|
} from './DropdownMenuRadioItem';
|
|
36
45
|
|
|
46
|
+
// Submenu — a single menu row that reveals a nested flyout of its own
|
|
47
|
+
// children/items. Data mode via DropdownMenuItemData.items.
|
|
48
|
+
export {
|
|
49
|
+
DropdownMenuSubMenu,
|
|
50
|
+
type DropdownMenuSubMenuProps,
|
|
51
|
+
} from './DropdownMenuSubMenu';
|
|
52
|
+
|
|
37
53
|
// Menu-coordination context — public so consumers can build custom menu items
|
|
38
54
|
// that read the menu size / close the menu.
|
|
39
55
|
export {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file menuItemHover.ts
|
|
7
|
+
* @output Exports focusMenuItemOnHover
|
|
8
|
+
* @position Shared helper for DropdownMenu/ContextMenu item components
|
|
9
|
+
*
|
|
10
|
+
* Menus keep a single highlighted item by using DOM focus as the sole
|
|
11
|
+
* highlight source. Mouse hover moves focus onto the pointed-at item so the
|
|
12
|
+
* one focus highlight follows the pointer — instead of a separate `:hover`
|
|
13
|
+
* background leaving the keyboard-focused item highlighted at the same time.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update the item components that use it:
|
|
16
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuItem.tsx
|
|
17
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuCheckboxItem.tsx
|
|
18
|
+
* - /packages/core/src/DropdownMenu/DropdownMenuRadioItem.tsx
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {PointerEvent} from 'react';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Move focus to a menu item as the pointer moves over it, so hover and
|
|
25
|
+
* keyboard navigation share a single focus-driven highlight. Only reacts to a
|
|
26
|
+
* real mouse (not touch/pen, which have no hover), and skips disabled items.
|
|
27
|
+
*/
|
|
28
|
+
export function focusMenuItemOnHover(
|
|
29
|
+
e: PointerEvent<HTMLElement>,
|
|
30
|
+
isDisabled?: boolean,
|
|
31
|
+
): void {
|
|
32
|
+
if (isDisabled || e.pointerType !== 'mouse') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const el = e.currentTarget;
|
|
36
|
+
if (el !== el.ownerDocument.activeElement) {
|
|
37
|
+
el.focus();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -20,3 +20,12 @@ export const MENU_ITEM_ROLES: ReadonlySet<string> = new Set([
|
|
|
20
20
|
export const MENU_ITEM_SELECTOR: string = [...MENU_ITEM_ROLES]
|
|
21
21
|
.map(role => `[role="${role}"]:not([aria-disabled="true"])`)
|
|
22
22
|
.join(',');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Boundary selector for a single menu level. A menu and its submenu flyouts
|
|
26
|
+
* both use `role="menu"`, and flyouts render inline (native popover, not a
|
|
27
|
+
* portal), so a nested menu's items and key events would otherwise be picked
|
|
28
|
+
* up by the parent. Pass this as `useListFocus`'s `boundarySelector` so each
|
|
29
|
+
* level scopes item collection and key handling to its own container.
|
|
30
|
+
*/
|
|
31
|
+
export const MENU_BOUNDARY_SELECTOR = '[role="menu"]';
|