Repository: Sage/carbon (ts)

Issue:
`Select` component to have a better truncated format
### Description

Currently, if content in `Select` component overflows, the content gets cropped without ...
Having elipses in this case would enhance accessibility.


<img width="270" alt="Screenshot 2024-03-22 at 12 10 34" src="https://github.com/Sage/carbon/assets/90848080/de8e31ba-d0a2-4db4-bd63-a4511dfbb01a">



### Suggested solution

Please add elipses when the full content needs to be cropped due to the limited width.

<img width="333" alt="Screenshot 2024-03-22 at 12 11 03" src="https://github.com/Sage/carbon/assets/90848080/bd232ffb-39e6-4ff0-888a-24e54c3eecbb">



### Demo URL

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

### Alternatives

_No response_

### Additional context

_No response_

### Confidentiality

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

Pull request description:
fix(filterable-select, simple-select): ensure an ellipsis is shown during text overflow
fix #6641

### 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.
-->

<img width="886" alt="Screenshot 2024-04-15 at 16 04 32" src="https://github.com/Sage/carbon/assets/114918852/af50701b-dbb7-465e-9a27-51d26580179c">

Ensures that an ellipsis is applied when text overflows, to make users fully aware that truncation is taking place

### 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.
-->

<img width="874" alt="Screenshot 2024-04-15 at 16 06 56" src="https://github.com/Sage/carbon/assets/114918852/0aa54e19-4d27-41e9-ac2c-82021cc99d3e">

Currently when text truncates no ellipsis is applied, meaning some users are not fully aware truncation is actually taking place

### 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] All themes are supported if required
- [x] Unit tests added or updated if required
- [ ] Playwright automation tests added or updated if required
- [x] 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
- [x] 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>
-->

Check the new test stories added for `FilterableSelect` and `SimpleSelect` to ensure that truncation occurs on both components

Patch:
diff --git a/src/__internal__/input/input.component.tsx b/src/__internal__/input/input.component.tsx
index d88447b4a..60a8814d7 100644
--- a/src/__internal__/input/input.component.tsx
+++ b/src/__internal__/input/input.component.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useContext, useRef, useCallback } from "react";
 import StyledInput from "./input.style";
 import { InputContext, InputGroupContext } from "../input-behaviour";
 import { BorderRadiusType } from "../../components/box/box.component";
+import { SelectTextboxContext } from "../../components/select/select-textbox/select-textbox-context";
 
 export type EnterKeyHintTypes =
   | "enter"
