Skip to content

Commit

Permalink
feat(timeline): create package (#4054)
Browse files Browse the repository at this point in the history
* feat(timeline): add base component

* feat(timeline): add basic styling

* feat(timeline): add stories

* feat(timeline): add collapsible item

* feat(timeline): add grouped item

* feat(timeline): add collapsibleHeading prop

* feat(timeline): refactor Timeline component and add element prop

* feat(timeline): update lock file

* feat(timeline): add element prop to all components

* feat(timeline): add customization story

* feat(timeline): add tests

* feat(timeline): add missing packages

* feat(timeline): update yarn lock

* feat(timeline): add changeset

* feat(timeline): hide orientation prop

* feat(timeline): implement changes from review

* feat(timeline): fix storybook a11y issue

* feat(timeline): add improvements from review

* feat(timeline): add changes from chromatic review

* feat(timeline): add changes from chromatic review

* feat(timeline): add changes from github review

* feat(timeline): update skeleton for infinte scroll

* feat(timeline): remove paragraph and skeleton loader from package

* feat(timeline): update yarn lock

* feat(timeline): resolve comments

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
PixeledCode and kodiakhq[bot] authored Sep 13, 2024
1 parent 200baeb commit 43bd80a
Show file tree
Hide file tree
Showing 23 changed files with 993 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/famous-experts-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@twilio-paste/timeline": major
"@twilio-paste/core": minor
---

[Timeline]: Added a new Timeline component to the library to display events in chronological order
5 changes: 5 additions & 0 deletions .changeset/late-vans-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@twilio-paste/codemods": minor
---

[Codemods] new export (timeline)
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"/packages/paste-core/components/textarea",
"/packages/paste-theme",
"/packages/paste-core/components/time-picker",
"/packages/paste-core/components/timeline",
"/packages/paste-core/components/toast",
"/packages/paste-core/components/tooltip",
"/packages/paste-core/primitives/tooltip",
Expand Down
3 changes: 3 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@
"TextArea": "@twilio-paste/core/textarea",
"TimePicker": "@twilio-paste/core/time-picker",
"formatReturnTime": "@twilio-paste/core/time-picker",
"Timeline": "@twilio-paste/core/timeline",
"TimelineItem": "@twilio-paste/core/timeline",
"TimelineItemGroup": "@twilio-paste/core/timeline",
"Toast": "@twilio-paste/core/toast",
"ToastContainer": "@twilio-paste/core/toast",
"Toaster": "@twilio-paste/core/toast",
Expand Down
Empty file.
177 changes: 177 additions & 0 deletions packages/paste-core/components/timeline/__tests__/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { render } from "@testing-library/react";
import { UserIcon } from "@twilio-paste/icons/esm/UserIcon";
import * as React from "react";

import { Timeline, TimelineItem } from "../src";
import { TimelineItemGroup } from "../src/TimelineItemGroup";

const ExampleTimeline: React.FC<{
element?: string;
}> = ({ element = "TIMELINE" }) => (
<Timeline element={element} data-testid="timeline">
<TimelineItem data-testid="timelineItem" element={`${element}_ITEM`} title="Start" timestamp="2018-03-01:10:00">
Event details
</TimelineItem>

<TimelineItem title="Inprogress" timestamp="2018-03-01:12:00">
Event details
</TimelineItem>

<TimelineItem title="Complete" timestamp="2018-03-01:14:00">
Event details
</TimelineItem>

<TimelineItem data-testid="timelineItemNoTimestamp" title="Item without timestamp">
Event details
</TimelineItem>

<TimelineItem
title="Icon Item"
timestamp="2018-03-01:12:00"
icon={UserIcon}
data-testid="timelineItemIcon"
element={`${element}_ITEM`}
>
Event details
</TimelineItem>

<TimelineItemGroup timestamp="2018-03-01" data-testid="timelineItemGroup" element={`${element}_ITEM_GROUP`}>
<TimelineItem title="Inprogress" timestamp="12:00">
Event details
</TimelineItem>

<TimelineItem title="Complete" timestamp="14:00">
Event details
</TimelineItem>

<TimelineItem title="Item without timestamp">Event details</TimelineItem>
</TimelineItemGroup>

<TimelineItem
title="Start"
timestamp="2018-03-01:10:00"
collapsible
data-testid="timelineItemCollapsible"
element={`${element}_ITEM`}
>
Event details
</TimelineItem>

<TimelineItem
title="Item without timestamp"
collapsible
collapsibleHeading="custom heading"
data-testid="timelineItemCollapsibleNoTimestamp"
>
Event details
</TimelineItem>

<TimelineItem
title="Item with timestamp and collapsibleHeading"
timestamp="2018-03-01:10:00"
collapsible
collapsibleHeading="custom heading"
data-testid="timelineItemCollapsibleHeading"
>
Event details
</TimelineItem>
</Timeline>
);

describe("Timeline", () => {
it("should render", () => {
expect(document.querySelector("ol")).toBeDefined();
expect(document.querySelector("li")).toBeDefined();
});

it("should display title and timestamp", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItem")).toHaveTextContent("Start");
expect(getByTestId("timelineItem")).toHaveTextContent("2018-03-01:10:00");
});

it("should display content", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItem")).toHaveTextContent("Event details");
});

it("should display item without timestamp", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(
getByTestId("timelineItemNoTimestamp").querySelector("[data-paste-element='TIMELINE_ITEM_TIMESTAMP']"),
).toBeNull();
});

