Product Collection: Add loading indicator for client-side pagination (#44571)

* Add animation for client-side pagination

This includes:
- Addition of animation state management in the frontend file to control the visual transition between pagination states.
- Introduction of new SCSS rules for the start and finish animations, ensuring a seamless and visually appealing pagination experience.
- Modification of the PHP logic to inject necessary HTML for the animation to be applied.

These updates aim to provide a more engaging and responsive interface for users navigating through product collection.

* Add changefile(s) from automation for the following project(s): woocommerce-blocks, woocommerce

* Allow user clicks under product collection's loading animation

This commit enhances the user experience of the loading animation for the product collection block. Changes include:

- Specifying `transform-origin: 0% 0%;` directly within the block's initial style to indicate the animation should start from the left
- Adding `pointer-events: none;` to allow user interactions with elements underneath the loading animation, thus improving usability by not blocking clicks.

Additionally, redundant `transform-origin` properties were removed from the `@keyframes` declaration to clean up the code and avoid unnecessary repetition. This simplification contributes to both the maintainability and readability of the stylesheet.

* Fix linting errors in SCSS file

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Manish Menaria 2024-02-16 15:43:58 +05:30 committed by GitHub
parent eb2b1fef1d
commit e210302e8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 105 additions and 16 deletions

View File

@ -9,8 +9,14 @@ import {
getContext,
} from '@woocommerce/interactivity';
/**
* Internal dependencies
*/
import './style.scss';
export type ProductCollectionStoreContext = {
isPrefetchNextOrPreviousLink: boolean;
animation: 'start' | 'finish';
};
const isValidLink = ( ref: HTMLAnchorElement ) =>
@ -63,6 +69,20 @@ function scrollToFirstProductIfNotVisible( wcNavigationId?: string ) {
}
const productCollectionStore = {
state: {
get startAnimation() {
return (
getContext< ProductCollectionStoreContext >().animation ===
'start'
);
},
get finishAnimation() {
return (
getContext< ProductCollectionStoreContext >().animation ===
'finish'
);
},
},
actions: {
*navigate( event: MouseEvent ) {
const ctx = getContext< ProductCollectionStoreContext >();
@ -73,8 +93,18 @@ const productCollectionStore = {
if ( isValidLink( ref ) && isValidEvent( event ) ) {
event.preventDefault();
// Don't start animation if it doesn't take long to navigate.
const timeout = setTimeout( () => {
ctx.animation = 'start';
}, 400 );
yield navigate( ref.href );
// Clear the timeout if the navigation is fast.
clearTimeout( timeout );
ctx.animation = 'finish';
ctx.isPrefetchNextOrPreviousLink = !! ref.href;
scrollToFirstProductIfNotVisible( wcNavigationId );

View File

@ -0,0 +1,46 @@
// This animation will be shown when user change the pages in the product collection block
.wc-block-product-collection__pagination-animation {
position: fixed;
top: 0;
left: 0;
margin: 0;
padding: 0;
width: 100vw;
max-width: 100vw;
height: 4px;
background-color: var(--wp--preset--color--primary, #000);
opacity: 0; // Hide the animation by default.
transform-origin: 0% 0%; // Start the animation from the left.
pointer-events: none; // Allows click events to pass through to elements underneath.
&.start-animation {
animation: wc-block-product-collection__pagination-start-animation 30s cubic-bezier(0.03, 0.5, 0, 1) forwards;
}
&.finish-animation {
animation: wc-block-product-collection__pagination-finish-animation 300ms ease-in;
}
}
@keyframes wc-block-product-collection__pagination-start-animation {
0% {
transform: scaleX(0);
opacity: 1;
}
100% {
transform: scaleX(1);
opacity: 1;
}
}
@keyframes wc-block-product-collection__pagination-finish-animation {
0% {
opacity: 1;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Product Collection: Add loading indicator for client-side pagination

View File

@ -45,14 +45,6 @@ class ProductCollection extends AbstractBlock {
*/
protected $custom_order_opts = array( 'popularity', 'rating' );
/**
* Get the frontend style handle for this block type.
*
* @return null
*/
protected function get_block_type_style() {
return null;
}
/**
* Initialize this block type.
@ -85,21 +77,24 @@ class ProductCollection extends AbstractBlock {
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
// Interactivity API: Add navigation directives to the product collection block.
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'add_navigation_id_directive' ), 10, 3 );
add_filter( 'render_block_woocommerce/product-collection', array( $this, 'enhance_product_collection_with_interactivity' ), 10, 2 );
add_filter( 'render_block_core/query-pagination', array( $this, 'add_navigation_link_directives' ), 10, 3 );
add_filter( 'posts_clauses', array( $this, 'add_price_range_filter_posts_clauses' ), 10, 2 );
}
/**
* Mark the Product Collection as an interactive region so it can be updated
* during client-side navigation.
* Enhances the Product Collection block with client-side pagination.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
* @param \WP_Block $instance The block instance.
* This function identifies Product Collection blocks and adds necessary data attributes
* to enable client-side navigation and animation effects. It also enqueues the Interactivity API runtime.
*
* @param string $block_content The HTML content of the block.
* @param array $block Block details, including its attributes.
*
* @return string Updated block content with added interactivity attributes.
*/
public function add_navigation_id_directive( $block_content, $block, $instance ) {
public function enhance_product_collection_with_interactivity( $block_content, $block ) {
$is_product_collection_block = $block['attrs']['query']['isProductCollectionBlock'] ?? false;
if ( $is_product_collection_block ) {
// Enqueue the Interactivity API runtime.
@ -107,7 +102,7 @@ class ProductCollection extends AbstractBlock {
$p = new \WP_HTML_Tag_Processor( $block_content );
// Add `data-wc-navigation-id to the query block.
// Add `data-wc-navigation-id to the product collection block.
if ( $p->next_tag( array( 'class_name' => 'wp-block-woocommerce-product-collection' ) ) ) {
$p->set_attribute(
'data-wc-navigation-id',
@ -125,6 +120,20 @@ class ProductCollection extends AbstractBlock {
);
$block_content = $p->get_updated_html();
}
// Add animation div to the block content.
$last_tag_position = strripos( $block_content, '</div>' );
$block_content = substr_replace(
$block_content,
'<div
data-wc-interactive="{&quot;namespace&quot;:&quot;woocommerce/product-collection&quot;}"
class="wc-block-product-collection__pagination-animation"
data-wc-class--start-animation="state.startAnimation"
data-wc-class--finish-animation="state.finishAnimation">
</div>',
$last_tag_position,
0
);
}
return $block_content;