diff --git a/assets/js/blocks/product-gallery/block.json b/assets/js/blocks/product-gallery/block.json index 062c82fa636..92cc1375b49 100644 --- a/assets/js/blocks/product-gallery/block.json +++ b/assets/js/blocks/product-gallery/block.json @@ -22,6 +22,7 @@ "pagerDisplayMode": "pagerDisplayMode", "hoverZoom": "hoverZoom", "fullScreenOnClick": "fullScreenOnClick", + "mode": "mode", "cropImages": "cropImages" }, "attributes": { diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx index c1d0b9140b0..962dbcef545 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/frontend.tsx @@ -87,7 +87,11 @@ interactivityStore( context: Context; event: Event; } ) => { - if ( ( event.target as HTMLElement ).tagName === 'IMG' ) { + if ( + ( event.target as HTMLElement ).classList.contains( + 'wc-block-product-gallery-dialog-on-click' + ) + ) { context.woocommerce.isDialogOpen = true; } }, diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json index 17e54305445..4cb598b27f2 100644 --- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json +++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block.json @@ -7,7 +7,7 @@ "description": "Display the Thumbnails of a product.", "category": "woocommerce", "keywords": [ "WooCommerce" ], - "usesContext": [ "postId", "thumbnailsPosition", "thumbnailsNumberOfThumbnails", "productGalleryClientId" ], + "usesContext": [ "postId", "thumbnailsPosition", "thumbnailsNumberOfThumbnails", "productGalleryClientId", "mode" ], "textdomain": "woo-gutenberg-products-block", "ancestor": [ "woocommerce/product-gallery" ], "supports": { diff --git a/assets/js/blocks/product-gallery/style.scss b/assets/js/blocks/product-gallery/style.scss index 3b485211f7b..6d19db24f3c 100644 --- a/assets/js/blocks/product-gallery/style.scss +++ b/assets/js/blocks/product-gallery/style.scss @@ -245,6 +245,35 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset)); width: 100px; height: 100px; margin: 5px; + position: relative; + + } + .wc-block-product-gallery-thumbnails__thumbnail__overlay { + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: absolute; + background-color: rgba(0, 0, 0, 0.4); + top: 0; + width: 100%; + height: 100%; + + .wc-block-product-gallery-thumbnails__thumbnail__remaining-thumbnails-count { + @include font-size(large); + font-weight: 700; + } + + .wc-block-product-gallery-thumbnails__thumbnail__view-all { + @include font-size(smaller); + text-decoration: underline; + } + + .wc-block-product-gallery-thumbnails__thumbnail__remaining-thumbnails-count, + .wc-block-product-gallery-thumbnails__thumbnail__view-all { + color: #fff; + } } } diff --git a/src/BlockTypes/ProductGallery.php b/src/BlockTypes/ProductGallery.php index 2c82642c36b..72357291e31 100644 --- a/src/BlockTypes/ProductGallery.php +++ b/src/BlockTypes/ProductGallery.php @@ -123,7 +123,7 @@ protected function render( $attributes, $content, $block ) { $number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0; $classname = $attributes['className'] ?? ''; - $dialog = ( true === $attributes['fullScreenOnClick'] && isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ) ? $this->render_dialog() : ''; + $dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : ''; $post_id = $block->context['postId'] ?? ''; $product = wc_get_product( $post_id ); $product_id = strval( $product->get_id() ); diff --git a/src/BlockTypes/ProductGalleryLargeImage.php b/src/BlockTypes/ProductGalleryLargeImage.php index 3a0507c83cb..89692a3d0a6 100644 --- a/src/BlockTypes/ProductGalleryLargeImage.php +++ b/src/BlockTypes/ProductGalleryLargeImage.php @@ -124,7 +124,7 @@ private function get_main_images_html( $context, $product_id ) { ); if ( $context['fullScreenOnClick'] ) { - $attributes['class'] .= ' wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click'; + $attributes['class'] .= ' wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click wc-block-product-gallery-dialog-on-click'; } if ( $context['hoverZoom'] ) { diff --git a/src/BlockTypes/ProductGalleryThumbnails.php b/src/BlockTypes/ProductGalleryThumbnails.php index be083c13290..2e82ad1fe35 100644 --- a/src/BlockTypes/ProductGalleryThumbnails.php +++ b/src/BlockTypes/ProductGalleryThumbnails.php @@ -37,7 +37,77 @@ protected function get_block_type_style() { * @return string[] */ protected function get_block_type_uses_context() { - return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition' ]; + return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition', 'mode' ]; + } + + /** + * Generate the View All markup. + * + * @param int $remaining_thumbnails_count The number of thumbnails that are not displayed. + * + * @return string + */ + protected function generate_view_all_html( $remaining_thumbnails_count ) { + $view_all_html = ''; + + return sprintf( + $view_all_html, + esc_html( $remaining_thumbnails_count ), + esc_html__( 'View all', 'woo-gutenberg-products-block' ) + ); + } + + /** + * Inject View All markup into the product thumbnail HTML. + * + * @param string $thumbnail_html The thumbnail HTML. + * @param string $view_all_html The view all HTML. + * + * @return string + */ + protected function inject_view_all( $thumbnail_html, $view_all_html ) { + + // Find the position of the last . + $pos = strrpos( $thumbnail_html, '' ); + + if ( false !== $pos ) { + // Inject the view_all_html at the correct position. + $html = substr_replace( $thumbnail_html, $view_all_html, $pos, 0 ); + + return $html; + } + } + + /** + * Check if the thumbnails should be limited. + * + * @param string $mode Mode of the gallery. Expected values: 'standard'. + * @param int $thumbnails_count Current count of processed thumbnails. + * @param int $number_of_thumbnails Number of thumbnails configured to display. + * + * @return bool + */ + protected function should_limit_thumbnails( $mode, $thumbnails_count, $number_of_thumbnails ) { + return 'standard' === $mode && $thumbnails_count > $number_of_thumbnails; + } + + /** + * Check if View All markup should be displayed. + * + * @param string $mode Mode of the gallery. Expected values: 'standard'. + * @param int $thumbnails_count Current count of processed thumbnails. + * @param array $product_gallery_images Array of product gallery image HTML strings. + * @param int $number_of_thumbnails Number of thumbnails configured to display. + * + * @return bool + */ + protected function should_display_view_all( $mode, $thumbnails_count, $product_gallery_images, $number_of_thumbnails ) { + return 'standard' === $mode && + $thumbnails_count === $number_of_thumbnails && + count( $product_gallery_images ) > $number_of_thumbnails; } /** @@ -67,22 +137,31 @@ protected function render( $attributes, $content, $block ) { if ( $product_gallery_images && $post_thumbnail_id ) { $html = ''; $number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3; + $mode = $block->context['mode'] ?? ''; $thumbnails_count = 1; foreach ( $product_gallery_images as $product_gallery_image_html ) { - if ( $thumbnails_count > $number_of_thumbnails ) { + // Limit the number of thumbnails only in the standard mode (and not in dialog). + if ( $this->should_limit_thumbnails( $mode, $thumbnails_count, $number_of_thumbnails ) ) { break; } - $processor = new \WP_HTML_Tag_Processor( $product_gallery_image_html ); + // If not in dialog and it's the last thumbnail and the number of product gallery images is greater than the number of thumbnails settings output the View All markup. + if ( $this->should_display_view_all( $mode, $thumbnails_count, $product_gallery_images, $number_of_thumbnails ) ) { + $remaining_thumbnails_count = count( $product_gallery_images ) - $number_of_thumbnails; + $product_gallery_image_html = $this->inject_view_all( $product_gallery_image_html, $this->generate_view_all_html( $remaining_thumbnails_count ) ); + $html .= $product_gallery_image_html; + } else { + $processor = new \WP_HTML_Tag_Processor( $product_gallery_image_html ); - if ( $processor->next_tag( 'img' ) ) { - $processor->set_attribute( - 'data-wc-on--click', - 'actions.woocommerce.thumbnails.handleClick' - ); + if ( $processor->next_tag( 'img' ) ) { + $processor->set_attribute( + 'data-wc-on--click', + 'actions.woocommerce.thumbnails.handleClick' + ); - $html .= $processor->get_updated_html(); + $html .= $processor->get_updated_html(); + } } $thumbnails_count++; diff --git a/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts b/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts index e2fb4906f02..aacd2256f2e 100644 --- a/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts +++ b/tests/e2e/tests/on-sale-badge/on-sale-badge-single-product-template.block_theme.side_effects.spec.ts @@ -59,12 +59,14 @@ const getBoundingClientRect = async ( { blockData.selectors[ isFrontend ? 'frontend' : 'editor' ] .productSaleBadge ) + .first() .evaluate( ( el ) => el.getBoundingClientRect() ), productSaleBadgeContainer: await page .locator( blockData.selectors[ isFrontend ? 'frontend' : 'editor' ] .productSaleBadgeContainer ) + .first() .evaluate( ( el ) => el.getBoundingClientRect() ), }; }; @@ -127,7 +129,7 @@ test.describe( `${ blockData.name }`, () => { const block = await frontendUtils.getBlockByName( blockData.name ); - await expect( block ).toBeVisible(); + await expect( block.first() ).toBeVisible(); } ); test( `should be not rendered when the product isn't on sale the frontend side`, async ( {