Skip to content

Commit

Permalink
refactor(pagination): ♻️ add jsdoc to pagination (#138)
Browse files Browse the repository at this point in the history
* refactor(pagination): ♻️  add jsdoc to pagination

* test(pagination): ✅  update tests for the new state changes

[skip ci]

* docs(pagination): ✏️  update types on storybook component

* docs(storybook): ✏️  fix controls type inferance
  • Loading branch information
navin-moorthy authored Nov 5, 2020
1 parent f6004ef commit c7da17c
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 146 deletions.
7 changes: 1 addition & 6 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const path = require("path");
const tsconfig = path.resolve(__dirname, "../tsconfig.json");
const tsconfig = path.resolve(__dirname, "../tsconfig.storybook.json");

module.exports = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
Expand All @@ -13,13 +13,8 @@ module.exports = {
// Need to configure typescript manually otherwise addons will not infer from types
// https://github.com/storybookjs/storybook/issues/11146#issuecomment-643878741
typescript: {
check: false,
checkOptions: { tsconfig },
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
tsconfigPath: tsconfig,
propFilter: (prop: { name: string }) => !/^(testID)$/.test(prop.name),
},
},
};
46 changes: 25 additions & 21 deletions src/pagination/PaginationButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,21 @@ import { ButtonHTMLProps, ButtonOptions, useButton } from "reakit";
import { PAGINATION_BUTTON_KEYS } from "./__keys";
import { PaginationStateReturn } from "./PaginationState";

export type TGoto = "next" | "prev" | "last" | "first" | number;
export type TGoto = "nextPage" | "prevPage" | "lastPage" | "firstPage" | number;

export type PaginationButtonOptions = ButtonOptions &
Pick<
PaginationStateReturn,
| "currentPage"
| "move"
| "next"
| "prev"
| "first"
| "last"
| "isAtMax"
| "isAtMin"
| "movePage"
| "nextPage"
| "prevPage"
| "firstPage"
| "lastPage"
| "isAtLastPage"
| "isAtFirstPage"
> & {
goto: TGoto;
getAriaLabel?: (goto: TGoto, isCurrent: boolean) => string;
};

export type PaginationButtonHTMLProps = ButtonHTMLProps;
Expand All @@ -38,15 +37,15 @@ export const usePaginationButton = createHook<
keys: PAGINATION_BUTTON_KEYS,

