Repository: Sage/carbon (ts)

Issue:
Controlled MultiSelect component inside PopoverContainer re-renders in bad initial position
### Description

We are rendering a controlled MultiSelect inside a PopoverContainer. When you add any option to the MultiSelect it seems it is re-rendered in position 0,0 and looks like flying from top-left screen corner

### Reproduction

https://stackblitz.com/edit/parsium-carbon-starter-s6xtma?file=src%2FApp.tsx

### Steps to reproduce

Select any option in 2nd Select.

It works well sometimes on remove options but never on adding.

### JIRA ticket numbers (Sage only)

_No response_

### Suggested solution

_No response_

### Carbon version

134.1.0

### Design tokens version

_No response_

### Relevant browsers

Chrome, Microsoft Edge, Other

### Relevant OSs

MacOS

### Additional context

- It's still happening in last carbon version 138.0.2

- In previous Carbon version from 134.1.0, (134.0.4) it works fine.

### Confidentiality

- [X] I confirm there is no confidential or commercially sensitive information included.

Pull request:
fix(popover-container): popover position re-renders with controlled MultiSelect
fix #6793

### Proposed behaviour

<!--
A clear and concise description of what changes this PR makes. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

- Selecting an option in a  controlled `MultiSelect` within a Popover container does not cause an unintended animation. 

### Current behaviour

<!--
A clear and concise description of the behaviour before this change. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

- When a controlled `MultiSelect` is placed inside a `PopoverContainer` and an option is selected, the popover re-renders with an unintended animation.

### Checklist

<!-- Each PR should include the following -->

- [x] Commits follow our style guide
- [x] Related issues linked in commit messages if required
- [ ] Screenshots are included in the PR if useful
- [ ] All themes are supported if required
- [ ] Unit tests added or updated if required
- [ ] Playwright automation tests added or updated if required
- [ ] Storybook added or updated if required
- [ ] Translations added or updated (including creating or amending translation keys table in storybook) if required
- [ ] Typescript `d.ts` file added or updated if required
- [ ] Related docs have been updated if required

#### QA

- [ ] Tested in provided StackBlitz sandbox/Storybook
- [ ] Add new Playwright test coverage if required
- [ ] Carbon implementation matches Design System/designs
- [ ] UI Tests GitHub check reviewed if required

### Additional context

<!-- Add any other context or links about the pull request here. -->

### Testing instructions

<!--
How can a reviewer test this PR?

If this PR addresses a pre-existing bug, please include a link to a sandbox that reproduces the original bug. A starter template has been provided to help you do this:
<https://stackblitz.com/fork/github/Parsium/carbon-starter>
-->

Cannot be tested with RTL/Playwright so manual testing needed - see 'with multi select' test story.


Patch:
diff --git a/src/components/popover-container/popover-container.component.tsx b/src/components/popover-container/popover-container.component.tsx
index b4fce0266..f114d4695 100644
--- a/src/components/popover-container/popover-container.component.tsx
+++ b/src/components/popover-container/popover-container.component.tsx
@@ -1,4 +1,10 @@
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, {
+  useCallback,
+  useEffect,
+  useRef,
+  useState,
+  useMemo,
+} from "react";
 import { PaddingProps } from "styled-system";
 import { Transition, TransitionStatus } from "react-transition-group";
 import { flip, offset } from "@floating-ui/dom";
@@ -143,17 +149,20 @@ export interface PopoverContainerProps extends PaddingProps {
   disableAnimation?: boolean;
 }
 
-function getPopoverMiddleware(shouldCoverButton: boolean) {
-  return [
-    offset(
-      shouldCoverButton
-        ? ({ rects }) => ({ mainAxis: -rects.reference.height })
-        : 6
-    ),
-    flip({
-      fallbackStrategy: "initialPlacement",
-    }),
-  ];
+function usePopoverMiddleware(shouldCoverButton: boolean) {
+  return useMemo(
+    () => [
+      offset(
+        shouldCoverButton
+          ? ({ rects }) => ({ mainAxis: -rects.reference.height })
+          : 6
+      ),
+      flip({
+        fallbackStrategy: "initialPlacement",
+      }),
+    ],
+    [shouldCoverButton]
+  );
 }
 
 export const PopoverContainer = ({
@@ -192,6 +201,8 @@ export const PopoverContainer = ({
     "screen and (prefers-reduced-motion: no-preference)"
   );
 
+  const popoverMiddleware = usePopoverMiddleware(shouldCoverButton);
+
   const closePopover = useCallback(
     (
       ev:
@@ -361,7 +372,7 @@ export const PopoverContainer = ({
               popoverStrategy={
                 disableAnimation || reduceMotion ? "fixed" : "absolute"
               }
-              middleware={getPopoverMiddleware(shouldCoverButton)}
+              middleware={popoverMiddleware}
               childRefOverride={popoverContentNodeRef}
             >
               {childrenToRender(state)}


Test patch:
diff --git a/src/components/popover-container/popover-container-test.stories.tsx b/src/components/popover-container/popover-container-test.stories.tsx
index 21021bde2..88b227c11 100644
--- a/src/components/popover-container/popover-container-test.stories.tsx
+++ b/src/components/popover-container/popover-container-test.stories.tsx
@@ -4,7 +4,7 @@ import Box from "../box";
 import PopoverContainer, {
   PopoverContainerProps,
 } from "./popover-container.component";
-import { Select, Option } from "../select";
+import { Select, MultiSelect, Option } from "../select";
 import { Menu, MenuItem, MenuSegmentTitle } from "../menu";
 import Heading from "../heading";
 import Typography from "../typography";
@@ -17,6 +17,7 @@ export default {
   includeStories: [
     "Default",
     "WithSelect",
+    "WithMultiSelect",
     "InAScrollableBlock",
     "InsideMenu",
     "InsideMenuWithOpenButton",
@@ -65,6 +66,33 @@ WithSelect.story = {
   name: "with select",
 };
 
+export const WithMultiSelect = () => {
+  const [value, setValue] = useState<string[]>([]);
+  function onChangeHandler(event: React.ChangeEvent<HTMLInputElement>) {
+    setValue((event.target.value as unknown) as string[]);
+  }
+
+  return (
+    <Box ml={5} mt={5}>
+      <PopoverContainer title="multiSelect example">
+        <MultiSelect
+          label="my multiselect"
+          value={value}
+          onChange={onChangeHandler}
+        >
+          <Option value="red" text="red" />
+          <Option value="green" text="green" />
+          <Option value="blue" text="blue" />
+        </MultiSelect>
+      </PopoverContainer>
+    </Box>
+  );
+};
+
+WithSelect.story = {
+  name: "with multiSelect",
+};
+
 export const InAScrollableBlock = () => {
   return (
     <Box>