Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typography): allow listitem to inherit variant from parent list #7124

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
damienrobson-sage marked this conversation as resolved.
Show resolved Hide resolved

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();
});
});
Loading