@atlaskit/dropdown-menu 16.8.9 → 16.8.11
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 +203 -0
- package/dist/cjs/dropdown-menu-top-layer.compiled.css +10 -0
- package/dist/cjs/dropdown-menu-top-layer.js +347 -0
- package/dist/cjs/dropdown-menu.js +62 -8
- package/dist/cjs/internal/components/group-title.js +6 -1
- package/dist/cjs/internal/use-arrow-navigation/index.js +25 -0
- package/dist/cjs/internal/use-arrow-navigation/use-arrow-navigation.js +18 -0
- package/dist/es2019/dropdown-menu-top-layer.compiled.css +10 -0
- package/dist/es2019/dropdown-menu-top-layer.js +323 -0
- package/dist/es2019/dropdown-menu.js +59 -9
- package/dist/es2019/internal/components/group-title.js +6 -1
- package/dist/es2019/internal/use-arrow-navigation/index.js +2 -0
- package/dist/es2019/internal/use-arrow-navigation/use-arrow-navigation.js +1 -0
- package/dist/esm/dropdown-menu-top-layer.compiled.css +10 -0
- package/dist/esm/dropdown-menu-top-layer.js +338 -0
- package/dist/esm/dropdown-menu.js +62 -8
- package/dist/esm/internal/components/group-title.js +6 -1
- package/dist/esm/internal/use-arrow-navigation/index.js +2 -0
- package/dist/esm/internal/use-arrow-navigation/use-arrow-navigation.js +1 -0
- package/dist/types/dropdown-menu-top-layer.d.ts +18 -0
- package/dist/types/dropdown-menu.d.ts +1 -1
- package/dist/types/internal/components/group-title.d.ts +6 -1
- package/dist/types/internal/use-arrow-navigation/index.d.ts +2 -0
- package/dist/types/internal/use-arrow-navigation/use-arrow-navigation.d.ts +1 -0
- package/dist/types/types.d.ts +20 -0
- package/dist/types-ts4.5/dropdown-menu-top-layer.d.ts +18 -0
- package/dist/types-ts4.5/dropdown-menu.d.ts +1 -1
- package/dist/types-ts4.5/internal/components/group-title.d.ts +6 -1
- package/dist/types-ts4.5/internal/use-arrow-navigation/index.d.ts +2 -0
- package/dist/types-ts4.5/internal/use-arrow-navigation/use-arrow-navigation.d.ts +1 -0
- package/dist/types-ts4.5/types.d.ts +20 -0
- package/package.json +21 -16
- package/offerings.json +0 -36
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/* dropdown-menu-top-layer.tsx generated by @compiled/babel-plugin v0.39.1 */
|
|
2
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
5
|
+
import "./dropdown-menu-top-layer.compiled.css";
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
8
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
9
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
10
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
|
+
import { bind } from 'bind-event-listener';
|
|
12
|
+
import Button from '@atlaskit/button/new';
|
|
13
|
+
import { KEY_DOWN, KEY_ENTER, KEY_SPACE } from '@atlaskit/ds-lib/keycodes';
|
|
14
|
+
import mergeRefs from '@atlaskit/ds-lib/merge-refs';
|
|
15
|
+
import noop from '@atlaskit/ds-lib/noop';
|
|
16
|
+
import useControlledState from '@atlaskit/ds-lib/use-controlled';
|
|
17
|
+
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
18
|
+
import ExpandIcon from '@atlaskit/icon/core/chevron-down';
|
|
19
|
+
import MenuGroup from '@atlaskit/menu/menu-group';
|
|
20
|
+
import Spinner from '@atlaskit/spinner';
|
|
21
|
+
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
22
|
+
import { fromLegacyPlacement } from '@atlaskit/top-layer/placement-map';
|
|
23
|
+
import { Popup } from '@atlaskit/top-layer/popup';
|
|
24
|
+
import SelectionStore from './internal/context/selection-store';
|
|
25
|
+
import { getFirstFocusable, isAtCurrentMenuLevel, useArrowNavigation } from './internal/use-arrow-navigation';
|
|
26
|
+
var MAX_HEIGHT = "calc(100vh - 16px)";
|
|
27
|
+
var styles = {
|
|
28
|
+
spinnerContainer: "_1e0c1txw _1ul91lit _1bah1h6o _1q51v47k _y4tiv47k _85i5v47k _bozgv47k",
|
|
29
|
+
menuContent: "_2rkofajl _bfhk1bhr _16qs130s"
|
|
30
|
+
};
|
|
31
|
+
var animation = slideAndFade();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Event types produced by trigger interactions.
|
|
35
|
+
*
|
|
36
|
+
* - `React.MouseEvent<Element>` — from the trigger's `onClick` handler
|
|
37
|
+
* - `React.KeyboardEvent<Element>` — from the trigger's `onClick` when activated via keyboard
|
|
38
|
+
* - `KeyboardEvent` — native event from the ArrowDown `bind(window, ...)` listener
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Determines whether a trigger interaction was keyboard-initiated.
|
|
43
|
+
*
|
|
44
|
+
* Keyboard signals:
|
|
45
|
+
* - `type === 'keydown'` (native KeyboardEvent from ArrowDown handler)
|
|
46
|
+
* - `clientX/clientY === 0` (assistive technology click)
|
|
47
|
+
* - `detail === 0` (keyboard-activated click via Enter/Space)
|
|
48
|
+
*/
|
|
49
|
+
function isKeyboardTriggered(event) {
|
|
50
|
+
if (event.type === 'keydown') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if ('clientX' in event && (event.clientX === 0 || event.clientY === 0)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (event.detail === 0) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Loading indicator for the dropdown menu.
|
|
64
|
+
*/
|
|
65
|
+
function LoadingIndicator(_ref) {
|
|
66
|
+
var _ref$statusLabel = _ref.statusLabel,
|
|
67
|
+
statusLabel = _ref$statusLabel === void 0 ? 'Loading' : _ref$statusLabel,
|
|
68
|
+
testId = _ref.testId;
|
|
69
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
70
|
+
role: "menuitem",
|
|
71
|
+
className: ax([styles.spinnerContainer])
|
|
72
|
+
}, /*#__PURE__*/React.createElement(Spinner, {
|
|
73
|
+
size: "small",
|
|
74
|
+
label: statusLabel,
|
|
75
|
+
testId: testId
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Top-layer implementation of DropdownMenu.
|
|
81
|
+
*
|
|
82
|
+
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
83
|
+
* with native Popover API via `@atlaskit/top-layer`.
|
|
84
|
+
*
|
|
85
|
+
* What's no longer needed:
|
|
86
|
+
* - Portal: top layer handles stacking natively
|
|
87
|
+
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
88
|
+
* - z-index: top layer is always above everything
|
|
89
|
+
* - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
|
|
90
|
+
* - handle-focus.tsx: replaced by `useArrowNavigation`
|
|
91
|
+
* - Layering context: top layer nesting is handled by the browser
|
|
92
|
+
* - Fallback placements / Popper: CSS Anchor Positioning handles positioning
|
|
93
|
+
*/
|
|
94
|
+
function DropdownMenuTopLayer(_ref2) {
|
|
95
|
+
var _ref6;
|
|
96
|
+
var _ref2$autoFocus = _ref2.autoFocus,
|
|
97
|
+
autoFocus = _ref2$autoFocus === void 0 ? false : _ref2$autoFocus,
|
|
98
|
+
children = _ref2.children,
|
|
99
|
+
_ref2$defaultOpen = _ref2.defaultOpen,
|
|
100
|
+
defaultOpen = _ref2$defaultOpen === void 0 ? false : _ref2$defaultOpen,
|
|
101
|
+
_ref2$isLoading = _ref2.isLoading,
|
|
102
|
+
isLoading = _ref2$isLoading === void 0 ? false : _ref2$isLoading,
|
|
103
|
+
isOpenProp = _ref2.isOpen,
|
|
104
|
+
_ref2$onOpenChange = _ref2.onOpenChange,
|
|
105
|
+
onOpenChange = _ref2$onOpenChange === void 0 ? noop : _ref2$onOpenChange,
|
|
106
|
+
_ref2$placement = _ref2.placement,
|
|
107
|
+
placement = _ref2$placement === void 0 ? 'bottom-start' : _ref2$placement,
|
|
108
|
+
_ref2$shouldFitContai = _ref2.shouldFitContainer,
|
|
109
|
+
shouldFitContainer = _ref2$shouldFitContai === void 0 ? false : _ref2$shouldFitContai,
|
|
110
|
+
returnFocusRef = _ref2.returnFocusRef,
|
|
111
|
+
spacing = _ref2.spacing,
|
|
112
|
+
statusLabel = _ref2.statusLabel,
|
|
113
|
+
testId = _ref2.testId,
|
|
114
|
+
trigger = _ref2.trigger,
|
|
115
|
+
label = _ref2.label,
|
|
116
|
+
interactionName = _ref2.interactionName,
|
|
117
|
+
menuLabel = _ref2.menuLabel;
|
|
118
|
+
var _useControlledState = useControlledState(isOpenProp, function () {
|
|
119
|
+
return defaultOpen;
|
|
120
|
+
}),
|
|
121
|
+
_useControlledState2 = _slicedToArray(_useControlledState, 2),
|
|
122
|
+
isLocalOpen = _useControlledState2[0],
|
|
123
|
+
setLocalIsOpen = _useControlledState2[1];
|
|
124
|
+
var triggerRef = useRef(null);
|
|
125
|
+
var menuRef = useRef(null);
|
|
126
|
+
var _useState = useState(false),
|
|
127
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
128
|
+
isTriggeredUsingKeyboard = _useState2[0],
|
|
129
|
+
setTriggeredUsingKeyboard = _useState2[1];
|
|
130
|
+
var topLayerPlacement = useMemo(function () {
|
|
131
|
+
return fromLegacyPlacement({
|
|
132
|
+
legacy: placement
|
|
133
|
+
});
|
|
134
|
+
}, [placement]);
|
|
135
|
+
|
|
136
|
+
// ── Close handling ──
|
|
137
|
+
// Focus restoration is handled natively by the Popover API:
|
|
138
|
+
// - Escape → browser restores focus to the trigger automatically
|
|
139
|
+
// - Click-outside → browser does NOT restore (correct behavior)
|
|
140
|
+
//
|
|
141
|
+
// The only custom focus handling needed is `returnFocusRef`: when provided,
|
|
142
|
+
// we redirect focus to a different element than the trigger. We do this
|
|
143
|
+
// in the onClose callback via rAF, which runs after the browser's native
|
|
144
|
+
// restoration — effectively overriding it.
|
|
145
|
+
var handleOnClose = useCallback(function (_ref3) {
|
|
146
|
+
var _reason = _ref3.reason;
|
|
147
|
+
if (returnFocusRef) {
|
|
148
|
+
requestAnimationFrame(function () {
|
|
149
|
+
var _returnFocusRef$curre;
|
|
150
|
+
(_returnFocusRef$curre = returnFocusRef.current) === null || _returnFocusRef$curre === void 0 || _returnFocusRef$curre.focus();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
setLocalIsOpen(false);
|
|
154
|
+
onOpenChange({
|
|
155
|
+
isOpen: false,
|
|
156
|
+
event: null
|
|
157
|
+
});
|
|
158
|
+
}, [onOpenChange, returnFocusRef, setLocalIsOpen]);
|
|
159
|
+
|
|
160
|
+
// ── Trigger click handling ──
|
|
161
|
+
var handleTriggerClicked = useCallback(function (event) {
|
|
162
|
+
var newValue = !isLocalOpen;
|
|
163
|
+
setTriggeredUsingKeyboard(isKeyboardTriggered(event));
|
|
164
|
+
setLocalIsOpen(newValue);
|
|
165
|
+
|
|
166
|
+
// Extract the native DOM event for onOpenChange
|
|
167
|
+
var nativeEvent = 'nativeEvent' in event ? event.nativeEvent : event;
|
|
168
|
+
onOpenChange({
|
|
169
|
+
isOpen: newValue,
|
|
170
|
+
event: nativeEvent
|
|
171
|
+
});
|
|
172
|
+
}, [isLocalOpen, setLocalIsOpen, onOpenChange]);
|
|
173
|
+
var _useFocus = useFocus(),
|
|
174
|
+
isFocused = _useFocus.isFocused,
|
|
175
|
+
bindFocus = _useFocus.bindFocus;
|
|
176
|
+
|
|
177
|
+
// When trigger is focused, open dropdown on ArrowDown (top-level only).
|
|
178
|
+
// Per WAI-ARIA, ArrowDown opens a menu from a menubar/button trigger,
|
|
179
|
+
// but inside a vertical submenu, ArrowDown navigates between siblings
|
|
180
|
+
// and ArrowRight opens nested menus instead.
|
|
181
|
+
useEffect(function () {
|
|
182
|
+
var _triggerRef$current;
|
|
183
|
+
if (!isFocused || isLocalOpen) {
|
|
184
|
+
return noop;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Don't open on ArrowDown if this trigger is inside a parent menu.
|
|
188
|
+
// Nested menus should only be opened via ArrowRight or Enter.
|
|
189
|
+
var isNestedTrigger = ((_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.closest('[role="menu"]')) != null;
|
|
190
|
+
return bind(window, {
|
|
191
|
+
type: 'keydown',
|
|
192
|
+
listener: function openOnKeyDown(e) {
|
|
193
|
+
if (e.key === KEY_DOWN && !isNestedTrigger) {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
handleTriggerClicked(e);
|
|
196
|
+
} else if ((e.code === KEY_SPACE || e.key === KEY_ENTER) && e.detail === 0) {
|
|
197
|
+
setTriggeredUsingKeyboard(true);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}, [isFocused, isLocalOpen, handleTriggerClicked]);
|
|
202
|
+
|
|
203
|
+
// ── Arrow navigation ──
|
|
204
|
+
// useArrowNavigation handles ArrowUp/Down, Home/End, and Tab-to-close
|
|
205
|
+
// by querying focusable elements in the menu DOM container.
|
|
206
|
+
var handleArrowClose = useCallback(function () {
|
|
207
|
+
handleOnClose({
|
|
208
|
+
reason: 'escape'
|
|
209
|
+
});
|
|
210
|
+
}, [handleOnClose]);
|
|
211
|
+
var handleNestedOpen = useCallback(function (_ref4) {
|
|
212
|
+
var trigger = _ref4.trigger;
|
|
213
|
+
trigger.click();
|
|
214
|
+
}, []);
|
|
215
|
+
var handleNestedClose = useCallback(function () {
|
|
216
|
+
handleOnClose({
|
|
217
|
+
reason: 'escape'
|
|
218
|
+
});
|
|
219
|
+
}, [handleOnClose]);
|
|
220
|
+
useArrowNavigation({
|
|
221
|
+
containerRef: menuRef,
|
|
222
|
+
onClose: handleArrowClose,
|
|
223
|
+
onNestedOpen: handleNestedOpen,
|
|
224
|
+
onNestedClose: handleNestedClose,
|
|
225
|
+
isEnabled: isLocalOpen,
|
|
226
|
+
filter: isAtCurrentMenuLevel
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ── Auto-focus first item on open ──
|
|
230
|
+
useEffect(function () {
|
|
231
|
+
if (!isLocalOpen || !isTriggeredUsingKeyboard && !autoFocus) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
requestAnimationFrame(function () {
|
|
235
|
+
var menu = menuRef.current;
|
|
236
|
+
if (!menu) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
var firstItem = getFirstFocusable({
|
|
240
|
+
container: menu
|
|
241
|
+
});
|
|
242
|
+
firstItem === null || firstItem === void 0 || firstItem.focus();
|
|
243
|
+
});
|
|
244
|
+
}, [isLocalOpen, isTriggeredUsingKeyboard, autoFocus]);
|
|
245
|
+
|
|
246
|
+
// shouldFitContainer is handled by the width prop on Popup.Content below.
|
|
247
|
+
var popupContentWidth = shouldFitContainer ? 'min-trigger' : 'content';
|
|
248
|
+
|
|
249
|
+
// ── Close on menu item click ──
|
|
250
|
+
// Close when a regular menuitem is clicked, but not checkboxes/radios
|
|
251
|
+
// and not nested triggers (items with aria-haspopup).
|
|
252
|
+
var handleMenuClick = useCallback(function (e) {
|
|
253
|
+
var _target$closest;
|
|
254
|
+
var target = e.target;
|
|
255
|
+
var menuItem = (_target$closest = target.closest) === null || _target$closest === void 0 ? void 0 : _target$closest.call(target, '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]');
|
|
256
|
+
if (!menuItem) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
var isCheckboxOrRadio = menuItem.getAttribute('role') === 'menuitemcheckbox' || menuItem.getAttribute('role') === 'menuitemradio';
|
|
260
|
+
// Don't close the menu when clicking a nested trigger (aria-haspopup).
|
|
261
|
+
// The nested dropdown will handle its own open/close.
|
|
262
|
+
var isNestedTrigger = menuItem.hasAttribute('aria-haspopup');
|
|
263
|
+
if (!isCheckboxOrRadio && !isNestedTrigger) {
|
|
264
|
+
setLocalIsOpen(false);
|
|
265
|
+
onOpenChange({
|
|
266
|
+
isOpen: false,
|
|
267
|
+
event: e.nativeEvent
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}, [setLocalIsOpen, onOpenChange]);
|
|
271
|
+
return /*#__PURE__*/React.createElement(SelectionStore, null, /*#__PURE__*/React.createElement(Popup, {
|
|
272
|
+
placement: topLayerPlacement,
|
|
273
|
+
onClose: handleOnClose
|
|
274
|
+
}, /*#__PURE__*/React.createElement(Popup.TriggerFunction, null, function (_ref5) {
|
|
275
|
+
var ref = _ref5.ref,
|
|
276
|
+
_toggle = _ref5.toggle,
|
|
277
|
+
ariaAttributes = _ref5.ariaAttributes;
|
|
278
|
+
var combinedRef = mergeRefs([ref, triggerRef]);
|
|
279
|
+
|
|
280
|
+
// FUDGE(top-layer-api): cast `ariaAttributes` to the narrow shape that adopter
|
|
281
|
+
// public types expect. `@atlaskit/top-layer` types `aria-haspopup` as the wider
|
|
282
|
+
// WAI-ARIA union (boolean | 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'),
|
|
283
|
+
// but the public `CustomTriggerProps` (extending `@atlaskit/popup` `TriggerProps`)
|
|
284
|
+
// is intentionally kept narrow (boolean | 'dialog') because the top-layer API
|
|
285
|
+
// surface is not yet settled. The runtime value is unchanged; only the
|
|
286
|
+
// TypeScript-visible type is narrowed at this boundary.
|
|
287
|
+
// REMOVE WHEN: the top-layer public API is committed (see
|
|
288
|
+
// packages/design-system/top-layer/notes/decisions/migration-roadmap.md "Open API
|
|
289
|
+
// decisions deferred to a follow-up PR") and a follow-up `minor` PR widens
|
|
290
|
+
// `TriggerProps['aria-haspopup']` on `@atlaskit/popup` to match.
|
|
291
|
+
var narrowAriaAttributes = ariaAttributes;
|
|
292
|
+
if (typeof trigger === 'function') {
|
|
293
|
+
return trigger(_objectSpread(_objectSpread(_objectSpread({}, narrowAriaAttributes), bindFocus), {}, {
|
|
294
|
+
triggerRef: combinedRef,
|
|
295
|
+
isSelected: isLocalOpen,
|
|
296
|
+
onClick: handleTriggerClicked,
|
|
297
|
+
testId: testId && "".concat(testId, "--trigger")
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
return /*#__PURE__*/React.createElement(Button, _extends({}, bindFocus, {
|
|
301
|
+
ref: combinedRef
|
|
302
|
+
}, narrowAriaAttributes, {
|
|
303
|
+
isSelected: isLocalOpen,
|
|
304
|
+
iconAfter: function iconAfter(iconProps) {
|
|
305
|
+
return /*#__PURE__*/React.createElement(ExpandIcon, _extends({}, iconProps, {
|
|
306
|
+
size: "small"
|
|
307
|
+
}));
|
|
308
|
+
},
|
|
309
|
+
onClick: handleTriggerClicked,
|
|
310
|
+
testId: testId && "".concat(testId, "--trigger"),
|
|
311
|
+
"aria-label": label,
|
|
312
|
+
interactionName: interactionName
|
|
313
|
+
}), trigger);
|
|
314
|
+
}), /*#__PURE__*/React.createElement(Popup.Content, {
|
|
315
|
+
role: "menu",
|
|
316
|
+
label: (_ref6 = menuLabel !== null && menuLabel !== void 0 ? menuLabel : label) !== null && _ref6 !== void 0 ? _ref6 : typeof trigger === 'string' ? trigger : 'Menu',
|
|
317
|
+
isOpen: isLocalOpen,
|
|
318
|
+
animate: animation,
|
|
319
|
+
width: popupContentWidth,
|
|
320
|
+
testId: testId && "".concat(testId, "--content")
|
|
321
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
322
|
+
ref: menuRef,
|
|
323
|
+
className: ax([styles.menuContent])
|
|
324
|
+
}, /*#__PURE__*/React.createElement(MenuGroup, {
|
|
325
|
+
isLoading: isLoading,
|
|
326
|
+
maxHeight: MAX_HEIGHT,
|
|
327
|
+
maxWidth: shouldFitContainer ? undefined : 800,
|
|
328
|
+
onClick: handleMenuClick,
|
|
329
|
+
role: "menu",
|
|
330
|
+
spacing: spacing,
|
|
331
|
+
testId: testId && "".concat(testId, "--menu-wrapper--menu-group"),
|
|
332
|
+
menuLabel: menuLabel
|
|
333
|
+
}, isLoading ? /*#__PURE__*/React.createElement(LoadingIndicator, {
|
|
334
|
+
statusLabel: statusLabel,
|
|
335
|
+
testId: testId && "".concat(testId, "--menu-wrapper--loading-indicator")
|
|
336
|
+
}) : children)))));
|
|
337
|
+
}
|
|
338
|
+
export default DropdownMenuTopLayer;
|
|
@@ -15,9 +15,11 @@ import useControlledState from '@atlaskit/ds-lib/use-controlled';
|
|
|
15
15
|
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
16
16
|
import ExpandIcon from '@atlaskit/icon/core/chevron-down';
|
|
17
17
|
import { useLayering } from '@atlaskit/layering';
|
|
18
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
18
19
|
import Popup from '@atlaskit/popup';
|
|
19
20
|
// eslint-disable-next-line @atlaskit/design-system/no-deprecated-imports
|
|
20
21
|
import { layers } from '@atlaskit/theme/constants';
|
|
22
|
+
import DropdownMenuTopLayer from './dropdown-menu-top-layer';
|
|
21
23
|
import FocusManager from './internal/components/focus-manager';
|
|
22
24
|
import MenuWrapper from './internal/components/menu-wrapper';
|
|
23
25
|
import SelectionStore from './internal/context/selection-store';
|
|
@@ -56,15 +58,9 @@ function isKeyboardEvent(event) {
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* A dropdown menu displays a list of actions or options to a user.
|
|
62
|
-
*
|
|
63
|
-
* - [Examples](https://atlassian.design/components/dropdown-menu/examples)
|
|
64
|
-
* - [Code](https://atlassian.design/components/dropdown-menu/code)
|
|
65
|
-
* - [Usage](https://atlassian.design/components/dropdown-menu/usage)
|
|
61
|
+
* Legacy Popper/Popup implementation (hooks run unconditionally when this component mounts).
|
|
66
62
|
*/
|
|
67
|
-
|
|
63
|
+
function DropdownMenuLegacy(_ref) {
|
|
68
64
|
var _ref$autoFocus = _ref.autoFocus,
|
|
69
65
|
autoFocus = _ref$autoFocus === void 0 ? false : _ref$autoFocus,
|
|
70
66
|
children = _ref.children,
|
|
@@ -336,5 +332,63 @@ var DropdownMenu = function DropdownMenu(_ref) {
|
|
|
336
332
|
}, children));
|
|
337
333
|
}
|
|
338
334
|
})));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* __Dropdown menu__
|
|
339
|
+
*
|
|
340
|
+
* A dropdown menu displays a list of actions or options to a user.
|
|
341
|
+
*
|
|
342
|
+
* - [Examples](https://atlassian.design/components/dropdown-menu/examples)
|
|
343
|
+
* - [Code](https://atlassian.design/components/dropdown-menu/code)
|
|
344
|
+
* - [Usage](https://atlassian.design/components/dropdown-menu/usage)
|
|
345
|
+
*/
|
|
346
|
+
var DropdownMenu = function DropdownMenu(props) {
|
|
347
|
+
var _props$autoFocus = props.autoFocus,
|
|
348
|
+
autoFocus = _props$autoFocus === void 0 ? false : _props$autoFocus,
|
|
349
|
+
children = props.children,
|
|
350
|
+
_props$defaultOpen = props.defaultOpen,
|
|
351
|
+
defaultOpen = _props$defaultOpen === void 0 ? false : _props$defaultOpen,
|
|
352
|
+
_props$isLoading = props.isLoading,
|
|
353
|
+
isLoading = _props$isLoading === void 0 ? false : _props$isLoading,
|
|
354
|
+
isOpen = props.isOpen,
|
|
355
|
+
_props$onOpenChange = props.onOpenChange,
|
|
356
|
+
onOpenChange = _props$onOpenChange === void 0 ? noop : _props$onOpenChange,
|
|
357
|
+
_props$placement = props.placement,
|
|
358
|
+
placement = _props$placement === void 0 ? 'bottom-start' : _props$placement,
|
|
359
|
+
_props$shouldFitConta = props.shouldFitContainer,
|
|
360
|
+
shouldFitContainer = _props$shouldFitConta === void 0 ? false : _props$shouldFitConta,
|
|
361
|
+
returnFocusRef = props.returnFocusRef,
|
|
362
|
+
spacing = props.spacing,
|
|
363
|
+
statusLabel = props.statusLabel,
|
|
364
|
+
testId = props.testId,
|
|
365
|
+
trigger = props.trigger,
|
|
366
|
+
label = props.label,
|
|
367
|
+
interactionName = props.interactionName,
|
|
368
|
+
menuLabel = props.menuLabel;
|
|
369
|
+
if (fg('platform-dst-top-layer')) {
|
|
370
|
+
return /*#__PURE__*/React.createElement(DropdownMenuTopLayer, {
|
|
371
|
+
autoFocus: autoFocus,
|
|
372
|
+
children: children,
|
|
373
|
+
defaultOpen: defaultOpen,
|
|
374
|
+
isLoading: isLoading,
|
|
375
|
+
isOpen: isOpen,
|
|
376
|
+
onOpenChange: onOpenChange,
|
|
377
|
+
placement: placement,
|
|
378
|
+
shouldFitContainer: shouldFitContainer,
|
|
379
|
+
returnFocusRef: returnFocusRef,
|
|
380
|
+
spacing: spacing,
|
|
381
|
+
statusLabel: statusLabel,
|
|
382
|
+
testId: testId,
|
|
383
|
+
trigger: trigger,
|
|
384
|
+
label: label,
|
|
385
|
+
interactionName: interactionName,
|
|
386
|
+
menuLabel: menuLabel
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Forward full public props to the legacy Popper/Popup implementation unchanged.
|
|
391
|
+
// eslint-disable-next-line @repo/internal/react/no-unsafe-spread-props -- wrapper delegates entire DropdownMenuProps API
|
|
392
|
+
return /*#__PURE__*/React.createElement(DropdownMenuLegacy, props);
|
|
339
393
|
};
|
|
340
394
|
export default DropdownMenu;
|
|
@@ -10,7 +10,12 @@ var styles = {
|
|
|
10
10
|
/**
|
|
11
11
|
* __Group title__
|
|
12
12
|
*
|
|
13
|
-
* Used to visually represent the title for DropdownMenu groups
|
|
13
|
+
* Used to visually represent the title for DropdownMenu groups.
|
|
14
|
+
*
|
|
15
|
+
* Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
|
|
16
|
+
* WAI-ARIA APG recommends `role="presentation"` for non-interactive group
|
|
17
|
+
* headings, with the group linked via `aria-labelledby`. Out of scope for
|
|
18
|
+
* the top-layer migration — this is legacy behavior.
|
|
14
19
|
*
|
|
15
20
|
* @internal
|
|
16
21
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useArrowNavigation, isAtCurrentMenuLevel } from '@atlaskit/top-layer/use-arrow-navigation';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DropdownMenuProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Top-layer implementation of DropdownMenu.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
6
|
+
* with native Popover API via `@atlaskit/top-layer`.
|
|
7
|
+
*
|
|
8
|
+
* What's no longer needed:
|
|
9
|
+
* - Portal: top layer handles stacking natively
|
|
10
|
+
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
11
|
+
* - z-index: top layer is always above everything
|
|
12
|
+
* - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
|
|
13
|
+
* - handle-focus.tsx: replaced by `useArrowNavigation`
|
|
14
|
+
* - Layering context: top layer nesting is handled by the browser
|
|
15
|
+
* - Fallback placements / Popper: CSS Anchor Positioning handles positioning
|
|
16
|
+
*/
|
|
17
|
+
declare function DropdownMenuTopLayer({ autoFocus, children, defaultOpen, isLoading, isOpen: isOpenProp, onOpenChange, placement, shouldFitContainer, returnFocusRef, spacing, statusLabel, testId, trigger, label, interactionName, menuLabel, }: DropdownMenuProps): React.JSX.Element;
|
|
18
|
+
export default DropdownMenuTopLayer;
|
|
@@ -9,5 +9,5 @@ import type { DropdownMenuProps } from './types';
|
|
|
9
9
|
* - [Code](https://atlassian.design/components/dropdown-menu/code)
|
|
10
10
|
* - [Usage](https://atlassian.design/components/dropdown-menu/usage)
|
|
11
11
|
*/
|
|
12
|
-
declare const DropdownMenu: <T extends HTMLElement = any>(
|
|
12
|
+
declare const DropdownMenu: <T extends HTMLElement = any>(props: DropdownMenuProps<T>) => React.JSX.Element;
|
|
13
13
|
export default DropdownMenu;
|
|
@@ -2,7 +2,12 @@ import React from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* __Group title__
|
|
4
4
|
*
|
|
5
|
-
* Used to visually represent the title for DropdownMenu groups
|
|
5
|
+
* Used to visually represent the title for DropdownMenu groups.
|
|
6
|
+
*
|
|
7
|
+
* Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
|
|
8
|
+
* WAI-ARIA APG recommends `role="presentation"` for non-interactive group
|
|
9
|
+
* headings, with the group linked via `aria-labelledby`. Out of scope for
|
|
10
|
+
* the top-layer migration — this is legacy behavior.
|
|
6
11
|
*
|
|
7
12
|
* @internal
|
|
8
13
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs, } from '@atlaskit/top-layer/use-arrow-navigation';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -114,6 +114,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
114
114
|
/**
|
|
115
115
|
* Allows the dropdown menu to be placed on the opposite side of its trigger if it does not
|
|
116
116
|
* fit in the viewport.
|
|
117
|
+
*
|
|
118
|
+
* @private
|
|
119
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
|
|
120
|
+
* handles flipping natively via `position-try-fallbacks`.
|
|
117
121
|
*/
|
|
118
122
|
shouldFlip?: boolean;
|
|
119
123
|
/**
|
|
@@ -121,6 +125,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
121
125
|
* `true` renders the dropdown menu in the DOM node closest to the trigger; focus is not trapped inside the element.
|
|
122
126
|
* `false` renders the dropdown menu in React.Portal and focus is trapped inside the element.
|
|
123
127
|
* Defaults to `false`.
|
|
128
|
+
*
|
|
129
|
+
* @private
|
|
130
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — content always
|
|
131
|
+
* renders in the browser's top layer.
|
|
124
132
|
*/
|
|
125
133
|
shouldRenderToParent?: boolean;
|
|
126
134
|
/**
|
|
@@ -162,6 +170,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
162
170
|
* Z-index that the popup should be displayed in.
|
|
163
171
|
* This is passed to the portal component.
|
|
164
172
|
* Defaults to `layers.modal()` from `@atlaskit/theme` which is 510.
|
|
173
|
+
*
|
|
174
|
+
* @private
|
|
175
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — the browser's
|
|
176
|
+
* top layer manages stacking without z-index.
|
|
165
177
|
*/
|
|
166
178
|
zIndex?: number;
|
|
167
179
|
/**
|
|
@@ -179,6 +191,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
179
191
|
/**
|
|
180
192
|
* This controls the positioning strategy to use. Can vary between `absolute` and `fixed`.
|
|
181
193
|
* The default is `fixed`.
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
|
|
197
|
+
* replaces Popper's positioning strategy.
|
|
182
198
|
*/
|
|
183
199
|
strategy?: 'absolute' | 'fixed';
|
|
184
200
|
/**
|
|
@@ -188,6 +204,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
188
204
|
/**
|
|
189
205
|
* When set to true, will call stopPropagation on the ESCAPE key event.
|
|
190
206
|
* This prevents the ESCAPE event from bubbling up to parent elements.
|
|
207
|
+
*
|
|
208
|
+
* @private
|
|
209
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — the native
|
|
210
|
+
* popover dismiss handles Escape propagation.
|
|
191
211
|
*/
|
|
192
212
|
shouldPreventEscapePropagation?: boolean;
|
|
193
213
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DropdownMenuProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Top-layer implementation of DropdownMenu.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the legacy `@atlaskit/popup` + `@atlaskit/portal` + `@atlaskit/layering` pipeline
|
|
6
|
+
* with native Popover API via `@atlaskit/top-layer`.
|
|
7
|
+
*
|
|
8
|
+
* What's no longer needed:
|
|
9
|
+
* - Portal: top layer handles stacking natively
|
|
10
|
+
* - FocusLock / react-focus-lock: popover=auto provides light dismiss
|
|
11
|
+
* - z-index: top layer is always above everything
|
|
12
|
+
* - FocusManager (ref registration): replaced by DOM-query-based `useArrowNavigation`
|
|
13
|
+
* - handle-focus.tsx: replaced by `useArrowNavigation`
|
|
14
|
+
* - Layering context: top layer nesting is handled by the browser
|
|
15
|
+
* - Fallback placements / Popper: CSS Anchor Positioning handles positioning
|
|
16
|
+
*/
|
|
17
|
+
declare function DropdownMenuTopLayer({ autoFocus, children, defaultOpen, isLoading, isOpen: isOpenProp, onOpenChange, placement, shouldFitContainer, returnFocusRef, spacing, statusLabel, testId, trigger, label, interactionName, menuLabel, }: DropdownMenuProps): React.JSX.Element;
|
|
18
|
+
export default DropdownMenuTopLayer;
|
|
@@ -9,5 +9,5 @@ import type { DropdownMenuProps } from './types';
|
|
|
9
9
|
* - [Code](https://atlassian.design/components/dropdown-menu/code)
|
|
10
10
|
* - [Usage](https://atlassian.design/components/dropdown-menu/usage)
|
|
11
11
|
*/
|
|
12
|
-
declare const DropdownMenu: <T extends HTMLElement = any>(
|
|
12
|
+
declare const DropdownMenu: <T extends HTMLElement = any>(props: DropdownMenuProps<T>) => React.JSX.Element;
|
|
13
13
|
export default DropdownMenu;
|
|
@@ -2,7 +2,12 @@ import React from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* __Group title__
|
|
4
4
|
*
|
|
5
|
-
* Used to visually represent the title for DropdownMenu groups
|
|
5
|
+
* Used to visually represent the title for DropdownMenu groups.
|
|
6
|
+
*
|
|
7
|
+
* Pre-existing a11y note: uses `role="menuitem"` with `aria-hidden="true"`.
|
|
8
|
+
* WAI-ARIA APG recommends `role="presentation"` for non-interactive group
|
|
9
|
+
* headings, with the group linked via `aria-labelledby`. Out of scope for
|
|
10
|
+
* the top-layer migration — this is legacy behavior.
|
|
6
11
|
*
|
|
7
12
|
* @internal
|
|
8
13
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useArrowNavigation, isAtCurrentMenuLevel, type TUseArrowNavigationArgs, } from '@atlaskit/top-layer/use-arrow-navigation';
|
|
@@ -114,6 +114,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
114
114
|
/**
|
|
115
115
|
* Allows the dropdown menu to be placed on the opposite side of its trigger if it does not
|
|
116
116
|
* fit in the viewport.
|
|
117
|
+
*
|
|
118
|
+
* @private
|
|
119
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
|
|
120
|
+
* handles flipping natively via `position-try-fallbacks`.
|
|
117
121
|
*/
|
|
118
122
|
shouldFlip?: boolean;
|
|
119
123
|
/**
|
|
@@ -121,6 +125,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
121
125
|
* `true` renders the dropdown menu in the DOM node closest to the trigger; focus is not trapped inside the element.
|
|
122
126
|
* `false` renders the dropdown menu in React.Portal and focus is trapped inside the element.
|
|
123
127
|
* Defaults to `false`.
|
|
128
|
+
*
|
|
129
|
+
* @private
|
|
130
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — content always
|
|
131
|
+
* renders in the browser's top layer.
|
|
124
132
|
*/
|
|
125
133
|
shouldRenderToParent?: boolean;
|
|
126
134
|
/**
|
|
@@ -162,6 +170,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
162
170
|
* Z-index that the popup should be displayed in.
|
|
163
171
|
* This is passed to the portal component.
|
|
164
172
|
* Defaults to `layers.modal()` from `@atlaskit/theme` which is 510.
|
|
173
|
+
*
|
|
174
|
+
* @private
|
|
175
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — the browser's
|
|
176
|
+
* top layer manages stacking without z-index.
|
|
165
177
|
*/
|
|
166
178
|
zIndex?: number;
|
|
167
179
|
/**
|
|
@@ -179,6 +191,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
179
191
|
/**
|
|
180
192
|
* This controls the positioning strategy to use. Can vary between `absolute` and `fixed`.
|
|
181
193
|
* The default is `fixed`.
|
|
194
|
+
*
|
|
195
|
+
* @private
|
|
196
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — CSS Anchor Positioning
|
|
197
|
+
* replaces Popper's positioning strategy.
|
|
182
198
|
*/
|
|
183
199
|
strategy?: 'absolute' | 'fixed';
|
|
184
200
|
/**
|
|
@@ -188,6 +204,10 @@ interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> {
|
|
|
188
204
|
/**
|
|
189
205
|
* When set to true, will call stopPropagation on the ESCAPE key event.
|
|
190
206
|
* This prevents the ESCAPE event from bubbling up to parent elements.
|
|
207
|
+
*
|
|
208
|
+
* @private
|
|
209
|
+
* @deprecated No-op when `platform-dst-top-layer` is enabled — the native
|
|
210
|
+
* popover dismiss handles Escape propagation.
|
|
191
211
|
*/
|
|
192
212
|
shouldPreventEscapePropagation?: boolean;
|
|
193
213
|
}
|