-
Notifications
You must be signed in to change notification settings - Fork 37
/
usePagination.ts
93 lines (87 loc) · 3.33 KB
/
usePagination.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { useMemo } from 'react';
import type { Dispatch, MouseEvent, SetStateAction } from 'react';
import type { PaginationButtonProps } from './PaginationButton';
const getSteps = (now: number, max: number, show: number) => {
const offset = (show - 1) / 2;
const start = Math.min(Math.max(now - Math.floor(offset), 1), max - show + 1);
const end = Math.min(Math.max(now + Math.ceil(offset), show), max);
const pages = Array.from({ length: end + 1 - start }, (_, i) => i + start);
if (show > 4 && start > 1) pages.splice(0, 2, 1, 0);
if (show > 3 && end < max) pages.splice(-2, 2, 0, max);
return pages;
};
export type UsePaginationProps = {
/**
* The current page number
* @default 1
*/
currentPage: number;
/**
* Function to change currentPage - typically returned from useState
*/
setCurrentPage?: (page: number) => void;
/**
* Callback when the page changes
* (event: MouseEvent<HTMLElement>, page: number) => void
*/
onChange?: (event: MouseEvent<HTMLElement>, page: number) => void;
/**
* The total number of pages
* @default 1
*/
totalPages: number;
/**
* The maximum number of pages to show
* @default 1
*/
showPages?: number;
};
/** Hook to help manage pagination state */
export const usePagination = ({
currentPage = 1,
setCurrentPage,
onChange,
totalPages,
showPages = 7,
}: UsePaginationProps) =>
useMemo(() => {
const hasNext = currentPage < totalPages;
const hasPrev = currentPage !== 1;
const handleClick = (page: number) => (event: MouseEvent<HTMLElement>) => {
if (page < 1 || page > totalPages) return event.preventDefault(); // Prevent out of bounds navigation
onChange?.(event, page);
if (!event.defaultPrevented) setCurrentPage?.(page); // Allow stopping change by calling event.preventDefault() in onChange
};
return {
/** Number of steps */
pages: getSteps(currentPage, totalPages, showPages).map(
(page, index) => ({
page: page || '',
itemKey: page ? `page-${page}` : `ellipsis-${index}`, // React key utility
buttonProps: {
'aria-current': page === currentPage ? 'page' : undefined,
'aria-hidden': !page || undefined, // Hide ellipsis from screen reader
onClick: handleClick(page),
tabIndex: page ? undefined : -1, // Hide ellipsis keyboard
variant: page === currentPage ? 'primary' : 'tertiary',
} as PaginationButtonProps,
}),
),
/** Properties to spread on Pagination.Button used for previous naviagation */
prevButtonProps: {
'aria-disabled': !hasPrev, // Using aria-disabled to support all HTML elements because of potential asChild
onClick: handleClick(currentPage - 1),
variant: 'tertiary',
} as PaginationButtonProps,
/** Properties to spread on Pagination.Button used for next naviagation */
nextButtonProps: {
'aria-disabled': !hasNext, // Using aria-disabled to support all HTML elements because of potential asChild
onClick: handleClick(currentPage + 1),
variant: 'tertiary',
} as PaginationButtonProps,
/** Indication if previous page action should be shown or not */
hasPrev,
/** Indication if next page action should be shown or not */
hasNext,
};
}, [currentPage, totalPages, showPages]);