Commit 1943a72118 for woocommerce

commit 1943a72118f477c23b436a0f9b9d297232524e80
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date:   Thu Apr 17 08:51:23 2025 +0200

    Product Gallery: improve UI for Next/Prev buttons (#57135)

    * Enable supports in arrows

    * Provide basic supports

    * Simplify the arrows structure

    * Align the frontend with editor

    * Add some basic styling

    * Fix lint

    * Fix lint

    * Update icons and align them correctly

    * Improve classnames and styling

    * Apply changes on the frontend

    * Add supports styles in editor

    * Support colors in editor

    * Simplify the arrows structure even more

    * Change sprintf to ob_clean

    * Fix styles

    * Add support for shadow

    * Add shadow support in StyleAttributesUtils

    * Fix lint

    * Change the disabled arrow opacity from 20% to 35%

    * Add changelog

    * Update selectors in tests

    * Remove unnecessary z-index

    * Fix the editor util for getting next/prev button

    * Replace str_contains with strpos for PHP 7 support

diff --git a/plugins/woocommerce/changelog/wooplug-3792-product-gallery-improve-ui-for-nextprev-buttons b/plugins/woocommerce/changelog/wooplug-3792-product-gallery-improve-ui-for-nextprev-buttons
new file mode 100644
index 0000000000..7329704ffc
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-3792-product-gallery-improve-ui-for-nextprev-buttons
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Product Gallery: revamp Next/Prev arrows UI
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/edit.tsx
index 35e4bccad3..ed4adc2c77 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/edit.tsx
@@ -52,7 +52,14 @@ const TEMPLATE: InnerBlockTemplate[] = [
 							},
 						},
 					],
-					[ 'woocommerce/product-gallery-large-image-next-previous' ],
+					[
+						'woocommerce/product-gallery-large-image-next-previous',
+						{
+							style: {
+								border: { radius: '100%' },
+							},
+						},
+					],
 				],
 			],
 		],
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/block.json b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/block.json
index 6fccb38194..47e520f8f9 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/block.json
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/block.json
@@ -9,7 +9,15 @@
 	"usesContext": [ "postId" ],
 	"textdomain": "woocommerce",
 	"supports": {
-		"interactivity": true
+		"interactivity": true,
+		"color": {
+			"background": true,
+			"text": true
+		},
+		"shadow": true,
+		"__experimentalBorder": {
+			"radius": true
+		}
 	},
 	"ancestor": [ "woocommerce/product-gallery-large-image" ]
 }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/edit.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/edit.tsx
index 2b17a4399e..418f3138a2 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/edit.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/edit.tsx
@@ -2,31 +2,25 @@
  * External dependencies
  */
 import { useBlockProps } from '@wordpress/block-editor';
-import clsx from 'clsx';

 /**
  * Internal dependencies
  */
-import { PrevButton, NextButton } from './icons';
+import { PrevIcon, NextIcon } from './icons';

 export const Edit = (): JSX.Element => {
 	const blockProps = useBlockProps( {
-		className: clsx(
-			'wc-block-editor-product-gallery-large-image-next-previous',
-			'wc-block-product-gallery-large-image-next-previous'
-		),
+		className: 'wc-block-product-gallery-large-image-next-previous__button',
 	} );

 	return (
-		<div { ...blockProps }>
-			<div
-				className={ clsx(
-					'wc-block-product-gallery-large-image-next-previous-container'
-				) }
-			>
-				<PrevButton />
-				<NextButton />
-			</div>
+		<div className="wc-block-product-gallery-large-image-next-previous">
+			<button { ...blockProps } disabled>
+				<PrevIcon className="wc-block-product-gallery-large-image-next-previous__icon wc-block-product-gallery-large-image-next-previous__icon--left" />
+			</button>
+			<button { ...blockProps }>
+				<NextIcon className="wc-block-product-gallery-large-image-next-previous__icon wc-block-product-gallery-large-image-next-previous__icon--right" />
+			</button>
 		</div>
 	);
 };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/icons.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/icons.tsx
