diff --git a/assets/js/atomic/blocks/index.js b/assets/js/atomic/blocks/index.js
index 5a1ff791cbd..6745263834d 100644
--- a/assets/js/atomic/blocks/index.js
+++ b/assets/js/atomic/blocks/index.js
@@ -13,3 +13,4 @@ import './product-elements/category-list';
import './product-elements/tag-list';
import './product-elements/stock-indicator';
import './product-elements/add-to-cart';
+import './product-elements/product-image-gallery';
diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json b/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json
new file mode 100644
index 00000000000..0a1247cbc34
--- /dev/null
+++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/block.json
@@ -0,0 +1,17 @@
+{
+ "name": "woocommerce/product-image-gallery",
+ "version": "1.0.0",
+ "title": "Product Image Gallery",
+ "icon": "gallery",
+ "description": "Display a product's images.",
+ "category": "woocommerce",
+ "supports": {
+ "align": true,
+ "reusable": false
+ },
+ "keywords": [ "WooCommerce" ],
+ "usesContext": [ "postId", "postType", "queryId" ],
+ "textdomain": "woo-gutenberg-products-block",
+ "apiVersion": 2,
+ "$schema": "https://schemas.wp.org/trunk/block.json"
+}
diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx b/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx
new file mode 100644
index 00000000000..df6fb8ed95e
--- /dev/null
+++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/edit.tsx
@@ -0,0 +1,64 @@
+/**
+ * External dependencies
+ */
+import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
+import { isEmptyObject } from '@woocommerce/types';
+import { useBlockProps } from '@wordpress/block-editor';
+import { BlockAttributes } from '@wordpress/blocks';
+import { Disabled } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import './editor.scss';
+
+const Placeholder = () => {
+ return (
+
+
+
+ { [ ...Array( 4 ).keys() ].map( ( index ) => {
+ return (
+
+ );
+ } ) }
+
+
+ );
+};
+
+type Context = {
+ postId: string;
+ postType: string;
+ queryId: string;
+};
+
+interface Props {
+ attributes: BlockAttributes;
+ context: Context;
+}
+
+const Edit = ( { context }: Props ) => {
+ const blockProps = useBlockProps();
+
+ if ( isEmptyObject( context ) ) {
+ return (
+
+ );
+ }
+ // We have work on this case when we will work on the Single Product block.
+ return '';
+};
+
+export default Edit;
diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss b/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss
new file mode 100644
index 00000000000..40696afdbba
--- /dev/null
+++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/editor.scss
@@ -0,0 +1,13 @@
+.wc-block-editor-product-gallery {
+ img {
+ width: 500px;
+ height: 500px;
+ }
+ .wc-block-editor-product-gallery__other-images {
+ img {
+ width: 100px;
+ height: 100px;
+ margin: 5px;
+ }
+ }
+}
diff --git a/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts b/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts
new file mode 100644
index 00000000000..ca81e18eee9
--- /dev/null
+++ b/assets/js/atomic/blocks/product-elements/product-image-gallery/index.ts
@@ -0,0 +1,26 @@
+/**
+ * External dependencies
+ */
+import { gallery as icon } from '@wordpress/icons';
+import { registerBlockType, unregisterBlockType } from '@wordpress/blocks';
+import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils';
+
+/**
+ * Internal dependencies
+ */
+import edit from './edit';
+import metadata from './block.json';
+
+registerBlockSingleProductTemplate( {
+ registerBlockFn: () => {
+ // @ts-expect-error: `registerBlockType` is a function that is typed in WordPress core.
+ registerBlockType( metadata, {
+ icon,
+ edit,
+ } );
+ },
+ unregisterBlockFn: () => {
+ unregisterBlockType( metadata.name );
+ },
+ blockName: metadata.name,
+} );
diff --git a/assets/js/atomic/utils/index.js b/assets/js/atomic/utils/index.js
index f10a14b6858..06280bb433f 100644
--- a/assets/js/atomic/utils/index.js
+++ b/assets/js/atomic/utils/index.js
@@ -2,3 +2,4 @@ export * from './get-block-map';
export * from './create-blocks-from-template';
export * from './render-parent-block';
export * from './render-standalone-blocks';
+export * from './register-block-single-product-template';
diff --git a/assets/js/atomic/utils/register-block-single-product-template.ts b/assets/js/atomic/utils/register-block-single-product-template.ts
new file mode 100644
index 00000000000..675e335b06d
--- /dev/null
+++ b/assets/js/atomic/utils/register-block-single-product-template.ts
@@ -0,0 +1,46 @@
+/**
+ * External dependencies
+ */
+import { getBlockType } from '@wordpress/blocks';
+import { subscribe, select } from '@wordpress/data';
+
+export const registerBlockSingleProductTemplate = ( {
+ registerBlockFn,
+ unregisterBlockFn,
+ blockName,
+}: {
+ registerBlockFn: () => void;
+ unregisterBlockFn: () => void;
+ blockName: string;
+} ) => {
+ let currentTemplateId: string | undefined;
+
+ subscribe( () => {
+ const previousTemplateId = currentTemplateId;
+ const store = select( 'core/edit-site' );
+ currentTemplateId = store?.getEditedPostId() as string | undefined;
+
+ if ( previousTemplateId === currentTemplateId ) {
+ return;
+ }
+
+ const parsedTemplate = currentTemplateId?.split( '//' )[ 1 ];
+
+ if ( parsedTemplate === null || parsedTemplate === undefined ) {
+ return;
+ }
+
+ const block = getBlockType( blockName );
+
+ if (
+ block === undefined &&
+ parsedTemplate.includes( 'single-product' )
+ ) {
+ registerBlockFn();
+ }
+
+ if ( block !== undefined ) {
+ unregisterBlockFn();
+ }
+ }, 'core/edit-site' );
+};
diff --git a/assets/js/types/type-guards/object.ts b/assets/js/types/type-guards/object.ts
index c90646df629..889ab7fc26a 100644
--- a/assets/js/types/type-guards/object.ts
+++ b/assets/js/types/type-guards/object.ts
@@ -21,3 +21,9 @@ export function objectHasProp< P extends PropertyKey >(
// The `in` operator throws a `TypeError` for non-object values.
return isObject( target ) && property in target;
}
+
+export const isEmptyObject = < T extends { [ key: string ]: unknown } >(
+ object: T
+) => {
+ return Object.keys( object ).length === 0;
+};
diff --git a/assets/js/types/type-guards/test/object.ts b/assets/js/types/type-guards/test/object.ts
new file mode 100644
index 00000000000..5ce4e73846c
--- /dev/null
+++ b/assets/js/types/type-guards/test/object.ts
@@ -0,0 +1,25 @@
+/**
+ * External dependencies
+ */
+import { isEmptyObject, isObject } from '@woocommerce/types';
+
+describe( 'Object type-guards', () => {
+ describe( 'Testing isObject()', () => {
+ it( 'Correctly identifies an object', () => {
+ expect( isObject( {} ) ).toBe( true );
+ expect( isObject( { test: 'object' } ) ).toBe( true );
+ } );
+ it( 'Correctly rejects object-like things', () => {
+ expect( isObject( [] ) ).toBe( false );
+ expect( isObject( null ) ).toBe( false );
+ } );
+ } );
+ describe( 'Testing isEmptyObject()', () => {
+ it( 'Correctly identifies an empty object', () => {
+ expect( isEmptyObject( {} ) ).toBe( true );
+ } );
+ it( 'Correctly identifies an not empty object', () => {
+ expect( isEmptyObject( { name: 'Woo' } ) ).toBe( false );
+ } );
+ } );
+} );
diff --git a/images/block-placeholders/product-image-gallery.svg b/images/block-placeholders/product-image-gallery.svg
new file mode 100644
index 00000000000..a9a75f82cf4
--- /dev/null
+++ b/images/block-placeholders/product-image-gallery.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 96d1f2aea73..06fdcaaf453 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -82,7 +82,7 @@
"@types/wordpress__blocks": "11.0.7",
"@types/wordpress__components": "^23.0.0",
"@types/wordpress__core-data": "^2.4.5",
- "@types/wordpress__data": "^6.0.1",
+ "@types/wordpress__data": "^6.0.2",
"@types/wordpress__data-controls": "2.2.0",
"@types/wordpress__editor": "^11.0.0",
"@types/wordpress__notices": "^3.5.0",
@@ -11680,9 +11680,10 @@
"license": "MIT"
},
"node_modules/@types/wordpress__data": {
- "version": "6.0.1",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-6.0.2.tgz",
+ "integrity": "sha512-Pu67knXXoTWgCpxTKwePNZz/iKkYe8AQbkkSD/Ba1mw8t4zgEM+jJs5IV5N5ij/awwjs4Subj8mkvS3jMTDwyw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/react": "*",
"redux": "^4.1.0"
@@ -58730,7 +58731,9 @@
"dev": true
},
"@types/wordpress__data": {
- "version": "6.0.1",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-6.0.2.tgz",
+ "integrity": "sha512-Pu67knXXoTWgCpxTKwePNZz/iKkYe8AQbkkSD/Ba1mw8t4zgEM+jJs5IV5N5ij/awwjs4Subj8mkvS3jMTDwyw==",
"dev": true,
"requires": {
"@types/react": "*",
diff --git a/package.json b/package.json
index 6b24618f34c..9474f765df1 100644
--- a/package.json
+++ b/package.json
@@ -126,7 +126,7 @@
"@types/wordpress__blocks": "11.0.7",
"@types/wordpress__components": "^23.0.0",
"@types/wordpress__core-data": "^2.4.5",
- "@types/wordpress__data": "^6.0.1",
+ "@types/wordpress__data": "^6.0.2",
"@types/wordpress__data-controls": "2.2.0",
"@types/wordpress__editor": "^11.0.0",
"@types/wordpress__notices": "^3.5.0",
diff --git a/src/BlockTypes/ProductImageGallery.php b/src/BlockTypes/ProductImageGallery.php
new file mode 100644
index 00000000000..43e84ebbe32
--- /dev/null
+++ b/src/BlockTypes/ProductImageGallery.php
@@ -0,0 +1,60 @@
+context['postId'];
+ global $product;
+ $product = wc_get_product( $post_id );
+
+ if ( class_exists( 'WC_Frontend_Scripts' ) ) {
+ $frontend_scripts = new \WC_Frontend_Scripts();
+ $frontend_scripts::load_scripts();
+ }
+
+ $classname = $attributes['className'] ?? '';
+
+ ob_start();
+ woocommerce_show_product_images();
+ $product_image_gallery_html = ob_get_clean();
+
+ return sprintf(
+ '%2$s
',
+ esc_attr( $classname ),
+ $product_image_gallery_html
+ );
+
+ }
+}
diff --git a/src/BlockTypesController.php b/src/BlockTypesController.php
index ad4ea3f4ee6..e9355759ec0 100644
--- a/src/BlockTypesController.php
+++ b/src/BlockTypesController.php
@@ -187,6 +187,7 @@ protected function get_block_types() {
'ProductCategory',
'ProductCategoryList',
'ProductImage',
+ 'ProductImageGallery',
'ProductNew',
'ProductOnSale',
'ProductPrice',
diff --git a/tsconfig.json b/tsconfig.json
index 2d88e8033f9..2a1a5b6c03b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,6 +4,7 @@
"./assets/js/**/*",
"./packages/checkout/**/*",
"./assets/js/blocks/**/block.json",
+ "./assets/js/atomic/blocks/**/block.json",
"./assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/**/block.json",
"./storybook/**/*",
"./tests/js/setup-after-env.ts"