Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(component): create basic pagination component #188

Merged
merged 12 commits into from
Sep 16, 2019
119 changes: 119 additions & 0 deletions packages/big-design/src/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ArrowDropDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@bigcommerce/big-design-icons';
import React, { useEffect, useState } from 'react';

import { MarginProps } from '../../mixins';
import { Dropdown } from '../Dropdown';
import { Flex } from '../Flex';

import { StyledButton } from './styled';

export interface PaginationProps extends MarginProps {
currentPage: number;
itemsPerPage: number;
itemsPerPageOptions: number[];
totalItems: number;
onPageChange(page: number): void;
onRangeChange(range: number): void;
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
}

export const Pagination: React.FC<PaginationProps> = props => {
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
const { itemsPerPage, currentPage, totalItems, itemsPerPageOptions, onPageChange, onRangeChange } = props;

const [maxPages, setMaxPages] = useState(Math.ceil(totalItems / itemsPerPage));
const [itemRange, setItemRange] = useState({ start: 0, end: 0 });

const handlePageOutOfBounds = () => {
if (currentPage < 1 || isNaN(currentPage) || currentPage === undefined) {
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
onPageChange(1);
} else if (currentPage > maxPages) {
onPageChange(maxPages);
}
};

const handlePerPageOutOfBounds = () => {
if (itemsPerPage < 1 || isNaN(itemsPerPage) || itemsPerPage === undefined) {
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
onRangeChange(itemsPerPageOptions[0]);
}
};

const calculateRange = () => {
let firstItemInRange = itemsPerPage * (currentPage - 1) + 1;
let lastItemInRange = itemsPerPage * currentPage;

firstItemInRange = Math.min(firstItemInRange, totalItems);
lastItemInRange = Math.min(lastItemInRange, totalItems);

if (lastItemInRange === 0 || isNaN(lastItemInRange) || isNaN(firstItemInRange)) {
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
firstItemInRange = 0;
lastItemInRange = 0;
}

setItemRange({ start: firstItemInRange, end: lastItemInRange });
};

useEffect(() => {
handlePageOutOfBounds();

handlePerPageOutOfBounds();

calculateRange();

setMaxPages(Math.ceil(totalItems / itemsPerPage));
}, [currentPage, itemsPerPage, totalItems]);

const handlePageIncrease = () => {
onPageChange(currentPage + 1);
};

const handlePageDecrease = () => {
onPageChange(currentPage - 1);
};

const handleRangeChange = (range: number) => {
onRangeChange(range);
};

const showRanges = () => {
return itemRange.start === itemRange.end
? `${itemRange.start} of ${totalItems}`
: `${itemRange.start} - ${itemRange.end} of ${totalItems}`;
};

return (
<Flex flexDirection="row" justifyContent="center" role="navigation" aria-label="pagination">
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
<Dropdown
onItemClick={handleRangeChange}
trigger={
<StyledButton variant="subtle" iconRight={<ArrowDropDownIcon size="xxLarge" />}>
{showRanges()}
</StyledButton>
}
>
{itemsPerPageOptions.map((range, key) => (
<Dropdown.Item key={key} value={range}>
{range} per page
</Dropdown.Item>
))}
</Dropdown>
<Flex.Item>
<StyledButton
variant="subtle"
disabled={currentPage <= 1}
aria-label="previous-page"
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
onClick={handlePageDecrease}
>
<ChevronLeftIcon />
</StyledButton>

<StyledButton
variant="subtle"
disabled={currentPage >= maxPages}
aria-label="next-page"
jordan-massingill marked this conversation as resolved.
Show resolved Hide resolved
onClick={handlePageIncrease}
>
<ChevronRightIcon />
</StyledButton>
</Flex.Item>
</Flex>
);
};
Loading