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