useOptions(options, { disabled: htmlDisabled }) {
const { goto, isAtMax, isAtMin } = options;
const { goto, isAtLastPage, isAtFirstPage } = options;
let disabled = false;

if (goto === "next" || goto === "last") {
disabled = isAtMax;
if (goto === "nextPage" || goto === "lastPage") {
disabled = isAtLastPage;
}

if (goto === "prev" || goto === "first") {
disabled = isAtMin;
if (goto === "prevPage" || goto === "firstPage") {
disabled = isAtFirstPage;
}

return {
Expand All @@ -56,21 +55,26 @@ export const usePaginationButton = createHook<
},

useProps(options, { onClick: htmlOnClick, ...htmlProps }) {
const { currentPage, goto, getAriaLabel } = options;
const { currentPage, goto } = options;
const isCurrent = currentPage === goto;

const onClick = React.useCallback(() => {
if (options.disabled) return;

if (isNumber(goto)) {
options.move?.(goto);
} else if (["next", "prev", "last", "first"].includes(goto)) {
options.movePage?.(goto);
return;
}

if (["nextPage", "prevPage", "lastPage", "firstPage"].includes(goto)) {
options[goto]?.();
return;
}
}, [goto, options]);

const ariaLabel =
getAriaLabel?.(goto, isCurrent) ?? isCurrent
? `Page ${goto}`
: `Go to ${goto === "prev" ? "previous" : goto} Page`;
const ariaLabel = isCurrent
? `Page ${goto}`
: `Go to ${goto === "prevPage" ? "previous" : goto} Page`;

return {
"aria-label": ariaLabel,
Expand Down
139 changes: 95 additions & 44 deletions src/pagination/PaginationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,86 @@
* Based on the logic from [usePagination Hook](https://github.com/mui-org/material-ui/blob/master/packages/material-ui-lab/src/Pagination/usePagination.js)
*/
import React from "react";
import { useControllableState } from "@chakra-ui/hooks";
import {
SealedInitialState,
useSealedState,
} from "reakit-utils/useSealedState";

export type PaginationState = {
/**
* The current active page
*
* @default 1
*/
currentPage: number;
/**
* All the page with start & end ellipsis
*/
pages: (string | number)[];
/**
* True, if the currentPage is at first page
*/
isAtFirstPage: boolean;
/**
* True, if the currentPage is at last page
*/
isAtLastPage: boolean;
};

export type PaginationAction = {
/**
* Go to the specified page number
*/
movePage: (page: number) => void;
/**
* Go to next page
*/
nextPage: () => void;
/**
* Go to previous page
*/
prevPage: () => void;
/**
* Go to first page
*/
firstPage: () => void;
/**
* Go to last page
*/
lastPage: () => void;
};

export interface UsePaginationProps {
export type PaginationInitialState = Pick<
Partial<PaginationState>,
"currentPage"
> & {
/**
* Total no. of pages
*/
count?: number;
defaultPage?: number;
/**
* No. of boundary pages to be visible
*/
boundaryCount?: number;
page?: number;
/**
* No. of sibiling pages allowed before/after the current page
*/
siblingCount?: number;
onChange?(value: number): void;
}
};

export type PaginationStateReturn = PaginationState & PaginationAction;

export const usePaginationState = (props: UsePaginationProps = {}) => {
export const usePaginationState = (
props: SealedInitialState<PaginationInitialState> = {},
): PaginationStateReturn => {
const {
currentPage: initialCurrentPage = 1,
count = 1,
defaultPage = 1,
boundaryCount = 1,
siblingCount = 1,
page: currentPage,
onChange,
} = props;
} = useSealedState(props);

const [page, setPage] = useControllableState({
value: currentPage,
defaultValue: defaultPage,
onChange,
shouldUpdate: (prev, next) => prev !== next,
});
const [currentPage, setCurrentPage] = React.useState(initialCurrentPage);

const startPages = range(1, Math.min(boundaryCount, count));
const endPages = range(
Expand All @@ -40,7 +93,7 @@ export const usePaginationState = (props: UsePaginationProps = {}) => {
const siblingsStart = Math.max(
Math.min(
// Natural start
page - siblingCount,
currentPage - siblingCount,
// Lower boundary when page is high
count - boundaryCount - siblingCount * 2 - 1,
),
Expand All @@ -51,7 +104,7 @@ export const usePaginationState = (props: UsePaginationProps = {}) => {
const siblingsEnd = Math.min(
Math.max(
// Natural end
page + siblingCount,
currentPage + siblingCount,
// Upper boundary when page is low
boundaryCount + siblingCount * 2 + 2,
),
Expand Down Expand Up @@ -84,44 +137,42 @@ export const usePaginationState = (props: UsePaginationProps = {}) => {
...endPages,
];

const next = React.useCallback(() => {
setPage(prevPage => prevPage + 1);
}, [setPage]);
const nextPage = React.useCallback(() => {
setCurrentPage(prevPage => prevPage + 1);
}, [setCurrentPage]);

const prev = React.useCallback(() => {
setPage(prevPage => prevPage - 1);
}, [setPage]);
const prevPage = React.useCallback(() => {
setCurrentPage(prevPage => prevPage - 1);
}, [setCurrentPage]);

const first = React.useCallback(() => {
setPage(1);
}, [setPage]);
const firstPage = React.useCallback(() => {
setCurrentPage(1);
}, [setCurrentPage]);

const last = React.useCallback(() => {
setPage(count);
}, [count, setPage]);
const lastPage = React.useCallback(() => {
setCurrentPage(count);
}, [count, setCurrentPage]);

const move = React.useCallback(
const movePage = React.useCallback(
page => {
if (page >= 1 && page <= count) setPage(page);
if (page >= 1 && page <= count) setCurrentPage(page);
},
[count, setPage],
[count, setCurrentPage],
);

return {
pages,
currentPage: page,
isAtMax: page >= count,
isAtMin: page <= 1,
next,
prev,
move,
first,
last,
currentPage,
isAtLastPage: currentPage >= count,
isAtFirstPage: currentPage <= 1,
nextPage,
prevPage,
movePage,
firstPage,
lastPage,
};
};

export type PaginationStateReturn = ReturnType<typeof usePaginationState>;

function range(start: number, end: number) {
const length = end - start + 1;

Expand Down
22 changes: 9 additions & 13 deletions src/pagination/__keys.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
// Automatically generated
const PAGINATION_STATE_KEYS = [
"pages",
"currentPage",
"isAtMax",
"isAtMin",
"next",
"prev",
"move",
"first",
"last",
"pages",
"isAtFirstPage",
"isAtLastPage",
"movePage",
"nextPage",
"prevPage",
"firstPage",
"lastPage",
] as const;
export const PAGINATION_KEYS = PAGINATION_STATE_KEYS;
export const PAGINATION_BUTTON_KEYS = [
...PAGINATION_KEYS,
"goto",
"getAriaLabel",
] as const;
export const PAGINATION_BUTTON_KEYS = [...PAGINATION_KEYS, "goto"] as const;
14 changes: 7 additions & 7 deletions src/pagination/__tests__/Pagination.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import {
Pagination,
PaginationButton,
usePaginationState,
UsePaginationProps,
} from "..";
PaginationInitialState,
} from "../index";
import { repeat } from "../../utils/test-utils";

const PaginationComp: React.FC<UsePaginationProps> = props => {
const PaginationComp: React.FC<PaginationInitialState> = props => {
const state = usePaginationState({ count: 10, ...props });

return (
<Pagination {...state}>
<ul style={{ display: "flex", listStyle: "none" }}>
<li>
<PaginationButton goto="first" {...state}>
<PaginationButton goto="firstPage" {...state}>
First
</PaginationButton>
</li>
<li>
<PaginationButton goto="prev" {...state}>
<PaginationButton goto="prevPage" {...state}>
Previous
</PaginationButton>
</li>
Expand All @@ -46,12 +46,12 @@ const PaginationComp: React.FC<UsePaginationProps> = props => {
);
})}
<li>
<PaginationButton goto="next" {...state}>
<PaginationButton goto="nextPage" {...state}>
Next
</PaginationButton>
</li>
<li>
<PaginationButton goto="last" {...state}>
<PaginationButton goto="lastPage" {...state}>
Last
</PaginationButton>
</li>
Expand Down
Loading

0 comments on commit c7da17c

Please sign in to comment.