Skip to content

Commit

Permalink
feat: upstream generic pagination component for tables
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Hu <[email protected]>
  • Loading branch information
mas-who committed Jan 12, 2024
1 parent 80a8005 commit d3a97a9
Show file tree
Hide file tree
Showing 11 changed files with 758 additions and 0 deletions.
40 changes: 40 additions & 0 deletions src/components/TablePagination/TablePagination.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import "~vanilla-framework/scss/settings";

.pagination {
align-items: baseline;
display: flex;
margin-top: 1.2rem;

.description {
flex-grow: 1;
}

.back {
margin: 0 $spv--large;

.p-icon--chevron-down {
rotate: 90deg;
}
}

.next {
margin: 0 $spv--large;

.p-icon--chevron-down {
rotate: 270deg;
}
}

.pagination-input {
margin-right: $spv--small;
min-width: 0;
width: 3rem;
}

.pagination-select {
margin-bottom: 0;
margin-left: $spv--x-large;
min-width: 0;
width: 7rem;
}
}
214 changes: 214 additions & 0 deletions src/components/TablePagination/TablePagination.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { ArgsTable, Canvas, Meta, Story } from "@storybook/addon-docs";

import TablePagination from "./TablePagination";
import MainTable from "../MainTable";

<Meta title="TablePagination" component={TablePagination} />

export const Template = (args) => <TablePagination {...args} />;

### TablePagination

