Repository: Sage/carbon (ts)

Issue:
Loader Spinner : custom text
### Description

Add the possibility to customise the text for each Loader Spinner

### Suggested solution

Add a label prop or the possibility to have a child

### 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:
feat(loader-spinner): add spinnerLabel prop to override the default loading label with custom text
fix #6694

### 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.
-->

Consumers can now use the `spinnerLabel` prop to override the default label of `"Loading..."` and provide their own custom string instead

### 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.
-->

Currently, consumers can only override the default label of `Loading...` by using an `<I18nProvider/ >`

### 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
- [x] Unit tests added or updated if required
- [x] 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

- [x] Tested in provided StackBlitz sandbox/Storybook
- [x] 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/loader-spinner/loader-spinner.component.tsx b/src/components/loader-spinner/loader-spinner.component.tsx
index fdd810ee5..385644369 100644
--- a/src/components/loader-spinner/loader-spinner.component.tsx
+++ b/src/components/loader-spinner/loader-spinner.component.tsx
@@ -18,6 +18,11 @@ import useMediaQuery from "../../hooks/useMediaQuery";
 import Typography from "../typography";
 
 export interface LoaderSpinnerProps extends MarginProps, TagProps {
+  /**
+   * Use the spinnerLabel prop to override the default `"Loading..."` label with
+   * any custom string
+   */
+  spinnerLabel?: string;
   /**
    * The size prop allows a specific size to be set, ranging from
    * `extra-small` to `extra-large`
@@ -49,6 +54,7 @@ export interface LoaderSpinnerProps extends MarginProps, TagProps {
 }
 
 export const LoaderSpinner = ({
+  spinnerLabel,
   size = "medium",
   showSpinnerLabel = true,
   variant = "action",
@@ -65,7 +71,7 @@ export const LoaderSpinner = ({
 
   const isLabelDark = variant !== "inverse" && variant !== "gradient-white";
 
-  const spinnerLabel = (
+  const renderSpinnerLabel = (
     <StyledLabel
       data-role="visible-label"
       variant="span"
@@ -83,7 +89,7 @@ export const LoaderSpinner = ({
         size === "extra-large" ? "var(--sizing300)" : "var(--sizing250)"
       }
     >
-      {locale.loaderSpinner.loading()}
+      {spinnerLabel || locale.loaderSpinner.loading()}
     </StyledLabel>
   );
 
@@ -106,7 +112,7 @@ export const LoaderSpinner = ({
       {...filterStyledSystemMarginProps(rest)}
     >
       {reduceMotion ? (
-        spinnerLabel
+        renderSpinnerLabel
       ) : (
         <>
           <StyledSpinnerCircleSvg
@@ -123,14 +129,14 @@ export const LoaderSpinner = ({
             <circle data-role="inner-arc" />
           </StyledSpinnerCircleSvg>
           {showSpinnerLabel ? (
-            spinnerLabel
+            renderSpinnerLabel
           ) : (
             <Typography
               data-role="hidden-label"
               variant="span"
               screenReaderOnly
             >
-              {locale.loaderSpinner.loading()}
+              {spinnerLabel || locale.loaderSpinner.loading()}
             </Typography>
           )}
         </>
diff --git a/src/components/loader-spinner/loader-spinner.mdx b/src/components/loader-spinner/loader-spinner.mdx
index f53e18457..4be6d2d40 100644
--- a/src/components/loader-spinner/loader-spinner.mdx
+++ b/src/components/loader-spinner/loader-spinner.mdx
@@ -44,6 +44,13 @@ loading label will be displayed.
 
 <Canvas of={LoaderSpinnerStories.Default} />
 
+### Override Spinner label 
+
+Please find an example of the `spinnerLabel` prop used below, any string can be passed to the prop to override
+the default `"Loading..."` label. 
+
+<Canvas of={LoaderSpinnerStories.OverrideSpinnerLabel} />
+
 ### Sizes
 
 Please find examples of all spinner sizes below. Ranging from sizes `extra-small` to `extra-large`.
diff --git a/src/components/loader-spinner/loader-spinner.pw.tsx b/src/components/loader-spinner/loader-spinner.pw.tsx
index 593bce4ee..b37d80bfc 100644
--- a/src/components/loader-spinner/loader-spinner.pw.tsx
+++ b/src/components/loader-spinner/loader-spinner.pw.tsx
@@ -17,6 +17,26 @@ import {
 } from "./loader-spinner.config";
 
 test.describe("Prop checks for Loader Spinner component", () => {
+  test("when the 'spinnerLabel' prop is passed a custom string value it overrides the default visible label", async ({
+    mount,
+    page,
+  }) => {
+    await mount(<LoaderSpinnerComponent spinnerLabel="foo" />);
+
+    await expect(loaderSpinnerVisibleLabel(page)).toHaveText("foo");
+  });
+
+  test("when the 'spinnerLabel' prop is passed a custom string value it overrides the visually hidden label", async ({
+    mount,
+    page,
+  }) => {
+    await mount(
+      <LoaderSpinnerComponent spinnerLabel="bar" showSpinnerLabel={false} />
+    );
+
+    await expect(loaderSpinnerHiddenLabel(page)).toHaveText("bar");
+  });
+
   test("when the 'size' prop is passed as 'extra-small' the component wrapper has a flex-direction of row", async ({
     mount,
     page,
diff --git a/src/components/loader-spinner/loader-spinner.spec.tsx b/src/components/loader-spinner/loader-spinner.spec.tsx
index 3b8961d3c..99229f321 100644
--- a/src/components/loader-spinner/loader-spinner.spec.tsx
+++ b/src/components/loader-spinner/loader-spinner.spec.tsx
@@ -56,6 +56,27 @@ describe("LoaderSpinner", () => {
   });
 
   describe("when custom props are passed", () => {
+    it("should be visible when the 'spinnerLabel' prop is passed a custom string value", () => {
+      render(<LoaderSpinner spinnerLabel="foo" />);
+      const visibleLabelElement = screen.getByText("foo");
+
+      expect(visibleLabelElement).toBeVisible();
+    });
+
+    it("should override the visible label text when the 'spinnerLabel' prop is passed a custom string value", () => {
+      render(<LoaderSpinner spinnerLabel="foo" />);
+      const wrapperElement = screen.getByRole("status");
+
+      expect(wrapperElement).toHaveTextContent("foo");
+    });
+
+    it("should override the visually hidden label text when the 'spinnerLabel' prop is passed a custom string value", () => {
+      render(<LoaderSpinner spinnerLabel="bar" showSpinnerLabel={false} />);
+      const hiddenLabelElement = screen.getByTestId("hidden-label");
+
+      expect(hiddenLabelElement).toHaveTextContent("bar");
+    });
+
     it("when the 'size' prop is passed as 'extra-small' the component wrapper has a flex-direction of row", () => {
       render(<LoaderSpinner size="extra-small" />);
       const wrapperElement = screen.getByRole("status");
diff --git a/src/components/loader-spinner/loader-spinner.stories.tsx b/src/components/loader-spinner/loader-spinner.stories.tsx
index c3c3f3d02..5edc5c7a1 100644
--- a/src/components/loader-spinner/loader-spinner.stories.tsx
+++ b/src/components/loader-spinner/loader-spinner.stories.tsx
@@ -25,6 +25,14 @@ type Story = StoryObj<typeof LoaderSpinner>;
 export const Default: Story = () => <LoaderSpinner />;
 Default.storyName = "Default";
 
+export const OverrideSpinnerLabel: Story = () => (
+  <Box display="flex">
+    <LoaderSpinner mx="3" spinnerLabel="Processing..." variant="action" />
+    <LoaderSpinner mx="3" spinnerLabel="Saving..." variant="neutral" />
+  </Box>
+);
+OverrideSpinnerLabel.storyName = "Override Spinner Label";
+
 export const Sizes: Story = () => {
   return (
     <Box display="flex" alignItems="baseline">


Test patch:
diff --git a/src/components/loader-spinner/loader-spinner-test.stories.tsx b/src/components/loader-spinner/loader-spinner-test.stories.tsx
index 5d8f20d1c..499b75e74 100644
--- a/src/components/loader-spinner/loader-spinner-test.stories.tsx
+++ b/src/components/loader-spinner/loader-spinner-test.stories.tsx
@@ -16,6 +16,11 @@ export default {
     },
   },
   argTypes: {
+    spinnerLabel: {
+      control: {
+        type: "text",
+      },
+    },
     size: {
       options: LOADER_SPINNER_SIZES,
       control: {