Skip to content

Commit

Permalink
feat(list): add OrderedDisplayList component (#3835)
Browse files Browse the repository at this point in the history
* feat(list): add OrderedDisplayList

* chore: pr fixes

* chore: update type-docs

* chore: fix lint

* fix: make list item number semiBold
  • Loading branch information
TheSisb committed Apr 2, 2024
1 parent 9d096e5 commit 33800d2
Show file tree
Hide file tree
Showing 13 changed files with 2,172 additions and 128 deletions.
9 changes: 9 additions & 0 deletions .changeset/fifty-pandas-joke.md
@@ -0,0 +1,9 @@
---
"@twilio-paste/codemods": minor
"@twilio-paste/list": minor
"@twilio-paste/core": minor
---

[List] add `OrderedDisplayList`, `OrderedDisplayListItem`, and `OrderedDisplayListHeading` components.

These new components provide a visually enhanced numbered list to improve the scannability of sequential steps.
3 changes: 3 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Expand Up @@ -133,6 +133,9 @@
"RequiredDot": "@twilio-paste/core/label",
"List": "@twilio-paste/core/list",
"ListItem": "@twilio-paste/core/list",
"OrderedDisplayList": "@twilio-paste/core/list",
"OrderedDisplayListHeading": "@twilio-paste/core/list",
"OrderedDisplayListItem": "@twilio-paste/core/list",
"OrderedList": "@twilio-paste/core/list",
"UnorderedList": "@twilio-paste/core/list",
"Menu": "@twilio-paste/core/menu",
Expand Down
198 changes: 72 additions & 126 deletions packages/paste-core/components/list/__tests__/index.spec.tsx
Expand Up @@ -2,20 +2,17 @@ import { render, screen } from "@testing-library/react";
import { CustomizationProvider } from "@twilio-paste/customization";
import * as React from "react";

import { ListItem, OrderedList, UnorderedList } from "../src";
import {
ListItem,
OrderedDisplayList,
OrderedDisplayListHeading,
OrderedDisplayListItem,
OrderedList,
UnorderedList,
} from "../src";

describe("Ordered List", () => {
describe("Render", () => {
it("should render a plain ordered list wrapper", () => {
render(
<CustomizationProvider baseTheme="default" theme={TestTheme}>
<OrderedList>Children</OrderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).not.toBeNull();
});

it("should allow marginTop and marginBottom styling props", () => {
render(
<CustomizationProvider baseTheme="default" theme={TestTheme}>
Expand Down Expand Up @@ -71,52 +68,10 @@ describe("Ordered List", () => {
expect(screen.getByRole("list").getAttribute("data-paste-element")).toEqual("foo");
});
});

describe("Customization", () => {
it("should add custom styles to OrderedList", (): void => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ ORDERED_LIST: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<OrderedList>Custom ordered list</OrderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedList).toHaveStyleRule("color", "rgb(96, 107, 133)");
});

it("should add custom styles to OrderedList with a custom element data attribute", (): void => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ foo: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<OrderedList element="foo">Custom ordered list</OrderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedList).toHaveStyleRule("color", "rgb(96, 107, 133)");
});
});
});

describe("Unordered List", () => {
describe("Render", () => {
it("should render a plain unordered list wrapper", () => {
render(
<CustomizationProvider baseTheme="default" theme={TestTheme}>
<UnorderedList>Children</UnorderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).not.toBeNull();
});

it("should allow marginTop and marginBottom styling props", () => {
render(
<CustomizationProvider baseTheme="default" theme={TestTheme}>
Expand Down Expand Up @@ -159,67 +114,19 @@ describe("Unordered List", () => {
expect(screen.getByRole("list").getAttribute("data-paste-element")).toEqual("foo");
});
});

describe("Customization", () => {
it("should add custom styles to UnorderedList", (): void => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ UNORDERED_LIST: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<UnorderedList>Custom unordered list</UnorderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedList).toHaveStyleRule("color", "rgb(96, 107, 133)");
});

it("should add custom styles to UnorderedList with a custom element data attribute", (): void => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ foo: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<UnorderedList element="foo">Custom unordered list</UnorderedList>
</CustomizationProvider>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedList).toHaveStyleRule("color", "rgb(96, 107, 133)");
});
});
});

