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) => ( <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 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<ListContextProps>({}); + +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. - <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 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 = () => ( <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 } }; 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> - <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(); + }); +});