Repository: Sage/carbon (ts)

Issue:
ListItem to inherit font styles from List
### Desired behaviour

`ListItem` inherits font styles from `List`. To apply a font variant to all list items, the variant prop may be set on `List` alone.

```javascript
<List variant="big">
	<ListItem>item 1</ListItem>
	<ListItem>item 2</ListItem>
	<ListItem>item 3</ListItem>
</List>
```

### Current behaviour

Currently, `ListItem` does not inherit font styles from `List`. To apply a font variant to all list items, each list item must have the variant set.

```javascript
<List>
	<ListItem variant="big">item 1</ListItem>
	<ListItem variant="big">item 2</ListItem>
	<ListItem variant="big">item 3</ListItem>
</List>
```

### Suggested Solution

_No response_

### CodeSandbox or Storybook URL

https://codesandbox.io/s/list-9tee68

### Anything else we should know?

_No response_

### Confidentiality

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

Pull request description:
feat(typography): allow listitem to inherit variant from parent list
In order to reduce replication of code, ListItem now receives information on the variant of the parent List item via context; setting variant on List will pass it to child ListItem components.

Resolves #5413

### Proposed behaviour

`ListItem` now inherits `variant` directly from it's parent `List` component

### Current behaviour

Customers have to manually define the `variant` on each `ListItem`

### Checklist

- [x] Commits follow our style guide
- [x] Related issues linked in commit messages if required
- [x] Unit tests added or updated if required
- [x] Storybook 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

### Testing instructions

Use Storybook to view the new approach/component structure. Additionally, verify that a deprecation warning is thrown if `variant` is added to any `ListItem`


Patch:
diff --git a/src/components/typography/list.component.tsx b/src/components/typography/list.component.tsx
index e28316c96..ba4e6d8ab 100644
--- a/src/components/typography/list.component.tsx
+++ b/src/components/typography/list.component.tsx
@@ -1,11 +1,20 @@
-import React from "react";
-import Typography, { TypographyProps } from "./typography.component";
+import React, { useContext } from "react";
+import Typography, {
+  TypographyProps,
+  VariantTypes,
+} from "./typography.component";
+import ListContext, { ListContextProps } from "./list.context";
+import Logger from "../../__internal__/utils/logger";
+
+let childVariantDeprecationWarning = false;
 
 export interface ListProps extends TypographyProps {
   children?: React.ReactNode;
 }
 
 export interface ListItemProps extends TypographyProps {
+  /** (Deprecated) The visual style to apply to the component */
+  variant?: VariantTypes;
   children?: React.ReactNode;
 }
 
@@ -16,20 +25,32 @@ const getListStyleType = (as?: React.ElementType) => {
   return "decimal";
 };
 
