-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: upstream generic pagination component for tables
Signed-off-by: Mason Hu <[email protected]>
- Loading branch information
Showing
11 changed files
with
758 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
214
src/components/TablePagination/TablePagination.stories.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
Oops, something went wrong.