diff --git a/packages/core/src/components/Dialog/Dialog.tsx b/packages/core/src/components/Dialog/Dialog.tsx index 47b3e92ee8..e91248dde2 100644 --- a/packages/core/src/components/Dialog/Dialog.tsx +++ b/packages/core/src/components/Dialog/Dialog.tsx @@ -19,6 +19,7 @@ import { ComponentDefaultTestId, getTestId } from "../../tests/test-ids-utils"; import { DialogAnimationType, DialogPosition, DialogTriggerEvent } from "./Dialog.types"; import LayerContext from "../LayerProvider/LayerContext"; import { isClient } from "../../utils/ssr-utils"; +import { createObserveContentResizeModifier } from "./modifiers/observeContentResizeModifier"; export interface DialogProps extends VibeComponentProps { /** @@ -584,22 +585,7 @@ export default class Dialog extends PureComponent { return state; } }, - { - name: "observeContentResize", - enabled: observeContentResize, - phase: "beforeWrite", - fn() {}, - effect({ state, instance }) { - const observer = new ResizeObserver(() => { - instance.update(); - }); - observer.observe(state.elements.popper); - - return () => { - observer.disconnect(); - }; - } - }, + createObserveContentResizeModifier(observeContentResize), ...modifiers ]} > diff --git a/packages/core/src/components/Dialog/modifiers/observeContentResizeModifier.ts b/packages/core/src/components/Dialog/modifiers/observeContentResizeModifier.ts new file mode 100644 index 0000000000..e6d90ff5be --- /dev/null +++ b/packages/core/src/components/Dialog/modifiers/observeContentResizeModifier.ts @@ -0,0 +1,18 @@ +import { Modifier } from "react-popper"; + +export const createObserveContentResizeModifier = (isEnabled = false): Modifier<"observeContentResize"> => ({ + name: "observeContentResize", + enabled: isEnabled, + phase: "beforeWrite", + fn() {}, + effect({ state, instance }) { + const observer = new ResizeObserver(() => { + instance.update(); + }); + observer.observe(state.elements.popper); + + return () => { + observer.disconnect(); + }; + } +}); diff --git a/packages/core/src/components/Menu/MenuItem/MenuItem.tsx b/packages/core/src/components/Menu/MenuItem/MenuItem.tsx index 7fdc05cc7f..280cfd1520 100644 --- a/packages/core/src/components/Menu/MenuItem/MenuItem.tsx +++ b/packages/core/src/components/Menu/MenuItem/MenuItem.tsx @@ -56,6 +56,12 @@ export interface MenuItemProps extends VibeComponentProps { splitMenuItem?: boolean; "aria-label"?: AriaAttributes["aria-label"]; submenuPosition?: SubmenuPosition; + /** + * When set to `true`, submenu's content and size changes would automatically trigger repositioning. + * + * This is useful for when submenu's content may grow or shrink without a re-render being triggered. + */ + autoAdjustOnSubMenuContentResize?: boolean; } export interface MenuItemTitleComponentProps extends Omit { diff --git a/packages/core/src/components/Menu/MenuItem/components/BaseMenuItem/BaseMenuItem.tsx b/packages/core/src/components/Menu/MenuItem/components/BaseMenuItem/BaseMenuItem.tsx index 9949310da3..ef9703b27d 100644 --- a/packages/core/src/components/Menu/MenuItem/components/BaseMenuItem/BaseMenuItem.tsx +++ b/packages/core/src/components/Menu/MenuItem/components/BaseMenuItem/BaseMenuItem.tsx @@ -39,7 +39,8 @@ const BaseMenuItem = forwardRef( "data-testid": dataTestId, splitMenuItem = false, children, - submenuPosition = "right" + submenuPosition = "right", + autoAdjustOnSubMenuContentResize }: BaseMenuItemProps, ref: React.ForwardedRef ) => { @@ -153,6 +154,7 @@ const BaseMenuItem = forwardRef( onClose={closeSubMenu} autoFocusOnMount={!useDocumentEventListeners} submenuPosition={submenuPosition} + autoAdjustOnSubMenuContentResize={autoAdjustOnSubMenuContentResize} > {subMenu} diff --git a/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.tsx b/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.tsx index abfd1859ce..a58fae2fb5 100644 --- a/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.tsx +++ b/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.tsx @@ -12,7 +12,8 @@ const MenuItemSubMenu = ({ autoFocusOnMount, onClose, children, - submenuPosition + submenuPosition, + autoAdjustOnSubMenuContentResize }: MenuItemSubMenuProps) => { const childRef = useRef(null); const popperElementRef = useRef(null); @@ -36,7 +37,8 @@ const MenuItemSubMenu = ({ popperElementRef?.current, { isOpen: open, - placement: submenuPlacement + placement: submenuPlacement, + observeContentResize: autoAdjustOnSubMenuContentResize } ); diff --git a/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.types.ts b/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.types.ts index 8eac7274de..7c6cc29ad7 100644 --- a/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.types.ts +++ b/packages/core/src/components/Menu/MenuItem/components/MenuItemSubMenu/MenuItemSubMenu.types.ts @@ -1,8 +1,9 @@ import React from "react"; import { CloseMenuOption, MenuChild } from "../../../Menu/MenuConstants"; import { SubmenuPosition } from "../../MenuItem.types"; +import { MenuItemProps } from "../../MenuItem"; -export interface MenuItemSubMenuProps { +export interface MenuItemSubMenuProps extends Pick { /** * Reference to the anchor element that the submenu is related to. This is used to position the submenu correctly relative to the parent menu item. */ diff --git a/packages/core/src/hooks/usePopover.ts b/packages/core/src/hooks/usePopover.ts index 6cf3159f50..10e36606c6 100644 --- a/packages/core/src/hooks/usePopover.ts +++ b/packages/core/src/hooks/usePopover.ts @@ -4,6 +4,7 @@ import { Placement } from "./popoverConstants"; import useIsomorphicLayoutEffect from "./ssr/useIsomorphicLayoutEffect"; import useForceUpdate from "./useForceUpdate"; import type { Options, State } from "@popperjs/core"; +import { createObserveContentResizeModifier } from "../components/Dialog/modifiers/observeContentResizeModifier"; const { RIGHT_START, RIGHT_END, LEFT_START, LEFT_END } = Placement; @@ -19,10 +20,12 @@ export default function usePopover( popperElement: HTMLElement, { isOpen, - placement = RIGHT_START + placement = RIGHT_START, + observeContentResize }: { isOpen?: boolean; placement?: Placement; + observeContentResize?: boolean; } ) { const forceUpdate = useForceUpdate(); @@ -46,10 +49,11 @@ export default function usePopover( state.styles.popper.visibility = isOpen ? "visible" : "hidden"; return state; } - } + }, + createObserveContentResizeModifier(observeContentResize) ] }; - }, [isOpen, placement]); + }, [isOpen, placement, observeContentResize]); const { styles, attributes } = usePopper(referenceElement, popperElement, popperOptions);