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:
parent
0b45200d36
commit
73f8f15bb3
|
@ -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">
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 } );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -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 };
|
||||||
|
};
|
|
@ -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;
|
Loading…
Reference in New Issue