initial commit
This commit is contained in:
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import Label from '@woocommerce/base-components/label';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getIndexes } from './utils.js';
|
||||
import './style.scss';
|
||||
|
||||
const Pagination = ( {
|
||||
currentPage,
|
||||
displayFirstAndLastPages,
|
||||
displayNextAndPreviousArrows,
|
||||
pagesToDisplay,
|
||||
onPageChange,
|
||||
totalPages,
|
||||
} ) => {
|
||||
let { minIndex, maxIndex } = getIndexes(
|
||||
pagesToDisplay,
|
||||
currentPage,
|
||||
totalPages
|
||||
);
|
||||
const showFirstPage = displayFirstAndLastPages && Boolean( minIndex !== 1 );
|
||||
const showLastPage =
|
||||
displayFirstAndLastPages && Boolean( maxIndex !== totalPages );
|
||||
const showFirstPageEllipsis =
|
||||
displayFirstAndLastPages && Boolean( minIndex > 3 );
|
||||
const showLastPageEllipsis =
|
||||
displayFirstAndLastPages && Boolean( maxIndex < totalPages - 2 );
|
||||
|
||||
// Handle the cases where there would be an ellipsis replacing one single page
|
||||
if ( showFirstPage && minIndex === 3 ) {
|
||||
minIndex = minIndex - 1;
|
||||
}
|
||||
if ( showLastPage && maxIndex === totalPages - 2 ) {
|
||||
maxIndex = maxIndex + 1;
|
||||
}
|
||||
|
||||
const pages = [];
|
||||
if ( minIndex && maxIndex ) {
|
||||
for ( let i = minIndex; i <= maxIndex; i++ ) {
|
||||
pages.push( i );
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wc-block-pagination wc-block-components-pagination">
|
||||
<Label
|
||||
screenReaderLabel={ __(
|
||||
'Navigate to another page',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
{ displayNextAndPreviousArrows && (
|
||||
<button
|
||||
className="wc-block-pagination-page wc-block-components-pagination__page wc-block-components-pagination-page--arrow"
|
||||
onClick={ () => onPageChange( currentPage - 1 ) }
|
||||
title={ __(
|
||||
'Previous page',
|
||||
'woocommerce'
|
||||
) }
|
||||
disabled={ currentPage <= 1 }
|
||||
>
|
||||
<Label
|
||||
label="←"
|
||||
screenReaderLabel={ __(
|
||||
'Previous page',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</button>
|
||||
) }
|
||||
{ showFirstPage && (
|
||||
<button
|
||||
className={ classNames(
|
||||
'wc-block-pagination-page',
|
||||
'wc-block-components-pagination__page',
|
||||
{
|
||||
'wc-block-pagination-page--active':
|
||||
currentPage === 1,
|
||||
'wc-block-components-pagination__page--active':
|
||||
currentPage === 1,
|
||||
}
|
||||
) }
|
||||
onClick={ () => onPageChange( 1 ) }
|
||||
disabled={ currentPage === 1 }
|
||||
>
|
||||
<Label
|
||||
label={ 1 }
|
||||
screenReaderLabel={ sprintf(
|
||||
/* translators: %d is the page number (1, 2, 3...). */
|
||||
__( 'Page %d', 'woocommerce' ),
|
||||
1
|
||||
) }
|
||||
/>
|
||||
</button>
|
||||
) }
|
||||
{ showFirstPageEllipsis && (
|
||||
<span
|
||||
className="wc-block-pagination-ellipsis wc-block-components-pagination__ellipsis"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{ __( '…', 'woocommerce' ) }
|
||||
</span>
|
||||
) }
|
||||
{ pages.map( ( page ) => {
|
||||
return (
|
||||
<button
|
||||
key={ page }
|
||||
className={ classNames(
|
||||
'wc-block-pagination-page',
|
||||
'wc-block-components-pagination__page',
|
||||
{
|
||||
'wc-block-pagination-page--active':
|
||||
currentPage === page,
|
||||
'wc-block-components-pagination__page--active':
|
||||
currentPage === page,
|
||||
}
|
||||
) }
|
||||
onClick={
|
||||
currentPage === page
|
||||
? null
|
||||
: () => onPageChange( page )
|
||||
}
|
||||
disabled={ currentPage === page }
|
||||
>
|
||||
<Label
|
||||
label={ page }
|
||||
screenReaderLabel={ sprintf(
|
||||
/* translators: %d is the page number (1, 2, 3...). */
|
||||
__( 'Page %d', 'woocommerce' ),
|
||||
page
|
||||
) }
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
} ) }
|
||||
{ showLastPageEllipsis && (
|
||||
<span
|
||||
className="wc-block-pagination-ellipsis wc-block-components-pagination__ellipsis"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{ __( '…', 'woocommerce' ) }
|
||||
</span>
|
||||
) }
|
||||
{ showLastPage && (
|
||||
<button
|
||||
className={ classNames(
|
||||
'wc-block-pagination-page',
|
||||
'wc-block-components-pagination__page',
|
||||
{
|
||||
'wc-block-pagination-page--active':
|
||||
currentPage === totalPages,
|
||||
'wc-block-components-pagination__page--active':
|
||||
currentPage === totalPages,
|
||||
}
|
||||
) }
|
||||
onClick={ () => onPageChange( totalPages ) }
|
||||
disabled={ currentPage === totalPages }
|
||||
>
|
||||
<Label
|
||||
label={ totalPages }
|
||||
screenReaderLabel={ sprintf(
|
||||
/* translators: %d is the page number (1, 2, 3...). */
|
||||
__( 'Page %d', 'woocommerce' ),
|
||||
totalPages
|
||||
) }
|
||||
/>
|
||||
</button>
|
||||
) }
|
||||
{ displayNextAndPreviousArrows && (
|
||||
<button
|
||||
className="wc-block-pagination-page wc-block-components-pagination__page wc-block-components-pagination-page--arrow"
|
||||
onClick={ () => onPageChange( currentPage + 1 ) }
|
||||
title={ __( 'Next page', 'woocommerce' ) }
|
||||
disabled={ currentPage >= totalPages }
|
||||
>
|
||||
<Label
|
||||
label="→"
|
||||
screenReaderLabel={ __(
|
||||
'Next page',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</button>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Pagination.propTypes = {
|
||||
/**
|
||||
* Number of the page currently being displayed.
|
||||
*/
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
/**
|
||||
* Total number of pages.
|
||||
*/
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
/**
|
||||
* Displays first and last pages if they are not in the current range of pages displayed.
|
||||
*/
|
||||
displayFirstAndLastPages: PropTypes.bool,
|
||||
/**
|
||||
* Displays arrows to navigate to the previous and next pages.
|
||||
*/
|
||||
displayNextAndPreviousArrows: PropTypes.bool,
|
||||
/**
|
||||
* Callback function called when the user triggers a page change.
|
||||
*/
|
||||
onPageChange: PropTypes.func,
|
||||
/**
|
||||
* Number of pages to display at the same time, including the active page
|
||||
* and the pages displayed before and after it. It doesn't include the first
|
||||
* and last pages.
|
||||
*/
|
||||
pagesToDisplay: PropTypes.number,
|
||||
};
|
||||
|
||||
Pagination.defaultProps = {
|
||||
displayFirstAndLastPages: true,
|
||||
displayNextAndPreviousArrows: true,
|
||||
pagesToDisplay: 3,
|
||||
};
|
||||
|
||||
export default Pagination;
|
@ -0,0 +1,56 @@
|
||||
.wc-block-components-pagination {
|
||||
margin: 0 auto $gap;
|
||||
}
|
||||
|
||||
.wc-block-components-pagination__page,
|
||||
.wc-block-components-pagination__ellipsis {
|
||||
@include font-size(regular);
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.wc-block-components-pagination__page {
|
||||
border-color: transparent;
|
||||
padding: 0.3em 0.6em;
|
||||
min-width: 2.2em;
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
padding: 0.1em 0.2em;
|
||||
min-width: 1.6em;
|
||||
}
|
||||
|
||||
// Twenty Twenty register a background color for buttons that is too specific
|
||||
// and broad at the same time `button:not(.toggle)` so we're engaing in a
|
||||
// specify war with them here.
|
||||
// https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/1203
|
||||
&:not(.toggle) {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-pagination__ellipsis {
|
||||
padding: 0.3em;
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
padding: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-pagination__page--active[disabled] {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
opacity: 1 !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: inherit;
|
||||
color: #333;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
html[dir="rtl"] .wc-block-components-pagination-page--arrow span {
|
||||
display: inline-block;
|
||||
transform: scale(-1, 1);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getIndexes } from '../utils.js';
|
||||
|
||||
describe( 'getIndexes', () => {
|
||||
describe( 'when on the first page', () => {
|
||||
test( 'indexes include the first pages available', () => {
|
||||
expect( getIndexes( 5, 1, 100 ) ).toEqual( {
|
||||
minIndex: 2,
|
||||
maxIndex: 6,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'indexes are null if there are 2 pages or less', () => {
|
||||
expect( getIndexes( 5, 1, 1 ) ).toEqual( {
|
||||
minIndex: null,
|
||||
maxIndex: null,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'when on a page in the middle', () => {
|
||||
test( 'indexes include pages before and after the current page', () => {
|
||||
expect( getIndexes( 5, 50, 100 ) ).toEqual( {
|
||||
minIndex: 48,
|
||||
maxIndex: 52,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'when on the last page', () => {
|
||||
test( 'indexes include the last pages available', () => {
|
||||
expect( getIndexes( 5, 100, 100 ) ).toEqual( {
|
||||
minIndex: 95,
|
||||
maxIndex: 99,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Given the number of pages to display, the current page and the total pages,
|
||||
* returns the min and max index of the pages to display in the pagination component.
|
||||
*
|
||||
* @param {number} pagesToDisplay Maximum number of pages to display in the pagination component.
|
||||
* @param {number} currentPage Page currently visible.
|
||||
* @param {number} totalPages Total pages available.
|
||||
* @return {Object} Object containing the min and max index to display in the pagination component.
|
||||
*/
|
||||
export const getIndexes = ( pagesToDisplay, currentPage, totalPages ) => {
|
||||
if ( totalPages <= 2 ) {
|
||||
return { minIndex: null, maxIndex: null };
|
||||
}
|
||||
const extraPagesToDisplay = pagesToDisplay - 1;
|
||||
const tentativeMinIndex = Math.max(
|
||||
Math.floor( currentPage - extraPagesToDisplay / 2 ),
|
||||
2
|
||||
);
|
||||
const maxIndex = Math.min(
|
||||
Math.ceil(
|
||||
currentPage +
|
||||
( extraPagesToDisplay - ( currentPage - tentativeMinIndex ) )
|
||||
),
|
||||
totalPages - 1
|
||||
);
|
||||
const minIndex = Math.max(
|
||||
Math.floor(
|
||||
currentPage - ( extraPagesToDisplay - ( maxIndex - currentPage ) )
|
||||
),
|
||||
2
|
||||
);
|
||||
|
||||
return { minIndex, maxIndex };
|
||||
};
|
Reference in New Issue
Block a user