Skip to content

Commit

Permalink
feat(summary-detail): add package (#3727)
Browse files Browse the repository at this point in the history
* feat(summary-detail): add package

* chore: add missing package

* chore: minor fixes

* fix: many improvements

* chore: add changeset

* chore: pr fixes

* chore: lockfile
  • Loading branch information
TheSisb committed Jan 26, 2024
1 parent 41b3668 commit 55786c6
Show file tree
Hide file tree
Showing 23 changed files with 940 additions and 4 deletions.
8 changes: 8 additions & 0 deletions .changeset/clean-chefs-pull.md
@@ -0,0 +1,8 @@
---
"@twilio-paste/summary-detail": major
"@twilio-paste/core": minor
---

[Summary Detail] Add new SummaryDetail component package.

A Summary Detail is a collapsible container that can be used to hide or show content.
1 change: 1 addition & 0 deletions .changeset/grumpy-islands-sell.md
Expand Up @@ -3,3 +3,4 @@
---

[Codemods] added a new package: page-header
[Codemods] added a new package: SummaryDetail
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Expand Up @@ -91,6 +91,7 @@
"/packages/paste-core/components/status",
"/packages/paste-style-props",
"/packages/paste-libraries/styling",
"/packages/paste-core/components/summary-detail",
"/packages/paste-core/components/switch",
"/packages/paste-libraries/syntax-highlighter",
"/packages/paste-core/components/table",
Expand Down
6 changes: 6 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Expand Up @@ -253,6 +253,12 @@
"StatusMenuItemRadio": "@twilio-paste/core/status",
"StatusObject": "@twilio-paste/core/status",
"useStatusMenuState": "@twilio-paste/core/status",
"SummaryDetail": "@twilio-paste/core/summary-detail",
"SummaryDetailContent": "@twilio-paste/core/summary-detail",
"SummaryDetailHeading": "@twilio-paste/core/summary-detail",
"SummaryDetailHeadingContent": "@twilio-paste/core/summary-detail",
"SummaryDetailToggleButton": "@twilio-paste/core/summary-detail",
"useSummaryDetailState": "@twilio-paste/core/summary-detail",
"Switch": "@twilio-paste/core/switch",
"SwitchGroup": "@twilio-paste/core/switch",
"TBody": "@twilio-paste/core/table",
Expand Down
Empty file.
272 changes: 272 additions & 0 deletions packages/paste-core/components/summary-detail/__tests__/index.spec.tsx
@@ -0,0 +1,272 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { CustomizationProvider } from "@twilio-paste/customization";
import { Theme } from "@twilio-paste/theme";
import * as React from "react";

import {
SummaryDetail,
SummaryDetailContent,
SummaryDetailHeading,
SummaryDetailHeadingContent,
type SummaryDetailProps,
SummaryDetailToggleButton,
useSummaryDetailState,
} from "../src";

const MockSummaryDetail: React.FC<{
visible?: SummaryDetailProps["visible"];
}> = ({ visible }) => {
return (
<Theme.Provider theme="default">
<SummaryDetail baseId="summary-detail" visible={visible} data-testid="summary-detail">
<SummaryDetailHeading data-testid="summary-detail-heading">
<SummaryDetailToggleButton data-testid="summary-detail-toggle-button" />
<SummaryDetailHeadingContent data-testid="summary-detail-heading-content">
Inbound Call
</SummaryDetailHeadingContent>
</SummaryDetailHeading>
<SummaryDetailContent data-testid="summary-detail-content">Agent: John Doe</SummaryDetailContent>
</SummaryDetail>
</Theme.Provider>
);
};

const MockDefaultElementSummaryDetail = (): JSX.Element => {
return (
<SummaryDetail baseId="summary-detail" data-testid="summary-detail">
<SummaryDetailHeading data-testid="summary-detail-heading">
<SummaryDetailToggleButton aria-label="BOOP" data-testid="summary-detail-toggle-button" />
<SummaryDetailHeadingContent data-testid="summary-detail-heading-content">
Inbound Call
</SummaryDetailHeadingContent>
</SummaryDetailHeading>
<SummaryDetailContent data-testid="summary-detail-content">Agent: John Doe</SummaryDetailContent>
</SummaryDetail>
);
};

const MockCustomElementSummaryDetail = (): JSX.Element => {
return (
<SummaryDetail element="MY_SUMMARY_DETAIL" baseId="summary-detail" data-testid="summary-detail">
<SummaryDetailHeading element="MY_SUMMARY_DETAIL_HEADING" data-testid="summary-detail-heading">
<SummaryDetailToggleButton
element="MY_SUMMARY_DETAIL_TOGGLE_BUTTON"
aria-label="BOOP"
data-testid="summary-detail-toggle-button"
/>
<SummaryDetailHeadingContent
element="MY_SUMMARY_DETAIL_HEADING_CONTENT"
data-testid="summary-detail-heading-content"
>
Inbound Call
</SummaryDetailHeadingContent>
</SummaryDetailHeading>
<SummaryDetailContent element="MY_SUMMARY_DETAIL_CONTENT" data-testid="summary-detail-content">
Agent: John Doe
</SummaryDetailContent>
</SummaryDetail>
);
};

const StateHookMock = (): JSX.Element => {
const summaryDetail = useSummaryDetailState();
return (
<Theme.Provider theme="default">
<button
onClick={() => {
summaryDetail.toggle();
}}
type="button"
data-testid="external-toggle"
>
Toggle summaryDetail
</button>
<SummaryDetail state={summaryDetail} baseId="summary-detail" data-testid="summary-detail">
<SummaryDetailHeading element="FOO_HEADING" data-testid="summary-detail-heading">
<SummaryDetailToggleButton
element="FOO_TOGGLE_BUTTON"
aria-label="BOOP"
data-testid="summary-detail-toggle-button"
/>
<SummaryDetailHeadingContent element="FOO_HEADING_CONTENT" data-testid="summary-detail-heading-content">
Inbound Call
</SummaryDetailHeadingContent>
</SummaryDetailHeading>
<SummaryDetailContent element="FOO_CONTENT" data-testid="summary-detail-content">
Agent: John Doe
</SummaryDetailContent>
</SummaryDetail>
</Theme.Provider>
);
};

describe("SummaryDetail", () => {
describe("Render", () => {
it("should render a summary-detail button with aria attributes", () => {
render(<MockDefaultElementSummaryDetail />);
const renderedSummaryDetailButton = screen.getByTestId("summary-detail-toggle-button");
expect(renderedSummaryDetailButton.getAttribute("aria-expanded")).toEqual("false");
expect(renderedSummaryDetailButton.getAttribute("aria-controls")).toEqual("summary-detail");
expect(renderedSummaryDetailButton.getAttribute("aria-label")).toEqual("BOOP");
expect(renderedSummaryDetailButton.getAttribute("aria-labelledby")).toBe(null);
});
it("should overwrite aria-labelledby if passing an aria-label", () => {
render(<MockSummaryDetail />);
const renderedSummaryDetailButton = screen.getByTestId("summary-detail-toggle-button");
expect(renderedSummaryDetailButton.getAttribute("aria-label")).toEqual(null);
expect(renderedSummaryDetailButton.getAttribute("aria-labelledby")).not.toBe(null);
});

it("should render a summary-detail open", async () => {
await waitFor(() => {
render(<MockSummaryDetail visible />);
});
const renderedSummaryDetailButton = screen.getByRole("button");
const summaryDetailContent = screen.getByTestId("summary-detail-content");
expect(renderedSummaryDetailButton.getAttribute("aria-expanded")).toEqual("true");
expect(summaryDetailContent).toBeVisible();
});

it("should toggle open state correctly", async () => {
render(<MockSummaryDetail />);
const summaryDetailButton = screen.getByTestId("summary-detail-toggle-button");
const summaryDetailContent = screen.getByTestId("summary-detail-content");
expect(summaryDetailButton.getAttribute("aria-expanded")).toEqual("false");
expect(summaryDetailContent).not.toBeVisible();
userEvent.click(summaryDetailButton);
expect(summaryDetailButton.getAttribute("aria-expanded")).toEqual("true");
waitFor(() => {
expect(summaryDetailContent).toBeVisible();
});
});

it("should toggle open state correctly when using a state hook", async () => {
render(<StateHookMock />);
const toggleButton = screen.getByTestId("external-toggle");
const summaryDetailButton = screen.getByTestId("summary-detail-toggle-button");
const summaryDetailContent = screen.getByTestId("summary-detail-content");
expect(summaryDetailButton.getAttribute("aria-expanded")).toEqual("false");
expect(summaryDetailContent).not.toBeVisible();
userEvent.click(toggleButton);
expect(summaryDetailButton.getAttribute("aria-expanded")).toEqual("true");
waitFor(() => {
expect(summaryDetailContent).toBeVisible();
});
});
});

describe("Customization", () => {
it("should set an element data attribute for SummaryDetail components", () => {
render(
<Theme.Provider theme="default">
<MockDefaultElementSummaryDetail />
</Theme.Provider>,
);

const renderedSummaryDetailHeading = screen.getByTestId("summary-detail-heading");
const renderedSummaryDetail = screen.getByTestId("summary-detail");
const renderedSummaryDetailContent = screen.getByTestId("summary-detail-content");
const renderedSummaryDetailHeadingContent = screen.getByTestId("summary-detail-heading-content");
const renderedSummaryDetailToggleButton = screen.getByTestId("summary-detail-toggle-button");

expect(renderedSummaryDetail.getAttribute("data-paste-element")).toEqual("SUMMARY_DETAIL");
expect(renderedSummaryDetailHeading.getAttribute("data-paste-element")).toEqual("SUMMARY_DETAIL_HEADING");
expect(renderedSummaryDetailContent.getAttribute("data-paste-element")).toEqual("SUMMARY_DETAIL_CONTENT");
expect(renderedSummaryDetailHeadingContent.getAttribute("data-paste-element")).toEqual(
"SUMMARY_DETAIL_HEADING_CONTENT",
);
expect(renderedSummaryDetailToggleButton.getAttribute("data-paste-element")).toEqual(
"SUMMARY_DETAIL_TOGGLE_BUTTON",
);
});

it("should set a custom element data attribute for custom named SummaryDetail components", () => {
render(
<Theme.Provider theme="default">
<MockCustomElementSummaryDetail />
</Theme.Provider>,
);

const renderedSummaryDetailHeading = screen.getByTestId("summary-detail-heading");
const renderedSummaryDetail = screen.getByTestId("summary-detail");
const renderedSummaryDetailContent = screen.getByTestId("summary-detail-content");
const renderedSummaryDetailHeadingContent = screen.getByTestId("summary-detail-heading-content");
const renderedSummaryDetailToggleButton = screen.getByTestId("summary-detail-toggle-button");

expect(renderedSummaryDetail.getAttribute("data-paste-element")).toEqual("MY_SUMMARY_DETAIL");
expect(renderedSummaryDetailHeading.getAttribute("data-paste-element")).toEqual("MY_SUMMARY_DETAIL_HEADING");
expect(renderedSummaryDetailContent.getAttribute("data-paste-element")).toEqual("MY_SUMMARY_DETAIL_CONTENT");
expect(renderedSummaryDetailHeadingContent.getAttribute("data-paste-element")).toEqual(
"MY_SUMMARY_DETAIL_HEADING_CONTENT",
);
expect(renderedSummaryDetailToggleButton.getAttribute("data-paste-element")).toEqual(
"MY_SUMMARY_DETAIL_TOGGLE_BUTTON",
);
});

it("should add custom styles to SummaryDetail components", () => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{
SUMMARY_DETAIL: { padding: "space100" },
SUMMARY_DETAIL_CONTENT: { color: "colorTextErrorStrong" },
SUMMARY_DETAIL_HEADING: { color: "colorTextWeakest", backgroundColor: "colorBackgroundDestructiveStrong" },
SUMMARY_DETAIL_HEADING_CONTENT: { color: "colorTextIconError" },
SUMMARY_DETAIL_TOGGLE_BUTTON: { borderColor: "colorBorderError" },
}}
>
<MockDefaultElementSummaryDetail />
</CustomizationProvider>,
);

const renderedSummaryDetailHeading = screen.getByTestId("summary-detail-heading");
const renderedSummaryDetail = screen.getByTestId("summary-detail");
const renderedSummaryDetailContent = screen.getByTestId("summary-detail-content");
const renderedSummaryDetailHeadingContent = screen.getByTestId("summary-detail-heading-content");
const renderedSummaryDetailToggleButton = screen.getByTestId("summary-detail-toggle-button");

expect(renderedSummaryDetail).toHaveStyleRule("padding", "2.25rem");
expect(renderedSummaryDetailHeading).toHaveStyleRule("color", "rgb(255, 255, 255)");
expect(renderedSummaryDetailHeading).toHaveStyleRule("background-color", "rgb(117, 12, 12)");
expect(renderedSummaryDetailContent).toHaveStyleRule("color", "rgb(173, 17, 17)");
expect(renderedSummaryDetailHeadingContent).toHaveStyleRule("color", "rgb(214, 31, 31)");
expect(renderedSummaryDetailToggleButton).toHaveStyleRule("border-color", "rgb(214, 31, 31)");
});

it("should add custom styles to custom named SummaryDetail components", () => {
render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{
MY_SUMMARY_DETAIL: { padding: "space100" },
MY_SUMMARY_DETAIL_CONTENT: { color: "colorTextErrorStrong" },
MY_SUMMARY_DETAIL_HEADING: {
color: "colorTextWeakest",
backgroundColor: "colorBackgroundDestructiveStrong",
},
MY_SUMMARY_DETAIL_HEADING_CONTENT: { color: "colorTextIconError" },
MY_SUMMARY_DETAIL_TOGGLE_BUTTON: { borderColor: "colorBorderError" },
}}
>
<MockCustomElementSummaryDetail />
</CustomizationProvider>,
);
const renderedSummaryDetailHeading = screen.getByTestId("summary-detail-heading");
const renderedSummaryDetail = screen.getByTestId("summary-detail");
const renderedSummaryDetailContent = screen.getByTestId("summary-detail-content");
const renderedSummaryDetailHeadingContent = screen.getByTestId("summary-detail-heading-content");
const renderedSummaryDetailToggleButton = screen.getByTestId("summary-detail-toggle-button");

expect(renderedSummaryDetail).toHaveStyleRule("padding", "2.25rem");
expect(renderedSummaryDetailHeading).toHaveStyleRule("color", "rgb(255, 255, 255)");
expect(renderedSummaryDetailHeading).toHaveStyleRule("background-color", "rgb(117, 12, 12)");
expect(renderedSummaryDetailContent).toHaveStyleRule("color", "rgb(173, 17, 17)");
expect(renderedSummaryDetailHeadingContent).toHaveStyleRule("color", "rgb(214, 31, 31)");
expect(renderedSummaryDetailToggleButton).toHaveStyleRule("border-color", "rgb(214, 31, 31)");
});
});
});
3 changes: 3 additions & 0 deletions packages/paste-core/components/summary-detail/build.js
@@ -0,0 +1,3 @@
const { build } = require("../../../../tools/build/esbuild");

