forked from grafana/grafana
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPagination.tsx
145 lines (131 loc) · 4.77 KB
/
Pagination.tsx
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { css, cx } from '@emotion/css';
import { useMemo } from 'react';
import { useStyles2 } from '../../themes';
import { Button } from '../Button';
import { Icon } from '../Icon/Icon';
export interface Props {
/** The current page index being shown. */
currentPage: number;
/** Number of total pages. */
numberOfPages: number;
/** Callback function for fetching the selected page. */
onNavigate: (toPage: number) => void;
/** When set to true and the pagination result is only one page it will not render the pagination at all. */
hideWhenSinglePage?: boolean;
/** Small version only shows the current page and the navigation buttons. */
showSmallVersion?: boolean;
className?: string;
}
export const Pagination = ({
currentPage,
numberOfPages,
onNavigate,
hideWhenSinglePage,
showSmallVersion,
className,
}: Props) => {
const styles = useStyles2(getStyles);
const pageLengthToCondense = showSmallVersion ? 1 : 8;
const pageButtons = useMemo(() => {
const pages = [...new Array(numberOfPages).keys()];
const condensePages = numberOfPages > pageLengthToCondense;
const getListItem = (page: number, fill?: 'outline') => (
<li key={page} className={styles.item}>
<Button size="sm" onClick={() => onNavigate(page)} fill={fill}>
{page}
</Button>
</li>
);
return pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
const page = pageIndex + 1;
const fill: 'outline' | undefined = page === currentPage ? undefined : 'outline';
// The indexes at which to start and stop condensing pages
const lowerBoundIndex = pageLengthToCondense;
const upperBoundIndex = numberOfPages - pageLengthToCondense + 1;
// When the indexes overlap one another this number is negative
const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
const isFirstOrLastPage = page === 1 || page === numberOfPages;
// This handles when the lowerBoundIndex < currentPage < upperBoundIndex
const currentPageIsBetweenBounds =
differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex;
// Show ellipsis after that many pages
const ellipsisOffset = showSmallVersion ? 1 : 3;
// The offset to show more pages when currentPageIsBetweenBounds
const pageOffset = showSmallVersion ? 0 : 2;
if (condensePages) {
if (
isFirstOrLastPage ||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
(currentPageIsBetweenBounds && page >= currentPage - pageOffset && page <= currentPage + pageOffset)
) {
// Renders a button for the page
pagesToRender.push(getListItem(page, fill));
} else if (
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
(currentPageIsBetweenBounds &&
(page === currentPage - ellipsisOffset || page === currentPage + ellipsisOffset))
) {
// Renders and ellipsis to represent condensed pages
pagesToRender.push(
<li key={page} className={styles.item}>
<Icon className={styles.ellipsis} name="ellipsis-v" />
</li>
);
}
} else {
pagesToRender.push(getListItem(page, fill));
}
return pagesToRender;
}, []);
}, [currentPage, numberOfPages, onNavigate, pageLengthToCondense, showSmallVersion, styles.ellipsis, styles.item]);
if (hideWhenSinglePage && numberOfPages <= 1) {
return null;
}
return (
<div className={cx(styles.container, className)}>
<ol>
<li className={styles.item}>
<Button
aria-label={`previous page`}
size="sm"
onClick={() => onNavigate(currentPage - 1)}
disabled={currentPage === 1}
fill="outline"
>
<Icon name="angle-left" />
</Button>
</li>
{pageButtons}
<li className={styles.item}>
<Button
aria-label={`next page`}
size="sm"
fill="outline"
onClick={() => onNavigate(currentPage + 1)}
disabled={currentPage === numberOfPages}
>
<Icon name="angle-right" />
</Button>
</li>
</ol>
</div>
);
};
const getStyles = () => {
return {
container: css({
float: 'right',
}),
item: css({
display: 'inline-block',
paddingLeft: '10px',
marginBottom: '5px',
}),
ellipsis: css({
transform: 'rotate(90deg)',
}),
};
};