describe("ListItem", () => {
describe("Render", () => {
it("should render a plain list item", () => {
it("should allow aria, html, and data attributes", () => {
render(
<CustomizationProvider baseTheme="default" theme={TestTheme}>
<ListItem>Children</ListItem>
</CustomizationProvider>,
<ListItem aria-pressed="true" data-rando="test-hook" title="random title">
Children
</ListItem>,
);
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).not.toBeNull();
});

it("should allow aria attributes", () => {
render(<ListItem aria-pressed="true">Children</ListItem>);
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).toHaveAttribute("aria-pressed", "true");
});

it("should allow data attributes", () => {
render(<ListItem data-rando="test-hook">Children</ListItem>);
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).toHaveAttribute("data-rando", "test-hook");
});

it("should allow HTML attributes", () => {
render(<ListItem title="random title">Children</ListItem>);
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).toHaveAttribute("title", "random title");
});
});
Expand All @@ -234,36 +141,75 @@ describe("ListItem", () => {
expect(screen.getByRole("listitem").getAttribute("data-paste-element")).toEqual("foo");
});
});
});

describe("Customization", () => {
it("should add custom styles to ListItem", (): void => {
describe("Ordered Display List", () => {
describe("Render", () => {
it("should allow marginTop and marginBottom styling props", () => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ LIST_ITEM: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<ListItem>Custom list item</ListItem>
<CustomizationProvider baseTheme="default" theme={TestTheme}>
<OrderedDisplayList marginTop="space40" marginBottom="space40">
Children
</OrderedDisplayList>
</CustomizationProvider>,
);
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedListItem).toHaveStyleRule("color", "rgb(96, 107, 133)");
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveStyleRule("margin-top", "0.75rem");
expect(renderedList).toHaveStyleRule("margin-bottom", "0.75rem");
});

it("should add custom styles to ListItem with a custom element data attribute", (): void => {
it("should allow aria, data, and html attributes", () => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{ foo: { color: "colorTextWeak", backgroundColor: "colorBackground" } }}
>
<ListItem element="foo">Custom list item</ListItem>
</CustomizationProvider>,
<OrderedDisplayList aria-expanded="true" data-test="test-hook" title="random title">
<OrderedDisplayListItem aria-expanded="true" data-test="test-hook">
<OrderedDisplayListHeading aria-expanded="true" data-test="test-hook" as="h2">
Heading20
</OrderedDisplayListHeading>
Text beneath heading
</OrderedDisplayListItem>
</OrderedDisplayList>,
);
const renderedList = screen.getByRole("list");
expect(renderedList).toHaveAttribute("aria-expanded", "true");
expect(renderedList).toHaveAttribute("data-test", "test-hook");
expect(renderedList).toHaveAttribute("title", "random title");
const renderedListItem = screen.getByRole("listitem");
expect(renderedListItem).toHaveStyleRule("background-color", "rgb(244, 244, 246)");
expect(renderedListItem).toHaveStyleRule("color", "rgb(96, 107, 133)");
expect(renderedListItem).toHaveAttribute("aria-expanded", "true");
expect(renderedListItem).toHaveAttribute("data-test", "test-hook");
const renderedHeading = screen.getByRole("heading");
expect(renderedHeading).toHaveAttribute("aria-expanded", "true");
expect(renderedHeading).toHaveAttribute("data-test", "test-hook");
});
});

describe("Customization", () => {
it("should set a element data attribute for OrderedDisplayList", () => {
render(
<OrderedDisplayList>
<OrderedDisplayListItem>
<OrderedDisplayListHeading as="h2">Heading</OrderedDisplayListHeading>
Text
</OrderedDisplayListItem>
</OrderedDisplayList>,
);
expect(screen.getByRole("list").getAttribute("data-paste-element")).toEqual("ORDERED_DISPLAY_LIST");
expect(screen.getByRole("listitem").getAttribute("data-paste-element")).toEqual("ORDERED_DISPLAY_LIST_ITEM");
expect(screen.getByRole("heading").getAttribute("data-paste-element")).toEqual("ORDERED_DISPLAY_LIST_HEADING");
});
it("should set a custom element data attribute for OrderedDisplayList", () => {
render(
<OrderedDisplayList element="foo">
<OrderedDisplayListItem element="fooitem">
<OrderedDisplayListHeading element="foohead" as="h2">
Heading
</OrderedDisplayListHeading>
Text
</OrderedDisplayListItem>
</OrderedDisplayList>,
);
expect(screen.getByRole("list").getAttribute("data-paste-element")).toEqual("foo");
expect(screen.getByRole("listitem").getAttribute("data-paste-element")).toEqual("fooitem");
expect(screen.getByRole("heading").getAttribute("data-paste-element")).toEqual("foohead");
});
});
});
4 changes: 4 additions & 0 deletions packages/paste-core/components/list/package.json
Expand Up @@ -26,9 +26,11 @@
},
"peerDependencies": {
"@twilio-paste/animation-library": "^2.0.0",
"@twilio-paste/box": "^10.3.0",
"@twilio-paste/color-contrast-utils": "^5.0.0",
"@twilio-paste/customization": "^8.0.0",
"@twilio-paste/design-tokens": "^10.0.0",
"@twilio-paste/heading": "^11.1.2",
"@twilio-paste/style-props": "^9.0.0",
"@twilio-paste/styling-library": "^3.0.0",
"@twilio-paste/text": "^10.0.0",
Expand All @@ -41,9 +43,11 @@
},
"devDependencies": {
"@twilio-paste/animation-library": "^2.0.0",
"@twilio-paste/box": "^10.3.0",
"@twilio-paste/color-contrast-utils": "^5.0.0",
"@twilio-paste/customization": "^8.1.1",
"@twilio-paste/design-tokens": "^10.3.0",
"@twilio-paste/heading": "^11.1.2",
"@twilio-paste/style-props": "^9.1.1",
"@twilio-paste/styling-library": "^3.0.0",
"@twilio-paste/text": "^10.1.1",
Expand Down
@@ -0,0 +1,57 @@
import { Box } from "@twilio-paste/box";
import { css, styled } from "@twilio-paste/styling-library";
import { safelySpreadTextProps } from "@twilio-paste/text";
import * as React from "react";

import type { OrderedDisplayListProps, OrderedDisplayListVariants } from "../types";
import { OrderedDisplayListContext } from "./OrderedDisplayListContext";

const GapStyles = {
heading20: "space100",
heading30: "space90",
heading40: "space80",
heading50: "space70",
heading60: "space60",
};

const StyledOrderedList = styled(Box)(({ variant }: { variant: OrderedDisplayListVariants }) => {
return css({
counterReset: "list-counter",
margin: "space0",
padding: "space0",
listStyle: "none",
display: "grid",
gap: GapStyles[variant],
});
});

export const OrderedDisplayList = React.forwardRef<HTMLOListElement, OrderedDisplayListProps>(
(
{
children,
element = "ORDERED_DISPLAY_LIST",
marginTop,
marginBottom = "space70",
variant = "heading30",
...props
},
ref,
) => {
return (
<OrderedDisplayListContext.Provider value={{ variant }}>
<StyledOrderedList
{...safelySpreadTextProps(props)}
as="ol"
element={element}
marginTop={marginTop}
marginBottom={marginBottom}
ref={ref}
variant={variant}
>
{children}
</StyledOrderedList>
</OrderedDisplayListContext.Provider>
);
},
);
OrderedDisplayList.displayName = "OrderedDisplayList";
@@ -0,0 +1,9 @@
import * as React from "react";

import type { OrderedDisplayListVariants } from "../types";

interface OrderedDisplayListState {
variant: OrderedDisplayListVariants;
}

export const OrderedDisplayListContext = React.createContext<OrderedDisplayListState>({ variant: "heading30" });
@@ -0,0 +1,19 @@
import { Heading } from "@twilio-paste/heading";
import * as React from "react";

import type { OrderedDisplayListHeadingProps } from "../types";
import { OrderedDisplayListContext } from "./OrderedDisplayListContext";

export const OrderedDisplayListHeading = React.forwardRef<HTMLHeadingElement, OrderedDisplayListHeadingProps>(
({ children, element = "ORDERED_DISPLAY_LIST_HEADING", as = "div", ...props }, ref) => {
const { variant } = React.useContext(OrderedDisplayListContext);

return (
<Heading {...props} ref={ref} as={as} variant={variant} element={element} marginBottom="space0">
{children}
</Heading>
);
},
);

OrderedDisplayListHeading.displayName = "OrderedDisplayListHeading";

0 comments on commit 33800d2

Please sign in to comment.