-const List = ({ children, as = "ul", ...props }: ListProps) => (
+const List = ({ children, as = "ul", variant = "p", ...props }: ListProps) => (
   <Typography
-    variant="p"
+    variant={variant}
     as={as}
     listStyleType={getListStyleType(as)}
     {...props}
   >
-    {children}
+    <ListContext.Provider value={{ variant }}>{children}</ListContext.Provider>
   </Typography>
 );
 
-const ListItem = ({ children, ...props }: ListItemProps) => (
-  <Typography as="li" variant="p" m="0 0 8px 16px" {...props}>
-    {children}
-  </Typography>
-);
+const ListItem = ({ children, ...props }: ListItemProps) => {
+  if (props.variant && !childVariantDeprecationWarning) {
+    Logger.deprecate(
+      "The use of `variant` on `ListItem` is deprecated. Please set it via `List` instead.",
+    );
+    childVariantDeprecationWarning = true;
+  }
+
+  const { variant: parentListVariant } =
+    useContext<ListContextProps>(ListContext);
+
+  return (
+    <Typography as="li" variant={parentListVariant} m="0 0 8px 16px" {...props}>
+      {children}
+    </Typography>
+  );
+};
 export { List, ListItem };
diff --git a/src/components/typography/list.context.tsx b/src/components/typography/list.context.tsx
new file mode 100644
index 000000000..0114fb4a8
--- /dev/null
+++ b/src/components/typography/list.context.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { VariantTypes } from "./typography.component";
+
+export interface ListContextProps {
+  variant?: VariantTypes;
+}
+
+export const ListContext = React.createContext<ListContextProps>({});
+
+export default ListContext;
diff --git a/src/components/typography/typography.component.tsx b/src/components/typography/typography.component.tsx
index 9b3a06801..b205fa55e 100644
--- a/src/components/typography/typography.component.tsx
+++ b/src/components/typography/typography.component.tsx
@@ -33,7 +33,9 @@ export const VARIANT_TYPES = [
   "ul",
   "ol",
 ] as const;
+
 export type VariantTypes = (typeof VARIANT_TYPES)[number];
+
 export interface TypographyProps extends SpaceProps, TagProps {
   /** Override the variant component */
   as?: React.ElementType;
diff --git a/src/components/typography/typography.mdx b/src/components/typography/typography.mdx
index 7f38fca07..0ab5d3ad8 100644
--- a/src/components/typography/typography.mdx
+++ b/src/components/typography/typography.mdx
@@ -29,15 +29,27 @@ It's possible to change the text colour and background, using the color props. S
 
 It's possible to change the text position, using the spacing props. See the prop tables below for more information.
 
-It's also possible to create ordered and unordered lists using the `List`, `ListItem` helpers. These components are syntactic sugar around the `Typography` component and accept the same props.
-
 <Canvas of={TypographyStories.VariantsStory} />
 
+## Lists
+
+Use `List` and `ListItem` to create ordered and unordered lists. These components are syntactic sugar around the `Typography` component and accept the same props.
+By default, `List` will render an unordered list, but you can pass `ol` to the `variant` prop to render an ordered list; all other typography variants will render
+the `ListItem` component as an unordered list (examples of which are shown in [ListItem Inheritance](#listitem-inheritance)).
+
+<Canvas of={TypographyStories.Lists} />
+
+## ListItem Inheritance
+
+When using `List` and `ListItem`, set the `variant` on the `List` component to apply the same variant to all `ListItem` components within it.
+
+<Canvas of={TypographyStories.ListItemInheritance} />
+
 ## Example of truncate
 
 The `truncate` prop is a boolean prop that automatically applies  `overflow: hidden` and `white-space: nowrap`, which are the styles required to truncate a piece of text.
 
-By default, this prop will truncate the text with an ellipsis, but you can also pass the `textOverflow` prop to override this behaviour. 
+By default, this prop will truncate the text with an ellipsis, but you can also pass the `textOverflow` prop to override this behaviour.
 
 This prop will only work with block level elements, so ensure `display="block"` or similar is set to enable this functionality.
 
diff --git a/src/components/typography/typography.stories.tsx b/src/components/typography/typography.stories.tsx
index c9bb059a9..d7a21290d 100644
--- a/src/components/typography/typography.stories.tsx
+++ b/src/components/typography/typography.stories.tsx
@@ -89,30 +89,6 @@ export const VariantsStory: Story = () => (
     <Typography variant="p">
       H<Typography variant="sub">2</Typography>O is an example of subscript
     </Typography>
-    <List>
-      <ListItem>
-        Milk <Typography variant="b">2L</Typography>{" "}
-        <Typography variant="em">Skimmed</Typography>
-      </ListItem>
-      <ListItem>
-        Bread <Typography variant="b">500g</Typography>
-      </ListItem>
-      <ListItem>
-        Sugar <Typography variant="b">1Kg</Typography>
-      </ListItem>
-    </List>
-    <List as="ol">
-      <ListItem>
-        Milk <Typography variant="b">2L</Typography>{" "}
-        <Typography variant="em">Skimmed</Typography>
-      </ListItem>
-      <ListItem>
-        Bread <Typography variant="b">500g</Typography>
-      </ListItem>
-      <ListItem>
-        Sugar <Typography variant="b">1Kg</Typography>
-      </ListItem>
-    </List>
   </>
 );
 VariantsStory.storyName = "Variants";
@@ -155,3 +131,71 @@ export const ScreenReaderOnlyStory: Story = () => (
 );
 ScreenReaderOnlyStory.storyName = "Screen Reader Only";
 ScreenReaderOnlyStory.parameters = { info: { disable: true } };
+
+export const Lists: Story = () => (
+  <>
+    <Typography>Unordered List</Typography>
+    <List>
+      <ListItem>
+        Milk <Typography variant="b">2L</Typography>{" "}
+        <Typography variant="em">Skimmed</Typography>
+      </ListItem>
+      <ListItem>
+        Bread <Typography variant="b">500g</Typography>
+      </ListItem>
+      <ListItem>
+        Sugar <Typography variant="b">1Kg</Typography>
+      </ListItem>
+    </List>
+
+    <Typography>Ordered List</Typography>
+    <List as="ol">
+      <ListItem>
+        Milk <Typography variant="b">2L</Typography>{" "}
+        <Typography variant="em">Skimmed</Typography>
+      </ListItem>
+      <ListItem>
+        Bread <Typography variant="b">500g</Typography>
+      </ListItem>
+      <ListItem>
+        Sugar <Typography variant="b">1Kg</Typography>
+      </ListItem>
+    </List>
+  </>
+);
+Lists.storyName = "Lists";
+Lists.parameters = { info: { disable: true } };
+
+export const ListItemInheritance: Story = () => (
+  <>
+    <Typography>Default</Typography>
+    <List>
+      <ListItem>item 1</ListItem>
+      <ListItem>item 2</ListItem>
+      <ListItem>item 3</ListItem>
+    </List>
+
+    <Typography>Big</Typography>
+    <List variant="big">
+      <ListItem>item 1</ListItem>
+      <ListItem>item 2</ListItem>
+      <ListItem>item 3</ListItem>
+    </List>
+
+    <Typography>Small</Typography>
+    <List variant="small">
+      <ListItem>item 1</ListItem>
+      <ListItem>item 2</ListItem>
+      <ListItem>item 3</ListItem>
+    </List>
+
+    <Typography>H1</Typography>
+    <List variant="h1">
+      <ListItem>item 1</ListItem>
+      <ListItem>item 2</ListItem>
+      <ListItem>item 3</ListItem>
+    </List>
+  </>
+);
+ListItemInheritance.storyName = "List Item Inheritance";
+ListItemInheritance.parameters = { info: { disable: true } };


Test patch:
diff --git a/src/components/typography/typography.test.tsx b/src/components/typography/typography.test.tsx
index 30cee69d8..9439a5d99 100644
--- a/src/components/typography/typography.test.tsx
+++ b/src/components/typography/typography.test.tsx
@@ -288,32 +288,6 @@ test("should override 'display' property when passed", () => {
   expect(screen.getByText("Test")).toHaveStyle({ display: "block" });
 });
 
-test("should render List with variant as 'ul' by default and listStyleType set to 'square", () => {
-  render(
-    <List>
-      <ListItem>List Item 1</ListItem>
-      <ListItem>List Item 2</ListItem>
-      <ListItem>List Item 3</ListItem>
-    </List>,
-  );
-
-  expect(screen.getByRole("list")).toHaveStyle({ listStyleType: "square" });
-  expect(screen.getAllByRole("listitem")).toHaveLength(3);
-});
-
-test("should render List with variant set to 'ol' and listStyleType set to 'decimal", () => {
-  render(
-    <List as="ol">
-      <ListItem>List Item 1</ListItem>
-      <ListItem>List Item 2</ListItem>
-      <ListItem>List Item 3</ListItem>
-    </List>,
-  );
-
-  expect(screen.getByRole("list")).toHaveStyle({ listStyleType: "decimal" });
-  expect(screen.getAllByRole("listitem")).toHaveLength(3);
-});
-
 testStyledSystemSpacing(
   (props) => <Typography {...props}>Test</Typography>,
   () => screen.getByText("Test"),
@@ -336,3 +310,70 @@ test("throws a deprecation warning if the 'className' prop is set", () => {
 
   loggerSpy.mockRestore();
 });
+
+describe("Lists", () => {
+  test("should render List with variant as 'ul' by default and listStyleType set to 'square", () => {
+    render(
+      <List>
+        <ListItem>List Item 1</ListItem>
+        <ListItem>List Item 2</ListItem>
+        <ListItem>List Item 3</ListItem>
+      </List>,
+    );
+
+    expect(screen.getByRole("list")).toHaveStyle({ listStyleType: "square" });
+    expect(screen.getAllByRole("listitem")).toHaveLength(3);
+  });
+
+  test("should render List with variant set to 'ol' and listStyleType set to 'decimal", () => {
+    render(
+      <List as="ol">
+        <ListItem>List Item 1</ListItem>
+        <ListItem>List Item 2</ListItem>
+        <ListItem>List Item 3</ListItem>
+      </List>,
+    );
+
+    expect(screen.getByRole("list")).toHaveStyle({ listStyleType: "decimal" });
+    expect(screen.getAllByRole("listitem")).toHaveLength(3);
+  });
+
+  test("passes the variant of `List` to the child `ListItem` elements", () => {
+    render(
+      <List variant="big">
+        <ListItem data-role="list-item-1">List Item 1</ListItem>
+        <ListItem data-role="list-item-2">List Item 2</ListItem>
+        <ListItem data-role="list-item-3">List Item 3</ListItem>
+      </List>,
+    );
+
+    ["list-item-1", "list-item-2", "list-item-3"].forEach((role) => {
+      expect(screen.getByTestId(role)).toHaveStyle({
+        "font-style": "normal",
+        "font-size": "16px",
+        "font-weight": "400",
+        "line-height": "24px",
+      });
+    });
+  });
+
+  test("throws a deprecation warning if the 'variant' prop is set on `ListItem`", () => {
+    const loggerSpy = jest
+      .spyOn(Logger, "deprecate")
+      .mockImplementation(() => {});
+    render(
+      <List>
+        <ListItem variant="b">List Item 1</ListItem>
+        <ListItem variant="b">List Item 2</ListItem>
+        <ListItem variant="b">List Item 3</ListItem>
+      </List>,
+    );
+
+    expect(loggerSpy).toHaveBeenCalledWith(
+      "The use of `variant` on `ListItem` is deprecated. Please set it via `List` instead.",
+    );
+    expect(loggerSpy).toHaveBeenCalledTimes(1);
+
+    loggerSpy.mockRestore();
+  });
+});