Skip to content

Commit

Permalink
Merge pull request #1338 from multiversx/mm-ledger-pagination
Browse files Browse the repository at this point in the history
Ledger Address Table Pagination Feature
  • Loading branch information
MiroMargineanu authored Dec 5, 2024
2 parents ac8d21f + b8a3781 commit 63d6f3c
Show file tree
Hide file tree
Showing 21 changed files with 4,104 additions and 3,402 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added address table pagination to the Ledger authentication flow](https://github.com/multiversx/mx-sdk-dapp/pull/1338)

## [[v3.0.23](https://github.com/multiversx/mx-sdk-dapp/pull/1337)] - 2024-11-29

- [Fix deps array which can cause minification errors](https://github.com/multiversx/mx-sdk-dapp/pull/1336)

## [[v3.0.22](https://github.com/multiversx/mx-sdk-dapp/pull/1335)] - 2024-11-29
Expand Down
202 changes: 202 additions & 0 deletions src/UI/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import React, { MouseEvent, useEffect, useState } from 'react';
import {
faAngleLeft,
faAngleRight,
faAnglesLeft,
faAnglesRight
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import BigNumber from 'bignumber.js';
import classNames from 'classnames';

import { DataTestIdsEnum } from 'constants/index';
import { WithStylesImportType } from 'hocs/useStyles';
import { withStyles } from 'hocs/withStyles';
import { WithClassnameType } from 'UI/types';
import { stringIsInteger } from 'utils';

import { PaginationEdgeButton } from './components';
import { getPagination } from './helpers';

export interface PaginationPropsType
extends WithStylesImportType,
WithClassnameType {
onPageChange: (page: number) => void;
currentPage: number;
totalPages: number;
isDisabled?: boolean;
showLabels?: boolean;
showEdgeButtons?: boolean;
disabledClassName?: string;
buttonsClassNames?: string;
}

const PaginationComponent = ({
currentPage = 1,
totalPages,
className,
disabledClassName,
buttonsClassNames,
onPageChange,
isDisabled,
showLabels,
showEdgeButtons = true,
styles
}: PaginationPropsType) => {
const [currentPageIndex, setCurrentPageIndex] = useState(currentPage);

const isLeftToggleDisabled = currentPageIndex === 1;
const isRightToggleDisabled = currentPageIndex === totalPages;

const optionalDisabledClassName = disabledClassName
? { [disabledClassName]: isDisabled }
: {};

const paginationItems = getPagination({
currentPage: currentPageIndex,
totalPages
});

const handlePageClick = (newPageIndex: number) => {
if (newPageIndex === currentPageIndex) {
return;
}

setCurrentPageIndex(newPageIndex);
onPageChange(newPageIndex);
};

const handlePaginationItemClick = (paginationItem: string) => {
if (stringIsInteger(paginationItem)) {
handlePageClick(new BigNumber(paginationItem).toNumber());
}
};

const handleEdgePageClick =
(pageToNavigateTo: number) => (event: MouseEvent<HTMLElement>) => {
event.preventDefault();
handlePageClick(pageToNavigateTo);
};

const isPaginationItemInTheHundreds = (paginationItem: string) =>
stringIsInteger(paginationItem) &&
new BigNumber(paginationItem).isGreaterThanOrEqualTo(100);

const isCurrentPageActive = (paginationItem: string) =>
new BigNumber(paginationItem).isEqualTo(currentPageIndex);

useEffect(() => {
if (currentPage !== currentPageIndex) {
setCurrentPageIndex(currentPage);
}
}, [currentPage, currentPageIndex]);

if (totalPages === 1) {
return null;
}

return (
<div className={classNames(styles?.pagination, className)}>
{showEdgeButtons && (
<span
onClick={handleEdgePageClick(1)}
className={classNames(styles?.paginationAngle, buttonsClassNames, {
[styles?.disabled]: isLeftToggleDisabled,
...optionalDisabledClassName
})}
>
<FontAwesomeIcon
className={styles?.paginationAngleIcon}
icon={faAnglesLeft}
/>
</span>
)}

<PaginationEdgeButton
label='Prev'
onClick={handleEdgePageClick(currentPageIndex - 1)}
data-testid={DataTestIdsEnum.prevBtn}
paginationButtonIcon={faAngleLeft}
isInactive={isLeftToggleDisabled}
showLabels={showLabels}
className={classNames(
styles?.paginationEdgeButton,
buttonsClassNames,
{ [styles?.disabled]: isDisabled },
optionalDisabledClassName
)}
/>

<div className={styles?.paginationItems}>
{paginationItems.map((paginationItem, paginationItemIndex) => (
<div
key={`page-item-${paginationItemIndex}`}
className={styles?.paginationItemWrapper}
>
{stringIsInteger(paginationItem) ? (
<div
onClick={() => handlePaginationItemClick(paginationItem)}
className={classNames(
styles?.paginationItem,
buttonsClassNames,
{ [styles?.active]: isCurrentPageActive(paginationItem) },
{ [styles?.ellipsis]: !stringIsInteger(paginationItem) },
{ [styles?.disabled]: isDisabled },
{
[styles?.hundreds]:
isPaginationItemInTheHundreds(paginationItem)
},
optionalDisabledClassName
)}
>
<span className={styles?.paginationItemText}>
{paginationItem}
</span>
</div>
) : (
<span className={styles?.paginationItemText}>
{paginationItem}
</span>
)}
</div>
))}
</div>

<PaginationEdgeButton
label='Next'
onClick={handleEdgePageClick(currentPageIndex + 1)}
data-testid={DataTestIdsEnum.nextBtn}
paginationButtonIcon={faAngleRight}
isInactive={isRightToggleDisabled}
showLabels={showLabels}
className={classNames(
styles?.paginationEdgeButton,
styles?.reversed,
buttonsClassNames,
{ [styles?.disabled]: isDisabled },
optionalDisabledClassName
)}
/>

{showEdgeButtons && (
<span
onClick={handleEdgePageClick(totalPages)}
className={classNames(styles?.paginationAngle, buttonsClassNames, {
[styles?.disabled]: isRightToggleDisabled,
...optionalDisabledClassName
})}
>
<FontAwesomeIcon
className={styles?.paginationAngleIcon}
icon={faAnglesRight}
/>
</span>
)}
</div>
);
};

export const Pagination = withStyles(PaginationComponent, {
ssrStyles: () => import('UI/Pagination/paginationStyles.scss'),
clientStyles: () => require('UI/Pagination/paginationStyles.scss').default
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { MouseEvent } from 'react';
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';

import { WithStylesImportType } from 'hocs/useStyles';
import { withStyles } from 'hocs/withStyles';
import { WithClassnameType } from 'UI/types';

interface PaginationEdgeButtonPropsType
extends WithClassnameType,
WithStylesImportType {
label: string;
isInactive: boolean;
showLabels?: boolean;
paginationButtonIcon: IconDefinition;
onClick: (event: MouseEvent<HTMLDivElement>) => void;
}

const PaginationEdgeButtonComponent = ({
label,
onClick,
showLabels,
isInactive,
paginationButtonIcon,
className,
'data-testid': dataTestId,
styles
}: PaginationEdgeButtonPropsType) => (
<div
onClick={onClick}
data-testid={dataTestId}
className={classNames(styles?.paginationEdgeButton, className, {
[styles?.inactive]: isInactive
})}
>
<FontAwesomeIcon
icon={paginationButtonIcon}
className={styles?.paginationEdgeButtonIcon}
/>

<span
className={classNames(styles?.paginationEdgeButtonText, {
[styles?.show]: showLabels
})}
>
{label}
</span>
</div>
);

export const PaginationEdgeButton = withStyles(PaginationEdgeButtonComponent, {
ssrStyles: () =>
import(
'UI/Pagination/components/PaginationEdgeButton/paginationEdgeButtonStyles.scss'
),
clientStyles: () =>
require('UI/Pagination/components/PaginationEdgeButton/paginationEdgeButtonStyles.scss')
.default
});
1 change: 1 addition & 0 deletions src/UI/Pagination/components/PaginationEdgeButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PaginationEdgeButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.pagination-edge-button {
display: none;
gap: 8px;
align-items: center;
color: #e5e5e5;
transition: all 200ms ease;
cursor: pointer;

@media (min-width: 576px) {
padding: 0;
margin: 0 4px;
display: flex;
}

&.inactive {
color: #737373;
pointer-events: none;

.pagination-edge-button-icon {
color: #737373;
}
}

&:hover {
color: #23f7dd;

.pagination-edge-button-icon {
color: #23f7dd;
}
}

.pagination-edge-button-text {
display: none;

&.show {
display: block;
}

@media (min-width: 576px) {
display: block;
}
}

.pagination-edge-button-icon {
transition: all 200ms ease;
color: #e5e5e5;
font-size: 12px;

@media (min-width: 375px) {
font-size: 16px;
}
}
}
1 change: 1 addition & 0 deletions src/UI/Pagination/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PaginationEdgeButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const MAX_PAGINATION_SLOTS = 7;
export const MIN_PAGINATION_BATCH_LENGTH = 3;
export const MAX_PAGINATION_BATCH_LENGTH = 5;
Loading

0 comments on commit 63d6f3c

Please sign in to comment.