Files
grafana/packages/grafana-ui/src/components/Pagination/Pagination.tsx
Hugo Häggmark f508a16a43 LibraryPanels: Improves the Get All experience (#32028)
* LibraryPanels: Improves Get All Api

* Refactor: using useReducer instead of useState

* Refactor: adds Pagination to UI

* Tests: adds reducer tests

* Refactor: using Observable instead to avoid flickering

* Refactor: moves exclusion to backend instead

* Chore: changing back the perPage default value
2021-03-18 11:19:41 +01:00

126 lines
4.1 KiB
TypeScript

import React from 'react';
import { css } from 'emotion';
import { stylesFactory } from '../../themes';
import { Button, ButtonVariant } from '../Button';
import { Icon } from '../Icon/Icon';
const PAGE_LENGTH_TO_CONDENSE = 8;
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;
}
export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavigate, hideWhenSinglePage }) => {
const styles = getStyles();
const pages = [...new Array(numberOfPages).keys()];
const condensePages = numberOfPages > PAGE_LENGTH_TO_CONDENSE;
const getListItem = (page: number, variant: 'primary' | 'secondary') => (
<li key={page} className={styles.item}>
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
{page}
</Button>
</li>
);
const pageButtons = pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
const page = pageIndex + 1;
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
// The indexes at which to start and stop condensing pages
const lowerBoundIndex = PAGE_LENGTH_TO_CONDENSE;
const upperBoundIndex = numberOfPages - PAGE_LENGTH_TO_CONDENSE + 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;
if (condensePages) {
if (
isFirstOrLastPage ||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
(currentPageIsBetweenBounds && page >= currentPage - 2 && page <= currentPage + 2)
) {
// Renders a button for the page
pagesToRender.push(getListItem(page, variant));
} else if (
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
(currentPageIsBetweenBounds && (page === currentPage - 3 || page === currentPage + 3))
) {
// 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, variant));
}
return pagesToRender;
}, []);
if (hideWhenSinglePage && numberOfPages <= 1) {
return null;
}
return (
<div className={styles.container}>
<ol>
<li className={styles.item}>
<Button
aria-label="previous"
size="sm"
variant="secondary"
onClick={() => onNavigate(currentPage - 1)}
disabled={currentPage === 1}
>
<Icon name="angle-left" />
</Button>
</li>
{pageButtons}
<li className={styles.item}>
<Button
aria-label="next"
size="sm"
variant="secondary"
onClick={() => onNavigate(currentPage + 1)}
disabled={currentPage === numberOfPages}
>
<Icon name="angle-right" />
</Button>
</li>
</ol>
</div>
);
};
const getStyles = stylesFactory(() => {
return {
container: css`
float: right;
`,
item: css`
display: inline-block;
padding-left: 10px;
margin-bottom: 5px;
`,
ellipsis: css`
transform: rotate(90deg);
`,
};
});