Repository: Sage/carbon (ts)

Issue:
Breadcrumbs component should have a color / backgroundcolor prop
### Description

Breadcrumbs component should have a color / backgroundcolor prop for the dark background screens

### Suggested solution

Additional prop for color/background color 

For example:
<img width="696" alt="image" src="https://github.com/Sage/carbon/assets/101108249/15782f02-4a1c-49be-b89b-8cfdbccaa6d5">


### 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(breadcrumbs): add isDarkBackground prop
fix #6724

### 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.
-->

Add `isDarkBackground` prop to `Breadcrumbs` to allow consumers to change the colour scheme of the component to be on a darker background.

![image](https://github.com/user-attachments/assets/2788c6a5-6fbd-41e6-bca3-54cdc4809ae4)

### 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.
-->

`Breadcrumbs` does not have a dark background variation. 

### 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
- [ ] 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

- [ ] 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/breadcrumbs/__internal__/breadcrumbs.context.tsx b/src/components/breadcrumbs/__internal__/breadcrumbs.context.tsx
new file mode 100644
index 000000000..c46b5ca06
--- /dev/null
+++ b/src/components/breadcrumbs/__internal__/breadcrumbs.context.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+export type BreadcrumbsContextType = {
+  isDarkBackground: boolean;
+};
+
+const BreadcrumbsContext = React.createContext<BreadcrumbsContextType>({
+  isDarkBackground: false,
+});
+
+export default BreadcrumbsContext;
diff --git a/src/components/breadcrumbs/breadcrumbs.component.tsx b/src/components/breadcrumbs/breadcrumbs.component.tsx
index be745a653..1a73ab33d 100644
--- a/src/components/breadcrumbs/breadcrumbs.component.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.component.tsx
@@ -3,25 +3,30 @@ import { SpaceProps } from "styled-system";
 import tagComponent, { TagProps } from "../../__internal__/utils/helpers/tags";
 import StyledBreadcrumbs from "./breadcrumbs.style";
 import useLocale from "../../hooks/__internal__/useLocale";
+import BreadcrumbsContext from "./__internal__/breadcrumbs.context";
 
 export interface BreadcrumbsProps extends TagProps, SpaceProps {
   /** Child crumbs to display */
   children: React.ReactNode;
+  /** Sets the colour styling when component is rendered on a dark background */
+  isDarkBackground?: boolean;
 }
 
 export const Breadcrumbs = React.forwardRef<HTMLElement, BreadcrumbsProps>(
-  ({ children, ...rest }: BreadcrumbsProps, ref) => {
+  ({ children, isDarkBackground = false, ...rest }: BreadcrumbsProps, ref) => {
     const l = useLocale();
     return (
-      <StyledBreadcrumbs
-        ref={ref}
-        role="navigation"
-        {...tagComponent("breadcrumbs", rest)}
-        aria-label={l.breadcrumbs.ariaLabel()}
-        {...rest}
-      >
-        <ol>{children}</ol>
-      </StyledBreadcrumbs>
+      <BreadcrumbsContext.Provider value={{ isDarkBackground }}>
+        <StyledBreadcrumbs
+          ref={ref}
+          role="navigation"
+          {...tagComponent("breadcrumbs", rest)}
+          aria-label={l.breadcrumbs.ariaLabel()}
+          {...rest}
+        >
+          <ol>{children}</ol>
+        </StyledBreadcrumbs>
+      </BreadcrumbsContext.Provider>
     );
   }
 );
diff --git a/src/components/breadcrumbs/breadcrumbs.mdx b/src/components/breadcrumbs/breadcrumbs.mdx
index 4f0e14d1d..d14baa68a 100644
--- a/src/components/breadcrumbs/breadcrumbs.mdx
+++ b/src/components/breadcrumbs/breadcrumbs.mdx
@@ -36,10 +36,16 @@ import { Breadcrumbs, Crumb } from "carbon-react/lib/components/breadcrumbs";
 
 ## Examples
 
-### Breadcrumbs
+### Default
 
 <Canvas of={BreadcrumbsStories.Default} />
 
+### On Dark Background
+
+You can use the `isDarkBackground` prop to change the colour of the breadcrumbs when they are on a dark background.
+
+<Canvas of={BreadcrumbsStories.OnDarkBackground} />
+
 ## Props
 
 ### Breadcrumbs
diff --git a/src/components/breadcrumbs/breadcrumbs.pw.tsx b/src/components/breadcrumbs/breadcrumbs.pw.tsx
index d210d14be..e342e5412 100644
--- a/src/components/breadcrumbs/breadcrumbs.pw.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.pw.tsx
@@ -9,6 +9,7 @@ import {
   Default,
   DefaultCrumb,
   FocusedCrumbBecomesCurrent,
+  OnDarkBackground,
 } from "./components.test-pw";
 import {
   checkAccessibility,
@@ -169,6 +170,19 @@ test.describe("should render Breadcrumbs component", async () => {
     );
   });
 
+  test("should have correct color when Crumb is current and isDarkBackground is true", async ({
+    mount,
+    page,
+  }) => {
+    await mount(<OnDarkBackground />);
+
+    const currentCrumb = crumbAtIndex(page, 3).locator("a");
+    await expect(currentCrumb).toHaveCSS("color", "rgb(255, 255, 255)");
+    await currentCrumb.hover();
+    await expect(currentCrumb).toHaveCSS("color", "rgb(255, 255, 255)");
+    await expect(currentCrumb).toHaveCSS("cursor", "text");
+  });
+
   [
     {
       isCurrent: true,
@@ -222,24 +236,6 @@ test("when Crumb's isCurrent prop is true, Crumb divider should not exist", asyn
   );
 });
 
-test("when Crumb's isCurrent prop is false, Crumb divider should be visible", async ({
-  mount,
-  page,
-}) => {
-  await mount(<DefaultCrumb isCurrent={false} />);
-
-  const crumbElement = crumbAtIndex(page, 0);
-  await expect(crumbElement.locator("a")).toHaveAttribute("href", "#");
-  await expect(crumbElement.locator("span").nth(1)).toHaveCSS(
-    "color",
-    "rgb(0, 126, 69)"
-  );
-  await expect(crumbElement.locator("span").nth(2)).toHaveCSS(
-    "color",
-    "rgba(0, 0, 0, 0.55)"
-  );
-});
-
 test("should call the onClick callback when clicked", async ({
   mount,
   page,
@@ -303,4 +299,12 @@ test.describe("Accessibility tests for Breadcrumbs component", async () => {
     await mount(<DefaultCrumb />);
     await checkAccessibility(page);
   });
+
+  test("should pass accessibility tests for Breadcrumbs dark background story", async ({
+    mount,
+    page,
+  }) => {
+    await mount(<OnDarkBackground />);
+    await checkAccessibility(page);
+  });
 });
diff --git a/src/components/breadcrumbs/breadcrumbs.stories.tsx b/src/components/breadcrumbs/breadcrumbs.stories.tsx
index f0a2c4b00..375f2c159 100644
--- a/src/components/breadcrumbs/breadcrumbs.stories.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.stories.tsx
@@ -1,8 +1,8 @@
 import React from "react";
 import { Meta, StoryObj } from "@storybook/react";
 
-import { Breadcrumbs } from ".";
-import { Crumb } from "./crumb";
+import { Breadcrumbs, Crumb } from ".";
+import Box from "../box";
 import generateStyledSystemProps from "../../../.storybook/utils/styled-system-props";
 
 const styledSystemProps = generateStyledSystemProps({
@@ -33,3 +33,19 @@ export const Default: Story = () => {
   );
 };
 Default.storyName = "Default";
+
+export const OnDarkBackground: Story = () => {
+  return (
+    <Box p={2} bg="#000">
+      <Breadcrumbs isDarkBackground>
+        <Crumb href="#">Breadcrumb 1</Crumb>
+        <Crumb href="#">Breadcrumb 2</Crumb>
+        <Crumb href="#">Breadcrumb 3</Crumb>
+        <Crumb href="#" isCurrent>
+          Current Page
+        </Crumb>
+      </Breadcrumbs>
+    </Box>
+  );
+};
+OnDarkBackground.storyName = "On Dark Background";
diff --git a/src/components/breadcrumbs/crumb/crumb.component.tsx b/src/components/breadcrumbs/crumb/crumb.component.tsx
index 5f2d94eee..8cc801703 100644
--- a/src/components/breadcrumbs/crumb/crumb.component.tsx
+++ b/src/components/breadcrumbs/crumb/crumb.component.tsx
@@ -1,9 +1,10 @@
-import React from "react";
+import React, { useContext } from "react";
 import { LinkProps } from "../../link";
 import tagComponent, {
   TagProps,
 } from "../../../__internal__/utils/helpers/tags";
 import { StyledCrumb, Divider } from "./crumb.style";
+import BreadcrumbsContext from "../__internal__/breadcrumbs.context";
 
 export interface CrumbProps
   extends Omit<
@@ -28,24 +29,35 @@ export interface CrumbProps
 }
 
 const Crumb = React.forwardRef<HTMLLinkElement, CrumbProps>(
-  ({ href, isCurrent, children, onClick, ...rest }: CrumbProps, ref) => (
-    <li>
-      <StyledCrumb
-        ref={ref}
-        isCurrent={isCurrent}
-        aria-current={isCurrent ? "page" : undefined}
-        {...tagComponent("crumb", rest)}
-        {...rest}
-        {...(!isCurrent && {
-          href,
-          onClick,
-        })}
-      >
-        {children}
-      </StyledCrumb>
-      {!isCurrent && <Divider />}
-    </li>
-  )
+  ({ href, isCurrent, children, onClick, ...rest }: CrumbProps, ref) => {
+    const { isDarkBackground } = useContext(BreadcrumbsContext);
+
+    return (
+      <li>
+        <StyledCrumb
+          ref={ref}
+          isCurrent={isCurrent}
+          aria-current={isCurrent ? "page" : undefined}
+          isDarkBackground={isDarkBackground}
+          {...tagComponent("crumb", rest)}
+          {...rest}
+          {...(!isCurrent && {
+            href,
+            onClick,
+          })}
+        >
+          {children}
+        </StyledCrumb>
+        {!isCurrent && (
+          <Divider
+            data-role="crumb-divider"
+            aria-hidden="true"
+            isDarkBackground={isDarkBackground}
+          />
+        )}
+      </li>
+    );
+  }
 );
 
 Crumb.displayName = "Crumb";
diff --git a/src/components/breadcrumbs/crumb/crumb.style.ts b/src/components/breadcrumbs/crumb/crumb.style.ts
index 08f4d5484..61da9acdf 100644
--- a/src/components/breadcrumbs/crumb/crumb.style.ts
+++ b/src/components/breadcrumbs/crumb/crumb.style.ts
@@ -3,20 +3,26 @@ import Link, { LinkProps } from "../../link";
 
 interface StyleCrumbProps extends LinkProps {
   isCurrent?: boolean;
+  isDarkBackground: boolean;
 }
 
+const getCurrentCrumbColor = (isDarkBackground: boolean) => {
+  return isDarkBackground
+    ? "var(--colorsUtilityYang100)"
+    : "var(--colorsUtilityYin090)";
+};
+
 export const StyledCrumb = styled(Link)<StyleCrumbProps>`
   font: var(--typographyLinkTextM);
-  ${({ isCurrent }) =>
+  ${({ isCurrent, isDarkBackground }) =>
     isCurrent &&
     css`
       a {
-        color: var(--colorsUtilityYin090);
+        color: ${getCurrentCrumbColor(isDarkBackground)};
         text-decoration: none;
         font: var(--typographyBreadcrumbCurrentPageM);
-        cursor: text;
         :hover {
-          color: var(--colorsUtilityYin090);
+          color: ${getCurrentCrumbColor(isDarkBackground)};
           text-decoration: none;
           cursor: text;
         }
@@ -24,14 +30,23 @@ export const StyledCrumb = styled(Link)<StyleCrumbProps>`
     `}
 `;
 
-export const Divider = styled.span.attrs({
-  children: "/",
-  "aria-hidden": "true",
-})`
-  margin: 0px var(--spacing050) 0px var(--spacing100);
-  line-height: 16px;
-  font: var(--typographyBreadcrumbSeparatorM);
-  color: var(--colorsUtilityYin055);
+interface DividerProps {
+  isDarkBackground: boolean;
+}
+
+export const Divider = styled.span<DividerProps>`
+  ::after {
+    content: "/";
+    margin: 0px var(--spacing050) 0px var(--spacing100);
+    line-height: 16px;
+    font: var(--typographyBreadcrumbSeparatorM);
+    ${({ isDarkBackground }) =>
+      css`
+        color: ${isDarkBackground
+          ? "var(--colorsUtilityYang100)"
+          : "var(--colorsUtilityYin055)"};
+      `}
+  }
 `;
 
 export default { StyledCrumb, Divider };


Test patch:
diff --git a/src/components/breadcrumbs/breadcrumbs-test.stories.tsx b/src/components/breadcrumbs/breadcrumbs-test.stories.tsx
index 8e58b012b..285f340af 100644
--- a/src/components/breadcrumbs/breadcrumbs-test.stories.tsx
+++ b/src/components/breadcrumbs/breadcrumbs-test.stories.tsx
@@ -22,6 +22,11 @@ export default {
         type: "text",
       },
     },
+    isDarkBackground: {
+      control: {
+        type: "boolean",
+      },
+    },
   },
 };
 
