Skip to content

Commit

Permalink
Merge pull request #7124 from Sage/FE-5336-listitem-font-style
Browse files Browse the repository at this point in the history
feat(typography): allow listitem to inherit variant from parent list
  • Loading branch information
damienrobson-sage authored Dec 16, 2024
2 parents d1a7d79 + 7983656 commit 0784fab
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 63 deletions.
41 changes: 31 additions & 10 deletions src/components/typography/list.component.tsx
Original file line number Diff line number Diff line change
@@ -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;
}

Expand All @@ -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 };
10 changes: 10 additions & 0 deletions src/components/typography/list.context.tsx
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/components/typography/typography.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 15 additions & 3 deletions src/components/typography/typography.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
92 changes: 68 additions & 24 deletions src/components/typography/typography.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 } };
93 changes: 67 additions & 26 deletions src/components/typography/typography.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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();
});
});

0 comments on commit 0784fab

Please sign in to comment.