Skip to content

Commit

Permalink
Adjustments to pagination design and accessibility (#39249)
Browse files Browse the repository at this point in the history
  • Loading branch information
nateweller authored Sep 5, 2024
1 parent 34e9ee8 commit 34ea4ec
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 115 deletions.
168 changes: 75 additions & 93 deletions projects/plugins/protect/src/js/components/threats-list/pagination.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Button } from '@automattic/jetpack-components';
import { Icon, chevronLeft, chevronRight } from '@wordpress/icons';
import React, { useCallback, useEffect, useState, useMemo, memo } from 'react';
import { Button, useBreakpointMatch } from '@automattic/jetpack-components';
import { __, sprintf } from '@wordpress/i18n';
import { chevronLeft, chevronRight } from '@wordpress/icons';
import React, { useCallback, useState, useMemo } from 'react';
import styles from './styles.module.scss';

const PaginationButton = memo( ( { pageNumber, currentPage, onPageChange } ) => {
const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => {
const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] );

const handleClick = useCallback( () => {
Expand All @@ -16,42 +17,30 @@ const PaginationButton = memo( ( { pageNumber, currentPage, onPageChange } ) =>
className={ ! isCurrentPage ? styles.unfocused : null }
onClick={ handleClick }
aria-current={ isCurrentPage ? 'page' : undefined }
aria-label={ sprintf(
/* translators: placeholder is a page number, i.e. "Page 123" */
__( 'Page %d', 'jetpack-protect' ),
pageNumber
) }
>
{ pageNumber }
</Button>
);
} );

const IconButton = ( { onClick, disabled, direction, iconSize } ) => {
const isLeft = direction === 'left';
return (
<Button
size={ 'medium' }
className={ styles[ 'icon-button' ] }
onClick={ onClick }
disabled={ disabled }
variant={ 'link' }
>
<div className={ isLeft ? styles[ 'first-icon' ] : styles[ 'last-icon' ] }>
<Icon
className={ `${ styles.icon } ${ styles.outside }` }
icon={ isLeft ? chevronLeft : chevronRight }
size={ iconSize }
/>
<Icon
className={ `${ styles.icon } ${ styles.inside }` }
icon={ isLeft ? chevronLeft : chevronRight }
size={ iconSize }
/>
</div>
</Button>
);
};

const Pagination = ( { list, itemPerPage = 10, children } ) => {
const iconSize = 24;
const [ isSm ] = useBreakpointMatch( 'sm' );

const [ currentPage, setCurrentPage ] = useState( 1 );
const [ isSmall, setIsSmall ] = useState( window.matchMedia( '(max-width: 1220px)' ).matches );

const handlePreviousPageClick = useCallback(
() => setCurrentPage( currentPage - 1 ),
[ currentPage, setCurrentPage ]
);
const handleNextPageClick = useCallback(
() => setCurrentPage( currentPage + 1 ),
[ currentPage, setCurrentPage ]
);

const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] );

Expand All @@ -61,97 +50,90 @@ const Pagination = ( { list, itemPerPage = 10, children } ) => {
return list.slice( indexOfFirstItem, indexOfLastItem );
}, [ currentPage, list, itemPerPage ] );

const onPageChange = useCallback( pageNumber => {
setCurrentPage( pageNumber );
}, [] );

useEffect( () => {
const mediaQuery = window.matchMedia( '(max-width: 1220px)' );
const handleMediaChange = event => {
setIsSmall( event.matches );
};
mediaQuery.addEventListener( 'change', handleMediaChange );
return () => {
mediaQuery.removeEventListener( 'change', handleMediaChange );
};
}, [] );

const handleFirstPageClick = useCallback( () => onPageChange( 1 ), [ onPageChange ] );
const handlePreviousPageClick = useCallback(
() => onPageChange( currentPage - 1 ),
[ currentPage, onPageChange ]
);
const handleNextPageClick = useCallback(
() => onPageChange( currentPage + 1 ),
[ currentPage, onPageChange ]
);
const handleLastPageClick = useCallback(
() => onPageChange( totalPages ),
[ onPageChange, totalPages ]
);

