diff --git a/assets/js/blocks/product-gallery/edit.tsx b/assets/js/blocks/product-gallery/edit.tsx
index 9264f5b07ec..6aeeb77d1f0 100644
--- a/assets/js/blocks/product-gallery/edit.tsx
+++ b/assets/js/blocks/product-gallery/edit.tsx
@@ -25,7 +25,13 @@ import type { ProductGalleryAttributes } from './types';
const TEMPLATE: InnerBlockTemplate[] = [
[
'core/group',
- { layout: { type: 'flex', flexWrap: 'nowrap' } },
+ {
+ layout: {
+ type: 'flex',
+ flexWrap: 'nowrap',
+ verticalAlignment: 'top',
+ },
+ },
[
[
'woocommerce/product-gallery-thumbnails',
@@ -38,6 +44,10 @@ const TEMPLATE: InnerBlockTemplate[] = [
type: 'flex',
orientation: 'vertical',
justifyContent: 'center',
+ verticalAlignment: 'top',
+ },
+ style: {
+ layout: { selfStretch: 'fixed', flexSize: '100%' },
},
...getInnerBlocksLockAttributes( 'lock' ),
},
diff --git a/assets/js/blocks/product-gallery/editor.scss b/assets/js/blocks/product-gallery/editor.scss
deleted file mode 100644
index 13116d7fc2d..00000000000
--- a/assets/js/blocks/product-gallery/editor.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.wc-block-product-gallery {
- .block-editor-inner-blocks {
- width: fit-content;
- }
-}
diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss
index 65b0e5b7bf6..1c3198620cc 100644
--- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss
+++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-large-image/editor.scss
@@ -1,4 +1,6 @@
.wc-block-editor-product-gallery-large-image {
+ width: 100%;
+
img {
max-width: 100%;
margin: 0 auto;
diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx
index cdf4e133cfb..640497103c4 100644
--- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx
+++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/block-settings/index.tsx
@@ -51,7 +51,7 @@ export const ProductGalleryThumbnailsBlockSettings = ( {
context,
}: ProductGalleryThumbnailsSettingsProps ) => {
const maxNumberOfThumbnails = 8;
- const minNumberOfThumbnails = 2;
+ const minNumberOfThumbnails = 3;
const { productGalleryClientId } = context;
// @ts-expect-error @wordpress/block-editor/store types not provided
const { updateBlockAttributes } = useDispatch( blockEditorStore );
@@ -110,7 +110,7 @@ export const ProductGalleryThumbnailsBlockSettings = ( {
} )
}
help={ __(
- 'Choose how many thumbnails (2-8) will display. If more images exist, a “View all” button will display.',
+ 'Choose how many thumbnails (3-8) will display. If more images exist, a “View all” button will display.',
'woo-gutenberg-products-block'
) }
max={ maxNumberOfThumbnails }
diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx
index d62da00ad1a..b925d005f05 100644
--- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx
+++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/edit.tsx
@@ -25,26 +25,29 @@ interface EditProps
export const Edit = ( { attributes, setAttributes, context }: EditProps ) => {
const blockProps = useBlockProps( {
- className: 'wc-block-product-gallery-thumbnails',
+ className: classNames(
+ 'wc-block-product-gallery-thumbnails',
+ `wc-block-product-gallery-thumbnails--number-of-thumbnails-${ context.thumbnailsNumberOfThumbnails }`,
+ `wc-block-product-gallery-thumbnails--position-${ context.thumbnailsPosition }`
+ ),
} );
const Placeholder = () => {
return context.thumbnailsPosition !== ThumbnailsPosition.OFF ? (
-
+
{ [
...Array( context.thumbnailsNumberOfThumbnails ).keys(),
].map( ( index ) => {
return (
-
+ >
+
+
);
} ) }
diff --git a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss
index 006c477d7f1..a789d44745b 100644
--- a/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss
+++ b/assets/js/blocks/product-gallery/inner-blocks/product-gallery-thumbnails/editor.scss
@@ -1,17 +1,30 @@
-.wc-block-product-gallery-thumbnails {
- width: fit-content;
- .wc-block-editor-product-gallery-thumbnails {
- display: flex;
- flex-flow: column nowrap;
+$thumbnails: ".wc-block-editor-product-gallery-thumbnails";
+$thumbnails-gap: 15px;
- &.wc-block-editor-product-gallery-thumbnails--bottom {
- flex-flow: row nowrap;
- }
+#{$thumbnails} {
+ display: flex;
- img {
- width: 100px;
- height: 100px;
- margin: 5px;
- }
+ .wc-block-product-gallery-thumbnails--position-bottom & {
+ flex-direction: row;
+ gap: 0 15px;
+ }
+
+ .wc-block-product-gallery-thumbnails:not(.wc-block-product-gallery-thumbnails--position-bottom) & {
+ flex-direction: column;
+ gap: 15px 0;
+ }
+}
+
+@for $i from 3 through 8 {
+ // Calculate the total width occupied by the gaps between thumbnails.
+ $gap-width: $thumbnails-gap * ($i - 1);
+
+ // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail.
+ $border-width: ($i * 1px * 2);
+
+ $additional-space: $i * 1px;
+
+ .wc-block-product-gallery-thumbnails--number-of-thumbnails-#{$i}:not(.wc-block-product-gallery-thumbnails--position-bottom) {
+ flex-basis: calc((100% - #{$gap-width} - #{$border-width} - #{$additional-space}) / #{$i});
}
}
diff --git a/assets/js/blocks/product-gallery/style.scss b/assets/js/blocks/product-gallery/style.scss
index f73e0d735cd..191bc88bf4b 100644
--- a/assets/js/blocks/product-gallery/style.scss
+++ b/assets/js/blocks/product-gallery/style.scss
@@ -10,6 +10,8 @@ $gallery-next-previous-inside-image: "#{$gallery}:not([data-next-previous-button
$outside-image-offset: 30px;
$outside-image-max-width: calc(100% - (2 * $outside-image-offset));
+$thumbnails-gap: 15px;
+$default-number-of-thumbnails: 3;
// Product Gallery
#{$gallery} {
@@ -55,6 +57,7 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset));
height: fit-content;
position: relative;
overflow: hidden;
+ flex-grow: 1;
// Restrict the width of the Large Image when the Next/Previous buttons are outside the image.
#{$gallery-next-previous-outside-image} & .wc-block-product-gallery-large-image__image-element {
@@ -64,10 +67,13 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset));
}
.wc-block-product-gallery-large-image__wrapper {
+ aspect-ratio: 1 / 1;
flex-shrink: 0;
max-width: 100%;
overflow: hidden;
width: 100%;
+ display: flex;
+ align-items: center;
}
.wc-block-product-gallery-large-image__container {
@@ -93,7 +99,8 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset));
margin: 0 auto;
z-index: 1;
transition: all 0.1s linear;
- width: auto;
+ aspect-ratio: 1 / 1;
+ object-fit: contain;
// Keep the order in this way. The hoverZoom class should override the full-screen-on-click class when both are applied.
&.wc-block-woocommerce-product-gallery-large-image__image--full-screen-on-click {
@@ -234,20 +241,82 @@ $outside-image-max-width: calc(100% - (2 * $outside-image-offset));
// Thumbnails
#{$thumbnails} {
+ display: flex;
+
img {
cursor: pointer;
+ height: auto;
+ width: auto;
+ max-width: 100%;
}
- .is-vertical & {
- display: flex;
+ #{$gallery}[data-thumbnails-position='bottom'] & {
flex-direction: row;
+ gap: 0 15px;
+ }
+
+ #{$gallery}:not([data-thumbnails-position='bottom']) & {
+ flex-direction: column;
+ gap: 15px 0;
+
+ // Calculate the total width occupied by the gaps between thumbnails.
+ $gap-width: $thumbnails-gap * ($default-number-of-thumbnails - 1);
+
+ // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail.
+ $border-width: #{$default-number-of-thumbnails * 1px * 2};
+
+ // Calculate the width of each thumbnail by accounting for the gap, border, and additional space.
+ flex-basis: calc((100% - #{$gap-width} - #{$border-width} - 4px) / #{$default-number-of-thumbnails});
+ }
+
+ @for $i from 3 through 8 {
+ #{$gallery}[data-thumbnails-number-of-thumbnails='#{$i}']:not([data-thumbnails-position='bottom']) & {
+ // Calculate the total width occupied by the gaps between thumbnails.
+ $gap-width: $thumbnails-gap * ($i - 1);
+
+ // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail.
+ $border-width: $i * 1px * 2;
+
+ flex-basis: calc((100% - #{$gap-width} - #{$border-width}) / $i);
+ }
}
.wc-block-product-gallery-thumbnails__thumbnail {
- width: 100px;
- height: 100px;
- margin: 5px;
+ border: 1px solid rgba($color: #000, $alpha: 0.1);
+ height: auto;
+ width: auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ aspect-ratio: 1 / 1;
position: relative;
+ flex-basis: 0;
+ flex-grow: 1;
+
+ img {
+ aspect-ratio: 1 / 1;
+ object-fit: contain;
+ }
+
+ &::before {
+ content: "";
+ display: block;
+ padding-top: 100%;
+ }
+
+ @for $i from 3 through 8 {
+ #{$gallery}[data-thumbnails-number-of-thumbnails='#{$i}'][data-thumbnails-position="bottom"] & {
+ // Calculate the total width occupied by the gaps between thumbnails.
+ $gap-width: $thumbnails-gap * ($i - 1);
+
+ // Calculate the border width, which is multiplied by 2 to account for both sides of each thumbnail.
+ $border-width: $i * 1px * 2;
+
+ $thumbnail-width: calc((100% - #{$gap-width} - #{$border-width}) / $i);
+
+ flex: 0 0 $thumbnail-width;
+ }
+ }
}
diff --git a/src/BlockTypes/ProductGalleryThumbnails.php b/src/BlockTypes/ProductGalleryThumbnails.php
index 3704b0f93a4..13d9f4a0e87 100644
--- a/src/BlockTypes/ProductGalleryThumbnails.php
+++ b/src/BlockTypes/ProductGalleryThumbnails.php
@@ -133,8 +133,8 @@ protected function render( $attributes, $content, $block ) {
if ( $product ) {
$post_thumbnail_id = $product->get_image_id();
- $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array(), 'wc-block-product-gallery-thumbnails__thumbnail' );
- if ( $product_gallery_images && $post_thumbnail_id ) {
+ $product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'full', array(), 'wc-block-product-gallery-thumbnails__thumbnail' );
+ if ( $product_gallery_images && count( $product_gallery_images ) > 1 && $post_thumbnail_id ) {
$html = '';
$number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3;
$mode = $block->context['mode'] ?? '';
diff --git a/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts b/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts
index 1b93c340685..f749265e177 100644
--- a/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts
+++ b/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-thumbnails/product-gallery-thumbnails.block_theme.side_effects.spec.ts
@@ -52,6 +52,8 @@ test.describe( `${ blockData.name }`, () => {
page,
editor,
pageObject,
+ editorUtils,
+ frontendUtils,
} ) => {
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
@@ -60,22 +62,38 @@ test.describe( `${ blockData.name }`, () => {
const thumbnailsBlock = await pageObject.getThumbnailsBlock( {
page: 'editor',
} );
- const largeImageBlock = await pageObject.getMainImageBlock( {
- page: 'editor',
- } );
-
- const thumbnailsBlockBoundingRect = await thumbnailsBlock.boundingBox();
- const largeImageBlockBoundingRect = await largeImageBlock.boundingBox();
await expect( thumbnailsBlock ).toBeVisible();
- // Check the default position: on the left of the large image
- await expect( thumbnailsBlockBoundingRect?.y ).toBeGreaterThan(
- largeImageBlockBoundingRect?.y as number
+
+ // We should refactor this.
+ // eslint-disable-next-line playwright/no-wait-for-timeout
+ await page.waitForTimeout( 500 );
+
+ // Test the default (left) position of thumbnails by cross-checking:
+ // - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
+ // - The Thumbnails block has a lower index than the Large Image block.
+
+ const groupBlock = (
+ await editorUtils.getBlockByTypeWithParent(
+ 'core/group',
+ 'woocommerce/product-gallery'
+ )
+ ).first();
+
+ const groupBlockClassAttribute = await groupBlock.getAttribute(
+ 'class'
);
- await expect( thumbnailsBlockBoundingRect?.x ).toBeLessThan(
- largeImageBlockBoundingRect?.x as number
+ expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
+ expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
+
+ const isThumbnailsBlockEarlier = await editorUtils.isBlockEarlierThan(
+ groupBlock,
+ 'woocommerce/product-gallery-thumbnails',
+ 'core/group'
);
+ expect( isThumbnailsBlockEarlier ).toBe( true );
+
await Promise.all( [
editor.saveSiteEditorEntities(),
page.waitForResponse( ( response ) =>
@@ -87,27 +105,27 @@ test.describe( `${ blockData.name }`, () => {
waitUntil: 'commit',
} );
- const thumbnailsBlockFrontend = await pageObject.getThumbnailsBlock( {
- page: 'frontend',
- } );
-
- const largeImageBlockFrontend = await pageObject.getMainImageBlock( {
- page: 'frontend',
- } );
+ const groupBlockFrontend = (
+ await frontendUtils.getBlockByClassWithParent(
+ 'wp-block-group',
+ 'woocommerce/product-gallery'
+ )
+ ).first();
+
+ const groupBlockFrontendClassAttribute =
+ await groupBlockFrontend.getAttribute( 'class' );
+ expect( groupBlockFrontendClassAttribute ).toContain(
+ 'is-layout-flex'
+ );
+ expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
- const thumbnailsBlockFrontendBoundingRect =
- await thumbnailsBlockFrontend.boundingBox();
- const largeImageBlockFrontendBoundingRect =
- await largeImageBlockFrontend.boundingBox();
+ const isThumbnailsFrontendBlockEarlier =
+ await frontendUtils.isBlockEarlierThanGroupBlock(
+ groupBlockFrontend,
+ 'woocommerce/product-gallery-thumbnails'
+ );
- await expect( thumbnailsBlockFrontend ).toBeVisible();
- // Check the default position: on the left of the large image
- await expect( thumbnailsBlockFrontendBoundingRect?.y ).toBeGreaterThan(
- largeImageBlockFrontendBoundingRect?.y as number
- );
- await expect( thumbnailsBlockFrontendBoundingRect?.x ).toBeLessThan(
- largeImageBlockFrontendBoundingRect?.x as number
- );
+ expect( isThumbnailsFrontendBlockEarlier ).toBe( true );
} );
test.describe( `${ blockData.name } Settings`, () => {
diff --git a/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts b/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts
index aff6c83330c..e4a051a4276 100644
--- a/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts
+++ b/tests/e2e/tests/product-gallery/product-gallery.block_theme.side_effects.spec.ts
@@ -327,6 +327,8 @@ test.describe( `${ blockData.name }`, () => {
const width = image?.width;
// Allow 1 pixel of difference.
- expect( width === height + 1 || width === height - 1 ).toBeTruthy();
+ expect(
+ width === height + 1 || width === height - 1 || width === height
+ ).toBeTruthy();
} );
} );