diff --git a/src/components/breadcrumbs/breadcrumbs.test.tsx b/src/components/breadcrumbs/breadcrumbs.test.tsx
index 9e42885e4..ed26212fb 100644
--- a/src/components/breadcrumbs/breadcrumbs.test.tsx
+++ b/src/components/breadcrumbs/breadcrumbs.test.tsx
@@ -5,26 +5,72 @@ import Breadcrumbs from "./breadcrumbs.component";
 import Crumb from "./crumb/crumb.component";
 import { testStyledSystemSpacing } from "../../__spec_helper__/__internal__/test-utils";
 
-describe("Breadcrumbs", () => {
-  testStyledSystemSpacing((props) => (
-    <Breadcrumbs {...props}>
-      <Crumb href="#">Breadcrumb</Crumb>
+testStyledSystemSpacing((props) => (
+  <Breadcrumbs {...props}>
+    <Crumb href="#">Breadcrumb</Crumb>
+  </Breadcrumbs>
+));
+
+test("renders children as expected", () => {
+  render(
+    <Breadcrumbs>
+      <Crumb href="#">Breadcrumb 1</Crumb>
+      <Crumb href="#">Breadcrumb 2</Crumb>
+      <Crumb href="#" isCurrent>
+        Breadcrumb 3
+      </Crumb>
     </Breadcrumbs>
-  ));
-
-  it("renders children as expected", () => {
-    render(
-      <Breadcrumbs>
-        <Crumb href="#">Breadcrumb</Crumb>
-        <Crumb href="#">Breadcrumb</Crumb>
-        <Crumb href="#">Breadcrumb</Crumb>
-        <Crumb href="#" isCurrent>
-          Breadcrumb
-        </Crumb>
-      </Breadcrumbs>
-    );
-
-    const crumbElement = screen.getAllByText("Breadcrumb");
-    expect(crumbElement.length).toBe(4);
+  );
+
+  expect(screen.getByRole("link", { name: "Breadcrumb 1" })).toBeVisible();
+  expect(screen.getByRole("link", { name: "Breadcrumb 2" })).toBeVisible();
+  expect(screen.getByText("Breadcrumb 3")).toBeVisible();
+});
+
+// coverage
+test("renders children with expected colour when isDarkBackground is false", () => {
+  render(
+    <Breadcrumbs>
+      <Crumb href="#">Breadcrumb 1</Crumb>
+      <Crumb href="#">Breadcrumb 2</Crumb>
+      <Crumb href="#" isCurrent data-role="current-crumb">
+        Breadcrumb 3
+      </Crumb>
+    </Breadcrumbs>
+  );
+
+  const currentCrumb = screen.getByTestId("current-crumb");
+
+  expect(currentCrumb).toHaveStyleRule("color", "var(--colorsUtilityYin090)", {
+    modifier: "a",
+  });
+  expect(screen.getAllByTestId("crumb-divider")[0]).toHaveStyleRule(
+    "color",
+    "var(--colorsUtilityYin055)",
+    { modifier: "::after" }
+  );
+});
+
+// coverage
+test("renders children with expected colour when isDarkBackground is true", () => {
+  render(
+    <Breadcrumbs isDarkBackground>
+      <Crumb href="#">Breadcrumb 1</Crumb>
+      <Crumb href="#">Breadcrumb 2</Crumb>
+      <Crumb href="#" isCurrent data-role="current-crumb">
+        Breadcrumb 3
+      </Crumb>
+    </Breadcrumbs>
+  );
+
+  const currentCrumb = screen.getByTestId("current-crumb");
+
+  expect(currentCrumb).toHaveStyleRule("color", "var(--colorsUtilityYang100)", {
+    modifier: "a",
   });
+  expect(screen.getAllByTestId("crumb-divider")[0]).toHaveStyleRule(
+    "color",
+    "var(--colorsUtilityYang100)",
+    { modifier: "::after" }
+  );
 });
diff --git a/src/components/breadcrumbs/components.test-pw.tsx b/src/components/breadcrumbs/components.test-pw.tsx
index 6d78925c4..94d7fd398 100644
--- a/src/components/breadcrumbs/components.test-pw.tsx
+++ b/src/components/breadcrumbs/components.test-pw.tsx
@@ -1,6 +1,7 @@
 import React from "react";
 import { Breadcrumbs, BreadcrumbsProps } from ".";
 import { Crumb, CrumbProps } from "./crumb";
+import Box from "../box";
 
 const Default = (props: Partial<BreadcrumbsProps>) => {
   return (
@@ -44,4 +45,19 @@ const FocusedCrumbBecomesCurrent = ({ hasHref = false }) => {
   );
 };
 
+export const OnDarkBackground = () => {
+  return (
+    <Box p={2} bg="#000">
+      <Breadcrumbs isDarkBackground>
+        <Crumb href="#">Breadcrumb 1</Crumb>
+        <Crumb href="#">Breadcrumb 2</Crumb>
+        <Crumb href="#">Breadcrumb 3</Crumb>
+        <Crumb href="#" isCurrent>
+          Current Page
+        </Crumb>
+      </Breadcrumbs>
+    </Box>
+  );
+};
+
 export { Default, DefaultCrumb, FocusedCrumbBecomesCurrent };
diff --git a/src/components/breadcrumbs/crumb/crumb.test.tsx b/src/components/breadcrumbs/crumb/crumb.test.tsx
index 5d2921a1e..ce7a46b4e 100644
--- a/src/components/breadcrumbs/crumb/crumb.test.tsx
+++ b/src/components/breadcrumbs/crumb/crumb.test.tsx
@@ -3,56 +3,51 @@ import { render, screen } from "@testing-library/react";
 import userEvent from "@testing-library/user-event";
 import Crumb from "./crumb.component";
 
-describe("Crumb", () => {
-  it("passes href to the anchor element isCurrent is false", () => {
-    render(<Crumb href="foo">Link text</Crumb>);
-
-    const link = screen.getByRole("link", { name: "Link text" });
-
-    expect(link).toHaveAttribute("href", "foo");
-  });
-
-  it("does not pass href to the anchor element when isCurrent is true", () => {
-    render(
-      <Crumb href="foo" data-role="crumb" isCurrent>
-        Link text
-      </Crumb>
-    );
-
-    const anchor = screen.getByTestId("link-anchor");
-    expect(anchor).not.toHaveAttribute("href", "foo");
-    expect(anchor).toHaveStyle({
-      color: "var(--colorsYin055)",
-      textDecoration: "none",
-      cursor: "text",
-    });
-  });
-
-  it("calls the handleClick callback if one is passed and isCurrent is false", async () => {
-    const handleClickFn = jest.fn();
-    render(
-      <Crumb href="#" onClick={handleClickFn}>
-        Link text
-      </Crumb>
-    );
-
-    const link = screen.getByRole("link", { name: "Link text" });
-    await userEvent.click(link);
-
-    expect(handleClickFn).toHaveBeenCalledTimes(1);
-  });
-
-  it("does not call the handleClick callback if one is passed and isCurrent is true", async () => {
-    const handleClickFn = jest.fn();
-    render(
-      <Crumb href="#" onClick={handleClickFn} isCurrent>
-        Link text
-      </Crumb>
-    );
-
-    const link = screen.getByText("Link text");
-    await userEvent.click(link);
-
-    expect(handleClickFn).toHaveBeenCalledTimes(0);
-  });
+test("passes href to the anchor element isCurrent is false", () => {
+  render(<Crumb href="foo">Link text</Crumb>);
+
+  const link = screen.getByRole("link", { name: "Link text" });
+
+  expect(link).toHaveAttribute("href", "foo");
+});
+
+test("does not pass href to the anchor element when isCurrent is true", () => {
+  render(
+    <Crumb href="foo" data-role="crumb" isCurrent>
+      Link text
+    </Crumb>
+  );
+
+  const anchor = screen.getByTestId("link-anchor");
+  expect(anchor).not.toHaveAttribute("href", "foo");
+});
+
+test("calls onClick callback when the crumb link is clicked", async () => {
+  const onClick = jest.fn();
+  const user = userEvent.setup();
+  render(
+    <Crumb href="#" onClick={onClick}>
+      Link text
+    </Crumb>
+  );
+
+  const link = screen.getByRole("link", { name: "Link text" });
+  await user.click(link);
+
+  expect(onClick).toHaveBeenCalledTimes(1);
+});
+
+test("does not call onClick callback when isCurrent is true", async () => {
+  const onClick = jest.fn();
+  const user = userEvent.setup();
+  render(
+    <Crumb href="#" onClick={onClick} isCurrent>
+      Link text
+    </Crumb>
+  );
+
+  const link = screen.getByText("Link text");
+  await user.click(link);
+
+  expect(onClick).toHaveBeenCalledTimes(0);
 });