Skip to content

Commit

Permalink
feat(MenuItem): allow observing submenu content resize when a re-rend…
Browse files Browse the repository at this point in the history
…er isn't triggered (#2713)
  • Loading branch information
YossiSaadi authored Jan 27, 2025
1 parent dd0d53a commit ef07e3c
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 23 deletions.
18 changes: 2 additions & 16 deletions packages/core/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -584,22 +585,7 @@ export default class Dialog extends PureComponent<DialogProps, DialogState> {
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
]}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
};
}
});
6 changes: 6 additions & 0 deletions packages/core/src/components/Menu/MenuItem/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<MenuItemProps, "title"> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const BaseMenuItem = forwardRef(
"data-testid": dataTestId,
splitMenuItem = false,
children,
submenuPosition = "right"
submenuPosition = "right",
autoAdjustOnSubMenuContentResize
}: BaseMenuItemProps,
ref: React.ForwardedRef<HTMLElement>
) => {
Expand Down Expand Up @@ -153,6 +154,7 @@ const BaseMenuItem = forwardRef(
onClose={closeSubMenu}
autoFocusOnMount={!useDocumentEventListeners}
submenuPosition={submenuPosition}
autoAdjustOnSubMenuContentResize={autoAdjustOnSubMenuContentResize}
>
{subMenu}
</MenuItemSubMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const MenuItemSubMenu = ({
autoFocusOnMount,
onClose,
children,
submenuPosition
submenuPosition,
autoAdjustOnSubMenuContentResize
}: MenuItemSubMenuProps) => {
const childRef = useRef<HTMLDivElement>(null);
const popperElementRef = useRef<HTMLDivElement>(null);
Expand All @@ -36,7 +37,8 @@ const MenuItemSubMenu = ({
popperElementRef?.current,
{
isOpen: open,
placement: submenuPlacement
placement: submenuPlacement,
observeContentResize: autoAdjustOnSubMenuContentResize
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -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<MenuItemProps, "autoAdjustOnSubMenuContentResize"> {
/**
* 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.
*/
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/hooks/usePopover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand All @@ -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);

Expand Down

0 comments on commit ef07e3c

Please sign in to comment.