const getPageNumbers = useCallback( () => {
if ( isSmall ) {
const pageNumbers = useMemo( () => {
if ( isSm ) {
return [ currentPage ];
}

const result = [ 1 ];
if ( currentPage > 3 && totalPages > 4 ) {
result.push( '…' );
}

if ( currentPage === 1 ) {
return [ 1, 2, 3 ].filter( page => page <= totalPages );
// Current page is the first page.
// i.e. [ 1 ] 2 3 4 ... 10
result.push( currentPage + 1, currentPage + 2, currentPage + 3 );
} else if ( currentPage === 2 ) {
// Current page is the second to first page.
// i.e. 1 [ 2 ] 3 4 ... 10
result.push( currentPage, currentPage + 1, currentPage + 2 );
} else if ( currentPage < totalPages - 1 ) {
// Current page is positioned in the middle of the pagination.
// i.e. 1 ... 3 [ 4 ] 5 ... 10
result.push( currentPage - 1, currentPage, currentPage + 1 );
} else if ( currentPage === totalPages - 1 ) {
// Current page is the second to last page.
// i.e. 1 ... 7 8 [ 9 ] 10
currentPage > 3 && result.push( currentPage - 2 );
currentPage > 2 && result.push( currentPage - 1 );
result.push( currentPage );
} else if ( currentPage === totalPages ) {
// Current page is the last page.
// i.e. 1 ... 7 8 9 [ 10 ]
currentPage >= 5 && result.push( currentPage - 3 );
currentPage >= 4 && result.push( currentPage - 2 );
result.push( currentPage - 1 );
}
if ( currentPage === totalPages ) {
return [ totalPages - 2, totalPages - 1, totalPages ].filter( page => page >= 1 );

if ( result[ result.length - 1 ] < totalPages - 1 ) {
result.push( '…' );
result.push( totalPages );
} else if ( result[ result.length - 1 ] < totalPages ) {
result.push( totalPages );
}
return [ currentPage - 1, currentPage, currentPage + 1 ];
}, [ currentPage, totalPages, isSmall ] );

return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) );
}, [ currentPage, isSm, totalPages ] );

return (
<>
{ children( { currentItems } ) }
{ totalPages > 1 && (
<div className={ styles[ 'pagination-container' ] }>
<IconButton
onClick={ handleFirstPageClick }
disabled={ currentPage === 1 }
direction="left"
iconSize={ iconSize }
/>
<nav
role="navigation"
aria-label={ __( 'Threat list pages', 'jetpack-protect' ) }
className={ styles[ 'pagination-container' ] }
>
<Button
className={ styles[ 'icon-button' ] }
onClick={ handlePreviousPageClick }
disabled={ currentPage === 1 }
variant={ 'link' }
icon={ chevronLeft }
iconSize={ iconSize }
></Button>
{ getPageNumbers().map( ( pageNumber, index ) =>
iconSize={ 24 }
aria-label={ __( 'Previous page', 'jetpack-protect' ) }
/>
{ pageNumbers.map( ( pageNumber, index ) =>
typeof pageNumber === 'number' ? (
<PaginationButton
key={ index }
key={ pageNumber }
pageNumber={ pageNumber }
currentPage={ currentPage }
onPageChange={ onPageChange }
onPageChange={ setCurrentPage }
/>
) : (
<span key={ index } className={ styles.ellipsis }>
{ pageNumber }
</span>
<span key={ `ellipses_${ index }` }>{ pageNumber }</span>
)
) }
<Button
className={ styles[ 'icon-button' ] }
onClick={ handleNextPageClick }
disabled={ currentPage === totalPages }
variant={ 'link' }
icon={ chevronRight }
iconSize={ iconSize }
></Button>
<IconButton
onClick={ handleLastPageClick }
disabled={ currentPage === totalPages }
direction="right"
iconSize={ iconSize }
iconSize={ 24 }
aria-label={ __( 'Next page', 'jetpack-protect' ) }
/>
</div>
</nav>
) }
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,39 +104,26 @@
display: flex;
justify-content: center;
align-items: center;
margin-top: calc( var( --spacing-base ) * 2 ); // 16px
gap: 4px;
margin-top: calc( var( --spacing-base ) * 4 ); // 24px
margin-bottom: calc(var(--spacing-base) * 2); // 16px

button {
font-size: var( --font-body );
padding: var( --spacing-base ) calc( var( --spacing-base ) * 2 ); // 8px | 16px
width: auto;
height: auto;
padding: 0 var( --spacing-base ); // 0 | 8px
line-height: 32px;
min-width: 32px;

&.unfocused {
color: var( --jp-black );
background: none;
box-shadow: none;

&:hover:not(:disabled), &:focus:not(:disabled) {
&:hover:not(:disabled) {
color: var( --jp-black );
background: none;
box-shadow: none;
}
}

&.icon-button:focus:not(:disabled) {
box-shadow: none;
}
}

.first-icon,
.last-icon {
display: flex;

.inside {
margin-left: var( --spacing-base );
}

.outside {
position: absolute;
}
}
}

0 comments on commit 34ea4ec

Please sign in to comment.