it("should display icon item", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItem").querySelector("[data-paste-element='TIMELINE_ITEM_ICON']")).toBeDefined();
});

it("should display grouped items", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItemGroup")).toBeDefined();
});

it("should display group item timestamp", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(
getByTestId("timelineItem").querySelector("[data-paste-element='TIMELINE_ITEM_GROUP_TIMESTAMP_DETAIL_TEXT']"),
).toBeDefined();
});

it("should display collapsible items", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItemCollapsible")).toBeDefined();
});

it("should display custom collapsible heading", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItemCollapsibleNoTimestamp")).toHaveTextContent("custom heading");
});

it("should not display custom collapsible heading text when timestamp is p", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timelineItemCollapsibleHeading")).toHaveTextContent("2018-03-01:10:00");
});

describe("Customization", () => {
it("should set element data attribute", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timeline").getAttribute("data-paste-element")).toEqual("TIMELINE");
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_GROUP']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_SEPARATOR']`)).toBeTruthy();
expect(
getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_CONTENT_WRAPPER']`),
).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON_WRAPPER']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON_DOT']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_TITLE']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_TIMESTAMP']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_CONTENT']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_SUMMARY_DETAIL']`)).toBeTruthy();
});

it("should set custom element data attribute", () => {
const { getByTestId } = render(<ExampleTimeline element="CUSTOMIZED" />);

expect(getByTestId("timeline").getAttribute("data-paste-element")).toEqual("CUSTOMIZED");
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_GROUP']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_SEPARATOR']`)).toBeTruthy();
expect(
getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_CONTENT_WRAPPER']`),
).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON_WRAPPER']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON_DOT']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_TITLE']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_TIMESTAMP']`)).toBeTruthy();
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_CONTENT']`)).toBeTruthy();
expect(
getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_SUMMARY_DETAIL']`),
).toBeTruthy();
});
});
});
3 changes: 3 additions & 0 deletions packages/paste-core/components/timeline/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { build } = require("../../../../tools/build/esbuild");

build(require("./package.json"));
83 changes: 83 additions & 0 deletions packages/paste-core/components/timeline/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"name": "@twilio-paste/timeline",
"version": "0.0.0",
"category": "layout",
"status": "production",
"description": "A Timeline is a visual representation of events displayed in chronological order.",
"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.2.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.3.0",
"@twilio-paste/detail-text": "^3.1.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/summary-detail": "^1.0.0",
"@twilio-paste/text": "^10.0.0",
"@twilio-paste/theme": "^11.0.1",
"@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.2.0",
"@twilio-paste/button": "^14.1.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.3.0",
"@twilio-paste/detail-text": "^3.1.0",
"@twilio-paste/disclosure-primitive": "^2.1.1",
"@twilio-paste/icons": "^12.2.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/summary-detail": "^1.0.0",
"@twilio-paste/text": "^10.1.1",
"@twilio-paste/theme": "^11.0.1",
"@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"
}
}
41 changes: 41 additions & 0 deletions packages/paste-core/components/timeline/src/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Box } from "@twilio-paste/box";
import { css, styled } from "@twilio-paste/styling-library";
import React from "react";

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

const Timeline = React.forwardRef<HTMLOListElement, TimelineProps>(
({ children, element = "TIMELINE", ...props }, ref) => {
const ContainerStyled = styled.ol(
css({
listStyleType: "none",
margin: "0",
padding: "0",
"li>div:first-child::after": {
content: "''",
borderLeftWidth: "borderWidth10",
borderLeftStyle: "solid",
borderLeftColor: "colorBorderWeaker",
minHeight: "sizeBase80",
flexGrow: "1",
},
}),
);

return (
<Box
ref={ref}
/* @ts-expect-error we don't have polymorphic box typings yet */
as={ContainerStyled}
element={element}
{...props}
>
{children}
</Box>
);
},
);

Timeline.displayName = "Timeline";

export { Timeline };
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as React from "react";

export const TimelineGroupContext = React.createContext<boolean>(false);
Loading

0 comments on commit 43bd80a

Please sign in to comment.