@astryxdesign/core 0.5.1 → 0.5.2-canary.00477a5
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 +19 -0
- package/dist/BottomSheet/BottomSheet.d.ts +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/dist/i18n/InternationalizationContext.d.ts +2 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +3 -1
- package/dist/i18n/resolve.d.ts +1 -1
- package/dist/i18n/resolve.d.ts.map +1 -1
- package/dist/i18n/resolve.js +37 -38
- package/dist/i18n/useTranslator.d.ts.map +1 -1
- package/dist/i18n/useTranslator.js +2 -3
- package/locales/af-ZA.json +0 -4
- package/locales/ar-SA.json +0 -4
- package/locales/ca-ES.json +0 -4
- package/locales/cs-CZ.json +0 -4
- package/locales/da-DK.json +0 -4
- package/locales/de-DE.json +0 -4
- package/locales/el-GR.json +0 -4
- package/locales/en.json +20 -0
- package/locales/es-ES.json +0 -4
- package/locales/fi-FI.json +0 -4
- package/locales/fr-FR.json +0 -4
- package/locales/he-IL.json +0 -4
- package/locales/hu-HU.json +0 -4
- package/locales/it-IT.json +0 -4
- package/locales/ja-JP.json +0 -4
- package/locales/ko-KR.json +0 -4
- package/locales/nl-NL.json +0 -4
- package/locales/no-NO.json +0 -4
- package/locales/pl-PL.json +0 -4
- package/locales/pseudo.json +15 -0
- package/locales/pt-BR.json +0 -4
- package/locales/pt-PT.json +0 -4
- package/locales/ro-RO.json +0 -4
- package/locales/ru-RU.json +0 -4
- package/locales/sr-SP.json +0 -4
- package/locales/sv-SE.json +0 -4
- package/locales/tr-TR.json +0 -4
- package/locales/uk-UA.json +0 -4
- package/locales/vi-VN.json +0 -4
- package/locales/zh-CN.json +0 -4
- package/locales/zh-TW.json +0 -4
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
- package/src/BottomSheet/BottomSheet.spec.md +177 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxList/CheckboxList.doc.mjs +173 -32
- package/src/CheckboxList/CheckboxList.spec.md +214 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +223 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +44 -2
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/DateInput/DateInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/FileInput/FileInput.doc.mjs +1 -1
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +161 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +191 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Indicator/Indicator.doc.mjs +1 -1
- package/src/InputGroup/InputGroup.doc.mjs +1 -1
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +141 -0
- package/src/Layout/Layout.doc.mjs +32 -0
- package/src/Layout/Layout.spec.md +199 -0
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +197 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +200 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +208 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +129 -1
- package/src/MultiSelector/MultiSelector.spec.md +266 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +133 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/NumberInput/NumberInput.doc.mjs +1 -1
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +161 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +156 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +225 -0
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/Resizable/Resizable.doc.mjs +59 -16
- package/src/Resizable/Resizable.spec.md +194 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +138 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
- package/src/Selector/Selector.doc.mjs +13 -1
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +126 -0
- package/src/Slider/Slider.doc.mjs +186 -32
- package/src/Slider/Slider.spec.md +223 -0
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +154 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +137 -0
- package/src/Switch/Switch.doc.mjs +41 -1
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +256 -46
- package/src/TextArea/TextArea.spec.md +204 -0
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +1 -1
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +173 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +141 -0
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +220 -0
- package/src/Typeahead/Typeahead.doc.mjs +84 -2
- package/src/Typeahead/Typeahead.spec.md +250 -0
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/i18n/InternationalizationContext.ts +3 -0
- package/src/i18n/InternationalizationProvider.tsx +8 -1
- package/src/i18n/__tests__/resolve.test.ts +23 -1
- package/src/i18n/resolve.ts +46 -49
- package/src/i18n/useTranslator.ts +2 -7
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Navigation overlay',
|
|
7
|
+
required: true,
|
|
8
|
+
description:
|
|
9
|
+
'Full-viewport dialog overlay that hosts the mobile navigation drawer.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Drawer',
|
|
13
|
+
required: true,
|
|
14
|
+
description:
|
|
15
|
+
'Painted panel that slides in from the resolved viewport edge.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Header',
|
|
19
|
+
required: true,
|
|
20
|
+
description:
|
|
21
|
+
'Fixed row containing optional header content and the close button.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Content',
|
|
25
|
+
required: true,
|
|
26
|
+
description: 'Scrollable region containing the navigation content.',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Close button',
|
|
30
|
+
required: true,
|
|
31
|
+
description: 'Button that closes the navigation drawer.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Toggle button',
|
|
35
|
+
required: false,
|
|
36
|
+
description:
|
|
37
|
+
'AppShell-aware Button that opens or closes the drawer on mobile viewports.',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
3
41
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
42
|
|
|
5
43
|
export const docs = {
|
|
@@ -8,12 +46,24 @@ export const docs = {
|
|
|
8
46
|
group: 'Navigation',
|
|
9
47
|
category: 'Navigation',
|
|
10
48
|
isHiddenFromOverview: true,
|
|
11
|
-
keywords: [
|
|
49
|
+
keywords: [
|
|
50
|
+
'mobilenav',
|
|
51
|
+
'drawer',
|
|
52
|
+
'sidebar',
|
|
53
|
+
'navigation',
|
|
54
|
+
'hamburger',
|
|
55
|
+
'menu',
|
|
56
|
+
'offcanvas',
|
|
57
|
+
'slideout',
|
|
58
|
+
'navdrawer',
|
|
59
|
+
'toggle',
|
|
60
|
+
],
|
|
12
61
|
components: [
|
|
13
62
|
{
|
|
14
63
|
name: 'MobileNav',
|
|
15
64
|
displayName: 'Mobile Nav',
|
|
16
|
-
description:
|
|
65
|
+
description:
|
|
66
|
+
'A slide-out drawer for mobile navigation. Accepts SideNav children.',
|
|
17
67
|
// The drawer opens via showModal() and renders nothing while closed —
|
|
18
68
|
// overlay mode gives the Properties preview an open trigger instead of
|
|
19
69
|
// an empty stage (#2706). Declared on this entry (not the directory
|
|
@@ -27,7 +77,10 @@ export const docs = {
|
|
|
27
77
|
__element: 'SideNavSection',
|
|
28
78
|
props: {title: 'Main'},
|
|
29
79
|
children: [
|
|
30
|
-
{
|
|
80
|
+
{
|
|
81
|
+
__element: 'SideNavItem',
|
|
82
|
+
props: {label: 'Dashboard', isSelected: true},
|
|
83
|
+
},
|
|
31
84
|
{__element: 'SideNavItem', props: {label: 'Projects'}},
|
|
32
85
|
{__element: 'SideNavItem', props: {label: 'Settings'}},
|
|
33
86
|
],
|
|
@@ -38,7 +91,8 @@ export const docs = {
|
|
|
38
91
|
{
|
|
39
92
|
name: 'isOpen',
|
|
40
93
|
type: 'boolean',
|
|
41
|
-
description:
|
|
94
|
+
description:
|
|
95
|
+
'Whether the drawer is open. Inside AppShell, this is managed automatically via context. Outside AppShell, provide this prop to control the drawer yourself.',
|
|
42
96
|
},
|
|
43
97
|
{
|
|
44
98
|
name: 'onOpenChange',
|
|
@@ -56,7 +110,8 @@ export const docs = {
|
|
|
56
110
|
{
|
|
57
111
|
name: 'header',
|
|
58
112
|
type: 'ReactNode',
|
|
59
|
-
description:
|
|
113
|
+
description:
|
|
114
|
+
'Header content for the drawer. Rendered next to the close button. Pass a string for a simple text heading, or a ReactNode for custom content (logo, search bar, etc.).',
|
|
60
115
|
},
|
|
61
116
|
{
|
|
62
117
|
name: 'width',
|
|
@@ -77,7 +132,8 @@ export const docs = {
|
|
|
77
132
|
{
|
|
78
133
|
name: 'MobileNavToggle',
|
|
79
134
|
displayName: 'Mobile Nav Toggle',
|
|
80
|
-
description:
|
|
135
|
+
description:
|
|
136
|
+
'Hamburger button that opens/closes the mobile nav drawer. Reads open state from AppShell context automatically: does NOT accept isOpen or onOpenChange props. Renders nothing above the mobile breakpoint.',
|
|
81
137
|
// The toggle renders null unless AppShell mobile context reports an
|
|
82
138
|
// enabled mobile viewport — the default context outside AppShell never
|
|
83
139
|
// does, so the Properties preview was an empty stage. appShellMobile
|
|
@@ -89,7 +145,8 @@ export const docs = {
|
|
|
89
145
|
{
|
|
90
146
|
name: 'children',
|
|
91
147
|
type: 'ReactNode',
|
|
92
|
-
description:
|
|
148
|
+
description:
|
|
149
|
+
'Custom content to render instead of the default hamburger icon.',
|
|
93
150
|
},
|
|
94
151
|
{
|
|
95
152
|
name: 'label',
|
|
@@ -101,18 +158,33 @@ export const docs = {
|
|
|
101
158
|
},
|
|
102
159
|
],
|
|
103
160
|
theming: {
|
|
104
|
-
targets: [
|
|
105
|
-
{className: 'astryx-mobile-nav', visualProps: ['side']},
|
|
106
|
-
],
|
|
161
|
+
targets: [{className: 'astryx-mobile-nav', visualProps: ['side']}],
|
|
107
162
|
},
|
|
108
163
|
usage: {
|
|
164
|
+
anatomy,
|
|
109
165
|
description:
|
|
110
166
|
'A slide-out drawer for mobile navigation. MobileNav is the mobile counterpart to SideNav and accepts the same children. Use it on narrow viewports where a persistent sidebar is not practical. Inside AppShell, use MobileNavToggle as the trigger; it reads state from context automatically.',
|
|
111
167
|
bestPractices: [
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
168
|
+
{
|
|
169
|
+
guidance: true,
|
|
170
|
+
description:
|
|
171
|
+
'Share the same nav items between MobileNav and SideNav by extracting them into a variable.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
guidance: true,
|
|
175
|
+
description:
|
|
176
|
+
"Provide a header when the drawer's purpose is not obvious from its content.",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
guidance: true,
|
|
180
|
+
description:
|
|
181
|
+
'Inside AppShell, use MobileNavToggle to open the drawer; it reads state from context. Do not pass isOpen/onOpenChange to the toggle.',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
guidance: false,
|
|
185
|
+
description:
|
|
186
|
+
'Use MobileNav on desktop: use a persistent SideNav instead.',
|
|
187
|
+
},
|
|
116
188
|
],
|
|
117
189
|
},
|
|
118
190
|
};
|
|
@@ -144,13 +216,13 @@ export const docsZh = {
|
|
|
144
216
|
{
|
|
145
217
|
name: 'header',
|
|
146
218
|
type: 'ReactNode',
|
|
147
|
-
description:
|
|
219
|
+
description:
|
|
220
|
+
'抽屉的头部内容。渲染在关闭按钮旁边。传入字符串作为简单文本标题,或传入 ReactNode 作为自定义内容。',
|
|
148
221
|
},
|
|
149
222
|
{
|
|
150
223
|
name: 'width',
|
|
151
224
|
type: 'number',
|
|
152
|
-
description:
|
|
153
|
-
'抽屉宽度(像素)。上限为 85vw 以防止在小屏幕上溢出。',
|
|
225
|
+
description: '抽屉宽度(像素)。上限为 85vw 以防止在小屏幕上溢出。',
|
|
154
226
|
default: '320',
|
|
155
227
|
},
|
|
156
228
|
{
|
|
@@ -162,18 +234,33 @@ export const docsZh = {
|
|
|
162
234
|
},
|
|
163
235
|
],
|
|
164
236
|
theming: {
|
|
165
|
-
targets: [
|
|
166
|
-
{className: 'astryx-mobile-nav', visualProps: ['side']},
|
|
167
|
-
],
|
|
237
|
+
targets: [{className: 'astryx-mobile-nav', visualProps: ['side']}],
|
|
168
238
|
},
|
|
169
239
|
usage: {
|
|
240
|
+
anatomy,
|
|
170
241
|
description:
|
|
171
242
|
'A slide-out drawer for mobile navigation. MobileNav is the mobile counterpart to SideNav and accepts the same children. Use it on narrow viewports where a persistent sidebar is not practical.',
|
|
172
243
|
bestPractices: [
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
244
|
+
{
|
|
245
|
+
guidance: true,
|
|
246
|
+
description:
|
|
247
|
+
'Share the same nav items between MobileNav and SideNav by extracting them into a variable.',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
guidance: true,
|
|
251
|
+
description:
|
|
252
|
+
"Provide a header when the drawer's purpose is not obvious from its content.",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
guidance: true,
|
|
256
|
+
description:
|
|
257
|
+
'Inside AppShell, use MobileNavToggle to open the drawer; it reads state from context. Do not pass isOpen/onOpenChange to the toggle.',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
guidance: false,
|
|
261
|
+
description:
|
|
262
|
+
'Use MobileNav on desktop: use a persistent SideNav instead.',
|
|
263
|
+
},
|
|
177
264
|
],
|
|
178
265
|
},
|
|
179
266
|
};
|
|
@@ -183,23 +270,42 @@ export const docsDense = {
|
|
|
183
270
|
description:
|
|
184
271
|
'Slide-out drawer overlay for mobile navigation. Mobile counterpart to SideNav; accepts same children (SideNavSection, SideNavItem, or any ReactNode).',
|
|
185
272
|
usage: {
|
|
273
|
+
anatomy,
|
|
186
274
|
description:
|
|
187
275
|
'A slide-out drawer for mobile navigation. MobileNav is the mobile counterpart to SideNav and accepts the same children. Use it on narrow viewports where a persistent sidebar is not practical.',
|
|
188
276
|
bestPractices: [
|
|
189
|
-
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
277
|
+
{
|
|
278
|
+
guidance: true,
|
|
279
|
+
description:
|
|
280
|
+
'Share nav items between MobileNav and SideNav by extracting into a variable.',
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
guidance: true,
|
|
284
|
+
description:
|
|
285
|
+
'Provide a header when the drawer purpose is not obvious from content.',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
guidance: true,
|
|
289
|
+
description:
|
|
290
|
+
'Inside AppShell, use MobileNavToggle to open the drawer; it reads state from context. Do not pass isOpen/onOpenChange to the toggle.',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
guidance: false,
|
|
294
|
+
description:
|
|
295
|
+
'Use MobileNav on desktop; use a persistent SideNav instead.',
|
|
296
|
+
},
|
|
193
297
|
],
|
|
194
298
|
},
|
|
195
299
|
components: [
|
|
196
300
|
{
|
|
197
301
|
name: 'MobileNav',
|
|
198
|
-
description:
|
|
302
|
+
description:
|
|
303
|
+
'Slide-out drawer for mobile navigation. Accepts SideNav children.',
|
|
199
304
|
},
|
|
200
305
|
{
|
|
201
306
|
name: 'MobileNavToggle',
|
|
202
|
-
description:
|
|
307
|
+
description:
|
|
308
|
+
'Hamburger button that opens/closes mobile nav drawer. Reads open state from AppShell context automatically. Renders nothing above mobile breakpoint.',
|
|
203
309
|
},
|
|
204
310
|
],
|
|
205
311
|
propDescriptions: {
|
|
@@ -208,8 +314,10 @@ export const docsDense = {
|
|
|
208
314
|
'called when drawer visibility changes (backdrop click, Escape, close button)',
|
|
209
315
|
children:
|
|
210
316
|
'drawer content; typically SideNavSection/SideNavItem or any ReactNode',
|
|
211
|
-
header:
|
|
212
|
-
|
|
317
|
+
header:
|
|
318
|
+
'header content (string or ReactNode), rendered next to close button',
|
|
319
|
+
width:
|
|
320
|
+
'drawer width px; capped at 85vw to prevent overflow on small screens',
|
|
213
321
|
side: 'slide direction; start=left LTR, right RTL',
|
|
214
322
|
},
|
|
215
323
|
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:MobileNav
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [behavior, theming, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/MobileNav/MobileNav.test.tsx,
|
|
16
|
+
packages/core/src/MobileNav/MobileNavToggle.test.tsx,
|
|
17
|
+
packages/core/src/MobileNav/MobileNavCloseEdgeCases.test.tsx,
|
|
18
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
19
|
+
scripts/check-knowledge.mjs,
|
|
20
|
+
]
|
|
21
|
+
families: [family:overlay-dismissal]
|
|
22
|
+
design_specs: []
|
|
23
|
+
architecture:
|
|
24
|
+
[
|
|
25
|
+
architecture:component-theming-surface,
|
|
26
|
+
architecture:layer-runtime,
|
|
27
|
+
architecture:public-component-api,
|
|
28
|
+
]
|
|
29
|
+
contributing: []
|
|
30
|
+
system_specs: []
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# MobileNav component contract
|
|
34
|
+
|
|
35
|
+
## Intent
|
|
36
|
+
|
|
37
|
+
MobileNav presents mobile navigation in a modal drawer and exports an AppShell-
|
|
38
|
+
aware Toggle button for opening and closing it. This draft records the aggregate
|
|
39
|
+
consumer anatomy and theming ownership of both exports without changing runtime
|
|
40
|
+
behavior, styling, targets, or public API.
|
|
41
|
+
|
|
42
|
+
## Compatibility and migration
|
|
43
|
+
|
|
44
|
+
- Released default preserved: `yes`
|
|
45
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
46
|
+
targets, aliases, and public API remain unchanged
|
|
47
|
+
- Controlled/uncontrolled behavior: unchanged
|
|
48
|
+
- Migration decision: none
|
|
49
|
+
|
|
50
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
51
|
+
|
|
52
|
+
## Ownership boundary
|
|
53
|
+
|
|
54
|
+
**Owns**
|
|
55
|
+
|
|
56
|
+
- The Navigation overlay and its current `mobile-nav` target.
|
|
57
|
+
- Drawer placement and motion, the stable Header and Content regions, and the
|
|
58
|
+
connection between AppShell state, MobileNav, and MobileNavToggle.
|
|
59
|
+
- Canonical anatomy for both exported components. `component:MobileNav` is the
|
|
60
|
+
parent owner; MobileNavToggle does not have a separate component contract.
|
|
61
|
+
|
|
62
|
+
**Does not own / non-goals**
|
|
63
|
+
|
|
64
|
+
- The Close and Toggle button surfaces — delegated to `component:Button`.
|
|
65
|
+
- Header or navigation content supplied by the caller.
|
|
66
|
+
- A public target for the painted Drawer, Header, or Content region; none exists
|
|
67
|
+
on current `main`.
|
|
68
|
+
- AppShell responsive policy or shared layer and dismissal policy.
|
|
69
|
+
|
|
70
|
+
## Public concepts
|
|
71
|
+
|
|
72
|
+
No new public concept is introduced. Consumer props, exports, and usage remain
|
|
73
|
+
documented in `MobileNav.doc.mjs`.
|
|
74
|
+
|
|
75
|
+
## Behavioral and layout contract
|
|
76
|
+
|
|
77
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
78
|
+
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
79
|
+
| FR1 | MobileNav renders one Navigation overlay containing a Drawer with a stable Header, Close button, and scrollable Content region. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
80
|
+
| FR2 | Navigation overlay carries the current `mobile-nav` target. Drawer, Header, and Content carry no MobileNav public target. | Current source and public docs | Verified current reachability; no target change |
|
|
81
|
+
| FR3 | Close button and the separately exported Toggle button use Button's `button` target; Toggle renders only when AppShell mobile context enables it and references the drawer by ID. | Current source, docs, and tests | Verified current delegation; no ownership change |
|
|
82
|
+
|
|
83
|
+
### Allowed variation
|
|
84
|
+
|
|
85
|
+
- **AV1 — Drawer edge.** The Drawer may resolve to logical start or end through
|
|
86
|
+
the current explicit or trigger-derived side behavior.
|
|
87
|
+
- **AV2 — Header content.** Header may contain a string rendered through Heading,
|
|
88
|
+
caller-provided content, or only the Close button.
|
|
89
|
+
- **AV3 — Navigation content.** Caller-provided navigation content may vary
|
|
90
|
+
without becoming MobileNav-owned anatomy.
|
|
91
|
+
- **AV4 — Toggle presence.** Toggle button is absent outside an enabled AppShell
|
|
92
|
+
mobile context.
|
|
93
|
+
|
|
94
|
+
### Representative states
|
|
95
|
+
|
|
96
|
+
| State | Required invariant | Allowed variation |
|
|
97
|
+
| ------------------------ | ---------------------------------------------------------------------- | -------------------------------------------------- |
|
|
98
|
+
| Standalone drawer | Navigation overlay contains Drawer, Header, Close button, and Content. | Caller controls open state and may choose an edge. |
|
|
99
|
+
| AppShell drawer | The same drawer anatomy consumes AppShell mobile state. | Header and navigation content remain caller-owned. |
|
|
100
|
+
| AppShell mobile toggle | Toggle button controls and references the current drawer. | Custom button content may replace the menu glyph. |
|
|
101
|
+
| Non-mobile/disabled host | Toggle button does not render. | MobileNav remains owned by the parent contract. |
|
|
102
|
+
|
|
103
|
+
### Transformation and precedence order
|
|
104
|
+
|
|
105
|
+
- No new side resolution, open-state, close timing, or styling precedence rule is
|
|
106
|
+
introduced.
|
|
107
|
+
|
|
108
|
+
### Performance and resources
|
|
109
|
+
|
|
110
|
+
- No new performance or resource rule is introduced.
|
|
111
|
+
|
|
112
|
+
## Accessibility contract
|
|
113
|
+
|
|
114
|
+
This draft does not change or extend MobileNav's existing dialog naming, focus,
|
|
115
|
+
Toggle relationship, Button labels, modal behavior, or dismissal behavior.
|
|
116
|
+
|
|
117
|
+
## Design relationships
|
|
118
|
+
|
|
119
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
120
|
+
| ------------------ | --------------------------------------------------------------------- | ------------------------------ | -------------- | ------------------ |
|
|
121
|
+
| Navigation overlay | Hosts the modal navigation layer and its backdrop. | Current source and public docs | Supporting | FR1, FR2 |
|
|
122
|
+
| Drawer | Paints the sliding mobile navigation surface. | Current source and public docs | Prominent | FR1, FR2 |
|
|
123
|
+
| Header | Arranges optional header content with the persistent Close button. | Current source and public docs | Supporting | FR1, FR2 |
|
|
124
|
+
| Content | Provides the scrolling region for caller-supplied navigation content. | Current source and public docs | Prominent | FR1, FR2 |
|
|
125
|
+
| Close button | Dismisses the current drawer. | `component:Button` | Supporting | FR1, FR3 |
|
|
126
|
+
| Toggle button | Opens or closes the AppShell-owned mobile drawer state. | `component:Button` | Supporting | FR3 |
|
|
127
|
+
|
|
128
|
+
Drawer is the primary painted surface, but the only current MobileNav target is
|
|
129
|
+
on the full-viewport dialog overlay. Its `none` disposition is a factual
|
|
130
|
+
reachability gap, not a decision that the Drawer must remain unthemeable. Header
|
|
131
|
+
and Content likewise have no current local target. Close and Toggle retain Button
|
|
132
|
+
ownership.
|
|
133
|
+
|
|
134
|
+
### Theming anatomy
|
|
135
|
+
|
|
136
|
+
<!-- anatomy-theming:v1 -->
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"Navigation overlay": {"target": "mobile-nav"},
|
|
141
|
+
"Drawer": {
|
|
142
|
+
"none": {
|
|
143
|
+
"reason": "reachability-gap: No current public target is applied to the painted drawer panel"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"Header": {
|
|
147
|
+
"none": {
|
|
148
|
+
"reason": "reachability-gap: No current public target is applied to the stable header row"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"Content": {
|
|
152
|
+
"none": {
|
|
153
|
+
"reason": "reachability-gap: No current public target is applied to the scrollable content region"
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"Close button": {
|
|
157
|
+
"delegatesTo": {"owner": "component:Button", "target": "button"}
|
|
158
|
+
},
|
|
159
|
+
"Toggle button": {
|
|
160
|
+
"delegatesTo": {"owner": "component:Button", "target": "button"}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Family and system relationships
|
|
166
|
+
|
|
167
|
+
- `architecture:component-theming-surface` owns anatomy qualification, target
|
|
168
|
+
mapping, delegation, and factual `none` dispositions.
|
|
169
|
+
- `architecture:layer-runtime` owns the current native-dialog host and shared
|
|
170
|
+
layer plumbing; MobileNav retains its current drawer motion and backdrop path.
|
|
171
|
+
- `family:overlay-dismissal` owns shared Escape and platform-close ordering and
|
|
172
|
+
records MobileNav as a current adopter.
|
|
173
|
+
- `architecture:public-component-api` owns the shared API boundary; this draft
|
|
174
|
+
changes neither MobileNav nor MobileNavToggle API.
|
|
175
|
+
- AppShell remains the higher-level responsive owner. This parent component
|
|
176
|
+
record owns the two MobileNav exports' shared anatomy only.
|
|
177
|
+
|
|
178
|
+
## Verification map
|
|
179
|
+
|
|
180
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
181
|
+
| ------------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------- |
|
|
182
|
+
| FR1 | `MobileNav.test.tsx` content/header/close assertions plus source inspection | Standalone open and closed drawer | Removing dialog content or the Close action fails tests; distinct Drawer/Header/Content wrappers rely on review. | `audit:MobileNav/anatomy` |
|
|
183
|
+
| FR2 | Source inspection and `themingTargets.test.ts` | Overlay, Drawer, Header, and Content | Removing the root target or documenting an unshipped child target fails evidence or inventory. | `audit:MobileNav/theming` |
|
|
184
|
+
| FR3 | `MobileNavToggle.test.tsx`, `MobileNavReopen.test.tsx`, and source inspection | Closed/open AppShell drawer and toggle | Breaking Button delegation or the ID/state relationship fails focused toggle coverage. | `audit:MobileNav/anatomy` |
|
|
185
|
+
| Close lifecycle | `MobileNavCloseEdgeCases.test.tsx` and related close/visibility suites | Escape, backdrop, close, reopen, unmount | Leaving the modal open or disconnecting state fails existing lifecycle assertions. | `audit:MobileNav/behavior` |
|
|
186
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical parent anatomy and current target | Missing, extra, prefixed, stale, or multiply assigned mappings fail repository validation. | `audit:MobileNav/theming` |
|
|
187
|
+
|
|
188
|
+
Current tests find header and navigation content by text and the Close action by
|
|
189
|
+
role, but do not identify the distinct Drawer, Header, or Content elements. Their
|
|
190
|
+
existence and nesting are source-inspected; merging or reordering those wrappers
|
|
191
|
+
currently lacks focused regression evidence.
|
|
192
|
+
|
|
193
|
+
## Decision log
|
|
194
|
+
|
|
195
|
+
None. This draft records current facts and introduces no component-local design,
|
|
196
|
+
API, theming, responsive, or layer-system decision.
|
|
197
|
+
|
|
198
|
+
## Open questions
|
|
199
|
+
|
|
200
|
+
- **OQ1 — Should Drawer gain a stable public theming target?** (`human-api`) The
|
|
201
|
+
current target reaches the Navigation overlay rather than the primary painted
|
|
202
|
+
drawer surface; this is an audit gap, not settled intent.
|
|
203
|
+
|
|
204
|
+
## Content boundary
|
|
205
|
+
|
|
206
|
+
This file does not duplicate consumer prop tables/examples, AppShell responsive
|
|
207
|
+
policy, close-timing mechanics, shared layer rules, current audit results, or
|
|
208
|
+
implementation steps. It links to their owners.
|
|
@@ -61,6 +61,12 @@ export const docs = {
|
|
|
61
61
|
"Alignment along the placement axis. Use 'end' to align the menu with the trigger's trailing edge, which is usually what an overflow menu wants.",
|
|
62
62
|
default: "'start'",
|
|
63
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: 'presentation',
|
|
66
|
+
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
67
|
+
description: 'Presentation policy forwarded to DropdownMenu. `adaptive` keeps the anchored popover on pointer-based layouts and uses a BottomSheet at 768px and below when the primary pointer is coarse.',
|
|
68
|
+
default: "'popover'",
|
|
69
|
+
},
|
|
64
70
|
{
|
|
65
71
|
name: 'onOpenChange',
|
|
66
72
|
type: '(isOpen: boolean) => void',
|
|
@@ -93,6 +99,7 @@ export const docs = {
|
|
|
93
99
|
bestPractices: [
|
|
94
100
|
{ guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
|
|
95
101
|
{ guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
|
|
102
|
+
{ guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
|
|
96
103
|
{ guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
|
|
97
104
|
],
|
|
98
105
|
},
|
|
@@ -153,6 +160,12 @@ export const docsZh = {
|
|
|
153
160
|
"沿放置轴的对齐方式。使用 'end' 让菜单与触发器的尾部边缘对齐,这通常是溢出菜单所需的效果。",
|
|
154
161
|
default: "'start'",
|
|
155
162
|
},
|
|
163
|
+
{
|
|
164
|
+
name: 'presentation',
|
|
165
|
+
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
166
|
+
description: '菜单呈现策略。`adaptive` 在指针布局中使用锚定浮层,在主指针为粗略指针且宽度不超过 768px 时使用 BottomSheet。',
|
|
167
|
+
default: "'popover'",
|
|
168
|
+
},
|
|
156
169
|
{
|
|
157
170
|
name: 'xstyle',
|
|
158
171
|
type: 'StyleXStyles',
|
|
@@ -171,6 +184,7 @@ export const docsZh = {
|
|
|
171
184
|
bestPractices: [
|
|
172
185
|
{ guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
|
|
173
186
|
{ guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
|
|
187
|
+
{ guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
|
|
174
188
|
{ guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
|
|
175
189
|
],
|
|
176
190
|
},
|
|
@@ -186,6 +200,7 @@ export const docsDense = {
|
|
|
186
200
|
bestPractices: [
|
|
187
201
|
{ guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
|
|
188
202
|
{ guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
|
|
203
|
+
{ guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
|
|
189
204
|
{ guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
|
|
190
205
|
],
|
|
191
206
|
},
|
|
@@ -199,6 +214,8 @@ export const docsDense = {
|
|
|
199
214
|
placement: 'Menu position relative to trigger. Logical (RTL mirrors).',
|
|
200
215
|
alignment:
|
|
201
216
|
"Alignment along the placement axis. 'end' aligns with the trigger's trailing edge.",
|
|
217
|
+
presentation:
|
|
218
|
+
'Popover, BottomSheet, or adaptive compact-touch presentation.',
|
|
202
219
|
xstyle:
|
|
203
220
|
'StyleX styles for layout customization (margins, positioning, sizing). Must be stylex.create() value.',
|
|
204
221
|
},
|
|
@@ -17,6 +17,30 @@ import {DropdownMenu} from '../DropdownMenu/DropdownMenu';
|
|
|
17
17
|
|
|
18
18
|
// Mock showPopover and hidePopover methods since they're not implemented in jsdom
|
|
19
19
|
beforeEach(() => {
|
|
20
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
21
|
+
this: HTMLDialogElement,
|
|
22
|
+
) {
|
|
23
|
+
this.setAttribute('open', '');
|
|
24
|
+
});
|
|
25
|
+
HTMLDialogElement.prototype.show = vi.fn(function (this: HTMLDialogElement) {
|
|
26
|
+
this.setAttribute('open', '');
|
|
27
|
+
});
|
|
28
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
29
|
+
this.removeAttribute('open');
|
|
30
|
+
});
|
|
31
|
+
vi.stubGlobal(
|
|
32
|
+
'matchMedia',
|
|
33
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
34
|
+
matches: false,
|
|
35
|
+
media: query,
|
|
36
|
+
onchange: null,
|
|
37
|
+
addEventListener: vi.fn(),
|
|
38
|
+
removeEventListener: vi.fn(),
|
|
39
|
+
addListener: vi.fn(),
|
|
40
|
+
removeListener: vi.fn(),
|
|
41
|
+
dispatchEvent: vi.fn(),
|
|
42
|
+
})),
|
|
43
|
+
);
|
|
20
44
|
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
21
45
|
this.setAttribute('popover-open', '');
|
|
22
46
|
const event = new Event('toggle', {bubbles: false});
|
|
@@ -85,6 +109,29 @@ describe('MoreMenu', () => {
|
|
|
85
109
|
expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
|
|
86
110
|
});
|
|
87
111
|
|
|
112
|
+
it('keeps the trigger visually pressed while the menu is open', async () => {
|
|
113
|
+
const user = userEvent.setup();
|
|
114
|
+
render(<MoreMenu items={defaultItems} />);
|
|
115
|
+
|
|
116
|
+
const trigger = screen.getByRole('button', {name: 'More options'});
|
|
117
|
+
expect(trigger.className).not.toContain('DropdownMenu__styles.triggerOpen');
|
|
118
|
+
|
|
119
|
+
await user.click(trigger);
|
|
120
|
+
|
|
121
|
+
expect(trigger.className).toContain('DropdownMenu__styles.triggerOpen');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('forwards BottomSheet presentation to DropdownMenu', async () => {
|
|
125
|
+
const user = userEvent.setup();
|
|
126
|
+
render(<MoreMenu items={defaultItems} presentation="bottom-sheet" />);
|
|
127
|
+
|
|
128
|
+
await user.click(screen.getByRole('button', {name: 'More options'}));
|
|
129
|
+
await waitFor(() =>
|
|
130
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledOnce(),
|
|
131
|
+
);
|
|
132
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
88
135
|
it('calls onClick when item is clicked', async () => {
|
|
89
136
|
const handleEdit = vi.fn();
|
|
90
137
|
const items = [
|
|
@@ -22,7 +22,10 @@ import type {ReactNode} from 'react';
|
|
|
22
22
|
import {useIcon} from '../Icon';
|
|
23
23
|
import {DropdownMenu} from '../DropdownMenu/DropdownMenu';
|
|
24
24
|
import {useSize} from '../SizeContext/SizeContext';
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
DropdownMenuOption,
|
|
27
|
+
DropdownMenuPresentation,
|
|
28
|
+
} from '../DropdownMenu';
|
|
26
29
|
import type {LayerAlignment, LayerPlacement} from '../Layer';
|
|
27
30
|
import type {ButtonVariant, ButtonSize} from '../Button';
|
|
28
31
|
import type {BaseProps} from '../BaseProps';
|
|
@@ -87,6 +90,13 @@ export interface MoreMenuProps extends Pick<
|
|
|
87
90
|
*/
|
|
88
91
|
alignment?: LayerAlignment;
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Menu presentation policy. `adaptive` uses a BottomSheet on compact
|
|
95
|
+
* coarse-pointer viewports and an anchored popover elsewhere.
|
|
96
|
+
* @default 'popover'
|
|
97
|
+
*/
|
|
98
|
+
presentation?: DropdownMenuPresentation;
|
|
99
|
+
|
|
90
100
|
/**
|
|
91
101
|
* Controlled open state for the menu.
|
|
92
102
|
*/
|
|
@@ -125,6 +135,7 @@ export function MoreMenu({
|
|
|
125
135
|
isDisabled = false,
|
|
126
136
|
placement,
|
|
127
137
|
alignment,
|
|
138
|
+
presentation,
|
|
128
139
|
isMenuOpen,
|
|
129
140
|
onOpenChange,
|
|
130
141
|
xstyle,
|
|
@@ -151,6 +162,7 @@ export function MoreMenu({
|
|
|
151
162
|
onOpenChange={onOpenChange}
|
|
152
163
|
placement={placement}
|
|
153
164
|
alignment={alignment}
|
|
165
|
+
presentation={presentation}
|
|
154
166
|
button={{
|
|
155
167
|
label,
|
|
156
168
|
icon: icon ?? moreIcon,
|