Create Pagination and ProductOrderSelect components (https://github.com/woocommerce/woocommerce-blocks/pull/926)

* Create ProductOrderSelect component

* Create Pagination component

* Add description to props

* Use BEM class name

* Use < > instead of ← →

* Update product order select options to match Shop core page

* Refactor pagination so it behaves like core pagination

* Update snapshots
This commit is contained in:
Albert Juhé Lluveras 2019-09-03 16:41:05 +02:00 committed by GitHub
parent 0b45200d36
commit 73f8f15bb3
7 changed files with 246 additions and 3 deletions

View File

@ -32,7 +32,7 @@ const Label = ( { label, screenReaderLabel, wrapperElement, wrapperProps } ) =>
if ( label && screenReaderLabel && label !== screenReaderLabel ) { if ( label && screenReaderLabel && label !== screenReaderLabel ) {
return ( return (
<Wrapper { ...wrapperProps }> <Wrapper { ...wrapperProps }>
<span aria-hidden> <span aria-hidden="true">
{ label } { label }
</span> </span>
<span className="screen-reader-text"> <span className="screen-reader-text">

View File

@ -6,7 +6,7 @@ exports[`Label with wrapperElement should render both label and screen reader la
data-foo="bar" data-foo="bar"
> >
<span <span
aria-hidden={true} aria-hidden="true"
> >
Lorem Lorem
</span> </span>
@ -39,7 +39,7 @@ exports[`Label with wrapperElement should render only the screen reader label 1`
exports[`Label without wrapperElement should render both label and screen reader label 1`] = ` exports[`Label without wrapperElement should render both label and screen reader label 1`] = `
Array [ Array [
<span <span
aria-hidden={true} aria-hidden="true"
> >
Lorem Lorem
</span>, </span>,

View File

@ -0,0 +1,135 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';
/**
* Internal dependencies
*/
import Label from '../label';
import { getIndexes } from './utils.js';
import './style.scss';
const Pagination = ( { currentPage, displayFirstAndLastPages, displayNextAndPreviousArrows, pagesToDisplay, onPageChange, totalPages } ) => {
const { minIndex, maxIndex } = getIndexes( pagesToDisplay, currentPage, totalPages );
const pages = [];
for ( let i = minIndex; i <= maxIndex; i++ ) {
pages.push( i );
}
const showFirstPage = displayFirstAndLastPages && Boolean( minIndex !== 1 );
const showLastPage = displayFirstAndLastPages && Boolean( maxIndex !== totalPages );
const showFirstPageEllipsis = displayFirstAndLastPages && Boolean( minIndex > 2 );
const showLastPageEllipsis = displayFirstAndLastPages && Boolean( maxIndex < totalPages - 1 );
const showPreviousArrow = displayNextAndPreviousArrows && Boolean( currentPage !== 1 );
const showNextArrow = displayNextAndPreviousArrows && Boolean( currentPage !== totalPages );
return (
<div className="wc-block-pagination">
<Label
screenReaderLabel={ __( 'Navigate to another page', 'woo-gutenberg-products-block' ) }
/>
{ showPreviousArrow && (
<button
className="wc-block-pagination-page"
onClick={ () => onPageChange( currentPage - 1 ) }
title={ __( 'Previous page', 'woo-gutenberg-products-block' ) }
>
<Label
label="<"
screenReaderLabel={ __( 'Previous page', 'woo-gutenberg-products-block' ) }
/>
</button>
) }
{ showFirstPage && (
<button
className="wc-block-pagination-page"
onClick={ () => onPageChange( 1 ) }
>
1
</button>
) }
{ showFirstPageEllipsis && (
<span className="wc-block-pagination-ellipsis" aria-hidden="true">
{ __( '…', 'woo-gutenberg-products-block' ) }
</span>
) }
{ pages.map( ( page ) => {
return (
<button
key={ page }
className={ classNames( 'wc-block-pagination-page', {
'wc-block-pagination-page--active': currentPage === page,
} ) }
onClick={ currentPage === page ? null : () => onPageChange( page ) }
>
{ page }
</button>
);
} ) }
{ showLastPageEllipsis && (
<span className="wc-block-pagination-ellipsis" aria-hidden="true">
{ __( '…', 'woo-gutenberg-products-block' ) }
</span>
) }
{ showLastPage && (
<button
className="wc-block-pagination-page"
onClick={ () => onPageChange( totalPages ) }
>
{ totalPages }
</button>
) }
{ showNextArrow && (
<button
className="wc-block-pagination-page"
onClick={ () => onPageChange( currentPage + 1 ) }
title={ __( 'Next page', 'woo-gutenberg-products-block' ) }
>
<Label
label=">"
screenReaderLabel={ __( 'Next page', 'woo-gutenberg-products-block' ) }
/>
</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;

View File

@ -0,0 +1,22 @@
.wc-block-pagination-page,
.wc-block-pagination-ellipsis {
color: #333;
display: inline-block;
font-size: 1em;
font-weight: normal;
}
.wc-block-pagination-page {
background-color: transparent;
border-color: transparent;
padding: 0.3em 0.6em;
min-width: 2.2em;
}
.wc-block-pagination-ellipsis {
padding: 0.3em;
}
.wc-block-pagination-page--active {
font-weight: bold;
}

View File

@ -0,0 +1,28 @@
/**
* 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: 1, maxIndex: 5 } );
} );
test( 'indexes include the only available page if there is only one', () => {
expect( getIndexes( 5, 1, 1 ) ).toEqual( { minIndex: 1, maxIndex: 1 } );
} );
} );
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: 96, maxIndex: 100 } );
} );
} );
} );

View File

@ -0,0 +1,17 @@
/**
* 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 {integer} pagesToDisplay Maximum number of pages to display in the pagination component.
* @param {integer} currentPage Page currently visible.
* @param {integer} 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 ) => {
const extraPagesToDisplay = pagesToDisplay - 1;
const tentativeMinIndex = Math.max( Math.floor( currentPage - ( extraPagesToDisplay / 2 ) ), 1 );
const maxIndex = Math.min( Math.ceil( currentPage + ( extraPagesToDisplay - ( currentPage - tentativeMinIndex ) ) ), totalPages );
const minIndex = Math.max( Math.floor( currentPage - ( extraPagesToDisplay - ( maxIndex - currentPage ) ) ), 1 );
return { minIndex, maxIndex };
};

View File

@ -0,0 +1,41 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
import OrderSelect from '../order-select';
const ProductOrderSelect = ( { defaultValue, onChange, readOnly, value } ) => {
return (
<OrderSelect
className="wc-block-product-order-select"
defaultValue={ defaultValue }
name="orderby"
onChange={ onChange }
options={ [
{ key: 'menu_order', label: __( 'Default sorting', 'woo-gutenberg-products-block' ) },
{ key: 'popularity', label: __( 'Popularity', 'woo-gutenberg-products-block' ) },
{ key: 'rating', label: __( 'Average rating', 'woo-gutenberg-products-block' ) },
{ key: 'date', label: __( 'Latest', 'woo-gutenberg-products-block' ) },
{ key: 'price', label: __( 'Price: low to high', 'woo-gutenberg-products-block' ) },
{ key: 'price-desc', label: __( 'Price: high to low', 'woo-gutenberg-products-block' ) },
] }
readOnly={ readOnly }
screenReaderLabel={ __( 'Order products by', 'woo-gutenberg-products-block' ) }
value={ value }
/>
);
};
ProductOrderSelect.propTypes = {
defaultValue: PropTypes.oneOf( [ 'menu_order', 'popularity', 'rating', 'date', 'price', 'price-desc' ] ),
onChange: PropTypes.func,
readOnly: PropTypes.bool,
value: PropTypes.oneOf( [ 'menu_order', 'popularity', 'rating', 'date', 'price', 'price-desc' ] ),
};
export default ProductOrderSelect;