This is an HOC [React](https://reactjs.org/) component for applying pagination to input data for direct child components.
This component is un-opinionated about the structure of the input data and can be used with any child component that displays
a list of data.

To use this component, simply wrap a child component with it and provide the data that you want to paginate to the ```data``` prop.
This component will then pass the paged data to all <bold>direct</bold> child components via a child prop specified by ```dataPropOverride```.

### Props

<ArgsTable of={TablePagination} />

### Default

<Canvas>
<Story
name="Default"
args={{
data: [{id: "row-1"}, {id: "row-2"}, {id: "row-3"}, {id: "row-4"}, {id: "row-5"}],
}}
>
{Template.bind({})}
</Story>
</Canvas>

### Custom page limit

<Canvas>
<Story
name="CustomPageLimit"
args={{
data: [{id: "row-1"}, {id: "row-2"}, {id: "row-3"}, {id: "row-4"}, {id: "row-5"}],
pageLimits: [1, 2, 3]
}}
>
{Template.bind({})}
</Story>
</Canvas>

### Custom display title

<Canvas>
<Story
name="CustomDisplayTitle"
args={{
data: [{id: "row-1"}, {id: "row-2"}, {id: "row-3"}, {id: "row-4"}, {id: "row-5"}],
displayTitle: <bold>Hello there</bold>
}}
>
{Template.bind({})}
</Story>
</Canvas>

### Render above

<Canvas>
<Story
name="RenderAbove"
>
{() => {
const data = [
{
columns: [
{ content: "Ready", role: "rowheader" },
{ content: 1, className: "u-align--right" },
{ content: "1 GiB", className: "u-align--right" },
{ content: 2, className: "u-align--right" },
{ content: 42, className: "u-align--right" },
],
sortData: {
status: "ready",
cores: 2,
ram: 1,
disks: 2,
},
},
{
columns: [
{ content: "Idle", role: "rowheader" },
{ content: 1, className: "u-align--right" },
{ content: "1 GiB", className: "u-align--right" },
{ content: 2, className: "u-align--right" },
{ content: 23, className: "u-align--right" },
],
sortData: {
status: "idle",
cores: 1,
ram: 1,
disks: 2,
},
},
{
columns: [
{ content: "Waiting", role: "rowheader" },
{ content: 8, className: "u-align--right" },
{ content: "3.9 GiB", className: "u-align--right" },
{ content: 3, className: "u-align--right" },
{ content: 0, className: "u-align--right" },
],
sortData: {
status: "waiting",
cores: 8,
ram: 3.9,
disks: 3,
},
},
];

const headers = [
{ content: "Status", sortKey: "status" },
{ content: "Cores", sortKey: "cores", className: "u-align--right" },
{ content: "RAM", sortKey: "ram", className: "u-align--right" },
{ content: "Disks", sortKey: "disks", className: "u-align--right" },
{ content: "Networks", className: "u-align--right" },
];

return (
<TablePagination data={data} pageLimits={[1, 2, 3]}>
<MainTable
headers={headers}
rows={data}
sortabl
/>
</TablePagination>
);
}}
</Story>
</Canvas>

### Render below

<Canvas>
<Story
name="RenderBelow"
>
{() => {
const data = [
{
columns: [
{ content: "Ready", role: "rowheader" },
{ content: 1, className: "u-align--right" },
{ content: "1 GiB", className: "u-align--right" },
{ content: 2, className: "u-align--right" },
{ content: 42, className: "u-align--right" },
],
sortData: {
status: "ready",
cores: 2,
ram: 1,
disks: 2,
},
},
{
columns: [
{ content: "Idle", role: "rowheader" },
{ content: 1, className: "u-align--right" },
{ content: "1 GiB", className: "u-align--right" },
{ content: 2, className: "u-align--right" },
{ content: 23, className: "u-align--right" },
],
sortData: {
status: "idle",
cores: 1,
ram: 1,
disks: 2,
},
},
{
columns: [
{ content: "Waiting", role: "rowheader" },
{ content: 8, className: "u-align--right" },
{ content: "3.9 GiB", className: "u-align--right" },
{ content: 3, className: "u-align--right" },
{ content: 0, className: "u-align--right" },
],
sortData: {
status: "waiting",
cores: 8,
ram: 3.9,
disks: 3,
},
},
];

const headers = [
{ content: "Status", sortKey: "status" },
{ content: "Cores", sortKey: "cores", className: "u-align--right" },
{ content: "RAM", sortKey: "ram", className: "u-align--right" },
{ content: "Disks", sortKey: "disks", className: "u-align--right" },
{ content: "Networks", className: "u-align--right" },
];

return (
<TablePagination data={data} pageLimits={[1, 2, 3]} renderBelow>
<MainTable
headers={headers}
rows={data}
sortabl
/>
</TablePagination>
);
}}
</Story>
</Canvas>
70 changes: 70 additions & 0 deletions src/components/TablePagination/TablePagination.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable testing-library/no-node-access */
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import TablePagination from "./TablePagination";
import userEvent from "@testing-library/user-event";

const dummyData = [
{ id: "row-1" },
{ id: "row-2" },
{ id: "row-3" },
{ id: "row-4" },
{ id: "row-5" },
];

describe("<TablePagination />", () => {
// snapshot tests
it("renders table pagination and matches the snapshot", () => {
render(<TablePagination data={dummyData} />);

expect(screen.getByRole("navigation")).toMatchSnapshot();
});

// unit tests
it("renders default display title correctly when no pagination takes place", () => {
render(<TablePagination data={dummyData} />);

expect(screen.getByRole("navigation")).toHaveTextContent(
"Showing all 5 items"
);
});

it("renders default display title correctly when pagination takes place", () => {
render(<TablePagination data={dummyData} pageLimits={[1]} />);

expect(screen.getByRole("navigation")).toHaveTextContent(
"Showing 1 out of 5 items"
);
});

it("has correct per page setting when changed", () => {
render(<TablePagination data={dummyData} pageLimits={[2, 5, 10]} />);

expect(screen.getByRole("navigation")).toHaveTextContent("2/page");
userEvent.selectOptions(
screen.getByRole("combobox", { name: "Items per page" }),
"5"
);
expect(screen.getByRole("navigation")).toHaveTextContent("5/page");
});

it("should paginate correctly in incrementing or decrementing directions", async () => {
render(<TablePagination data={dummyData} pageLimits={[1]} />);
const incButton = screen.getByRole("button", { name: "Next page" });
const decButton = screen.getByRole("button", { name: "Previous page" });
const currentPageInput = screen.getByRole("spinbutton", {
name: "Page number",
});
expect(currentPageInput).toHaveValue(1);
await userEvent.click(decButton);
expect(currentPageInput).toHaveValue(1);
await userEvent.click(incButton);
expect(currentPageInput).toHaveValue(2);
await fireEvent.change(currentPageInput, { target: { value: 5 } });
expect(currentPageInput).toHaveValue(5);
await userEvent.click(incButton);
expect(currentPageInput).toHaveValue(5);
await userEvent.click(decButton);
expect(currentPageInput).toHaveValue(4);
});
});
Loading

0 comments on commit d3a97a9

Please sign in to comment.