Repository: Sage/carbon (ts)

Issue:
Allow the width of a select dropdown (e.g. Filterable) to be wider than the original component. 
### Description

When implementing a select component, in this case Filterable - the length of the text for the options in the dropdown often far exceed the width of the control itself. We need to be able to set the dropdown width to be larger than that of the main select component. 

### Suggested solution

Allow the dropdown width to be configurable and unlock this so that the width can exceed that of the parent control. 

### Demo URL

_No response_

### Alternatives

_No response_

### Additional context

_No response_

### Confidentiality

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

Pull request description:
Select list width
fix #6861

### 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.
-->
Removes `left` and `right` options from `listPlacement` type, only `top` and `bottom` remain supported

BREAKING CHANGE: `listPlacement` prop only supports `top` and `bottom` now


Adds support for overriding the list width via `listWidth` prop. Adds support for placing
list at `top-start`, `top-end`, `bottom-start` and `botton-end`. When `top` or `bottom`
is passed to `listPlacement` it will internally append `-end` to it.

![image](https://github.com/user-attachments/assets/ec44bd6c-9540-4303-acbf-66fa7d5d3d78)


![image](https://github.com/user-attachments/assets/d07ad8a6-b7eb-4de2-9330-c96b44627dce)


### 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.
-->
`listPlacement` supports `left` and `right`
no support for `listWidth`

### Checklist

<!-- Each PR should include the following -->

- [x] Commits follow our style guide
- [x] Related issues linked in commit messages if required
- [x] Screenshots are included in the PR if useful
- [x] Unit tests added or updated if required
- [x] Playwright automation tests added or updated if required
- [x] Storybook added or updated if required
- [x] Typescript `d.ts` file added or 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>
-->


Patch:
diff --git a/src/components/select/__internal__/select-list/select-list.component.tsx b/src/components/select/__internal__/select-list/select-list.component.tsx
index ef4b71f51..95e642e87 100644
--- a/src/components/select/__internal__/select-list/select-list.component.tsx
+++ b/src/components/select/__internal__/select-list/select-list.component.tsx
@@ -6,7 +6,7 @@ import React, {
   useRef,
   useMemo,
 } from "react";
-import { flip, offset, size, Side } from "@floating-ui/dom";
+import { flip, offset, size } from "@floating-ui/dom";
 import {
   useVirtualizer,
   defaultRangeExtractor,
@@ -38,6 +38,14 @@ import Loader from "../../../loader";
 import Option, { OptionProps } from "../../option";
 import SelectListContext from "./select-list.context";
 
+export type ListPlacement =
+  | "top"
+  | "bottom"
+  | "top-start"
+  | "bottom-start"
+  | "top-end"
+  | "bottom-end";
+
 export interface SelectListProps {
   /** The ID for the parent <div> */
   id?: string;
@@ -78,7 +86,7 @@ export interface SelectListProps {
   /** When true component will work in multi column mode, children should consist of OptionRow components in this mode */
   multiColumn?: boolean;
   /** Placement of the select list relative to the input element */
-  listPlacement?: Side;
+  listPlacement?: ListPlacement;
   /** Use the opposite list placement if the set placement does not fit */
   flipEnabled?: boolean;
   /** @private @ignore
@@ -96,6 +104,8 @@ export interface SelectListProps {
   virtualScrollOverscan?: number;
   /** @private @ignore A callback for when a mouseDown event occurs on the component */
   onMouseDown?: () => void;
+  /** Override the default width of the list element. Number passed is converted into pixel value */
+  listWidth?: number;
 }
 
 const TABLE_HEADER_HEIGHT = 48;
@@ -125,6 +135,7 @@ const SelectList = React.forwardRef(
       multiselectValues,
       enableVirtualScroll,
       virtualScrollOverscan = 5,
+      listWidth,
       ...listProps
     }: SelectListProps,
     listContainerRef: React.ForwardedRef<HTMLDivElement>
@@ -614,7 +625,7 @@ const SelectList = React.forwardRef(
         size({
           apply({ rects, elements }) {
             Object.assign(elements.floating.style, {
-              width: `${rects.reference.width}px`,
+              width: `${listWidth ?? rects.reference.width}px`,
             });
           },
         }),
@@ -626,7 +637,7 @@ const SelectList = React.forwardRef(
             ]
           : []),
       ],
-      [flipEnabled]
+      [listWidth, flipEnabled]
     );
 
     const loader = isLoading ? (
diff --git a/src/components/select/filterable-select/filterable-select.component.tsx b/src/components/select/filterable-select/filterable-select.component.tsx
index 948ce3c52..c0454f3a1 100644
--- a/src/components/select/filterable-select/filterable-select.component.tsx
+++ b/src/components/select/filterable-select/filterable-select.component.tsx
@@ -1,6 +1,5 @@
 import React, { useState, useEffect, useRef, useCallback } from "react";
 import invariant from "invariant";
-import { Side } from "@floating-ui/dom";
 
 import { ButtonProps } from "../../button";
 import { filterOutStyledSystemSpacingProps } from "../../../style/utils";
@@ -12,6 +11,7 @@ import withFilter from "../__internal__/utils/with-filter.hoc";
 import StyledSelect from "../select.style";
 import SelectList, {
   SelectListProps,
+  ListPlacement,
 } from "../__internal__/select-list/select-list.component";
 import isExpectedOption from "../__internal__/utils/is-expected-option";
 import isNavigationKey from "../__internal__/utils/is-navigation-key";
@@ -73,7 +73,7 @@ export interface FilterableSelectProps
   /** Maximum list height - defaults to 180 */
   listMaxHeight?: number;
   /** Placement of the select list in relation to the input element */
-  listPlacement?: Side;
+  listPlacement?: ListPlacement;
   /** Use the opposite list placement if the set placement does not fit */
   flipEnabled?: boolean;
   /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
@@ -94,6 +94,8 @@ export interface FilterableSelectProps
   onChange?: (
     ev: CustomSelectChangeEvent | React.ChangeEvent<HTMLInputElement>
   ) => void;
+  /** Override the default width of the list element. Number passed is converted into pixel value */
+  listWidth?: number;
 }
 
 export const FilterableSelect = React.forwardRef<
@@ -139,6 +141,7 @@ export const FilterableSelect = React.forwardRef<
       disableDefaultFiltering = false,
       isOptional,
       required,
+      listWidth,
       ...textboxProps
     },
     ref
@@ -643,6 +646,19 @@ export const FilterableSelect = React.forwardRef<
       };
     }
 
