From ad4f4e761cf14927a5a2e122e4450f9ee995722d Mon Sep 17 00:00:00 2001 From: Damien Robson Date: Wed, 11 Dec 2024 09:14:21 +0000 Subject: [PATCH] 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 --- src/components/typography/list.component.tsx | 41 ++++++-- src/components/typography/list.context.tsx | 10 ++ .../typography/typography.component.tsx | 2 + src/components/typography/typography.mdx | 18 +++- .../typography/typography.stories.tsx | 92 +++++++++++++----- src/components/typography/typography.test.tsx | 93 +++++++++++++------ 6 files changed, 193 insertions(+), 63 deletions(-) create mode 100644 src/components/typography/list.context.tsx diff --git a/src/components/typography/list.component.tsx b/src/components/typography/list.component.tsx index e28316c967..ba4e6d8abc 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) => ( - {children} + {children} ); -const ListItem = ({ children, ...props }: ListItemProps) => ( - - {children} - -); +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(ListContext); + + return ( + + {children} + + ); +}; export { List, ListItem }; diff --git a/src/components/typography/list.context.tsx b/src/components/typography/list.context.tsx new file mode 100644 index 0000000000..0114fb4a88 --- /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({}); + +export default ListContext; diff --git a/src/components/typography/typography.component.tsx b/src/components/typography/typography.component.tsx index 9b3a06801f..b205fa55eb 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 7f38fca07b..0ab5d3ad8c 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. - +## 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)). + + + +## 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. + + + ## 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 c9bb059a93..d7a21290d9 100644 --- a/src/components/typography/typography.stories.tsx +++ b/src/components/typography/typography.stories.tsx @@ -89,30 +89,6 @@ export const VariantsStory: Story = () => ( H2O is an example of subscript - - - Milk 2L{" "} - Skimmed - - - Bread 500g - - - Sugar 1Kg - - - - - Milk 2L{" "} - Skimmed - - - Bread 500g - - - Sugar 1Kg - - ); 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 = () => ( + <> + Unordered List + + + Milk 2L{" "} + Skimmed + + + Bread 500g + + + Sugar 1Kg + + + + Ordered List + + + Milk 2L{" "} + Skimmed + + + Bread 500g + + + Sugar 1Kg + + + +); +Lists.storyName = "Lists"; +Lists.parameters = { info: { disable: true } }; + +export const ListItemInheritance: Story = () => ( + <> + Default + + item 1 + item 2 + item 3 + + + Big + + item 1 + item 2 + item 3 + + + Small + + item 1 + item 2 + item 3 + + + H1 + + item 1 + item 2 + item 3 + + +); +ListItemInheritance.storyName = "List Item Inheritance"; +ListItemInheritance.parameters = { info: { disable: true } }; diff --git a/src/components/typography/typography.test.tsx b/src/components/typography/typography.test.tsx index 30cee69d80..9439a5d99a 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 Item 1 - List Item 2 - List Item 3 - , - ); - - 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 Item 1 - List Item 2 - List Item 3 - , - ); - - expect(screen.getByRole("list")).toHaveStyle({ listStyleType: "decimal" }); - expect(screen.getAllByRole("listitem")).toHaveLength(3); -}); - testStyledSystemSpacing( (props) => Test, () => 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 Item 1 + List Item 2 + List Item 3 + , + ); + + 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 Item 1 + List Item 2 + List Item 3 + , + ); + + 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 Item 1 + List Item 2 + List Item 3 + , + ); + + ["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 Item 1 + List Item 2 + List Item 3 + , + ); + + expect(loggerSpy).toHaveBeenCalledWith( + "The use of `variant` on `ListItem` is deprecated. Please set it via `List` instead.", + ); + expect(loggerSpy).toHaveBeenCalledTimes(1); + + loggerSpy.mockRestore(); + }); +});