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(suite): rework pagination #16588

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 134 additions & 26 deletions packages/suite/src/components/wallet/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ const Wrapper = styled.div`
gap: ${spacingsPx.xxxs};
`;

const PageItem = styled.div<{ $isActive?: boolean }>`
const Ellipsis = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: ${spacingsPx.xxl};
height: ${spacingsPx.xxl};
padding: ${spacingsPx.xxs} ${spacingsPx.xs};
text-align: center;
${typography.hint};
`;

const PageItem = styled.div<{ $isActive?: boolean; $isDisabled?: boolean }>`
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -31,14 +42,22 @@ const PageItem = styled.div<{ $isActive?: boolean }>`
${typography.hint};
cursor: pointer;

${({ $isActive, theme }) =>
${({ $isActive, $isDisabled, theme }) =>
!$isActive &&
!$isDisabled &&
css`
&:hover {
background: ${theme.backgroundTertiaryDefaultOnElevation0};
color: ${theme.textOnTertiary};
}
`};

${({ $isDisabled }) =>
$isDisabled &&
css`
color: ${({ theme }) => theme.textDisabled};
cursor: not-allowed;
`};
`;

const Actions = styled.div<{ $isActive: boolean }>`
Expand All @@ -56,27 +75,90 @@ interface PaginationProps {
onPageSelected: (page: number) => void;
}

const SLIDING_WINDOW_SIZE = 7; // should be an even number, so that the active page can be centered
const calculatePages = ({
currentPage,
totalPages,
}: {
currentPage: number;
totalPages: number;
}) => {
const calculatedPages: (number | null)[] = [];

// center the current page
let windowBeginning = currentPage - Math.floor((SLIDING_WINDOW_SIZE - 1) / 2);

// prevent window overflow on the right
if (windowBeginning + SLIDING_WINDOW_SIZE > totalPages)
windowBeginning = totalPages - (SLIDING_WINDOW_SIZE - 1);

// prevent window overflow on the left
if (windowBeginning < 1) windowBeginning = 1;

for (
let page = windowBeginning;
page < windowBeginning + SLIDING_WINDOW_SIZE && page <= totalPages;
page++
) {
const indexInWindow = calculatedPages.length;

// first button override
if (indexInWindow === 0) {
calculatedPages.push(1);
continue;
}

// second button override
if (indexInWindow === 1 && page !== 2) {
calculatedPages.push(null);
continue;
}

// second to last button override
if (
indexInWindow === SLIDING_WINDOW_SIZE - 2 &&
page !== totalPages - 1 &&
totalPages > SLIDING_WINDOW_SIZE
) {
calculatedPages.push(null);
continue;
}

// last button override
if (indexInWindow === SLIDING_WINDOW_SIZE - 1 && page !== totalPages) {
calculatedPages.push(totalPages);
continue;
}

calculatedPages.push(page);
}

return calculatedPages;
};

export const Pagination = ({
currentPage,
onPageSelected,
hasPages = true,
isLastPage,
isLastPage: _isLastPage,
perPage,
totalItems,
...rest
}: PaginationProps) => {
const totalPages = Math.ceil(totalItems / perPage);
const showPrevious = currentPage > 1;
// array of int used for creating all page buttons
const isFirstPage = currentPage === 1;
const isLastPage = hasPages ? currentPage === totalPages : _isLastPage;

// array of pages to be rendered as buttons
const calculatedPages = useMemo(
() => [...Array(totalPages)].map((_p, i) => i + 1),
[totalPages],
() => calculatePages({ currentPage, totalPages }),
[currentPage, totalPages],
);

if (!hasPages) {
return (
<Wrapper {...rest}>
<Actions $isActive={showPrevious}>
<Actions $isActive={!isFirstPage}>
<PageItem onClick={() => onPageSelected(currentPage - 1)}>
‹ <Translation id="TR_PAGINATION_NEWER" />
</PageItem>
Expand All @@ -92,23 +174,39 @@ export const Pagination = ({

return (
<Wrapper {...rest}>
<Actions $isActive={showPrevious}>
{currentPage > 2 && <PageItem onClick={() => onPageSelected(1)}>«</PageItem>}
<PageItem onClick={() => onPageSelected(currentPage - 1)}>‹</PageItem>
<Actions $isActive={true}>
<PageItem
$isDisabled={isFirstPage}
onClick={!isFirstPage ? () => onPageSelected(1) : undefined}
>
«
</PageItem>
<PageItem
$isDisabled={isFirstPage}
onClick={!isFirstPage ? () => onPageSelected(currentPage - 1) : undefined}
>
</PageItem>
</Actions>

{totalPages ? (
calculatedPages.map(i => (
<PageItem
key={i}
data-testid={`@wallet/accounts/pagination/${i}`}
data-test-activated={i === currentPage}
onClick={() => onPageSelected(i)}
$isActive={i === currentPage}
>
{i}
</PageItem>
))
<>
{calculatedPages.map((page, i) =>
page === null ? (
<Ellipsis key={i}>…</Ellipsis>
) : (
<PageItem
key={i}
data-testid={`@wallet/accounts/pagination/${page}`}
data-test-activated={page === currentPage}
onClick={() => onPageSelected(page)}
$isActive={page === currentPage}
>
{page}
</PageItem>
),
)}
</>
) : (
<>
{[...Array(currentPage - 1)].map((_p, i) => (
Expand All @@ -129,10 +227,20 @@ export const Pagination = ({
</>
)}

<Actions $isActive={currentPage < (totalPages || 1)}>
<PageItem onClick={() => onPageSelected(currentPage + 1)}>›</PageItem>
{totalPages && totalPages > 2 && (
<PageItem onClick={() => onPageSelected(totalPages)}>»</PageItem>
<Actions $isActive={true}>
<PageItem
$isDisabled={isLastPage}
onClick={!isLastPage ? () => onPageSelected(currentPage + 1) : undefined}
>
</PageItem>
{totalPages > 0 && (
<PageItem
$isDisabled={isLastPage}
onClick={!isLastPage ? () => onPageSelected(totalPages) : undefined}
>
»
</PageItem>
)}
</Actions>
</Wrapper>
Expand Down
Loading