build(require("./package.json"));
77 changes: 77 additions & 0 deletions packages/paste-core/components/summary-detail/package.json
@@ -0,0 +1,77 @@
{
"name": "@twilio-paste/summary-detail",
"version": "0.0.1",
"category": "interaction",
"status": "production",
"description": "A Summary Detail is a collapsible container that can be used to hide or show content.",
"author": "Twilio Inc.",
"license": "MIT",
"main:dev": "src/index.tsx",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn clean && NODE_ENV=production node build.js && tsc",
"build:js": "NODE_ENV=development node build.js",
"build:typedocs": "tsx ../../../../tools/build/generate-type-docs",
"clean": "rm -rf ./dist",
"tsc": "tsc"
},
"peerDependencies": {
"@twilio-paste/anchor": "^12.0.0",
"@twilio-paste/animation-library": "^2.0.0",
"@twilio-paste/box": "^10.3.0",
"@twilio-paste/button": "^14.0.0",
"@twilio-paste/card": "^9.1.0",
"@twilio-paste/color-contrast-utils": "^5.0.0",
"@twilio-paste/customization": "^8.0.0",
"@twilio-paste/design-tokens": "^10.0.0",
"@twilio-paste/disclosure-primitive": "^2.1.1",
"@twilio-paste/icons": "^12.0.0",
"@twilio-paste/reakit-library": "^2.0.0",
"@twilio-paste/spinner": "^14.0.0",
"@twilio-paste/stack": "^8.0.0",
"@twilio-paste/style-props": "^9.0.0",
"@twilio-paste/styling-library": "^3.0.0",
"@twilio-paste/theme": "^11.0.0",
"@twilio-paste/types": "^6.0.0",
"@twilio-paste/uid-library": "^2.0.0",
"@types/react": "^16.8.6 || ^17.0.2 || ^18.0.27",
"@types/react-dom": "^16.8.6 || ^17.0.2 || ^18.0.10",
"react": "^16.8.6 || ^17.0.2 || ^18.0.0",
"react-dom": "^16.8.6 || ^17.0.2 || ^18.0.0"
},
"devDependencies": {
"@twilio-paste/anchor": "^12.0.0",
"@twilio-paste/animation-library": "^2.0.0",
"@twilio-paste/box": "^10.3.0",
"@twilio-paste/button": "^14.0.0",
"@twilio-paste/card": "^9.1.0",
"@twilio-paste/color-contrast-utils": "^5.0.0",
"@twilio-paste/customization": "^8.1.1",
"@twilio-paste/design-tokens": "^10.4.0",
"@twilio-paste/disclosure-primitive": "^2.1.1",
"@twilio-paste/icons": "^12.0.0",
"@twilio-paste/reakit-library": "^2.0.0",
"@twilio-paste/spinner": "^14.0.0",
"@twilio-paste/stack": "^8.0.0",
"@twilio-paste/style-props": "^9.1.1",
"@twilio-paste/styling-library": "^3.0.0",
"@twilio-paste/theme": "^11.0.0",
"@twilio-paste/types": "^6.0.0",
"@twilio-paste/uid-library": "^2.0.0",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tsx": "^4.0.0",
"typescript": "^4.9.4"
}
}

0 comments on commit 55786c6

Please sign in to comment.