+    let placement: ListPlacement;
+
+    switch (listPlacement) {
+      case "top":
+        placement = "top-end";
+        break;
+      case "bottom":
+        placement = "bottom-end";
+        break;
+      default:
+        placement = listPlacement;
+    }
+
     const selectListProps = {
       ref: listboxRef,
       id: selectListId.current,
@@ -661,11 +677,12 @@ export const FilterableSelect = React.forwardRef<
       onListScrollBottom,
       tableHeader,
       multiColumn,
-      listPlacement,
+      listPlacement: listWidth !== undefined ? placement : listPlacement,
       flipEnabled,
       isOpen,
       enableVirtualScroll,
       virtualScrollOverscan,
+      listWidth,
     };
 
     const selectList = disableDefaultFiltering ? (
diff --git a/src/components/select/filterable-select/filterable-select.mdx b/src/components/select/filterable-select/filterable-select.mdx
index 0c033ff7f..bd32f042f 100644
--- a/src/components/select/filterable-select/filterable-select.mdx
+++ b/src/components/select/filterable-select/filterable-select.mdx
@@ -47,6 +47,16 @@ You can use `listMaxHeight` prop to override default max height value of select
 
 <Canvas of={FilterableSelectStories.ListHeight} />
 
+### List width
+
+You can use `listWidth` prop to override the width of the select list. By default the list 
+will have the same width as the input.
+
+<Canvas
+  name="list width"
+  of={FilterableSelectStories.ListWidth}
+/>
+
 ### Controlled Usage
 
 <Canvas of={FilterableSelectStories.Controlled} />
diff --git a/src/components/select/filterable-select/filterable-select.pw.tsx b/src/components/select/filterable-select/filterable-select.pw.tsx
index d004fb0e2..70de33d39 100644
--- a/src/components/select/filterable-select/filterable-select.pw.tsx
+++ b/src/components/select/filterable-select/filterable-select.pw.tsx
@@ -833,8 +833,6 @@ test.describe("FilterableSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     FilterableSelectProps["listPlacement"],
     string,
@@ -864,12 +862,6 @@ test.describe("FilterableSelect component", () => {
       if (position === "bottom") {
         flipPosition = "top";
       }
-      if (position === "left") {
-        flipPosition = "right";
-      }
-      if (position === "right") {
-        flipPosition = "left";
-      }
       await dropdownButton(page).click();
       const listElement = selectListPosition(page);
       await expect(listElement).toHaveAttribute(
@@ -2005,8 +1997,6 @@ test.describe("Accessibility tests for FilterableSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     FilterableSelectProps["listPlacement"],
     string,
diff --git a/src/components/select/filterable-select/filterable-select.spec.tsx b/src/components/select/filterable-select/filterable-select.spec.tsx
index bd763b4e4..87a459828 100644
--- a/src/components/select/filterable-select/filterable-select.spec.tsx
+++ b/src/components/select/filterable-select/filterable-select.spec.tsx
@@ -487,7 +487,7 @@ describe("FilterableSelect", () => {
         ).toBeVisible();
       });
 
-      it.each(["top", "bottom", "right", "left"])(
+      it.each(["top", "bottom", "top-end", "bottom-end"])(
         "the listPlacement prop should be passed",
         (listPlacement) => {
           const wrapper = renderSelect({ listPlacement });
@@ -500,6 +500,18 @@ describe("FilterableSelect", () => {
         }
       );
 
+      it.each(["top", "bottom"])(
+        "the listPlacement prop should be overridden when listWidth is set",
+        (listPlacement) => {
+          const wrapper = renderSelect({ listPlacement, listWidth: 100 });
+
+          simulateDropdownEvent(wrapper, "click");
+          expect(wrapper.find(SelectList).prop("listPlacement")).toBe(
+            `${listPlacement}-end`
+          );
+        }
+      );
+
       it("the flipEnabled prop should be passed", () => {
         const wrapper = renderSelect({ flipEnabled: false });
 
diff --git a/src/components/select/filterable-select/filterable-select.stories.tsx b/src/components/select/filterable-select/filterable-select.stories.tsx
index e726c613c..6e97b4284 100644
--- a/src/components/select/filterable-select/filterable-select.stories.tsx
+++ b/src/components/select/filterable-select/filterable-select.stories.tsx
@@ -12,6 +12,7 @@ import Typography from "../../typography";
 import {
   CustomSelectChangeEvent,
   FilterableSelect,
+  FilterableSelectProps,
   Option,
   OptionRow,
 } from "..";
@@ -850,3 +851,47 @@ CustomFilterAndOptionStyle.storyName = "Custom Filter and Option Style";
 CustomFilterAndOptionStyle.parameters = {
   chromatic: { disableSnapshot: true },
 };
+
+export const ListWidth: Story = () => {
+  const [listPlacement, setListPlacement] = useState<
+    FilterableSelectProps["listPlacement"]
+  >("bottom-end");
+  const [value, setValue] = useState<string>("");
+  const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
+    setValue(ev.target.value);
+  };
+  return (
+    <>
+      <Button mr={1} onClick={() => setListPlacement("top-end")}>
+        Top end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("bottom-end")}>
+        Bottom end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("top-start")}>
+        Top start
+      </Button>
+      <Button onClick={() => setListPlacement("bottom-start")}>
+        Bottom start
+      </Button>
+      <Box mt="200px" ml="200px" width="200px">
+        <FilterableSelect
+          name="listWidth"
+          id="listWidth"
+          label="color"
+          labelInline
+          listWidth={350}
+          listPlacement={listPlacement}
+          value={value}
+          onChange={handleChange}
+        >
+          <Option text="Amber" value="1" />
+          <Option text="Black" value="2" />
+          <Option text="Blue" value="3" />
+        </FilterableSelect>
+      </Box>
+    </>
+  );
+};
+ListWidth.storyName = "List width";
+ListWidth.parameters = { chromatic: { disableSnapshot: true } };
diff --git a/src/components/select/multi-select/multi-select.component.tsx b/src/components/select/multi-select/multi-select.component.tsx
index a206200a4..4120f8257 100644
--- a/src/components/select/multi-select/multi-select.component.tsx
+++ b/src/components/select/multi-select/multi-select.component.tsx
@@ -6,7 +6,6 @@ import React, {
   useMemo,
 } from "react";
 import invariant from "invariant";
-import { Side } from "@floating-ui/dom";
 
 import { filterOutStyledSystemSpacingProps } from "../../../style/utils";
 import SelectTextbox, {
@@ -14,7 +13,9 @@ import SelectTextbox, {
 } from "../__internal__/select-textbox";
 import guid from "../../../__internal__/utils/helpers/guid";
 import withFilter from "../__internal__/utils/with-filter.hoc";
-import SelectList from "../__internal__/select-list/select-list.component";
+import SelectList, {
+  ListPlacement,
+} from "../__internal__/select-list/select-list.component";
 import {
   StyledSelectPillContainer,
   StyledSelectMultiSelect,
@@ -77,7 +78,7 @@ export interface MultiSelectProps
   /** Maximum list height - defaults to 180 */
   listMaxHeight?: number;
   /** Placement of the select list in relation to the input element */
-  listPlacement?: Side;
+  listPlacement?: ListPlacement;
   /** Use the opposite list placement if the set placement does not fit */
   flipEnabled?: boolean;
   /** Wraps the pill text when it would overflow the input width */
@@ -95,6 +96,8 @@ export interface MultiSelectProps
   onChange?: (
     ev: CustomSelectChangeEvent | React.ChangeEvent<HTMLInputElement>
   ) => void;
+  /** Override the default width of the list element. Number passed is converted into pixel value */
+  listWidth?: number;
 }
 
 export const MultiSelect = React.forwardRef<HTMLInputElement, MultiSelectProps>(
@@ -136,6 +139,7 @@ export const MultiSelect = React.forwardRef<HTMLInputElement, MultiSelectProps>(
       virtualScrollOverscan,
       isOptional,
       required,
+      listWidth,
       ...textboxProps
     },
     ref
@@ -653,6 +657,19 @@ export const MultiSelect = React.forwardRef<HTMLInputElement, MultiSelectProps>(
       };
     }
 
+    let placement: ListPlacement;
+
+    switch (listPlacement) {
+      case "top":
+        placement = "top-end";
+        break;
+      case "bottom":
+        placement = "bottom-end";
+        break;
+      default:
+        placement = listPlacement;
+    }
+
     const selectList = (
       <FilterableSelectList
         ref={listboxRef}
@@ -668,13 +685,14 @@ export const MultiSelect = React.forwardRef<HTMLInputElement, MultiSelectProps>(
         isLoading={isLoading}
         tableHeader={tableHeader}
         multiColumn={multiColumn}
-        listPlacement={listPlacement}
+        listPlacement={listWidth !== undefined ? placement : listPlacement}
         listMaxHeight={listMaxHeight}
         flipEnabled={flipEnabled}
         multiselectValues={actualValue}
         isOpen={isOpen}
         enableVirtualScroll={enableVirtualScroll}
         virtualScrollOverscan={virtualScrollOverscan}
+        listWidth={listWidth}
       >
         {children}
       </FilterableSelectList>
diff --git a/src/components/select/multi-select/multi-select.mdx b/src/components/select/multi-select/multi-select.mdx
index 208a1c5da..d167cc00d 100644
--- a/src/components/select/multi-select/multi-select.mdx
+++ b/src/components/select/multi-select/multi-select.mdx
@@ -46,6 +46,16 @@ You can use `listMaxHeight` prop to override default max height value of select
 
 <Canvas of={MultiSelectStories.ListHeight} />
 
+### List width
+
+You can use `listWidth` prop to override the width of the select list. By default the list 
+will have the same width as the input.
+
+<Canvas
+  name="list width"
+  of={MultiSelectStories.ListWidth}
+/>
+
 ### Controlled Usage
 
 <Canvas of={MultiSelectStories.Controlled} />
diff --git a/src/components/select/multi-select/multi-select.pw.tsx b/src/components/select/multi-select/multi-select.pw.tsx
index 3812e3baf..bf124a812 100644
--- a/src/components/select/multi-select/multi-select.pw.tsx
+++ b/src/components/select/multi-select/multi-select.pw.tsx
@@ -735,8 +735,6 @@ test.describe("MultiSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     MultiSelectProps["listPlacement"],
     string,
@@ -766,12 +764,6 @@ test.describe("MultiSelect component", () => {
       if (position === "bottom") {
         flipPosition = "top";
       }
-      if (position === "left") {
-        flipPosition = "right";
-      }
-      if (position === "right") {
-        flipPosition = "left";
-      }
       await dropdownButton(page).click();
       const listElement = selectListPosition(page);
       await expect(listElement).toHaveAttribute(
@@ -1838,8 +1830,6 @@ test.describe("Accessibility tests for MultiSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     MultiSelectProps["listPlacement"],
     string,
diff --git a/src/components/select/multi-select/multi-select.spec.tsx b/src/components/select/multi-select/multi-select.spec.tsx
index 0f9395e7e..53d6d992d 100644
--- a/src/components/select/multi-select/multi-select.spec.tsx
+++ b/src/components/select/multi-select/multi-select.spec.tsx
@@ -299,7 +299,7 @@ describe("MultiSelect", () => {
     });
   });
 
-  it.each(["top", "bottom", "right", "left"])(
+  it.each(["top", "bottom", "top-end", "bottom-end"])(
     "the listPlacement prop should be passed",
     (listPlacement) => {
       const wrapper = renderSelect({ listPlacement });
@@ -311,6 +311,18 @@ describe("MultiSelect", () => {
     }
   );
 
+  it.each(["top", "bottom"])(
+    "the listPlacement prop should be overridden when listWidth is set",
+    (listPlacement) => {
+      const wrapper = renderSelect({ listPlacement, listWidth: 100 });
+
+      simulateDropdownEvent(wrapper, "click");
+      expect(wrapper.find(SelectList).prop("listPlacement")).toBe(
+        `${listPlacement}-end`
+      );
+    }
+  );
+
   it("the flipEnabled prop should be passed", () => {
     const wrapper = renderSelect({ flipEnabled: false });
 
diff --git a/src/components/select/multi-select/multi-select.stories.tsx b/src/components/select/multi-select/multi-select.stories.tsx
index 8ee697a41..e58f47f69 100644
--- a/src/components/select/multi-select/multi-select.stories.tsx
+++ b/src/components/select/multi-select/multi-select.stories.tsx
@@ -6,7 +6,7 @@ import generateStyledSystemProps from "../../../../.storybook/utils/styled-syste
 import Button from "../../button";
 import CarbonProvider from "../../carbon-provider";
 import Box from "../../box";
-import { MultiSelect, Option, OptionRow } from "..";
+import { MultiSelect, MultiSelectProps, Option, OptionRow } from "..";
 
 const styledSystemProps = generateStyledSystemProps({
   margin: true,
@@ -569,3 +569,51 @@ export const Virtualised: Story = () => {
   );
 };
 Virtualised.storyName = "Virtualised";
+
+export const ListWidth: Story = () => {
+  const [listPlacement, setListPlacement] = useState<
+    MultiSelectProps["listPlacement"]
+  >("bottom-end");
+  const [value, setValue] = useState<string[]>([]);
+  const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
+    const { value: updatedValue } = ev.target;
+
+    if (Array.isArray(updatedValue)) {
+      setValue(updatedValue);
+    }
+  };
+  return (
+    <>
+      <Button mr={1} onClick={() => setListPlacement("top-end")}>
+        Top end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("bottom-end")}>
+        Bottom end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("top-start")}>
+        Top start
+      </Button>
+      <Button onClick={() => setListPlacement("bottom-start")}>
+        Bottom start
+      </Button>
+      <Box mt="200px" ml="200px" width="200px">
+        <MultiSelect
+          name="listWidth"
+          id="listWidth"
+          label="color"
+          labelInline
+          listWidth={350}
+          listPlacement={listPlacement}
+          value={value}
+          onChange={handleChange}
+        >
+          <Option text="Amber" value="1" />
+          <Option text="Black" value="2" />
+          <Option text="Blue" value="3" />
+        </MultiSelect>
+      </Box>
+    </>
+  );
+};
+ListWidth.storyName = "List width";
+ListWidth.parameters = { chromatic: { disableSnapshot: true } };
diff --git a/src/components/select/simple-select/simple-select.component.tsx b/src/components/select/simple-select/simple-select.component.tsx
index 255800642..5f7f2e9cf 100644
--- a/src/components/select/simple-select/simple-select.component.tsx
+++ b/src/components/select/simple-select/simple-select.component.tsx
@@ -6,14 +6,15 @@ import React, {
   useMemo,
 } from "react";
 import invariant from "invariant";
-import { Side } from "@floating-ui/dom";
 
 import { filterOutStyledSystemSpacingProps } from "../../../style/utils";
 import StyledSelect from "../select.style";
 import SelectTextbox, {
   FormInputPropTypes,
 } from "../__internal__/select-textbox";
-import SelectList from "../__internal__/select-list/select-list.component";
+import SelectList, {
+  ListPlacement,
+} from "../__internal__/select-list/select-list.component";
 import guid from "../../../__internal__/utils/helpers/guid";
 import getNextChildByText from "../__internal__/utils/get-next-child-by-text";
 import isExpectedOption from "../__internal__/utils/is-expected-option";
@@ -78,7 +79,7 @@ export interface SimpleSelectProps
   /** Maximum list height - defaults to 180 */
   listMaxHeight?: number;
   /** Placement of the select list in relation to the input element */
-  listPlacement?: Side;
+  listPlacement?: ListPlacement;
   /** Use the opposite list placement if the set placement does not fit */
   flipEnabled?: boolean;
   /** Set this prop to enable a virtualised list of options. If it is not used then all options will be in the
@@ -96,6 +97,8 @@ export interface SimpleSelectProps
   onChange?: (
     ev: CustomSelectChangeEvent | React.ChangeEvent<HTMLInputElement>
   ) => void;
+  /** Override the default width of the list element. Number passed is converted into pixel value */
+  listWidth?: number;
 }
 
 export const SimpleSelect = React.forwardRef<
@@ -137,6 +140,7 @@ export const SimpleSelect = React.forwardRef<
       virtualScrollOverscan,
       isOptional,
       required,
+      listWidth,
       ...props
     },
     ref
@@ -490,6 +494,20 @@ export const SimpleSelect = React.forwardRef<
         ...filterOutStyledSystemSpacingProps(props),
       };
     }
+
+    let placement: ListPlacement;
+
+    switch (listPlacement) {
+      case "top":
+        placement = "top-end";
+        break;
+      case "bottom":
+        placement = "bottom-end";
+        break;
+      default:
+        placement = listPlacement;
+    }
+
     const selectList = (
       <SelectList
         ref={listboxRef}
@@ -505,11 +523,12 @@ export const SimpleSelect = React.forwardRef<
         onListScrollBottom={onListScrollBottom}
         tableHeader={tableHeader}
         multiColumn={multiColumn}
-        listPlacement={listPlacement}
+        listPlacement={listWidth !== undefined ? placement : listPlacement}
         flipEnabled={flipEnabled}
         isOpen={isOpen}
         enableVirtualScroll={enableVirtualScroll}
         virtualScrollOverscan={virtualScrollOverscan}
+        listWidth={listWidth}
       >
         {children}
       </SelectList>
diff --git a/src/components/select/simple-select/simple-select.mdx b/src/components/select/simple-select/simple-select.mdx
index 4da179bcc..9c6437eba 100644
--- a/src/components/select/simple-select/simple-select.mdx
+++ b/src/components/select/simple-select/simple-select.mdx
@@ -68,6 +68,16 @@ You can use `listMaxHeight` prop to override default max height value of select
 
 <Canvas of={SimpleSelectStories.ListHeight} />
 
+### List width
+
+You can use `listWidth` prop to override the width of the select list. By default the list 
+will have the same width as the input.
+
+<Canvas
+  name="list width"
+  of={SimpleSelectStories.ListWidth}
+/>
+
 ### Controlled usage
 
 <Canvas of={SimpleSelectStories.Controlled} />
diff --git a/src/components/select/simple-select/simple-select.pw.tsx b/src/components/select/simple-select/simple-select.pw.tsx
index 3f3788b1e..813f22ab4 100644
--- a/src/components/select/simple-select/simple-select.pw.tsx
+++ b/src/components/select/simple-select/simple-select.pw.tsx
@@ -18,6 +18,7 @@ import {
   SelectWithDynamicallyAddedOption,
   SimpleSelectControlled,
   WithObjectAsValue,
+  ListWidth,
 } from "./components.test-pw";
 import {
   commonDataElementInputPreview,
@@ -929,8 +930,6 @@ test.describe("SimpleSelect component", () => {
   ([
     ["top", "300px", "0px", "200px", "20px"],
     ["bottom", "0px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "500px", "20px"],
-    ["right", "200px", "0px", "0px", "500px"],
   ] as const).forEach(([position, top, bottom, left, right]) => {
     test(`should render list in ${position} position when margins are top ${top}, bottom ${bottom}, left ${left} and right ${right}`, async ({
       mount,
@@ -959,8 +958,6 @@ test.describe("SimpleSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     SimpleSelectProps["listPlacement"],
     string,
@@ -990,12 +987,6 @@ test.describe("SimpleSelect component", () => {
       if (position === "bottom") {
         flipPosition = "top";
       }
-      if (position === "left") {
-        flipPosition = "right";
-      }
-      if (position === "right") {
-        flipPosition = "left";
-      }
       await selectText(page).click();
       const listElement = selectListPosition(page);
       await expect(listElement).toHaveAttribute(
@@ -1006,6 +997,91 @@ test.describe("SimpleSelect component", () => {
     });
   });
 
+  ([
+    ["top", "100px 300px", 150, 2],
+    ["bottom", "0px 300px", 150, 68],
+    ["top-end", "100px 300px", 150, 2],
+    ["bottom-end", "0px 300px", 150, 68],
+    ["top-start", "100px 0px", 0, 2],
+    ["bottom-start", "100px 0px", 0, 168],
+  ] as [
+    NonNullable<SimpleSelectProps["listPlacement"]>,
+    string,
+    number,
+    number
+  ][]).forEach(([position, margin, left, top]) => {
+    test(`should render list wider than the input when listWidth is 350 and positioned based on listPlacement of ${position}`, async ({
+      mount,
+      page,
+    }) => {
+      await mount(
+        <ListWidth margin={margin} listPlacement={position} listWidth={350} />
+      );
+
+      const positionValue =
+        !position.includes("end") && !position.includes("start")
+          ? `${position}-end`
+          : position;
+      await selectText(page).click();
+      const listElement = selectListPosition(page);
+      const { inputWidth } = await selectText(page).evaluate((el) => ({
+        inputWidth: window
+          .getComputedStyle(el.parentElement as HTMLElement)
+          .getPropertyValue("width"),
+      }));
+      const { listLeftPosition, listTopPosition } = await listElement.evaluate(
+        (el) => ({
+          listLeftPosition: el.getBoundingClientRect().left,
+          listTopPosition: el.getBoundingClientRect().top,
+        })
+      );
+
+      await expect(listElement).toHaveAttribute(
+        "data-floating-placement",
+        positionValue
+      );
+      await expect(listElement).toBeVisible();
+      await expect(listElement).toHaveCSS("width", "350px");
+      expect(inputWidth).toBe("200px");
+
+      // sub-pixel rendering means this is not always exact
+      expect(listLeftPosition).toBeCloseTo(left);
+      expect(listTopPosition).toBeCloseTo(top);
+    });
+  });
+
+  ([
+    ["top", "0px"],
+    ["top-end", "0px"],
+  ] as [NonNullable<SimpleSelectProps["listPlacement"]>, string][]).forEach(
+    ([position, margin]) => {
+      test(`should render list wider than the input when listWidth is 350 and flip the placement based on original listPlacement of ${position} when there's no space to render it`, async ({
+        mount,
+        page,
+      }) => {
+        await mount(
+          <ListWidth margin={margin} listPlacement={position} listWidth={350} />
+        );
+
+        await selectText(page).click();
+        const listElement = selectListPosition(page);
+        const { inputWidth } = await selectText(page).evaluate((el) => ({
+          inputWidth: window
+            .getComputedStyle(el.parentElement as HTMLElement)
+            .getPropertyValue("width"),
+        }));
+
+        await expect(listElement).toHaveAttribute(
+          "data-floating-placement",
+          "bottom-start"
+        );
+        await expect(listElement).toBeVisible();
+        await expect(listElement).toHaveCSS("width", "350px");
+        expect(inputWidth).toBe("200px");
+      });
+    }
+  );
+
   [
     ["bottom", "0px", "0px", "0px", "20px"],
     ["top", "600px", "0px", "0px", "20px"],
@@ -1784,8 +1860,6 @@ test.describe("Accessibility tests for SimpleSelect component", () => {
   ([
     ["top", "300px", "0px", "200px", "20px"],
     ["bottom", "0px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "500px", "20px"],
-    ["right", "200px", "0px", "0px", "500px"],
   ] as [
     SimpleSelectProps["listPlacement"],
     string,
@@ -1815,8 +1889,6 @@ test.describe("Accessibility tests for SimpleSelect component", () => {
   ([
     ["top", "0px", "0px", "0px", "20px"],
     ["bottom", "600px", "0px", "0px", "20px"],
-    ["left", "200px", "0px", "0px", "900px"],
-    ["right", "200px", "0px", "500px", "20px"],
   ] as [
     SimpleSelectProps["listPlacement"],
     string,
diff --git a/src/components/select/simple-select/simple-select.stories.tsx b/src/components/select/simple-select/simple-select.stories.tsx
index 777a1c635..ad6384258 100644
--- a/src/components/select/simple-select/simple-select.stories.tsx
+++ b/src/components/select/simple-select/simple-select.stories.tsx
@@ -15,7 +15,7 @@ import Box from "../../box";
 import Typography from "../../typography";
 import generateStyledSystemProps from "../../../../.storybook/utils/styled-system-props";
 
-import SimpleSelect from "./simple-select.component";
+import SimpleSelect, { SimpleSelectProps } from "./simple-select.component";
 
 const styledSystemProps = generateStyledSystemProps({
   margin: true,
@@ -854,3 +854,47 @@ export const SelectWithDynamicallyAddedOption: Story = () => {
   );
 };
 SelectWithDynamicallyAddedOption.storyName = "Dynamically Adding Options";
+
+export const ListWidth: Story = () => {
+  const [listPlacement, setListPlacement] = useState<
+    SimpleSelectProps["listPlacement"]
+  >("bottom-end");
+  const [value, setValue] = useState("");
+  const handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
+    setValue(ev.target.value);
+  };
+  return (
+    <>
+      <Button mr={1} onClick={() => setListPlacement("top-end")}>
+        Top end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("bottom-end")}>
+        Bottom end
+      </Button>
+      <Button mr={1} onClick={() => setListPlacement("top-start")}>
+        Top start
+      </Button>
+      <Button onClick={() => setListPlacement("bottom-start")}>
+        Bottom Start
+      </Button>
+      <Box mt="200px" ml="200px" width="200px">
+        <Select
+          name="listWidth"
+          id="listWidth"
+          label="color"
+          labelInline
+          listWidth={350}
+          listPlacement={listPlacement}
+          value={value}
+          onChange={handleChange}
+        >
+          <Option text="Amber" value="1" />
+          <Option text="Black" value="2" />
+          <Option text="Blue" value="3" />
+        </Select>
+      </Box>
+    </>
+  );
+};
+ListWidth.storyName = "List width";
+ListWidth.parameters = { chromatic: { disableSnapshot: true } };


Test patch:
diff --git a/src/components/select/__internal__/select-list/select-list.test.tsx b/src/components/select/__internal__/select-list/select-list.test.tsx
index be9a49d34..5456a137d 100644
--- a/src/components/select/__internal__/select-list/select-list.test.tsx
+++ b/src/components/select/__internal__/select-list/select-list.test.tsx
@@ -109,7 +109,7 @@ describe("rendered content", () => {
     ).toBeVisible();
   });
 
-  it.each(["top", "bottom", "left", "right"] as const)(
+  it.each(["top", "bottom"] as const)(
     "computes correct position for list when listPlacement prop is %s",
     async (listPlacement) => {
       const { rerender } = render(
diff --git a/src/components/select/simple-select/components.test-pw.tsx b/src/components/select/simple-select/components.test-pw.tsx
index 997ad36a2..5369c2d22 100644
--- a/src/components/select/simple-select/components.test-pw.tsx
+++ b/src/components/select/simple-select/components.test-pw.tsx
@@ -603,3 +603,30 @@ export const WithObjectAsValue = () => {
     </>
   );
 };
+
+export const ListWidth = ({
+  listPlacement,
+  listWidth,
+  margin,
+}: Partial<SimpleSelectProps>) => {
+  const [value, setValue] = useState("");
+  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setValue(event.target.value);
+  };
+
+  return (
+    <Box width="200px" margin={margin}>
+      <Select
+        label="label"
+        value={value}
+        onChange={handleChange}
+        listPlacement={listPlacement}
+        listWidth={listWidth}
+      >
+        <Option text="Amber" value="1" />
+        <Option text="Black" value="2" />
+        <Option text="Blue" value="3" />
+      </Select>
+    </Box>
+  );
+};
diff --git a/src/components/select/simple-select/simple-select-test.stories.tsx b/src/components/select/simple-select/simple-select-test.stories.tsx
index ea52e73bc..4e2983e95 100644
--- a/src/components/select/simple-select/simple-select-test.stories.tsx
+++ b/src/components/select/simple-select/simple-select-test.stories.tsx
@@ -35,21 +35,12 @@ export default {
     "data-role": { table: { disable: true }, control: false },
     listPlacement: {
       options: [
-        "auto",
-        "auto-start",
-        "auto-end",
         "top",
-        "top-start",
-        "top-end",
         "bottom",
+        "top-start",
         "bottom-start",
+        "top-end",
         "bottom-end",
-        "right",
-        "right-start",
-        "right-end",
-        "left",
-        "left-start",
-        "left-end",
       ],
       control: {
         type: "select",
@@ -108,6 +99,7 @@ Default.args = {
   mt: 0,
   listPlacement: undefined,
   flipEnabled: true,
+  listWidth: undefined,
 };
 
 export const DelayedReposition = () => {
diff --git a/src/components/select/simple-select/simple-select.test.tsx b/src/components/select/simple-select/simple-select.test.tsx
index 5abd4d709..790ac95ea 100644
--- a/src/components/select/simple-select/simple-select.test.tsx
+++ b/src/components/select/simple-select/simple-select.test.tsx
@@ -198,6 +198,54 @@ test("associates the dropdown list with the correct accessible name from the lab
   expect(await screen.findByRole("listbox")).toHaveAccessibleName("Colour");
 });
 
+test.each(["top", "bottom"] as const)(
+  "should override the data attribute on the list when listWidth is set and placement is %s",
+  async (listPlacement) => {
+    const user = userEvent.setup();
+    render(
+      <SimpleSelect
+        listPlacement={listPlacement}
+        listWidth={100}
+        label="Colour"
+        onChange={() => {}}
+      >
+        <Option text="amber" value="amber" />
+      </SimpleSelect>
+    );
+
+    await user.click(screen.getByRole("combobox"));
+
+    expect(await screen.findByTestId("select-list-wrapper")).toHaveAttribute(
+      "data-floating-placement",
+      `${listPlacement}-end`
+    );
+  }
+);
+
+test.each(["top-end", "bottom-end", "top-start", "bottom-start"] as const)(
+  "should not override the data attribute on the list when listWidth is set and placement is %s",
+  async (listPlacement) => {
+    const user = userEvent.setup();
+    render(
+      <SimpleSelect
+        listPlacement={listPlacement}
+        listWidth={100}
+        label="Colour"
+        onChange={() => {}}
+      >
+        <Option text="amber" value="amber" />
+      </SimpleSelect>
+    );
+
+    await user.click(screen.getByRole("combobox"));
+
+    expect(await screen.findByTestId("select-list-wrapper")).toHaveAttribute(
+      "data-floating-placement",
+      listPlacement
+    );
+  }
+);
+
 describe("typing into the input", () => {
   it("selects the first option with text starting with the typed printable character", async () => {
     const user = userEvent.setup();
diff --git a/src/hooks/__internal__/useFloating/useFloating.test.tsx b/src/hooks/__internal__/useFloating/useFloating.test.tsx
index f42bddceb..2ef893a34 100644
--- a/src/hooks/__internal__/useFloating/useFloating.test.tsx
+++ b/src/hooks/__internal__/useFloating/useFloating.test.tsx
@@ -105,9 +105,7 @@ test("when using size middleware, the original width and height are restored aft
     }),
   ];
 
-  const { rerender } = await render(
-    <MockComponent isOpen middleware={middleWare} />
-  );
+  const { rerender } = render(<MockComponent isOpen middleware={middleWare} />);
 
   const floatingElement = await screen.findByTestId("floating-element");
   const positionedStyle = window.getComputedStyle(floatingElement);