@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e4f8e4e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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/locales/en.json +20 -0
- package/locales/pseudo.json +15 -0
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +6 -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/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/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/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/IconButton/IconButton.doc.mjs +38 -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 +16 -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/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/ProgressBar/ProgressBar.spec.md +208 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
- package/src/Selector/Selector.doc.mjs +12 -0
- 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/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -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/theme/themingTargets.test.ts +91 -25
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file DropdownMenu.tsx
|
|
7
|
-
* @input Uses React, StyleX, usePopover, Button,
|
|
8
|
-
*
|
|
7
|
+
* @input Uses React, StyleX, usePopover, MenuBottomSheet, Button, List,
|
|
8
|
+
* useListFocus, and the shared viewport-safe menu-width resolver
|
|
9
|
+
* @output Exports DropdownMenu with caller-selected popover or bottom-sheet
|
|
10
|
+
* presentation
|
|
9
11
|
* @position Core implementation; consumed by index.ts
|
|
10
12
|
*
|
|
11
13
|
* Supports two modes with a single keyboard/focus path:
|
|
@@ -39,6 +41,7 @@ import React, {
|
|
|
39
41
|
import * as stylex from '@stylexjs/stylex';
|
|
40
42
|
import {usePopoverInternal} from '../Popover/usePopover';
|
|
41
43
|
import {Button, type ButtonProps} from '../Button';
|
|
44
|
+
import {Heading} from '../Heading';
|
|
42
45
|
import {Icon} from '../Icon';
|
|
43
46
|
|
|
44
47
|
import {renderDropdownItems} from './renderDropdownItems';
|
|
@@ -54,27 +57,65 @@ import {
|
|
|
54
57
|
} from './DropdownMenuContext';
|
|
55
58
|
import {useListFocus} from '../hooks/useListFocus';
|
|
56
59
|
import {useTypeahead} from '../hooks/useTypeahead';
|
|
60
|
+
import {useFocusReturnVisibility} from '../hooks/useFocusReturnVisibility';
|
|
61
|
+
import {useMenuOverflow} from './useMenuOverflow';
|
|
62
|
+
import {resolveMenuWidth} from './menuWidth';
|
|
63
|
+
import {
|
|
64
|
+
useAdaptivePresentation,
|
|
65
|
+
type AdaptivePresentation,
|
|
66
|
+
} from '../hooks/useAdaptivePresentation';
|
|
67
|
+
import {MenuBottomSheet} from './MenuBottomSheet';
|
|
68
|
+
import {MenuBottomSheetActionList} from './MenuBottomSheetActionList';
|
|
57
69
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
58
70
|
import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
|
|
59
71
|
import {
|
|
60
72
|
spacingVars,
|
|
73
|
+
colorVars,
|
|
61
74
|
radiusVars,
|
|
62
75
|
durationVars,
|
|
63
76
|
easeVars,
|
|
64
77
|
} from '../theme/tokens.stylex';
|
|
65
|
-
import {mergeProps} from '../utils';
|
|
78
|
+
import {mergeProps, rtlStyles} from '../utils';
|
|
66
79
|
import type {BaseProps} from '../BaseProps';
|
|
67
80
|
import {themeProps} from '../utils/themeProps';
|
|
81
|
+
import {
|
|
82
|
+
getInteractionModality,
|
|
83
|
+
trackInteractionModality,
|
|
84
|
+
} from '../utils/interactionModality';
|
|
68
85
|
import {useTranslator} from '../i18n';
|
|
86
|
+
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
87
|
+
|
|
88
|
+
const MENU_VIEWPORT_GUTTER = spacingVars['--spacing-4'];
|
|
89
|
+
const MENU_MAX_INLINE_SIZE = `calc(100vi - max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px)) - max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-right, 0px)))`;
|
|
90
|
+
const MENU_MAX_INLINE_SIZE_FALLBACK = `calc(100vw - ${MENU_VIEWPORT_GUTTER} - ${MENU_VIEWPORT_GUTTER})`;
|
|
91
|
+
const MENU_MAX_BLOCK_SIZE = `min(300px, calc(100dvb - max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-top, 0px)) - max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-bottom, 0px))))`;
|
|
92
|
+
const MENU_MAX_BLOCK_SIZE_FALLBACK = `min(300px, calc(100vh - ${MENU_VIEWPORT_GUTTER} - ${MENU_VIEWPORT_GUTTER}))`;
|
|
93
|
+
const MENU_POSITION_AREA_MAX_INLINE_SIZE = `calc(100% - max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)))`;
|
|
94
|
+
const MENU_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK = `calc(100% - ${MENU_VIEWPORT_GUTTER})`;
|
|
95
|
+
const MENU_INLINE_EDGE_GUTTER = `max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px))`;
|
|
96
|
+
const MENU_TRIGGER_OPEN_BACKGROUND = `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`;
|
|
69
97
|
|
|
70
98
|
const styles = stylex.create({
|
|
99
|
+
triggerOpen: {
|
|
100
|
+
backgroundImage: MENU_TRIGGER_OPEN_BACKGROUND,
|
|
101
|
+
},
|
|
71
102
|
dropdown: {
|
|
72
103
|
boxSizing: 'border-box',
|
|
73
104
|
display: 'flex',
|
|
74
105
|
flexDirection: 'column',
|
|
75
106
|
gap: spacingVars['--spacing-0-5'],
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
// Pointer opens focus this container so subsequent arrow keys and Escape
|
|
108
|
+
// stay owned by the menu. It is an internal focus target, not a control,
|
|
109
|
+
// so suppress the browser ring; keyboard focus moves to an item instead.
|
|
110
|
+
outline: 'none',
|
|
111
|
+
maxInlineSize: stylex.firstThatWorks(
|
|
112
|
+
MENU_MAX_INLINE_SIZE,
|
|
113
|
+
MENU_MAX_INLINE_SIZE_FALLBACK,
|
|
114
|
+
),
|
|
115
|
+
maxHeight: stylex.firstThatWorks(
|
|
116
|
+
MENU_MAX_BLOCK_SIZE,
|
|
117
|
+
MENU_MAX_BLOCK_SIZE_FALLBACK,
|
|
118
|
+
),
|
|
78
119
|
'--_dropdown-menu-radius': radiusVars['--radius-container'],
|
|
79
120
|
'--_dropdown-menu-padding': spacingVars['--spacing-1'],
|
|
80
121
|
padding: spacingVars['--spacing-1'],
|
|
@@ -84,14 +125,95 @@ const styles = stylex.create({
|
|
|
84
125
|
transitionDuration: durationVars['--duration-fast'],
|
|
85
126
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
86
127
|
},
|
|
87
|
-
|
|
88
|
-
|
|
128
|
+
scrollable: {
|
|
129
|
+
overflowY: 'auto',
|
|
130
|
+
overflowX: 'hidden',
|
|
131
|
+
overscrollBehavior: 'contain',
|
|
132
|
+
},
|
|
133
|
+
popoverViewport: {
|
|
134
|
+
boxSizing: 'border-box',
|
|
135
|
+
maxBlockSize: stylex.firstThatWorks(
|
|
136
|
+
MENU_MAX_BLOCK_SIZE,
|
|
137
|
+
MENU_MAX_BLOCK_SIZE_FALLBACK,
|
|
138
|
+
),
|
|
139
|
+
},
|
|
140
|
+
popoverViewportAligned: {
|
|
141
|
+
maxInlineSize: stylex.firstThatWorks(
|
|
142
|
+
MENU_POSITION_AREA_MAX_INLINE_SIZE,
|
|
143
|
+
MENU_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK,
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
popoverViewportStart: {
|
|
147
|
+
marginInlineEnd: MENU_INLINE_EDGE_GUTTER,
|
|
148
|
+
},
|
|
149
|
+
popoverViewportEnd: {
|
|
150
|
+
marginInlineStart: MENU_INLINE_EDGE_GUTTER,
|
|
151
|
+
},
|
|
152
|
+
popoverViewportBlockStart: {
|
|
153
|
+
marginBlockEnd: `max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-bottom, 0px))`,
|
|
154
|
+
},
|
|
155
|
+
popoverViewportBlockEnd: {
|
|
156
|
+
marginBlockStart: `max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-top, 0px))`,
|
|
157
|
+
},
|
|
158
|
+
popoverViewportCentered: {
|
|
159
|
+
marginInlineStart: MENU_INLINE_EDGE_GUTTER,
|
|
160
|
+
marginInlineEnd: MENU_INLINE_EDGE_GUTTER,
|
|
161
|
+
maxInlineSize: stylex.firstThatWorks(
|
|
162
|
+
MENU_MAX_INLINE_SIZE,
|
|
163
|
+
MENU_MAX_INLINE_SIZE_FALLBACK,
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
popoverViewportBlockCentered: {
|
|
167
|
+
marginBlockStart: `max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-top, 0px))`,
|
|
168
|
+
marginBlockEnd: `max(${MENU_VIEWPORT_GUTTER}, env(safe-area-inset-bottom, 0px))`,
|
|
169
|
+
maxInlineSize: stylex.firstThatWorks(
|
|
170
|
+
MENU_MAX_INLINE_SIZE,
|
|
171
|
+
MENU_MAX_INLINE_SIZE_FALLBACK,
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
popoverAligned: {
|
|
175
|
+
minWidth: stylex.firstThatWorks(
|
|
176
|
+
`min(anchor-size(width), ${MENU_POSITION_AREA_MAX_INLINE_SIZE})`,
|
|
177
|
+
`min(anchor-size(width), ${MENU_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK})`,
|
|
178
|
+
'anchor-size(width)',
|
|
179
|
+
),
|
|
89
180
|
},
|
|
90
|
-
|
|
91
|
-
minWidth:
|
|
181
|
+
popoverCentered: {
|
|
182
|
+
minWidth: stylex.firstThatWorks(
|
|
183
|
+
`min(anchor-size(width), ${MENU_MAX_INLINE_SIZE})`,
|
|
184
|
+
`min(anchor-size(width), ${MENU_MAX_INLINE_SIZE_FALLBACK})`,
|
|
185
|
+
'anchor-size(width)',
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
popoverCustomWidth: (width: string) => ({
|
|
189
|
+
minWidth: width,
|
|
190
|
+
}),
|
|
191
|
+
popoverCustomIntrinsicWidth: (width: string) => ({
|
|
192
|
+
inlineSize: width,
|
|
92
193
|
}),
|
|
93
194
|
});
|
|
94
195
|
|
|
196
|
+
const bottomSheetStyles = stylex.create({
|
|
197
|
+
content: {
|
|
198
|
+
width: '100%',
|
|
199
|
+
},
|
|
200
|
+
header: {
|
|
201
|
+
display: 'flex',
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: spacingVars['--spacing-1'],
|
|
205
|
+
marginBottom: spacingVars['--spacing-2'],
|
|
206
|
+
},
|
|
207
|
+
rootHeading: {
|
|
208
|
+
// Match the content edge of a spacious ListItem. Icon-bearing rows place
|
|
209
|
+
// their icon here; rows without an icon place their label here.
|
|
210
|
+
marginInlineStart: spacingVars['--spacing-3'],
|
|
211
|
+
},
|
|
212
|
+
viewHeading: {
|
|
213
|
+
outline: 'none',
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
95
217
|
// =============================================================================
|
|
96
218
|
// Types
|
|
97
219
|
// =============================================================================
|
|
@@ -157,22 +279,31 @@ export type DropdownMenuOption =
|
|
|
157
279
|
|
|
158
280
|
export type DropdownMenuButtonProps = Omit<ButtonProps, 'onClick'>;
|
|
159
281
|
|
|
282
|
+
export type DropdownMenuPresentation = AdaptivePresentation;
|
|
283
|
+
|
|
160
284
|
interface DropdownMenuBaseProps extends BaseProps {
|
|
161
285
|
button?: DropdownMenuButtonProps;
|
|
162
286
|
isMenuOpen?: boolean;
|
|
163
287
|
onOpenChange?: (isOpen: boolean) => void;
|
|
288
|
+
/**
|
|
289
|
+
* Minimum popover width. The menu may grow for its content but is capped to
|
|
290
|
+
* the available viewport space. Ignored by bottom-sheet presentation.
|
|
291
|
+
* Defaults to the trigger width.
|
|
292
|
+
*/
|
|
164
293
|
menuWidth?: number | string;
|
|
165
294
|
onClick?: () => void;
|
|
166
295
|
hasChevron?: boolean;
|
|
167
296
|
/**
|
|
168
|
-
*
|
|
297
|
+
* Popover position relative to the trigger. Ignored by bottom-sheet
|
|
298
|
+
* presentation.
|
|
169
299
|
* Uses the same placement values as other Astryx layer-based components.
|
|
170
300
|
* @default 'below'
|
|
171
301
|
*/
|
|
172
302
|
placement?: LayerPlacement;
|
|
173
303
|
|
|
174
304
|
/**
|
|
175
|
-
*
|
|
305
|
+
* Popover alignment along the placement axis. Ignored by bottom-sheet
|
|
306
|
+
* presentation.
|
|
176
307
|
* Uses the same alignment values as other Astryx layer-based components.
|
|
177
308
|
* @default 'start'
|
|
178
309
|
*/
|
|
@@ -182,11 +313,19 @@ interface DropdownMenuBaseProps extends BaseProps {
|
|
|
182
313
|
}
|
|
183
314
|
|
|
184
315
|
interface DropdownMenuDataProps extends DropdownMenuBaseProps {
|
|
316
|
+
/**
|
|
317
|
+
* Surface used to present the actions. Pass a value selected by product
|
|
318
|
+
* policy (for example, a compact-touch media query) when the presentation
|
|
319
|
+
* should adapt. Bottom-sheet presentation is intended for short action sets.
|
|
320
|
+
* @default 'popover'
|
|
321
|
+
*/
|
|
322
|
+
presentation?: DropdownMenuPresentation;
|
|
185
323
|
items: DropdownMenuOption[];
|
|
186
324
|
children?: undefined;
|
|
187
325
|
}
|
|
188
326
|
|
|
189
327
|
interface DropdownMenuCompoundProps extends DropdownMenuBaseProps {
|
|
328
|
+
presentation?: 'popover';
|
|
190
329
|
items?: undefined;
|
|
191
330
|
children: ReactNode;
|
|
192
331
|
}
|
|
@@ -202,15 +341,16 @@ export type DropdownMenuProps =
|
|
|
202
341
|
* A dropdown menu component that displays a list of actionable items.
|
|
203
342
|
*
|
|
204
343
|
* Supports two modes:
|
|
205
|
-
* - **Data-driven**: pass `items`
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
344
|
+
* - **Data-driven**: pass `items` and choose either the default anchored
|
|
345
|
+
* popover or the modal bottom-sheet presentation.
|
|
346
|
+
* - **Compound-component**: pass JSX children for dynamic, stateful, or
|
|
347
|
+
* lazy-loaded anchored popover menus.
|
|
209
348
|
*
|
|
210
349
|
* @example
|
|
211
350
|
* ```
|
|
212
351
|
* <DropdownMenu
|
|
213
352
|
* button={{ label: 'Actions' }}
|
|
353
|
+
* presentation={useBottomSheet ? 'bottom-sheet' : 'popover'}
|
|
214
354
|
* items={[
|
|
215
355
|
* { label: 'Edit', onClick: () => handleEdit() },
|
|
216
356
|
* { label: 'Delete', onClick: () => handleDelete() },
|
|
@@ -223,7 +363,210 @@ export type DropdownMenuProps =
|
|
|
223
363
|
// locale.
|
|
224
364
|
const DEFAULT_BUTTON_I18N_KEY = '@astryx.dropdownMenu.label' as const;
|
|
225
365
|
|
|
226
|
-
|
|
366
|
+
function DropdownMenuBottomSheet({
|
|
367
|
+
button: buttonFromProps,
|
|
368
|
+
isMenuOpen: controlledIsOpen,
|
|
369
|
+
onOpenChange,
|
|
370
|
+
onClick,
|
|
371
|
+
hasChevron = true,
|
|
372
|
+
items,
|
|
373
|
+
presentation: _presentation,
|
|
374
|
+
menuWidth: _menuWidth,
|
|
375
|
+
placement: _placement,
|
|
376
|
+
alignment: _alignment,
|
|
377
|
+
className,
|
|
378
|
+
style,
|
|
379
|
+
xstyle,
|
|
380
|
+
'data-testid': testId,
|
|
381
|
+
...rest
|
|
382
|
+
}: DropdownMenuDataProps & {presentation: 'bottom-sheet'}) {
|
|
383
|
+
const t = useTranslator();
|
|
384
|
+
const button = buttonFromProps ?? {label: t(DEFAULT_BUTTON_I18N_KEY)};
|
|
385
|
+
const backLabel = t('@astryx.dropdownMenu.back');
|
|
386
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
387
|
+
const actionListRef = useRef<HTMLDivElement>(null);
|
|
388
|
+
const openModalityRef = useRef<'keyboard' | 'pointer'>('pointer');
|
|
389
|
+
const {
|
|
390
|
+
isFocusRingSuppressed,
|
|
391
|
+
onFocusReturnTargetFocus,
|
|
392
|
+
prepareFocusReturn,
|
|
393
|
+
resetFocusReturn,
|
|
394
|
+
} = useFocusReturnVisibility();
|
|
395
|
+
const viewHeadingRef = useRef<HTMLHeadingElement>(null);
|
|
396
|
+
const previousSubmenuDepthRef = useRef(0);
|
|
397
|
+
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
398
|
+
const [submenuPath, setSubmenuPath] = useState<DropdownMenuItemData[]>([]);
|
|
399
|
+
const isControlled = controlledIsOpen !== undefined;
|
|
400
|
+
const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
|
|
401
|
+
const [previousIsOpen, setPreviousIsOpen] = useState(isOpen);
|
|
402
|
+
if (previousIsOpen !== isOpen) {
|
|
403
|
+
setPreviousIsOpen(isOpen);
|
|
404
|
+
if (!isOpen && submenuPath.length > 0) {
|
|
405
|
+
setSubmenuPath([]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const currentSubmenu = submenuPath.at(-1);
|
|
409
|
+
const currentItems = currentSubmenu?.items ?? items;
|
|
410
|
+
const currentTitle = currentSubmenu?.label ?? button.label;
|
|
411
|
+
const sheetLabel =
|
|
412
|
+
typeof currentTitle === 'string' ? currentTitle : button.label;
|
|
413
|
+
|
|
414
|
+
const setOpen = useCallback(
|
|
415
|
+
(nextIsOpen: boolean) => {
|
|
416
|
+
if (!nextIsOpen) {
|
|
417
|
+
setSubmenuPath([]);
|
|
418
|
+
prepareFocusReturn();
|
|
419
|
+
} else {
|
|
420
|
+
resetFocusReturn();
|
|
421
|
+
}
|
|
422
|
+
onOpenChange?.(nextIsOpen);
|
|
423
|
+
if (!isControlled) {
|
|
424
|
+
setInternalIsOpen(nextIsOpen);
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
[isControlled, onOpenChange, prepareFocusReturn, resetFocusReturn],
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const handleSelect = useCallback(
|
|
431
|
+
(item: DropdownMenuItemData) => {
|
|
432
|
+
if (item.isDisabled) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
item.onClick?.();
|
|
436
|
+
if (item.hasCloseOnSelect !== false) {
|
|
437
|
+
setOpen(false);
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
[setOpen],
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const isIconOnly = button.isIconOnly === true;
|
|
444
|
+
const resolvedEndContent =
|
|
445
|
+
button.endContent ??
|
|
446
|
+
(hasChevron && !isIconOnly ? (
|
|
447
|
+
<Icon icon="chevronDown" size="sm" color="inherit" />
|
|
448
|
+
) : undefined);
|
|
449
|
+
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (!isOpen || openModalityRef.current !== 'keyboard') {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const frame = requestAnimationFrame(() => {
|
|
455
|
+
actionListRef.current
|
|
456
|
+
?.querySelector<HTMLElement>('button:not(:disabled), a[href]')
|
|
457
|
+
?.focus({preventScroll: true});
|
|
458
|
+
});
|
|
459
|
+
return () => cancelAnimationFrame(frame);
|
|
460
|
+
}, [currentItems, isOpen]);
|
|
461
|
+
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
if (!isOpen) {
|
|
464
|
+
previousSubmenuDepthRef.current = 0;
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (submenuPath.length === previousSubmenuDepthRef.current) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
previousSubmenuDepthRef.current = submenuPath.length;
|
|
471
|
+
const frame = requestAnimationFrame(() => {
|
|
472
|
+
viewHeadingRef.current?.focus({preventScroll: true});
|
|
473
|
+
});
|
|
474
|
+
return () => cancelAnimationFrame(frame);
|
|
475
|
+
}, [isOpen, submenuPath.length]);
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<>
|
|
479
|
+
<Button
|
|
480
|
+
{...button}
|
|
481
|
+
ref={buttonRef}
|
|
482
|
+
xstyle={[
|
|
483
|
+
isOpen && styles.triggerOpen,
|
|
484
|
+
button.xstyle,
|
|
485
|
+
isFocusRingSuppressed && focusOutlineStyles.suppressed,
|
|
486
|
+
]}
|
|
487
|
+
tooltip={isOpen ? undefined : button.tooltip}
|
|
488
|
+
endContent={resolvedEndContent}
|
|
489
|
+
onPointerDown={event => {
|
|
490
|
+
button.onPointerDown?.(event);
|
|
491
|
+
openModalityRef.current = 'pointer';
|
|
492
|
+
}}
|
|
493
|
+
onKeyDown={event => {
|
|
494
|
+
button.onKeyDown?.(event);
|
|
495
|
+
if (
|
|
496
|
+
event.key === 'ArrowDown' ||
|
|
497
|
+
event.key === 'Enter' ||
|
|
498
|
+
event.key === ' '
|
|
499
|
+
) {
|
|
500
|
+
openModalityRef.current = 'keyboard';
|
|
501
|
+
}
|
|
502
|
+
}}
|
|
503
|
+
onFocus={event => {
|
|
504
|
+
button.onFocus?.(event);
|
|
505
|
+
onFocusReturnTargetFocus();
|
|
506
|
+
}}
|
|
507
|
+
onClick={() => {
|
|
508
|
+
onClick?.();
|
|
509
|
+
setOpen(!isOpen);
|
|
510
|
+
}}
|
|
511
|
+
aria-haspopup="dialog"
|
|
512
|
+
aria-expanded={isOpen}
|
|
513
|
+
data-testid={testId}
|
|
514
|
+
/>
|
|
515
|
+
|
|
516
|
+
<MenuBottomSheet
|
|
517
|
+
isOpen={isOpen}
|
|
518
|
+
onOpenChange={setOpen}
|
|
519
|
+
finalFocusRef={buttonRef}
|
|
520
|
+
label={sheetLabel}>
|
|
521
|
+
<div
|
|
522
|
+
ref={actionListRef}
|
|
523
|
+
{...rest}
|
|
524
|
+
{...mergeProps(
|
|
525
|
+
themeProps('dropdown-menu', {presentation: 'bottom-sheet'}),
|
|
526
|
+
stylex.props(bottomSheetStyles.content, xstyle),
|
|
527
|
+
className,
|
|
528
|
+
style,
|
|
529
|
+
)}>
|
|
530
|
+
<div {...stylex.props(bottomSheetStyles.header)}>
|
|
531
|
+
{submenuPath.length > 0 && (
|
|
532
|
+
<Button
|
|
533
|
+
label={backLabel}
|
|
534
|
+
variant="ghost"
|
|
535
|
+
size="sm"
|
|
536
|
+
icon={
|
|
537
|
+
<Icon
|
|
538
|
+
icon="chevronLeft"
|
|
539
|
+
size="sm"
|
|
540
|
+
xstyle={rtlStyles.mirror}
|
|
541
|
+
/>
|
|
542
|
+
}
|
|
543
|
+
isIconOnly
|
|
544
|
+
onClick={() => setSubmenuPath(path => path.slice(0, -1))}
|
|
545
|
+
/>
|
|
546
|
+
)}
|
|
547
|
+
<Heading
|
|
548
|
+
ref={viewHeadingRef}
|
|
549
|
+
level={3}
|
|
550
|
+
tabIndex={-1}
|
|
551
|
+
xstyle={[
|
|
552
|
+
bottomSheetStyles.viewHeading,
|
|
553
|
+
submenuPath.length === 0 && bottomSheetStyles.rootHeading,
|
|
554
|
+
]}>
|
|
555
|
+
{currentTitle}
|
|
556
|
+
</Heading>
|
|
557
|
+
</div>
|
|
558
|
+
<MenuBottomSheetActionList
|
|
559
|
+
items={currentItems}
|
|
560
|
+
onSelect={handleSelect}
|
|
561
|
+
onOpenSubmenu={item => setSubmenuPath(path => [...path, item])}
|
|
562
|
+
/>
|
|
563
|
+
</div>
|
|
564
|
+
</MenuBottomSheet>
|
|
565
|
+
</>
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function DropdownMenuPopover({
|
|
227
570
|
button: buttonFromProps,
|
|
228
571
|
isMenuOpen: controlledIsOpen,
|
|
229
572
|
onOpenChange,
|
|
@@ -232,6 +575,7 @@ export function DropdownMenu({
|
|
|
232
575
|
hasChevron = true,
|
|
233
576
|
placement = 'below',
|
|
234
577
|
alignment = 'start',
|
|
578
|
+
presentation: _presentation,
|
|
235
579
|
className,
|
|
236
580
|
style,
|
|
237
581
|
xstyle,
|
|
@@ -261,13 +605,25 @@ export function DropdownMenu({
|
|
|
261
605
|
const isControlled = controlledIsOpen !== undefined;
|
|
262
606
|
const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
|
|
263
607
|
|
|
264
|
-
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
trackInteractionModality();
|
|
610
|
+
}, []);
|
|
611
|
+
|
|
612
|
+
// Keyboard dismissal returns focus to the trigger. Pointer dismissal leaves
|
|
613
|
+
// focus where the browser put it; Safari can otherwise paint a focus-visible
|
|
614
|
+
// ring on the trigger after a touch selection. Native popover restoration
|
|
615
|
+
// can happen before `toggle`, so explicitly blur that pointer-restored case.
|
|
265
616
|
const handleLayerHide = useCallback(() => {
|
|
266
617
|
onOpenChange?.(false);
|
|
267
618
|
if (!isControlled) {
|
|
268
619
|
setInternalIsOpen(false);
|
|
269
620
|
}
|
|
270
|
-
buttonRef.current
|
|
621
|
+
const trigger = buttonRef.current;
|
|
622
|
+
if (getInteractionModality() === 'keyboard') {
|
|
623
|
+
trigger?.focus();
|
|
624
|
+
} else if (document.activeElement === trigger) {
|
|
625
|
+
trigger?.blur();
|
|
626
|
+
}
|
|
271
627
|
}, [isControlled, onOpenChange]);
|
|
272
628
|
|
|
273
629
|
// Defer item focus until the layer has committed open, so focus restore
|
|
@@ -469,9 +825,21 @@ export function DropdownMenu({
|
|
|
469
825
|
<Icon icon="chevronDown" size="sm" color="inherit" />
|
|
470
826
|
) : undefined);
|
|
471
827
|
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
828
|
+
const requestedWidthLimit =
|
|
829
|
+
alignment === 'center'
|
|
830
|
+
? MENU_MAX_INLINE_SIZE_FALLBACK
|
|
831
|
+
: MENU_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK;
|
|
832
|
+
const resolvedMenuWidth = menuWidth
|
|
833
|
+
? resolveMenuWidth(menuWidth, requestedWidthLimit)
|
|
834
|
+
: null;
|
|
835
|
+
const popoverXstyle = resolvedMenuWidth
|
|
836
|
+
? resolvedMenuWidth.property === 'inlineSize'
|
|
837
|
+
? styles.popoverCustomIntrinsicWidth(resolvedMenuWidth.value)
|
|
838
|
+
: styles.popoverCustomWidth(resolvedMenuWidth.value)
|
|
839
|
+
: alignment === 'center'
|
|
840
|
+
? styles.popoverCentered
|
|
841
|
+
: styles.popoverAligned;
|
|
842
|
+
const isSidePlacement = placement === 'start' || placement === 'end';
|
|
475
843
|
// Context for compound items
|
|
476
844
|
const contextValue = useMemo<DropdownMenuContextValue>(
|
|
477
845
|
() => ({closeMenu, menuSize}),
|
|
@@ -481,6 +849,7 @@ export function DropdownMenu({
|
|
|
481
849
|
// Resolve menu content: data-driven items become components
|
|
482
850
|
const menuContent =
|
|
483
851
|
props.items !== undefined ? renderDropdownItems(items) : children;
|
|
852
|
+
const hasOverflow = useMenuOverflow(listRef, menuContent, popover.isOpen);
|
|
484
853
|
|
|
485
854
|
return (
|
|
486
855
|
<>
|
|
@@ -498,6 +867,7 @@ export function DropdownMenu({
|
|
|
498
867
|
/* eslint-enable react-compiler/react-compiler */
|
|
499
868
|
}
|
|
500
869
|
}}
|
|
870
|
+
xstyle={[isOpen && styles.triggerOpen, button.xstyle]}
|
|
501
871
|
tooltip={isOpen ? undefined : button.tooltip}
|
|
502
872
|
endContent={resolvedEndContent}
|
|
503
873
|
onClick={handleButtonClick}
|
|
@@ -514,11 +884,11 @@ export function DropdownMenu({
|
|
|
514
884
|
ref={listRef}
|
|
515
885
|
id={menuId}
|
|
516
886
|
role="menu"
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
tabIndex={-1}
|
|
887
|
+
// Pointer opens focus the container so arrows, typeahead, Escape and
|
|
888
|
+
// Tab remain owned by the menu without pre-highlighting an item.
|
|
889
|
+
// An overflowing menu joins the Tab order so its scrollable region is
|
|
890
|
+
// keyboard-accessible; Tab still dismisses through listKeyDown.
|
|
891
|
+
tabIndex={hasOverflow ? 0 : -1}
|
|
522
892
|
// Give the menu an accessible name from its trigger's label, so
|
|
523
893
|
// screen readers announce e.g. "Actions menu" rather than an unnamed
|
|
524
894
|
// menu (menus-13).
|
|
@@ -526,7 +896,11 @@ export function DropdownMenu({
|
|
|
526
896
|
onKeyDown={listKeyDown}
|
|
527
897
|
{...mergeProps(
|
|
528
898
|
themeProps('dropdown-menu'),
|
|
529
|
-
stylex.props(
|
|
899
|
+
stylex.props(
|
|
900
|
+
styles.dropdown,
|
|
901
|
+
hasOverflow && styles.scrollable,
|
|
902
|
+
xstyle,
|
|
903
|
+
),
|
|
530
904
|
className,
|
|
531
905
|
style,
|
|
532
906
|
)}>
|
|
@@ -538,11 +912,68 @@ export function DropdownMenu({
|
|
|
538
912
|
placement,
|
|
539
913
|
alignment,
|
|
540
914
|
offset: spacingVars['--spacing-1'],
|
|
541
|
-
xstyle: [
|
|
915
|
+
xstyle: [
|
|
916
|
+
styles.popoverViewport,
|
|
917
|
+
alignment === 'center'
|
|
918
|
+
? isSidePlacement
|
|
919
|
+
? styles.popoverViewportBlockCentered
|
|
920
|
+
: styles.popoverViewportCentered
|
|
921
|
+
: [
|
|
922
|
+
styles.popoverViewportAligned,
|
|
923
|
+
isSidePlacement
|
|
924
|
+
? alignment === 'start'
|
|
925
|
+
? styles.popoverViewportBlockStart
|
|
926
|
+
: styles.popoverViewportBlockEnd
|
|
927
|
+
: alignment === 'start'
|
|
928
|
+
? styles.popoverViewportStart
|
|
929
|
+
: styles.popoverViewportEnd,
|
|
930
|
+
],
|
|
931
|
+
popoverXstyle,
|
|
932
|
+
layerAnimations[placement],
|
|
933
|
+
],
|
|
542
934
|
},
|
|
543
935
|
)}
|
|
544
936
|
</>
|
|
545
937
|
);
|
|
546
938
|
}
|
|
547
939
|
|
|
940
|
+
export function DropdownMenu(props: DropdownMenuProps) {
|
|
941
|
+
const {onOpenChange} = props;
|
|
942
|
+
const requestedPresentation =
|
|
943
|
+
'items' in props ? (props.presentation ?? 'popover') : 'popover';
|
|
944
|
+
const resolvedPresentation = useAdaptivePresentation(requestedPresentation);
|
|
945
|
+
const isControlled = props.isMenuOpen !== undefined;
|
|
946
|
+
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
947
|
+
const isOpen = isControlled ? props.isMenuOpen : internalIsOpen;
|
|
948
|
+
const handleOpenChange = useCallback(
|
|
949
|
+
(nextIsOpen: boolean) => {
|
|
950
|
+
onOpenChange?.(nextIsOpen);
|
|
951
|
+
if (!isControlled) {
|
|
952
|
+
setInternalIsOpen(nextIsOpen);
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
[isControlled, onOpenChange],
|
|
956
|
+
);
|
|
957
|
+
const sharedProps = {
|
|
958
|
+
...props,
|
|
959
|
+
isMenuOpen: isOpen,
|
|
960
|
+
onOpenChange: handleOpenChange,
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
if (
|
|
964
|
+
resolvedPresentation === 'bottom-sheet' &&
|
|
965
|
+
'items' in props &&
|
|
966
|
+
props.items !== undefined
|
|
967
|
+
) {
|
|
968
|
+
return (
|
|
969
|
+
<DropdownMenuBottomSheet
|
|
970
|
+
{...(sharedProps as DropdownMenuDataProps)}
|
|
971
|
+
presentation="bottom-sheet"
|
|
972
|
+
/>
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return <DropdownMenuPopover {...sharedProps} presentation="popover" />;
|
|
977
|
+
}
|
|
978
|
+
|
|
548
979
|
DropdownMenu.displayName = 'DropdownMenu';
|
|
@@ -18,7 +18,7 @@ import {createContext, use} from 'react';
|
|
|
18
18
|
export type DropdownMenuSize = 'sm' | 'md' | 'lg';
|
|
19
19
|
|
|
20
20
|
export interface DropdownMenuContextValue {
|
|
21
|
-
/** Close the menu
|
|
21
|
+
/** Close the menu; keyboard dismissal returns focus to the trigger. */
|
|
22
22
|
closeMenu: () => void;
|
|
23
23
|
/** Menu size derived from button size */
|
|
24
24
|
menuSize: DropdownMenuSize;
|
|
@@ -52,6 +52,8 @@ const menuItemStyles = stylex.create({
|
|
|
52
52
|
backgroundColor: {
|
|
53
53
|
default: 'transparent',
|
|
54
54
|
':focus': colorVars['--color-overlay-hover'],
|
|
55
|
+
':active:where(:not(:disabled,[aria-disabled="true"]))':
|
|
56
|
+
colorVars['--color-overlay-pressed'],
|
|
55
57
|
},
|
|
56
58
|
border: 'none',
|
|
57
59
|
cursor: {
|
|
@@ -52,7 +52,8 @@ export const docs = {
|
|
|
52
52
|
{
|
|
53
53
|
name: 'menuWidth',
|
|
54
54
|
type: 'number | string',
|
|
55
|
-
description:
|
|
55
|
+
description:
|
|
56
|
+
'Minimum flyout width. The flyout may grow for its content, but it is capped to the available viewport space. Defaults to intrinsic sizing (min 160px).',
|
|
56
57
|
},
|
|
57
58
|
{
|
|
58
59
|
name: 'onOpenChange',
|
|
@@ -81,7 +82,8 @@ export const docsDense = {
|
|
|
81
82
|
children: 'flyout menu items (same components as top level)',
|
|
82
83
|
isDisabled: 'renders trigger but never opens',
|
|
83
84
|
hasSpinner: 'spinner instead of caret for async children',
|
|
84
|
-
menuWidth:
|
|
85
|
+
menuWidth:
|
|
86
|
+
'minimum flyout width, capped to the viewport (default: content, min 160px)',
|
|
85
87
|
onOpenChange: 'called when flyout opens/closes',
|
|
86
88
|
xstyle: 'StyleX styles for the trigger row',
|
|
87
89
|
},
|