index 3892e7adca..38ea96860e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/icons.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/inner-blocks/product-gallery-next-previous-buttons/icons.tsx
@@ -1,8 +1,3 @@
-/**
- * External dependencies
- */
-import { SVG } from '@wordpress/primitives';
-
 export const Icon = () => (
 	<svg
 		width="18"
@@ -20,104 +15,36 @@ export const Icon = () => (
 	</svg>
 );

-export const NextButton = () => (
-	<SVG
+export const PrevIcon = ( { className }: { className: string } ) => (
+	<svg
 		xmlns="http://www.w3.org/2000/svg"
-		width="49"
-		height="48"
-		viewBox="0 0 49 48"
+		width="8"
+		height="12"
 		fill="none"
-		className={ `wc-block-product-gallery-large-image-next-previous-right` }
+		className={ className }
 	>
-		<g filter="url(#filter0_b_397_11354)">
-			<rect
-				x="0.5"
-				width="48"
-				height="48"
-				rx="5"
-				fill="black"
-				fillOpacity="0.5"
-			/>
-			<path
-				d="M21.7001 12L19.3 14L28.5 24L19.3 34L21.7001 36L32.5 24L21.7001 12Z"
-				fill="white"
-			/>
-		</g>
-		<defs>
-			<filter
-				id="filter0_b_397_11354"
-				x="-9.5"
-				y="-10"
-				width="68"
-				height="68"
-				filterUnits="userSpaceOnUse"
-				colorInterpolationFilters="sRGB"
-			>
-				<feFlood floodOpacity="0" result="BackgroundImageFix" />
-				<feGaussianBlur in="BackgroundImageFix" stdDeviation="5" />
-				<feComposite
-					in2="SourceAlpha"
-					operator="in"
-					result="effect1_backgroundBlur_397_11354"
-				/>
-				<feBlend
-					mode="normal"
-					in="SourceGraphic"
-					in2="effect1_backgroundBlur_397_11354"
-					result="shape"
-				/>
-			</filter>
-		</defs>
-	</SVG>
+		<path
+			fill="currentColor"
+			fillRule="evenodd"
+			d="M6.445 12.005.986 6 6.445-.005l1.11 1.01L3.014 6l4.54 4.995-1.109 1.01Z"
+			clipRule="evenodd"
+		/>
+	</svg>
 );

-export const PrevButton = () => (
-	<SVG
+export const NextIcon = ( { className }: { className: string } ) => (
+	<svg
 		xmlns="http://www.w3.org/2000/svg"
-		width="49"
-		height="48"
-		viewBox="0 0 49 48"
+		width="8"
+		height="12"
 		fill="none"
-		className={ `wc-block-product-gallery-large-image-next-previous-left` }
+		className={ className }
 	>
-		<g filter="url(#filter0_b_397_11356)">
-			<rect
-				x="0.5"
-				width="48"
-				height="48"
-				rx="5"
-				fill="black"
-				fillOpacity="0.5"
-			/>
-			<path
-				d="M28.1 12L30.5 14L21.3 24L30.5 34L28.1 36L17.3 24L28.1 12Z"
-				fill="white"
-			/>
-		</g>
-		<defs>
-			<filter
-				id="filter0_b_397_11356"
-				x="-9.5"
-				y="-10"
-				width="68"
-				height="68"
-				filterUnits="userSpaceOnUse"
-				colorInterpolationFilters="sRGB"
-			>
-				<feFlood floodOpacity="0" result="BackgroundImageFix" />
-				<feGaussianBlur in="BackgroundImageFix" stdDeviation="5" />
-				<feComposite
-					in2="SourceAlpha"
-					operator="in"
-					result="effect1_backgroundBlur_397_11356"
-				/>
-				<feBlend
-					mode="normal"
-					in="SourceGraphic"
-					in2="effect1_backgroundBlur_397_11356"
-					result="shape"
-				/>
-			</filter>
-		</defs>
-	</SVG>
+		<path
+			fill="currentColor"
+			fillRule="evenodd"
+			d="M1.555-.004 7.014 6l-5.459 6.005-1.11-1.01L4.986 6 .446 1.005l1.109-1.01Z"
+			clipRule="evenodd"
+		/>
+	</svg>
 );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/style.scss
index 463e970dd0..0c7859fb0e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-gallery/style.scss
@@ -78,32 +78,40 @@ $dialog-padding: 20px;
 }

 .wc-block-product-gallery-large-image-next-previous {
-	position: absolute;
 	display: flex;
 	align-items: center;
+	justify-content: space-between;
 	width: 100%;
 	height: 100%;

-	&-container {
-		width: 100%;
-		display: flex;
-		justify-content: space-between;
+	&__icon {
+		color: inherit;
 	}

-	svg {
-		z-index: 1;
+	// Icons are centred but they are perceived off hence adjusting with padding.
+	&__icon--left {
+		padding: 2px 2px 0 0;
+	}
+
+	&__icon--right {
+		padding: 2px 0 0 2px;
 	}

-	button {
+	&__button {
 		cursor: pointer;
 		z-index: 3;
 		pointer-events: all;
-		padding: 0;
 		border: none;
-		background: none;
+		width: 40px;
+		height: 40px;
+		font-size: 12px;
+		padding: 0;
+		background: #fff;

 		&:disabled {
-			opacity: 50%;
+			.wc-block-product-gallery-large-image-next-previous__icon {
+				opacity: 35%;
+			}
 			cursor: not-allowed;
 		}
 	}
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.spec.ts
index bed023f691..5b9cd39ad3 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/inner-blocks/product-gallery-large-image-next-previous/product-gallery-large-image-next-previous.block_theme.spec.ts
@@ -17,9 +17,9 @@ const blockData = {
 		frontend: {},
 		editor: {
 			leftArrow:
-				'.wc-block-product-gallery-large-image-next-previous-left',
+				'.wc-block-product-gallery-large-image-next-previous__icon--left',
 			rightArrow:
-				'.wc-block-product-gallery-large-image-next-previous-right',
+				'.wc-block-product-gallery-large-image-next-previous__icon--right',
 		},
 	},
 	slug: 'single-product',
@@ -89,11 +89,13 @@ test.describe( `${ blockData.name }`, () => {
 			name: 'woocommerce/product-gallery',
 		} );

-		const block = await pageObject.getNextPreviousButtonsBlock( {
+		const blocks = await pageObject.getNextPreviousButtonsBlock( {
 			page: 'editor',
 		} );

-		await expect( block ).toBeVisible();
+		// There are two "instances" of the block in the editor, so we need to check both.
+		await expect( blocks.nth( 0 ) ).toBeVisible();
+		await expect( blocks.nth( 1 ) ).toBeVisible();
 	} );

 	test( 'Renders Next/Previous Button block on the frontend side', async ( {
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.spec.ts
index c29ed50c16..3a89b57078 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/product-gallery/product-gallery.block_theme.spec.ts
@@ -212,7 +212,7 @@ test.describe( `${ blockData.name }`, () => {

 			const nextButton = page
 				.locator(
-					'.wc-block-product-gallery-large-image-next-previous--button'
+					'.wc-block-product-gallery-large-image-next-previous__button'
 				)
 				.nth( 1 );
 			await nextButton.click();
@@ -227,7 +227,7 @@ test.describe( `${ blockData.name }`, () => {

 			const previousButton = page
 				.locator(
-					'.wc-block-product-gallery-large-image-next-previous--button'
+					'.wc-block-product-gallery-large-image-next-previous__button'
 				)
 				.first();
 			await previousButton.click();
@@ -275,7 +275,7 @@ test.describe( `${ blockData.name }`, () => {

 			const nextButton = page
 				.locator(
-					'.wc-block-product-gallery-large-image-next-previous--button'
+					'.wc-block-product-gallery-large-image-next-previous__button'
 				)
 				.nth( 1 );
 			await nextButton.click();
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryLargeImageNextPrevious.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryLargeImageNextPrevious.php
index f5cf4b30f5..ba80644b36 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryLargeImageNextPrevious.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryLargeImageNextPrevious.php
@@ -3,7 +3,7 @@
 namespace Automattic\WooCommerce\Blocks\BlockTypes;

 use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
-
+use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
 /**
  * ProductGalleryLargeImage class.
  */
@@ -59,96 +59,62 @@ class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
 			return '';
 		}

-		$prev_button = $this->get_button( 'previous' );
-		$p           = new \WP_HTML_Tag_Processor( $prev_button );
-
-		if ( $p->next_tag() ) {
-			$p->set_attribute(
-				'data-wp-on--click',
-				'actions.selectPreviousImage'
-			);
-			$p->set_attribute(
-				'aria-label',
-				__( 'Previous image', 'woocommerce' )
-			);
-			$prev_button = $p->get_updated_html();
-		}
-
-		$next_button = $this->get_button( 'next' );
-		$p           = new \WP_HTML_Tag_Processor( $next_button );
-
-		if ( $p->next_tag() ) {
-			$p->set_attribute(
-				'data-wp-on--click',
-				'actions.selectNextImage'
-			);
-			$p->set_attribute(
-				'aria-label',
-				__( 'Next image', 'woocommerce' )
-			);
-			$next_button = $p->get_updated_html();
-		}
-
-		return strtr(
-			'<div
-				class="wc-block-product-gallery-large-image-next-previous wp-block-woocommerce-product-gallery-large-image-next-previous"
-				data-wp-interactive=\'{data_wp_interactive}\'
+		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
+
+		ob_start();
+		?>
+		<div
+			class="wc-block-product-gallery-large-image-next-previous"
+			data-wp-interactive="woocommerce/product-gallery"
+		>
+			<button
+				class="wc-block-product-gallery-large-image-next-previous__button <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+				style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+				data-wp-on--click="actions.selectPreviousImage"
+				data-wp-bind--disabled="context.disableLeft"
+				aria-label="Previous image"
 			>
-				<div class="wc-block-product-gallery-large-image-next-previous-container">
-					{prev_button}
-					{next_button}
-				</div>
-		</div>',
-			array(
-				'{prev_button}'         => $prev_button,
-				'{next_button}'         => $next_button,
-				'{data_wp_interactive}' => 'woocommerce/product-gallery',
-			)
-		);
-	}
-
-	/**
-	 * Generates the HTML for a next or previous button for the product gallery large image.
-	 *
-	 * @param string $button_type The type of button to generate. Either 'previous' or 'next'.
-	 * @return string The HTML for the generated button.
-	 */
-	protected function get_button( $button_type ) {
-		$previous_button_icon_path = 'M28.1 12L30.5 14L21.3 24L30.5 34L28.1 36L17.3 24L28.1 12Z';
-		$next_button_icon_path     = 'M21.7001 12L19.3 14L28.5 24L19.3 34L21.7001 36L32.5 24L21.7001 12Z';
-		$icon_path                 = $previous_button_icon_path;
-		$button_side_class         = 'left';
-		$button_disabled_directive = 'context.disableLeft';
-
-		if ( 'next' === $button_type ) {
-			$icon_path                 = $next_button_icon_path;
-			$button_side_class         = 'right';
-			$button_disabled_directive = 'context.disableRight';
-		}
-
-		return sprintf(
-			'<button
-				data-wp-bind--disabled="%1$s"
-				class="wc-block-product-gallery-large-image-next-previous--button wc-block-product-gallery-large-image-next-previous-%2$s"
+				<svg
+					class="wc-block-product-gallery-large-image-next-previous__icon wc-block-product-gallery-large-image-next-previous__icon--left"
+					xmlns="http://www.w3.org/2000/svg"
+					width="8"
+					height="12"
+					fill="none"
+				>
+					<path
+						fill="currentColor"
+						fillRule="evenodd"
+						d="M6.445 12.005.986 6 6.445-.005l1.11 1.01L3.014 6l4.54 4.995-1.109 1.01Z"
+						clipRule="evenodd"
+					/>
+				</svg>
+			</button>
+			<button
+				class="wc-block-product-gallery-large-image-next-previous__button <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+				style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+				data-wp-on--click="actions.selectNextImage"
+				data-wp-bind--disabled="context.disableRight"
+				aria-label="Next image"
 			>
-				<svg xmlns="http://www.w3.org/2000/svg" width="49" height="48" viewBox="0 0 49 48" fill="none">
-					<g filter="url(#filter0_b_397_11354)">
-						<rect x="0.5" width="48" height="48" rx="5" fill="black" fill-opacity="0.5"/>
-						<path d="%3$s" fill="white"/>
-					</g>
-					<defs>
-						<filter id="filter0_b_397_11354" x="-9.5" y="-10" width="68" height="68" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-							<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-							<feGaussianBlur in="BackgroundImageFix" stdDeviation="5"/>
-							<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_397_11354"/>
-							<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_397_11354" result="shape"/>
-						</filter>
-					</defs>
+				<svg
+					class="wc-block-product-gallery-large-image-next-previous__icon wc-block-product-gallery-large-image-next-previous__icon--right"
+					xmlns="http://www.w3.org/2000/svg"
+					width="8"
+					height="12"
+					fill="none"
+				>
+					<path
+						fill="currentColor"
+						fillRule="evenodd"
+						d="M1.555-.004 7.014 6l-5.459 6.005-1.11-1.01L4.986 6 .446 1.005l1.109-1.01Z"
+						clipRule="evenodd"
+					/>
 				</svg>
-			</button>',
-			$button_disabled_directive,
-			$button_side_class,
-			$icon_path
-		);
+			</button>
+		</div>
+		<?php
+		$template = ob_get_clean();
+
+		return $template;
 	}
 }
diff --git a/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php b/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php
index ea106afa5b..8314c0c964 100644
--- a/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php
+++ b/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php
@@ -24,7 +24,7 @@ class StyleAttributesUtils {
 	 * @return (string)
 	 */
 	public static function get_color_value( $color_value ) {
-		if ( is_string( $color_value ) && str_contains( $color_value, 'var:preset|color|' ) ) {
+		if ( is_string( $color_value ) && strpos( $color_value, 'var:preset|color|' ) !== false ) {
 			$color_value = str_replace( 'var:preset|color|', '', $color_value );
 			return sprintf( 'var(--wp--preset--color--%s)', $color_value );
 		}
@@ -43,6 +43,22 @@ class StyleAttributesUtils {
 		return "var(--wp--preset--color--$preset_name)";
 	}

+	/**
+	 * Get CSS value for shadow preset. Returns the same value if it's not a preset.
+	 *
+	 * @param string $shadow_name Shadow name.
+	 *
+	 * @return string CSS value for shadow preset.
+	 */
+	public static function get_shadow_value( $shadow_name ) {
+		if ( is_string( $shadow_name ) && strpos( $shadow_name, 'var:preset|shadow|' ) !== false ) {
+			$shadow_name = str_replace( 'var:preset|shadow|', '', $shadow_name );
+			return "var(--wp--preset--shadow--{$shadow_name})";
+		}
+
+		return $shadow_name;
+	}
+
 	/**
 	 * If spacing value is in preset format, convert it to a CSS var. Else return same value
 	 * For example:
@@ -55,7 +71,7 @@ class StyleAttributesUtils {
 	 */
 	public static function get_spacing_value( $spacing_value ) {
 		// Used following code as reference: https://github.com/WordPress/gutenberg/blob/cff6d70d6ff5a26e212958623dc3130569f95685/lib/block-supports/layout.php/#L219-L225.
-		if ( is_string( $spacing_value ) && str_contains( $spacing_value, 'var:preset|spacing|' ) ) {
+		if ( is_string( $spacing_value ) && strpos( $spacing_value, 'var:preset|spacing|' ) !== false ) {
 			$spacing_value = str_replace( 'var:preset|spacing|', '', $spacing_value );
 			return sprintf( 'var(--wp--preset--spacing--%s)', $spacing_value );
 		}
@@ -583,6 +599,25 @@ class StyleAttributesUtils {
 		);
 	}

+	/**
+	 * Get class and style for shadow from attributes.
+	 *
+	 * @param array $attributes Block attributes.
+	 * @return array
+	 */
+	public static function get_shadow_class_and_style( $attributes ) {
+		$shadow = $attributes['style']['shadow'] ?? null;
+
+		if ( ! $shadow ) {
+			return self::EMPTY_STYLE;
+		}
+
+		return array(
+			'class' => null,
+			'style' => sprintf( 'box-shadow: %s;', self::get_shadow_value( $shadow ) ),
+		);
+	}
+
 	/**
 	 * Get space-separated style rules from block attributes.
 	 *
@@ -734,6 +769,7 @@ class StyleAttributesUtils {
 			'line_height'      => self::get_line_height_class_and_style( $attributes ),
 			'margin'           => self::get_margin_class_and_style( $attributes ),
 			'padding'          => self::get_padding_class_and_style( $attributes ),
+			'shadow'           => self::get_shadow_class_and_style( $attributes ),
 			'text_align'       => self::get_text_align_class_and_style( $attributes ),
 			'text_color'       => self::get_text_color_class_and_style( $attributes ),
 			'text_decoration'  => self::get_text_decoration_class_and_style( $attributes ),