@spaced-out/ui-design-system 0.3.46 → 0.3.47-beta.0

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/components/AvatarGroup/AvatarGroup.js.flow +2 -2
  3. package/lib/components/ButtonDropdown/ButtonDropdown.js +22 -10
  4. package/lib/components/ButtonDropdown/ButtonDropdown.js.flow +58 -33
  5. package/lib/components/ButtonDropdown/ButtonDropdown.module.css +8 -1
  6. package/lib/components/ButtonDropdown/SimpleButtonDropdown.js +3 -0
  7. package/lib/components/ButtonDropdown/SimpleButtonDropdown.js.flow +6 -1
  8. package/lib/components/ButtonTabs/ButtonTabDropdown.js +13 -6
  9. package/lib/components/ButtonTabs/ButtonTabDropdown.js.flow +40 -24
  10. package/lib/components/ButtonTabs/ButtonTabDropdown.module.css +4 -0
  11. package/lib/components/ButtonTabs/ButtonTabs.js +2 -0
  12. package/lib/components/ButtonTabs/ButtonTabs.js.flow +4 -0
  13. package/lib/components/Dropdown/Dropdown.js +23 -8
  14. package/lib/components/Dropdown/Dropdown.js.flow +57 -32
  15. package/lib/components/Dropdown/Dropdown.module.css +5 -0
  16. package/lib/components/Dropdown/SimpleDropdown.js +2 -0
  17. package/lib/components/Dropdown/SimpleDropdown.js.flow +4 -0
  18. package/lib/components/InlineDropdown/InlineDropdown.js +25 -8
  19. package/lib/components/InlineDropdown/InlineDropdown.js.flow +57 -30
  20. package/lib/components/InlineDropdown/InlineDropdown.module.css +5 -0
  21. package/lib/components/InlineDropdown/SimpleInlineDropdown.js +2 -0
  22. package/lib/components/InlineDropdown/SimpleInlineDropdown.js.flow +4 -0
  23. package/lib/components/Table/StaticTable.js +1 -1
  24. package/lib/components/Table/StaticTable.js.flow +1 -1
  25. package/lib/components/Table/Table.docs.js +1 -1
  26. package/lib/components/Table/Table.docs.js.flow +1 -1
  27. package/lib/components/Tabs/TabList/TabDropdown.js +14 -7
  28. package/lib/components/Tabs/TabList/TabDropdown.js.flow +38 -22
  29. package/lib/components/Tabs/TabList/TabDropdown.module.css +4 -0
  30. package/lib/components/Tabs/TabList/TabList.js +4 -2
  31. package/lib/components/Tabs/TabList/TabList.js.flow +4 -0
  32. package/lib/components/TokenListInput/TokenListInput.js +26 -7
  33. package/lib/components/TokenListInput/TokenListInput.js.flow +58 -32
  34. package/lib/components/TokenListInput/TokenListInput.module.css +5 -0
  35. package/lib/components/Tooltip/Tooltip.js +2 -1
  36. package/lib/components/Tooltip/Tooltip.js.flow +2 -2
  37. package/lib/components/Typeahead/SimpleTypeahead.js +3 -1
  38. package/lib/components/Typeahead/SimpleTypeahead.js.flow +4 -0
  39. package/lib/components/Typeahead/Typeahead.js +25 -8
  40. package/lib/components/Typeahead/Typeahead.js.flow +58 -30
  41. package/lib/components/Typeahead/Typeahead.module.css +5 -0
  42. package/lib/hooks/index.js +11 -0
  43. package/lib/hooks/index.js.flow +1 -0
  44. package/lib/hooks/useReferenceElementWidth/index.js +16 -0
  45. package/lib/hooks/useReferenceElementWidth/index.js.flow +3 -0
  46. package/lib/hooks/useReferenceElementWidth/useReferenceElementWidth.js +21 -0
  47. package/lib/hooks/useReferenceElementWidth/useReferenceElementWidth.js.flow +23 -0
  48. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.3.47-beta.0](https://github.com/spaced-out/ui-design-system/compare/v0.3.46...v0.3.47-beta.0) (2025-06-20)
6
+
7
+
8
+ ### Features
9
+
10
+ * **GDS-524:** render dropdown menus via portal for improved positioning ([#353](https://github.com/spaced-out/ui-design-system/issues/353)) ([3421218](https://github.com/spaced-out/ui-design-system/commit/342121840775dfa142307858f4d8ced40c311bfd))
11
+
5
12
  ### [0.3.46](https://github.com/spaced-out/ui-design-system/compare/v0.3.45...v0.3.46) (2025-06-17)
6
13
 
7
14
 
@@ -11,7 +11,7 @@ import {
11
11
  import classify from '../../utils/classify';
12
12
  import type {AvatarSize} from '../Avatar';
13
13
  import {BaseAvatar} from '../Avatar';
14
- import type {PlacementType} from '../Tooltip';
14
+ import type {ElevationType, PlacementType} from '../Tooltip';
15
15
  import {Tooltip} from '../Tooltip';
16
16
 
17
17
  import css from './AvatarGroup.module.css';
@@ -26,7 +26,7 @@ export type AvatarGroupProps = {
26
26
  maxTooltipLines?: number,
27
27
  placement?: PlacementType,
28
28
  maxAvatars?: number,
29
- tooltipElevation?: string,
29
+ tooltipElevation?: ElevationType,
30
30
  };
31
31
 
32
32
  export const AvatarGroup: React$AbstractComponent<
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.STRATEGY_TYPE = exports.ButtonDropdown = exports.ANCHOR_POSITION_TYPE = void 0;
7
7
  var React = _interopRequireWildcard(require("react"));
8
8
  var _react2 = require("@floating-ui/react");
9
- var _size = require("../../styles/variables/_size");
9
+ var _hooks = require("../../hooks");
10
10
  var _space = require("../../styles/variables/_space");
11
11
  var _classify = require("../../utils/classify");
12
12
  var _clickAway = require("../../utils/click-away");
@@ -48,17 +48,17 @@ const ButtonDropdown = exports.ButtonDropdown = /*#__PURE__*/React.forwardRef((_
48
48
  isFluid,
49
49
  tooltip,
50
50
  onClick,
51
+ elevation = 'modal',
51
52
  clickAwayRef,
52
53
  ...restButtonProps
53
54
  } = _ref;
54
- const menuBtnRef = React.useRef(null);
55
55
  const [isMenuOpen, setIsMenuOpen] = React.useState(false);
56
- React.useImperativeHandle(forwardRef, () => menuBtnRef.current);
57
56
  const {
58
57
  x,
59
58
  y,
60
59
  refs,
61
- strategy
60
+ strategy,
61
+ context
62
62
  } = (0, _react2.useFloating)({
63
63
  open: true,
64
64
  strategy: positionStrategy,
@@ -66,6 +66,7 @@ const ButtonDropdown = exports.ButtonDropdown = /*#__PURE__*/React.forwardRef((_
66
66
  whileElementsMounted: _react2.autoUpdate,
67
67
  middleware: [(0, _react2.shift)(), (0, _react2.flip)(), (0, _react2.offset)(parseInt(_space.spaceXXSmall))]
68
68
  });
69
+ const dropdownWidth = (0, _hooks.useReferenceElementWidth)(refs.reference?.current);
69
70
  const onMenuToggle = isOpen => {
70
71
  if (isOpen) {
71
72
  onMenuOpen?.();
@@ -91,7 +92,7 @@ const ButtonDropdown = exports.ButtonDropdown = /*#__PURE__*/React.forwardRef((_
91
92
  className: (0, _classify.classify)(_ButtonDropdownModule.default.buttonDropdownContainer, {
92
93
  [_ButtonDropdownModule.default.isFluid]: isFluid === true
93
94
  }, classNames?.dropdownContainer),
94
- ref: menuBtnRef
95
+ ref: forwardRef
95
96
  }, /*#__PURE__*/React.createElement(_ConditionalWrapper.ConditionalWrapper, {
96
97
  condition: Boolean(tooltip),
97
98
  wrapper: children => /*#__PURE__*/React.createElement(_Tooltip.Tooltip, _extends({}, tooltip, {
@@ -113,16 +114,27 @@ const ButtonDropdown = exports.ButtonDropdown = /*#__PURE__*/React.forwardRef((_
113
114
  wrapper: classNames?.buttonWrapper,
114
115
  icon: classNames?.buttonIcon
115
116
  }
116
- }), children)), isOpen && menu && /*#__PURE__*/React.createElement("div", {
117
+ }), children)), isOpen && menu && /*#__PURE__*/React.createElement(_react2.FloatingPortal, null, /*#__PURE__*/React.createElement(_react2.FloatingFocusManager, {
118
+ modal: false,
119
+ context: context,
120
+ initialFocus: refs.reference
121
+ }, /*#__PURE__*/React.createElement("div", {
122
+ className: _ButtonDropdownModule.default.menuWrapper,
117
123
  ref: (0, _mergeRefs.mergeRefs)([refs.setFloating, boundaryRef]),
118
124
  style: {
119
125
  display: 'flex',
120
126
  position: strategy,
121
127
  top: y ?? _space.spaceNone,
122
128
  left: x ?? _space.spaceNone,
123
- ...(isFluid && {
124
- width: _size.sizeFluid
125
- })
129
+ /* NOTE(Sharad): The FloatingPortal renders the menu outside the normal DOM structure,
130
+ so its parent is effectively the <body> element. This means the menu
131
+ would otherwise default to the body's width. To support fluid width,
132
+ we must manually set the dropdown width here; otherwise, it uses a fixed width.
133
+ Also, Only treat menu as non-fluid if isFluid is strictly false, since default is true in menu and undefined means fluid. */
134
+ ...(menu.isFluid !== false && {
135
+ '--dropdown-width': dropdownWidth
136
+ }),
137
+ '--menu-elevation': (0, _Tooltip.getElevationValue)(elevation)
126
138
  }
127
139
  }, /*#__PURE__*/React.createElement(_Menu.Menu, _extends({}, menu, {
128
140
  onSelect: (option, e) => {
@@ -135,6 +147,6 @@ const ButtonDropdown = exports.ButtonDropdown = /*#__PURE__*/React.forwardRef((_
135
147
  },
136
148
  size: menu.size || size,
137
149
  onTabOut: clickAway
138
- }))));
150
+ }))))));
139
151
  });
140
152
  });
@@ -7,6 +7,10 @@ import {
7
7
  // $FlowFixMe[untyped-import]
8
8
  flip,
9
9
  // $FlowFixMe[untyped-import]
10
+ FloatingFocusManager,
11
+ // $FlowFixMe[untyped-import]
12
+ FloatingPortal,
13
+ // $FlowFixMe[untyped-import]
10
14
  offset,
11
15
  // $FlowFixMe[untyped-import]
12
16
  shift,
@@ -14,7 +18,7 @@ import {
14
18
  useFloating,
15
19
  } from '@floating-ui/react';
16
20
 
17
- import {sizeFluid} from '../../styles/variables/_size';
21
+ import {useReferenceElementWidth} from '../../hooks';
18
22
  import {spaceNone, spaceXXSmall} from '../../styles/variables/_space';
19
23
  import {classify} from '../../utils/classify';
20
24
  import {type ClickAwayRefType, ClickAway} from '../../utils/click-away';
@@ -24,8 +28,8 @@ import {Button} from '../Button';
24
28
  import {ConditionalWrapper} from '../ConditionalWrapper';
25
29
  import type {MenuOption, MenuProps} from '../Menu';
26
30
  import {Menu} from '../Menu';
27
- import type {BaseTooltipProps} from '../Tooltip';
28
- import {Tooltip} from '../Tooltip';
31
+ import type {BaseTooltipProps, ElevationType} from '../Tooltip';
32
+ import {getElevationValue, Tooltip} from '../Tooltip';
29
33
 
30
34
  import css from './ButtonDropdown.module.css';
31
35
 
@@ -63,6 +67,7 @@ export type ButtonDropdownProps = {
63
67
  onMenuOpen?: () => mixed,
64
68
  onMenuClose?: () => mixed,
65
69
  tooltip?: BaseTooltipProps,
70
+ elevation?: ElevationType,
66
71
  clickAwayRef?: ClickAwayRefType,
67
72
  ...
68
73
  };
@@ -88,15 +93,15 @@ export const ButtonDropdown: React$AbstractComponent<
88
93
  isFluid,
89
94
  tooltip,
90
95
  onClick,
96
+ elevation = 'modal',
91
97
  clickAwayRef,
92
98
  ...restButtonProps
93
99
  }: ButtonDropdownProps,
94
100
  forwardRef,
95
101
  ) => {
96
- const menuBtnRef = React.useRef(null);
97
102
  const [isMenuOpen, setIsMenuOpen] = React.useState(false);
98
- React.useImperativeHandle(forwardRef, () => menuBtnRef.current);
99
- const {x, y, refs, strategy} = useFloating({
103
+
104
+ const {x, y, refs, strategy, context} = useFloating({
100
105
  open: true,
101
106
  strategy: positionStrategy,
102
107
  placement: anchorPosition,
@@ -104,6 +109,8 @@ export const ButtonDropdown: React$AbstractComponent<
104
109
  middleware: [shift(), flip(), offset(parseInt(spaceXXSmall))],
105
110
  });
106
111
 
112
+ const dropdownWidth = useReferenceElementWidth(refs.reference?.current);
113
+
107
114
  const onMenuToggle = (isOpen: boolean) => {
108
115
  if (isOpen) {
109
116
  onMenuOpen?.();
@@ -126,7 +133,7 @@ export const ButtonDropdown: React$AbstractComponent<
126
133
  },
127
134
  classNames?.dropdownContainer,
128
135
  )}
129
- ref={menuBtnRef}
136
+ ref={forwardRef}
130
137
  >
131
138
  <ConditionalWrapper
132
139
  condition={Boolean(tooltip)}
@@ -166,32 +173,50 @@ export const ButtonDropdown: React$AbstractComponent<
166
173
  </ConditionalWrapper>
167
174
 
168
175
  {isOpen && menu && (
169
- <div
170
- ref={mergeRefs([refs.setFloating, boundaryRef])}
171
- style={{
172
- display: 'flex',
173
- position: strategy,
174
- top: y ?? spaceNone,
175
- left: x ?? spaceNone,
176
- ...(isFluid && {width: sizeFluid}),
177
- }}
178
- >
179
- <Menu
180
- {...menu}
181
- onSelect={(option, e) => {
182
- onOptionSelect && onOptionSelect(option, e);
183
- if (
184
- // option.keepMenuOpenOnOptionSelect - to allow the menu persist its open stat upon option selection in normal variant
185
- !option.keepMenuOpenOnOptionSelect &&
186
- (!menu.optionsVariant || menu.optionsVariant === 'normal')
187
- ) {
188
- clickAway();
189
- }
190
- }}
191
- size={menu.size || size}
192
- onTabOut={clickAway}
193
- />
194
- </div>
176
+ <FloatingPortal>
177
+ <FloatingFocusManager
178
+ modal={false}
179
+ context={context}
180
+ initialFocus={refs.reference}
181
+ >
182
+ <div
183
+ className={css.menuWrapper}
184
+ ref={mergeRefs([refs.setFloating, boundaryRef])}
185
+ style={{
186
+ display: 'flex',
187
+ position: strategy,
188
+ top: y ?? spaceNone,
189
+ left: x ?? spaceNone,
190
+ /* NOTE(Sharad): The FloatingPortal renders the menu outside the normal DOM structure,
191
+ so its parent is effectively the <body> element. This means the menu
192
+ would otherwise default to the body's width. To support fluid width,
193
+ we must manually set the dropdown width here; otherwise, it uses a fixed width.
194
+ Also, Only treat menu as non-fluid if isFluid is strictly false, since default is true in menu and undefined means fluid. */
195
+ ...(menu.isFluid !== false && {
196
+ '--dropdown-width': dropdownWidth,
197
+ }),
198
+ '--menu-elevation': getElevationValue(elevation),
199
+ }}
200
+ >
201
+ <Menu
202
+ {...menu}
203
+ onSelect={(option, e) => {
204
+ onOptionSelect && onOptionSelect(option, e);
205
+ if (
206
+ // option.keepMenuOpenOnOptionSelect - to allow the menu persist its open stat upon option selection in normal variant
207
+ !option.keepMenuOpenOnOptionSelect &&
208
+ (!menu.optionsVariant ||
209
+ menu.optionsVariant === 'normal')
210
+ ) {
211
+ clickAway();
212
+ }
213
+ }}
214
+ size={menu.size || size}
215
+ onTabOut={clickAway}
216
+ />
217
+ </div>
218
+ </FloatingFocusManager>
219
+ </FloatingPortal>
195
220
  )}
196
221
  </div>
197
222
  )}
@@ -1,4 +1,6 @@
1
- @value ( sizeFluid) from '../../styles/variables/_size.css';
1
+ @value (
2
+ sizeFluid
3
+ ) from '../../styles/variables/_size.css';
2
4
 
3
5
  .buttonDropdownContainer {
4
6
  display: flex;
@@ -8,3 +10,8 @@
8
10
  .isFluid {
9
11
  width: sizeFluid;
10
12
  }
13
+
14
+ .menuWrapper {
15
+ width: var(--dropdown-width);
16
+ z-index: var(--menu-elevation);
17
+ }
@@ -32,6 +32,7 @@ const SimpleButtonDropdownBase = (props, ref) => {
32
32
  showLabelTooltip,
33
33
  allowWrap = false,
34
34
  clickAwayRef,
35
+ elevation = 'modal',
35
36
  ...buttonProps
36
37
  } = props;
37
38
  const [btnText, setBtnText] = React.useState('');
@@ -65,9 +66,11 @@ const SimpleButtonDropdownBase = (props, ref) => {
65
66
  size: size,
66
67
  onOptionSelect: handleOptionChange,
67
68
  onMenuOpen: onMenuOpen,
69
+ elevation: elevation,
68
70
  onMenuClose: onMenuClose,
69
71
  clickAwayRef: clickAwayRef,
70
72
  menu: {
73
+ // TODO (Sharad): The isFluid prop here controls menu width unlike ButtonDropdown, and is independent from the isFluid prop in ButtonDropdown. There should be a way to set the isFluid prop of ButtonDropdown.
71
74
  isFluid,
72
75
  options,
73
76
  selectedKeys: buttonDropdownSelectedKeys,
@@ -10,7 +10,7 @@ import {
10
10
  } from '../../utils/menu';
11
11
  import type {ButtonProps} from '../Button';
12
12
  import type {MenuOption, MenuOptionsVariant, Virtualization} from '../Menu';
13
- import type {BaseTooltipProps} from '../Tooltip';
13
+ import type {BaseTooltipProps, ElevationType} from '../Tooltip';
14
14
 
15
15
  import type {AnchorType} from './ButtonDropdown';
16
16
  import {ButtonDropdown} from './ButtonDropdown';
@@ -30,6 +30,7 @@ export type SimpleButtonDropdownProps = {
30
30
  // Input props
31
31
  ...ButtonProps,
32
32
 
33
+ elevation?: ElevationType,
33
34
  classNames?: ClassNames,
34
35
  anchorPosition?: AnchorType,
35
36
  tooltip?: BaseTooltipProps,
@@ -45,6 +46,7 @@ export type SimpleButtonDropdownProps = {
45
46
  menuClassNames?: MenuClassNames,
46
47
  showLabelTooltip?: MenuLabelTooltip,
47
48
  allowWrap?: boolean,
49
+
48
50
  // events
49
51
  onOptionSelect?: (option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed,
50
52
  onMenuOpen?: () => mixed,
@@ -80,6 +82,7 @@ const SimpleButtonDropdownBase = (props: SimpleButtonDropdownProps, ref) => {
80
82
  showLabelTooltip,
81
83
  allowWrap = false,
82
84
  clickAwayRef,
85
+ elevation = 'modal',
83
86
  ...buttonProps
84
87
  } = props;
85
88
 
@@ -132,9 +135,11 @@ const SimpleButtonDropdownBase = (props: SimpleButtonDropdownProps, ref) => {
132
135
  size={size}
133
136
  onOptionSelect={handleOptionChange}
134
137
  onMenuOpen={onMenuOpen}
138
+ elevation={elevation}
135
139
  onMenuClose={onMenuClose}
136
140
  clickAwayRef={clickAwayRef}
137
141
  menu={{
142
+ // TODO (Sharad): The isFluid prop here controls menu width unlike ButtonDropdown, and is independent from the isFluid prop in ButtonDropdown. There should be a way to set the isFluid prop of ButtonDropdown.
138
143
  isFluid,
139
144
  options,
140
145
  selectedKeys: buttonDropdownSelectedKeys,
@@ -12,6 +12,7 @@ var _classify = _interopRequireDefault(require("../../utils/classify"));
12
12
  var _clickAway = require("../../utils/click-away");
13
13
  var _mergeRefs = require("../../utils/merge-refs");
14
14
  var _Menu = require("../Menu");
15
+ var _Tooltip = require("../Tooltip");
15
16
  var _ButtonTab = require("./ButtonTab");
16
17
  var _ButtonTabDropdownModule = _interopRequireDefault(require("./ButtonTabDropdown.module.css"));
17
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -20,10 +21,10 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
20
21
  const ButtonTabDropdown = _ref => {
21
22
  let {
22
23
  title,
24
+ elevation = 'modal',
23
25
  anchorPosition = 'bottom-end',
24
26
  ...buttonTabProps
25
27
  } = _ref;
26
- const menuBtnRef = React.useRef();
27
28
  const {
28
29
  size,
29
30
  children,
@@ -63,7 +64,8 @@ const ButtonTabDropdown = _ref => {
63
64
  x,
64
65
  y,
65
66
  refs,
66
- strategy
67
+ strategy,
68
+ context
67
69
  } = (0, _react2.useFloating)({
68
70
  open: true,
69
71
  strategy: 'absolute',
@@ -81,7 +83,6 @@ const ButtonTabDropdown = _ref => {
81
83
  } = _ref3;
82
84
  return /*#__PURE__*/React.createElement("div", {
83
85
  "data-testid": "ButtonTabDropdown",
84
- ref: menuBtnRef,
85
86
  className: (0, _classify.default)(_ButtonTabDropdownModule.default.buttonTabDropDownWrapper, dropdownClass)
86
87
  }, /*#__PURE__*/React.createElement(_ButtonTab.ButtonTab, _extends({}, buttonTabProps, {
87
88
  ref: (0, _mergeRefs.mergeRefs)([refs.setReference, triggerRef]),
@@ -90,13 +91,19 @@ const ButtonTabDropdown = _ref => {
90
91
  e?.stopPropagation();
91
92
  onOpen();
92
93
  }
93
- }), title), isOpen && /*#__PURE__*/React.createElement("div", {
94
+ }), title), isOpen && /*#__PURE__*/React.createElement(_react2.FloatingPortal, null, /*#__PURE__*/React.createElement(_react2.FloatingFocusManager, {
95
+ modal: false,
96
+ context: context,
97
+ initialFocus: refs.reference
98
+ }, /*#__PURE__*/React.createElement("div", {
99
+ className: _ButtonTabDropdownModule.default.menuWrapper,
94
100
  ref: (0, _mergeRefs.mergeRefs)([refs.setFloating, boundaryRef]),
95
101
  style: {
96
102
  display: 'flex',
97
103
  position: strategy,
98
104
  top: y ?? _space.spaceNone,
99
- left: x ?? _space.spaceNone
105
+ left: x ?? _space.spaceNone,
106
+ '--menu-elevation': (0, _Tooltip.getElevationValue)(elevation)
100
107
  }
101
108
  }, /*#__PURE__*/React.createElement(_Menu.Menu, {
102
109
  onSelect: (option, e) => {
@@ -107,7 +114,7 @@ const ButtonTabDropdown = _ref => {
107
114
  options: menuOptions,
108
115
  onTabOut: clickAway,
109
116
  selectedKeys: [selectedButtonTabId ?? '']
110
- })));
117
+ })))));
111
118
  });
112
119
  };
113
120
  exports.ButtonTabDropdown = ButtonTabDropdown;
@@ -7,6 +7,10 @@ import {
7
7
  // $FlowFixMe[untyped-import]
8
8
  flip,
9
9
  // $FlowFixMe[untyped-import]
10
+ FloatingFocusManager,
11
+ // $FlowFixMe[untyped-import]
12
+ FloatingPortal,
13
+ // $FlowFixMe[untyped-import]
10
14
  offset,
11
15
  // $FlowFixMe[untyped-import]
12
16
  shift,
@@ -21,6 +25,8 @@ import {ClickAway} from '../../utils/click-away';
21
25
  import {mergeRefs} from '../../utils/merge-refs';
22
26
  import type {AnchorType} from '../ButtonDropdown';
23
27
  import {Menu} from '../Menu';
28
+ import type {ElevationType} from '../Tooltip';
29
+ import {getElevationValue} from '../Tooltip';
24
30
 
25
31
  import type {ButtonTabProps} from './ButtonTab';
26
32
  import {ButtonTab} from './ButtonTab';
@@ -31,17 +37,18 @@ import css from './ButtonTabDropdown.module.css';
31
37
  export type ButtonTabDropdownProps = {
32
38
  ...ButtonTabProps,
33
39
  title: string,
34
- anchorPosition?: AnchorType,
40
+ elevation?: ElevationType,
35
41
  dropdownClass?: string,
42
+ anchorPosition?: AnchorType,
36
43
  ...
37
44
  };
38
45
 
39
46
  export const ButtonTabDropdown = ({
40
47
  title,
48
+ elevation = 'modal',
41
49
  anchorPosition = 'bottom-end',
42
50
  ...buttonTabProps
43
51
  }: ButtonTabDropdownProps): React.Node => {
44
- const menuBtnRef = React.useRef();
45
52
  const {
46
53
  size,
47
54
  children,
@@ -78,7 +85,7 @@ export const ButtonTabDropdown = ({
78
85
  ? 'more-tab'
79
86
  : selectedButtonTabId;
80
87
 
81
- const {x, y, refs, strategy} = useFloating({
88
+ const {x, y, refs, strategy, context} = useFloating({
82
89
  open: true,
83
90
  strategy: 'absolute',
84
91
  placement: anchorPosition,
@@ -91,7 +98,6 @@ export const ButtonTabDropdown = ({
91
98
  {({isOpen, onOpen, clickAway, boundaryRef, triggerRef}) => (
92
99
  <div
93
100
  data-testid="ButtonTabDropdown"
94
- ref={menuBtnRef}
95
101
  className={classify(css.buttonTabDropDownWrapper, dropdownClass)}
96
102
  >
97
103
  <ButtonTab
@@ -106,26 +112,36 @@ export const ButtonTabDropdown = ({
106
112
  {title}
107
113
  </ButtonTab>
108
114
  {isOpen && (
109
- <div
110
- ref={mergeRefs([refs.setFloating, boundaryRef])}
111
- style={{
112
- display: 'flex',
113
- position: strategy,
114
- top: y ?? spaceNone,
115
- left: x ?? spaceNone,
116
- }}
117
- >
118
- <Menu
119
- onSelect={(option, e) => {
120
- onTabSelect && onTabSelect(option.key, e);
121
- clickAway();
122
- }}
123
- size={size}
124
- options={menuOptions}
125
- onTabOut={clickAway}
126
- selectedKeys={[selectedButtonTabId ?? '']}
127
- />
128
- </div>
115
+ <FloatingPortal>
116
+ <FloatingFocusManager
117
+ modal={false}
118
+ context={context}
119
+ initialFocus={refs.reference}
120
+ >
121
+ <div
122
+ className={css.menuWrapper}
123
+ ref={mergeRefs([refs.setFloating, boundaryRef])}
124
+ style={{
125
+ display: 'flex',
126
+ position: strategy,
127
+ top: y ?? spaceNone,
128
+ left: x ?? spaceNone,
129
+ '--menu-elevation': getElevationValue(elevation),
130
+ }}
131
+ >
132
+ <Menu
133
+ onSelect={(option, e) => {
134
+ onTabSelect && onTabSelect(option.key, e);
135
+ clickAway();
136
+ }}
137
+ size={size}
138
+ options={menuOptions}
139
+ onTabOut={clickAway}
140
+ selectedKeys={[selectedButtonTabId ?? '']}
141
+ />
142
+ </div>
143
+ </FloatingFocusManager>
144
+ </FloatingPortal>
129
145
  )}
130
146
  </div>
131
147
  )}
@@ -2,3 +2,7 @@
2
2
  display: flex;
3
3
  flex-shrink: 0;
4
4
  }
5
+
6
+ .menuWrapper {
7
+ z-index: var(--menu-elevation);
8
+ }
@@ -20,6 +20,7 @@ const ButtonTabs = exports.ButtonTabs = /*#__PURE__*/React.forwardRef((_ref, ref
20
20
  selectedButtonTabId,
21
21
  onButtonTabSelect,
22
22
  wrapAfter,
23
+ elevation = 'modal',
23
24
  wrapTabTitle = 'more',
24
25
  anchorPosition
25
26
  } = _ref;
@@ -34,6 +35,7 @@ const ButtonTabs = exports.ButtonTabs = /*#__PURE__*/React.forwardRef((_ref, ref
34
35
  id: "more-tab",
35
36
  title: wrapTabTitle,
36
37
  iconName: "ellipsis-vertical",
38
+ elevation: elevation,
37
39
  anchorPosition: anchorPosition,
38
40
  dropdownClass: classNames?.buttonTabDropdownWrapper
39
41
  }, wrappedNodes));
@@ -4,6 +4,7 @@ import * as React from 'react';
4
4
 
5
5
  import classify from '../../utils/classify';
6
6
  import type {AnchorType} from '../ButtonDropdown';
7
+ import type {ElevationType} from '../Tooltip';
7
8
 
8
9
  import {ButtonTabDropdown} from './ButtonTabDropdown';
9
10
 
@@ -24,6 +25,7 @@ export type ButtonTabsProps = {
24
25
  selectedButtonTabId?: string,
25
26
  onButtonTabSelect?: ?(id: string, e?: ?SyntheticEvent<HTMLElement>) => mixed,
26
27
  wrapAfter?: number,
28
+ elevation?: ElevationType,
27
29
  wrapTabTitle?: string,
28
30
  anchorPosition?: AnchorType,
29
31
  };
@@ -42,6 +44,7 @@ export const ButtonTabs: React$AbstractComponent<
42
44
  selectedButtonTabId,
43
45
  onButtonTabSelect,
44
46
  wrapAfter,
47
+ elevation = 'modal',
45
48
  wrapTabTitle = 'more',
46
49
  anchorPosition,
47
50
  }: ButtonTabsProps,
@@ -62,6 +65,7 @@ export const ButtonTabs: React$AbstractComponent<
62
65
  id="more-tab"
63
66
  title={wrapTabTitle}
64
67
  iconName="ellipsis-vertical"
68
+ elevation={elevation}
65
69
  anchorPosition={anchorPosition}
66
70
  dropdownClass={classNames?.buttonTabDropdownWrapper}
67
71
  >