Skip to content

feat(timeline): add docs #4076

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

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a83c134
feat(timeline): add base component
PixeledCode Aug 27, 2024
3737adb
feat(timeline): add basic styling
PixeledCode Sep 2, 2024
7f2c43f
feat(timeline): add stories
PixeledCode Sep 2, 2024
bb3f199
feat(timeline): add collapsible item
PixeledCode Sep 2, 2024
d578b90
feat(timeline): add grouped item
PixeledCode Sep 2, 2024
068db42
feat(timeline): add collapsibleHeading prop
PixeledCode Sep 3, 2024
e12b0e0
feat(timeline): refactor Timeline component and add element prop
PixeledCode Sep 3, 2024
5bed9b0
feat(timeline): update lock file
PixeledCode Sep 3, 2024
b87ba3c
feat(timeline): add element prop to all components
PixeledCode Sep 4, 2024
d7a676a
feat(timeline): add customization story
PixeledCode Sep 4, 2024
9c98115
feat(timeline): add tests
PixeledCode Sep 4, 2024
f0d8914
feat(timeline): add missing packages
PixeledCode Sep 4, 2024
a41777a
feat(timeline): update yarn lock
PixeledCode Sep 4, 2024
2812d09
feat(timeline): add changeset
PixeledCode Sep 4, 2024
149ee88
feat(timeline): hide orientation prop
PixeledCode Sep 4, 2024
9790628
feat(timeline): implement changes from review
PixeledCode Sep 6, 2024
20f696e
feat(timeline): fix storybook a11y issue
PixeledCode Sep 6, 2024
ccd9842
feat(timeline-docs): add docs examples
PixeledCode Sep 9, 2024
d45bfe1
feat(timeline-docs): add dodont examples
PixeledCode Sep 9, 2024
49cfcd7
feat(timeline-docs): update api page
PixeledCode Sep 9, 2024
3fcd1a0
feat(timeline-docs): add airtable data and update links
PixeledCode Sep 10, 2024
621fea1
feat(timeline-docs): add infinite timeline example
PixeledCode Sep 17, 2024
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
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
5 changes: 5 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,11 @@
"TextArea": "@twilio-paste/core/textarea",
"TimePicker": "@twilio-paste/core/time-picker",
"formatReturnTime": "@twilio-paste/core/time-picker",
"Timeline": "@twilio-paste/core/timeline",
"TimelineContext": "@twilio-paste/core/timeline",
"TimelineItem": "@twilio-paste/core/timeline",
"TimelineItemGroup": "@twilio-paste/core/timeline",
"TimelineItemIcon": "@twilio-paste/core/timeline",
"Toast": "@twilio-paste/core/toast",
"ToastContainer": "@twilio-paste/core/toast",
"Toaster": "@twilio-paste/core/toast",
Expand Down
Empty file.
141 changes: 141 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,141 @@
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}>
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">
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", () => {
const { getByTestId } = render(<ExampleTimeline />);
expect(getByTestId("timeline")).toBeDefined();
expect(getByTestId("timelineItem")).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("timelineItem").getAttribute("data-paste-element")).toEqual("TIMELINE_ITEM");
expect(getByTestId("timelineItemGroup").getAttribute("data-paste-element")).toEqual("TIMELINE_ITEM_GROUP");
});

it("should set custom element data attribute", () => {
const { getByTestId } = render(<ExampleTimeline element="CUSTOMIZED" />);
expect(getByTestId("timeline").getAttribute("data-paste-element")).toEqual("CUSTOMIZED");
expect(getByTestId("timelineItem").getAttribute("data-paste-element")).toEqual("CUSTOMIZED_ITEM");
expect(getByTestId("timelineItemGroup").getAttribute("data-paste-element")).toEqual("CUSTOMIZED_ITEM_GROUP");
});
});
});
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"
}
}
77 changes: 77 additions & 0 deletions packages/paste-core/components/timeline/src/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Box, type BoxStyleProps } from "@twilio-paste/box";
import { css, styled } from "@twilio-paste/styling-library";
import React from "react";

import { TimelineContext } from "./TimelineContext";
import type { Orientation, TimelineProps } from "./types";

const VariantStyles: {
[key in Orientation]: BoxStyleProps;
} = {
vertical: {
flexDirection: "column",
alignItems: "flex-start",
},
horizontal: {
alignItems: "center",
flexWrap: "nowrap",
},
};

const ItemSeparatortyles: {
[key in Orientation]: Record<string, Record<string, string | BoxStyleProps>>;
} = {
vertical: {
"li>div:first-child::after": {
content: "''",
borderLeftWidth: "borderWidth10",
borderLeftStyle: "solid",
borderLeftColor: "colorBorderWeaker",
minHeight: "32px",
flexGrow: "1",
},
},
horizontal: {
"li>div:first-child": {
"&::after, &::before": {
content: "''",
borderBottomWidth: "borderWidth10",
borderBottomStyle: "solid",
borderBottomColor: "colorBorderWeaker",
minWidth: "32px",
flexGrow: 1,
},
},
"li:first-child>div:first-child::before, li:last-child>div:first-child::after": {
borderBottomColor: "transparent",
},
},
};

const Timeline = React.forwardRef<HTMLDivElement, TimelineProps>(
({ children, element = "TIMELINE", ...props }, ref) => {
const ContainerStyled = styled.ol(
css({
listStyleType: "none",
margin: "0",
padding: "0",
display: "flex",
...VariantStyles.vertical,
...ItemSeparatortyles.vertical,
}),
);

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

Timeline.displayName = "Timeline";

export { Timeline };
11 changes: 11 additions & 0 deletions packages/paste-core/components/timeline/src/TimelineContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from "react";

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

interface TimelineState {
orientation: Orientation;
}

export const TimelineContext = React.createContext<TimelineState>({} as TimelineState);

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