diff --git a/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js b/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js
index a2fa40c5f05a..35049b334d9b 100644
--- a/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js
+++ b/packages/react/src/components/OverflowMenu/OverflowMenu.featureflag.stories.js
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import React from 'react';
+import React, { useRef, useEffect } from 'react';
import { action } from '@storybook/addon-actions';
import { ArrowsVertical } from '@carbon/icons-react';
@@ -62,6 +62,36 @@ export const _OverflowMenu = () => {
);
};
+export const AutoAlign = () => {
+ const ref = useRef();
+
+ useEffect(() => {
+ console.log(ref);
+ ref?.current?.scrollIntoView({ block: 'center', inline: 'center' });
+ });
+
+ return (
+
+ );
+};
+
export const Nested = () => {
return (
diff --git a/packages/react/src/components/OverflowMenu/next/index.tsx b/packages/react/src/components/OverflowMenu/next/index.tsx
index 73d70931d2d9..4262b53df804 100644
--- a/packages/react/src/components/OverflowMenu/next/index.tsx
+++ b/packages/react/src/components/OverflowMenu/next/index.tsx
@@ -9,13 +9,16 @@ import React, {
type ComponentType,
type FunctionComponent,
useRef,
+ useEffect,
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { OverflowMenuVertical } from '@carbon/icons-react';
+import { useFloating, flip, autoUpdate } from '@floating-ui/react';
import { IconButton } from '../../IconButton';
import { Menu } from '../../Menu';
+import mergeRefs from '../../../tools/mergeRefs';
import { useId } from '../../../internal/useId';
import { usePrefix } from '../../../internal/usePrefix';
@@ -24,6 +27,11 @@ import { useAttachedMenu } from '../../../internal/useAttachedMenu';
const defaultSize = 'md';
interface OverflowMenuProps {
+ /**
+ * **Experimental**: Will attempt to automatically align the floating element to avoid collisions with the viewport and being clipped by ancestor elements.
+ */
+ autoAlign?: boolean;
+
/**
* A collection of MenuItems to be rendered within this OverflowMenu.
*/
@@ -71,6 +79,7 @@ interface OverflowMenuProps {
const OverflowMenu = React.forwardRef(
function OverflowMenu(
{
+ autoAlign = false,
children,
className,
label = 'Options',
@@ -82,10 +91,39 @@ const OverflowMenu = React.forwardRef(
},
forwardRef
) {
+ const { refs, floatingStyles, placement, middlewareData } = useFloating(
+ autoAlign
+ ? {
+ placement: menuAlignment,
+
+ // The floating element is positioned relative to its nearest
+ // containing block (usually the viewport). It will in many cases also
+ // “break” the floating element out of a clipping ancestor.
+ // https://floating-ui.com/docs/misc#clipping
+ strategy: 'fixed',
+
+ // Middleware order matters, arrow should be last
+ middleware: [
+ flip({
+ fallbackAxisSideDirection: 'start',
+ fallbackPlacements: [
+ 'top-start',
+ 'top-end',
+ 'bottom-start',
+ 'bottom-end',
+ ],
+ }),
+ ],
+ whileElementsMounted: autoUpdate,
+ }
+ : {} // When autoAlign is turned off, floating-ui will not be used
+ );
+
const id = useId('overflowmenu');
const prefix = usePrefix();
const triggerRef = useRef(null);
+
const {
open,
x,
@@ -94,6 +132,22 @@ const OverflowMenu = React.forwardRef(
handleMousedown,
handleClose,
} = useAttachedMenu(triggerRef);
+ useEffect(() => {
+ if (autoAlign) {
+ Object.keys(floatingStyles).forEach((style) => {
+ if (refs.floating.current) {
+ refs.floating.current.style[style] = floatingStyles[style];
+ }
+ });
+ }
+ }, [
+ floatingStyles,
+ autoAlign,
+ refs.floating,
+ open,
+ placement,
+ middlewareData,
+ ]);
function handleTriggerClick() {
if (triggerRef.current) {
@@ -118,6 +172,8 @@ const OverflowMenu = React.forwardRef(
size !== defaultSize && `${prefix}--overflow-menu--${size}`
);
+ const floatingRef = mergeRefs(triggerRef, refs.setReference);
+
return (
(
className={triggerClasses}
onClick={handleTriggerClick}
onMouseDown={handleMousedown}
- ref={triggerRef}
+ ref={floatingRef}
label={label}
align={tooltipAlignment}>