Skip to content

Commit 43bd80a

Browse files
feat(timeline): create package (#4054)
* 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>
1 parent 200baeb commit 43bd80a

File tree

23 files changed

+993
-0
lines changed

23 files changed

+993
-0
lines changed

.changeset/famous-experts-speak.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/timeline": major
3+
"@twilio-paste/core": minor
4+
---
5+
6+
[Timeline]: Added a new Timeline component to the library to display events in chronological order

.changeset/late-vans-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@twilio-paste/codemods": minor
3+
---
4+
5+
[Codemods] new export (timeline)

.codesandbox/ci.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"/packages/paste-core/components/textarea",
105105
"/packages/paste-theme",
106106
"/packages/paste-core/components/time-picker",
107+
"/packages/paste-core/components/timeline",
107108
"/packages/paste-core/components/toast",
108109
"/packages/paste-core/components/tooltip",
109110
"/packages/paste-core/primitives/tooltip",

packages/paste-codemods/tools/.cache/mappings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@
309309
"TextArea": "@twilio-paste/core/textarea",
310310
"TimePicker": "@twilio-paste/core/time-picker",
311311
"formatReturnTime": "@twilio-paste/core/time-picker",
312+
"Timeline": "@twilio-paste/core/timeline",
313+
"TimelineItem": "@twilio-paste/core/timeline",
314+
"TimelineItemGroup": "@twilio-paste/core/timeline",
312315
"Toast": "@twilio-paste/core/toast",
313316
"ToastContainer": "@twilio-paste/core/toast",
314317
"Toaster": "@twilio-paste/core/toast",

packages/paste-core/components/timeline/CHANGELOG.md

Whitespace-only changes.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { render } from "@testing-library/react";
2+
import { UserIcon } from "@twilio-paste/icons/esm/UserIcon";
3+
import * as React from "react";
4+
5+
import { Timeline, TimelineItem } from "../src";
6+
import { TimelineItemGroup } from "../src/TimelineItemGroup";
7+
8+
const ExampleTimeline: React.FC<{
9+
element?: string;
10+
}> = ({ element = "TIMELINE" }) => (
11+
<Timeline element={element} data-testid="timeline">
12+
<TimelineItem data-testid="timelineItem" element={`${element}_ITEM`} title="Start" timestamp="2018-03-01:10:00">
13+
Event details
14+
</TimelineItem>
15+
16+
<TimelineItem title="Inprogress" timestamp="2018-03-01:12:00">
17+
Event details
18+
</TimelineItem>
19+
20+
<TimelineItem title="Complete" timestamp="2018-03-01:14:00">
21+
Event details
22+
</TimelineItem>
23+
24+
<TimelineItem data-testid="timelineItemNoTimestamp" title="Item without timestamp">
25+
Event details
26+
</TimelineItem>
27+
28+
<TimelineItem
29+
title="Icon Item"
30+
timestamp="2018-03-01:12:00"
31+
icon={UserIcon}
32+
data-testid="timelineItemIcon"
33+
element={`${element}_ITEM`}
34+
>
35+
Event details
36+
</TimelineItem>
37+
38+
<TimelineItemGroup timestamp="2018-03-01" data-testid="timelineItemGroup" element={`${element}_ITEM_GROUP`}>
39+
<TimelineItem title="Inprogress" timestamp="12:00">
40+
Event details
41+
</TimelineItem>
42+
43+
<TimelineItem title="Complete" timestamp="14:00">
44+
Event details
45+
</TimelineItem>
46+
47+
<TimelineItem title="Item without timestamp">Event details</TimelineItem>
48+
</TimelineItemGroup>
49+
50+
<TimelineItem
51+
title="Start"
52+
timestamp="2018-03-01:10:00"
53+
collapsible
54+
data-testid="timelineItemCollapsible"
55+
element={`${element}_ITEM`}
56+
>
57+
Event details
58+
</TimelineItem>
59+
60+
<TimelineItem
61+
title="Item without timestamp"
62+
collapsible
63+
collapsibleHeading="custom heading"
64+
data-testid="timelineItemCollapsibleNoTimestamp"
65+
>
66+
Event details
67+
</TimelineItem>
68+
69+
<TimelineItem
70+
title="Item with timestamp and collapsibleHeading"
71+
timestamp="2018-03-01:10:00"
72+
collapsible
73+
collapsibleHeading="custom heading"
74+
data-testid="timelineItemCollapsibleHeading"
75+
>
76+
Event details
77+
</TimelineItem>
78+
</Timeline>
79+
);
80+
81+
describe("Timeline", () => {
82+
it("should render", () => {
83+
expect(document.querySelector("ol")).toBeDefined();
84+
expect(document.querySelector("li")).toBeDefined();
85+
});
86+
87+
it("should display title and timestamp", () => {
88+
const { getByTestId } = render(<ExampleTimeline />);
89+
expect(getByTestId("timelineItem")).toHaveTextContent("Start");
90+
expect(getByTestId("timelineItem")).toHaveTextContent("2018-03-01:10:00");
91+
});
92+
93+
it("should display content", () => {
94+
const { getByTestId } = render(<ExampleTimeline />);
95+
expect(getByTestId("timelineItem")).toHaveTextContent("Event details");
96+
});
97+
98+
it("should display item without timestamp", () => {
99+
const { getByTestId } = render(<ExampleTimeline />);
100+
expect(
101+
getByTestId("timelineItemNoTimestamp").querySelector("[data-paste-element='TIMELINE_ITEM_TIMESTAMP']"),
102+
).toBeNull();
103+
});
104+
105+
it("should display icon item", () => {
106+
const { getByTestId } = render(<ExampleTimeline />);
107+
expect(getByTestId("timelineItem").querySelector("[data-paste-element='TIMELINE_ITEM_ICON']")).toBeDefined();
108+
});
109+
110+
it("should display grouped items", () => {
111+
const { getByTestId } = render(<ExampleTimeline />);
112+
expect(getByTestId("timelineItemGroup")).toBeDefined();
113+
});
114+
115+
it("should display group item timestamp", () => {
116+
const { getByTestId } = render(<ExampleTimeline />);
117+
expect(
118+
getByTestId("timelineItem").querySelector("[data-paste-element='TIMELINE_ITEM_GROUP_TIMESTAMP_DETAIL_TEXT']"),
119+
).toBeDefined();
120+
});
121+
122+
it("should display collapsible items", () => {
123+
const { getByTestId } = render(<ExampleTimeline />);
124+
expect(getByTestId("timelineItemCollapsible")).toBeDefined();
125+
});
126+
127+
it("should display custom collapsible heading", () => {
128+
const { getByTestId } = render(<ExampleTimeline />);
129+
expect(getByTestId("timelineItemCollapsibleNoTimestamp")).toHaveTextContent("custom heading");
130+
});
131+
132+
it("should not display custom collapsible heading text when timestamp is p", () => {
133+
const { getByTestId } = render(<ExampleTimeline />);
134+
expect(getByTestId("timelineItemCollapsibleHeading")).toHaveTextContent("2018-03-01:10:00");
135+
});
136+
137+
describe("Customization", () => {
138+
it("should set element data attribute", () => {
139+
const { getByTestId } = render(<ExampleTimeline />);
140+
expect(getByTestId("timeline").getAttribute("data-paste-element")).toEqual("TIMELINE");
141+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM']`)).toBeTruthy();
142+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_GROUP']`)).toBeTruthy();
143+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_SEPARATOR']`)).toBeTruthy();
144+
expect(
145+
getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_CONTENT_WRAPPER']`),
146+
).toBeTruthy();
147+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON']`)).toBeTruthy();
148+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON_WRAPPER']`)).toBeTruthy();
149+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_ICON_DOT']`)).toBeTruthy();
150+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_TITLE']`)).toBeTruthy();
151+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_TIMESTAMP']`)).toBeTruthy();
152+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_CONTENT']`)).toBeTruthy();
153+
expect(getByTestId("timeline").querySelector(`[data-paste-element='TIMELINE_ITEM_SUMMARY_DETAIL']`)).toBeTruthy();
154+
});
155+
156+
it("should set custom element data attribute", () => {
157+
const { getByTestId } = render(<ExampleTimeline element="CUSTOMIZED" />);
158+
159+
expect(getByTestId("timeline").getAttribute("data-paste-element")).toEqual("CUSTOMIZED");
160+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM']`)).toBeTruthy();
161+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_GROUP']`)).toBeTruthy();
162+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_SEPARATOR']`)).toBeTruthy();
163+
expect(
164+
getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_CONTENT_WRAPPER']`),
165+
).toBeTruthy();
166+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON']`)).toBeTruthy();
167+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON_WRAPPER']`)).toBeTruthy();
168+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_ICON_DOT']`)).toBeTruthy();
169+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_TITLE']`)).toBeTruthy();
170+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_TIMESTAMP']`)).toBeTruthy();
171+
expect(getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_CONTENT']`)).toBeTruthy();
172+
expect(
173+
getByTestId("timeline").querySelector(`[data-paste-element='CUSTOMIZED_ITEM_SUMMARY_DETAIL']`),
174+
).toBeTruthy();
175+
});
176+
});
177+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const { build } = require("../../../../tools/build/esbuild");
2+
3+
build(require("./package.json"));
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"name": "@twilio-paste/timeline",
3+
"version": "0.0.0",
4+
"category": "layout",
5+
"status": "production",
6+
"description": "A Timeline is a visual representation of events displayed in chronological order.",
7+
"author": "Twilio Inc.",
8+
"license": "MIT",
9+
"main:dev": "src/index.tsx",
10+
"main": "dist/index.js",
11+
"module": "dist/index.es.js",
12+
"types": "dist/index.d.ts",
13+
"sideEffects": false,
14+
"publishConfig": {
15+
"access": "public"
16+
},
17+
"files": [
18+
"dist"
19+
],
20+
"scripts": {
21+
"build": "yarn clean && NODE_ENV=production node build.js && tsc",
22+
"build:js": "NODE_ENV=development node build.js",
23+
"build:typedocs": "tsx ../../../../tools/build/generate-type-docs",
24+
"clean": "rm -rf ./dist",
25+
"tsc": "tsc"
26+
},
27+
"peerDependencies": {
28+
"@twilio-paste/anchor": "^12.0.0",
29+
"@twilio-paste/animation-library": "^2.0.0",
30+
"@twilio-paste/box": "^10.2.0",
31+
"@twilio-paste/button": "^14.0.0",
32+
"@twilio-paste/card": "^9.1.0",
33+
"@twilio-paste/color-contrast-utils": "^5.0.0",
34+
"@twilio-paste/customization": "^8.1.1",
35+
"@twilio-paste/design-tokens": "^10.3.0",
36+
"@twilio-paste/detail-text": "^3.1.0",
37+
"@twilio-paste/disclosure-primitive": "^2.1.1",
38+
"@twilio-paste/icons": "^12.0.0",
39+
"@twilio-paste/reakit-library": "^2.0.0",
40+
"@twilio-paste/spinner": "^14.0.0",
41+
"@twilio-paste/stack": "^8.0.0",
42+
"@twilio-paste/style-props": "^9.1.1",
43+
"@twilio-paste/styling-library": "^3.0.0",
44+
"@twilio-paste/summary-detail": "^1.0.0",
45+
"@twilio-paste/text": "^10.0.0",
46+
"@twilio-paste/theme": "^11.0.1",
47+
"@twilio-paste/types": "^6.0.0",
48+
"@twilio-paste/uid-library": "^2.0.0",
49+
"@types/react": "^16.8.6 || ^17.0.2 || ^18.0.27",
50+
"@types/react-dom": "^16.8.6 || ^17.0.2 || ^18.0.10",
51+
"react": "^16.8.6 || ^17.0.2 || ^18.0.0",
52+
"react-dom": "^16.8.6 || ^17.0.2 || ^18.0.0"
53+
},
54+
"devDependencies": {
55+
"@twilio-paste/anchor": "^12.0.0",
56+
"@twilio-paste/animation-library": "^2.0.0",
57+
"@twilio-paste/box": "^10.2.0",
58+
"@twilio-paste/button": "^14.1.0",
59+
"@twilio-paste/card": "^9.1.0",
60+
"@twilio-paste/color-contrast-utils": "^5.0.0",
61+
"@twilio-paste/customization": "^8.1.1",
62+
"@twilio-paste/design-tokens": "^10.3.0",
63+
"@twilio-paste/detail-text": "^3.1.0",
64+
"@twilio-paste/disclosure-primitive": "^2.1.1",
65+
"@twilio-paste/icons": "^12.2.0",
66+
"@twilio-paste/reakit-library": "^2.0.0",
67+
"@twilio-paste/spinner": "^14.0.0",
68+
"@twilio-paste/stack": "^8.0.0",
69+
"@twilio-paste/style-props": "^9.1.1",
70+
"@twilio-paste/styling-library": "^3.0.0",
71+
"@twilio-paste/summary-detail": "^1.0.0",
72+
"@twilio-paste/text": "^10.1.1",
73+
"@twilio-paste/theme": "^11.0.1",
74+
"@twilio-paste/types": "^6.0.0",
75+
"@twilio-paste/uid-library": "^2.0.0",
76+
"@types/react": "^18.0.27",
77+
"@types/react-dom": "^18.0.10",
78+
"react": "^18.0.0",
79+
"react-dom": "^18.0.0",
80+
"tsx": "^4.0.0",
81+
"typescript": "^4.9.4"
82+
}
83+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Box } from "@twilio-paste/box";
2+
import { css, styled } from "@twilio-paste/styling-library";
3+
import React from "react";
4+
5+
import type { TimelineProps } from "./types";
6+
7+
const Timeline = React.forwardRef<HTMLOListElement, TimelineProps>(
8+
({ children, element = "TIMELINE", ...props }, ref) => {
9+
const ContainerStyled = styled.ol(
10+
css({
11+
listStyleType: "none",
12+
margin: "0",
13+
padding: "0",
14+
"li>div:first-child::after": {
15+
content: "''",
16+
borderLeftWidth: "borderWidth10",
17+
borderLeftStyle: "solid",
18+
borderLeftColor: "colorBorderWeaker",
19+
minHeight: "sizeBase80",
20+
flexGrow: "1",
21+
},
22+
}),
23+
);
24+
25+
return (
26+
<Box
27+
ref={ref}
28+
/* @ts-expect-error we don't have polymorphic box typings yet */
29+
as={ContainerStyled}
30+
element={element}
31+
{...props}
32+
>
33+
{children}
34+
</Box>
35+
);
36+
},
37+
);
38+
39+
Timeline.displayName = "Timeline";
40+
41+
export { Timeline };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as React from "react";
2+
3+
export const TimelineGroupContext = React.createContext<boolean>(false);

0 commit comments

Comments
 (0)