@@ -115,6 +116,7 @@ const Input = React.forwardRef<
   ): JSX.Element => {
     const context = useContext(InputContext);
     const groupContext = useContext(InputGroupContext);
+    const { isInputInSelect } = useContext(SelectTextboxContext);
     const deferredTimeout = useRef<null | ReturnType<typeof setTimeout>>(null);
     let input = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
 
@@ -225,6 +227,7 @@ const Input = React.forwardRef<
     return (
       <StyledInput
         {...rest}
+        isInputInSelect={isInputInSelect}
         data-has-autofocus={autoFocus ? true : undefined}
         aria-describedby={combinedDescription}
         aria-labelledby={ariaLabelledBy}
diff --git a/src/__internal__/input/input.spec.tsx b/src/__internal__/input/input.spec.tsx
index 4ed4adab2..e4a45994b 100644
--- a/src/__internal__/input/input.spec.tsx
+++ b/src/__internal__/input/input.spec.tsx
@@ -5,6 +5,7 @@ import Input, { InputProps, EnterKeyHintTypes } from "./input.component";
 import StyledInput from "./input.style";
 
 import { InputContext, InputContextProps } from "../input-behaviour";
+import { SelectTextboxContext } from "../../components/select/select-textbox/select-textbox-context";
 
 import { assertStyleMatch } from "../../__spec_helper__/test-utils";
 
@@ -66,6 +67,21 @@ describe("Input", () => {
     );
   });
 
+  it("contextually renders with text-overflow: ellipsis", () => {
+    const wrapper = mount(
+      <SelectTextboxContext.Provider value={{ isInputInSelect: true }}>
+        <Input />
+      </SelectTextboxContext.Provider>
+    );
+
+    assertStyleMatch(
+      {
+        textOverflow: "ellipsis",
+      },
+      wrapper
+    );
+  });
+
   it.each([
     "enter",
     "done",
diff --git a/src/__internal__/input/input.style.ts b/src/__internal__/input/input.style.ts
index 1ceb82e5a..bd6b802ae 100644
--- a/src/__internal__/input/input.style.ts
+++ b/src/__internal__/input/input.style.ts
@@ -2,8 +2,11 @@ import styled, { css } from "styled-components";
 import { CommonInputProps } from "./input.component";
 
 const StyledInput = styled.input<
-  Pick<CommonInputProps, "align" | "disabled" | "inputBorderRadius">
+  Pick<CommonInputProps, "align" | "disabled" | "inputBorderRadius"> & {
+    isInputInSelect: boolean;
+  }
 >`
+  ${({ isInputInSelect }) => isInputInSelect && "text-overflow: ellipsis"};
   background: transparent;
   border: none;
   color: var(--colorsUtilityYin090);
diff --git a/src/components/pager/pager.style.ts b/src/components/pager/pager.style.ts
index 84aaa2655..f923fa104 100644
--- a/src/components/pager/pager.style.ts
+++ b/src/components/pager/pager.style.ts
@@ -5,7 +5,7 @@ import StyledInput from "../../__internal__/input/input.style";
 import StyledInputPresentation from "../../__internal__/input/input-presentation.style";
 import StyledFormField from "../../__internal__/form-field/form-field.style";
 import InputIconToggleStyle from "../../__internal__/input-icon-toggle/input-icon-toggle.style";
-import StyledSelectText from "../select/__internal__/select-text/select-text.style";
+import { StyledSelectText } from "../select/__internal__/select-text/select-text.style";
 import Link from "../link";
 
 interface StyledPagerProps {
diff --git a/src/components/select/__internal__/select-text/select-text.component.tsx b/src/components/select/__internal__/select-text/select-text.component.tsx
index 9d8fa12a5..6c5864c23 100644
--- a/src/components/select/__internal__/select-text/select-text.component.tsx
+++ b/src/components/select/__internal__/select-text/select-text.component.tsx
@@ -1,5 +1,8 @@
 import React from "react";
-import StyledSelectText from "./select-text.style";
+import {
+  StyledSelectText,
+  StyledSelectTextChildrenWrapper,
+} from "./select-text.style";
 
 export interface SelectTextProps {
   /** If true the Component will be disabled */
@@ -53,7 +56,9 @@ const SelectText = ({
       transparent={transparent}
       size={size}
     >
-      {hasPlaceholder ? placeholder : formattedValue}
+      <StyledSelectTextChildrenWrapper>
+        {hasPlaceholder ? placeholder : formattedValue}
+      </StyledSelectTextChildrenWrapper>
     </StyledSelectText>
   );
 };
diff --git a/src/components/select/__internal__/select-text/select-text.spec.tsx b/src/components/select/__internal__/select-text/select-text.spec.tsx
index 2c43b4298..252304c64 100644
--- a/src/components/select/__internal__/select-text/select-text.spec.tsx
+++ b/src/components/select/__internal__/select-text/select-text.spec.tsx
@@ -2,6 +2,7 @@ import { mount } from "enzyme";
 import React from "react";
 import { assertStyleMatch } from "../../../../__spec_helper__/test-utils";
 import SelectText, { SelectTextProps } from ".";
+import { StyledSelectTextChildrenWrapper } from "./select-text.style";
 
 function renderSelectText(props: SelectTextProps = {}) {
   return mount(<SelectText {...props} />);
@@ -29,6 +30,20 @@ describe("SelectText", () => {
     expect(wrapper.text()).toBe(placeholder);
   });
 
+  it("should have proper styling applied to the children wrapper to ensure truncation is displayed", () => {
+    const wrapper = renderSelectText({});
+
+    assertStyleMatch(
+      {
+        display: "block",
+        whiteSpace: "nowrap",
+        overflow: "hidden",
+        textOverflow: "ellipsis",
+      },
+      wrapper.find(StyledSelectTextChildrenWrapper)
+    );
+  });
+
   it("should have proper styling when disabled", () => {
     const wrapper = renderSelectText({ disabled: true });
 
@@ -65,7 +80,6 @@ describe("SelectText", () => {
       {
         textAlign: "right",
         fontWeight: "900",
-        flexDirection: "row-reverse",
       },
       wrapper
     );
diff --git a/src/components/select/__internal__/select-text/select-text.style.ts b/src/components/select/__internal__/select-text/select-text.style.ts
index f958394c7..487a007ba 100644
--- a/src/components/select/__internal__/select-text/select-text.style.ts
+++ b/src/components/select/__internal__/select-text/select-text.style.ts
@@ -15,10 +15,7 @@ const StyledSelectText = styled.span<StyledSelectTextProps>`
     flex-grow: 1;
     font-size: 14px;
     height: auto;
-    overflow: hidden;
     outline: none;
-    text-overflow: ellipsis;
-    white-space: nowrap;
     width: 30px;
     z-index: 1;
     padding-left: ${sizes[size].horizontalPadding};
@@ -53,4 +50,11 @@ const StyledSelectText = styled.span<StyledSelectTextProps>`
   `}
 `;
 
-export default StyledSelectText;
+const StyledSelectTextChildrenWrapper = styled.span`
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+export { StyledSelectText, StyledSelectTextChildrenWrapper };
diff --git a/src/components/select/select-textbox/select-textbox-context.ts b/src/components/select/select-textbox/select-textbox-context.ts
new file mode 100644
index 000000000..592abe43b
--- /dev/null
+++ b/src/components/select/select-textbox/select-textbox-context.ts
@@ -0,0 +1,9 @@
+import React from "react";
+
+export type SelectTextboxContextProps = {
+  isInputInSelect?: boolean;
+};
+
+export const SelectTextboxContext = React.createContext<SelectTextboxContextProps>(
+  {}
+);
diff --git a/src/components/select/select-textbox/select-textbox.component.tsx b/src/components/select/select-textbox/select-textbox.component.tsx
index fe88b945d..512ee01b3 100644
--- a/src/components/select/select-textbox/select-textbox.component.tsx
+++ b/src/components/select/select-textbox/select-textbox.component.tsx
@@ -7,6 +7,7 @@ import SelectText from "../__internal__/select-text";
 import useLocale from "../../../hooks/__internal__/useLocale";
 import { ValidationProps } from "../../../__internal__/validations";
 import { CustomSelectChangeEvent } from "../simple-select/simple-select.component";
+import { SelectTextboxContext } from "./select-textbox-context";
 
 const floatingMiddleware = [
   offset(({ rects }) => ({
@@ -217,35 +218,37 @@ const SelectTextbox = React.forwardRef(
       (Array.isArray(selectedValue) && typeof selectedValue[0] === "string");
 
     return (
-      <Textbox
-        aria-label={ariaLabel}
-        data-element="select-input"
-        inputIcon="dropdown"
-        autoComplete="off"
-        size={size}
-        onChange={onChange}
-        value={
-          hasStringValue ? (selectedValue as string | string[]) : undefined
-        }
-        placeholder={
-          hasTextCursor ? placeholder || l.select.placeholder() : undefined
-        }
-        {...inputAriaAttributes}
-        {...textboxProps}
-        my={0} // prevents any form spacing being applied
-      >
-        {!hasTextCursor && (
-          <SelectText
-            transparent={transparent}
-            placeholder={placeholder || l.select.placeholder()}
-            onClick={disabled || readOnly ? undefined : handleTextboxClick}
-            disabled={disabled}
-            readOnly={readOnly}
-            size={size}
-            {...restProps}
-          />
-        )}
-      </Textbox>
+      <SelectTextboxContext.Provider value={{ isInputInSelect: true }}>
+        <Textbox
+          aria-label={ariaLabel}
+          data-element="select-input"
+          inputIcon="dropdown"
+          autoComplete="off"
+          size={size}
+          onChange={onChange}
+          value={
+            hasStringValue ? (selectedValue as string | string[]) : undefined
+          }
+          placeholder={
+            hasTextCursor ? placeholder || l.select.placeholder() : undefined
+          }
+          {...inputAriaAttributes}
+          {...textboxProps}
+          my={0} // prevents any form spacing being applied
+        >
+          {!hasTextCursor && (
+            <SelectText
+              transparent={transparent}
+              placeholder={placeholder || l.select.placeholder()}
+              onClick={disabled || readOnly ? undefined : handleTextboxClick}
+              disabled={disabled}
+              readOnly={readOnly}
+              size={size}
+              {...restProps}
+            />
+          )}
+        </Textbox>
+      </SelectTextboxContext.Provider>
     );
   }
 );


Test patch:
diff --git a/src/components/select/filterable-select/filterable-select-test.stories.tsx b/src/components/select/filterable-select/filterable-select-test.stories.tsx
index 31218b372..f9b61491f 100644
--- a/src/components/select/filterable-select/filterable-select-test.stories.tsx
+++ b/src/components/select/filterable-select/filterable-select-test.stories.tsx
@@ -630,3 +630,29 @@ export const SingleOption = () => (
 );
 
 SingleOption.storyName = "Single Option";
+
+export const FilterableSelectWithTruncatedText = () => {
+  const longValueText =
+    "Like a lot of intelligent animals, most crows are quite social." +
+    "For instance, American crows spend most of the year living in pairs or small family groups." +
+    "During the winter months, they will congregate with hundreds or even thousands of their peers to sleep together at night.";
+  const [value, setValue] = useState("1");
+
+  function onChangeHandler(event: React.ChangeEvent<HTMLInputElement>) {
+    setValue(event.target.value);
+  }
+
+  return (
+    <FilterableSelect
+      name="testing"
+      value={value}
+      onChange={onChangeHandler}
+      openOnFocus
+      label="Test"
+      maxWidth="70%"
+    >
+      <Option value="1" text={longValueText} />
+      <Option value="2" text={longValueText} />
+    </FilterableSelect>
+  );
+};
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 62c87011d..ea52e73bc 100644
--- a/src/components/select/simple-select/simple-select-test.stories.tsx
+++ b/src/components/select/simple-select/simple-select-test.stories.tsx
@@ -645,3 +645,29 @@ export const SimpleSelectWithDisabledOption = () => {
     </Select>
   );
 };
+
+export const SimpleSelectWithTruncatedText = () => {
+  const longValueText =
+    "Like a lot of intelligent animals, most crows are quite social." +
+    "For instance, American crows spend most of the year living in pairs or small family groups." +
+    "During the winter months, they will congregate with hundreds or even thousands of their peers to sleep together at night.";
+  const [value, setValue] = useState("1");
+
+  function onChangeHandler(event: React.ChangeEvent<HTMLInputElement>) {
+    setValue(event.target.value);
+  }
+
+  return (
+    <Select
+      name="testing"
+      value={value}
+      onChange={onChangeHandler}
+      openOnFocus
+      label="Test"
+      maxWidth="70%"
+    >
+      <Option value="1" text={longValueText} />
+      <Option value="2" text={longValueText} />
+    </Select>
+  );
+};