Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Add size settings to the Product Image block (#10034)
Browse files Browse the repository at this point in the history
* Add height & width setting to the image sidebar settings

* Extract settings and add scale options

* Add width settings

* Apply settings on the frontend

* Style placeholder image

* Replace post featured image with product image

* Allow the width to be wider than container

* Fix image on top of other elements
  • Loading branch information
albarin authored Jul 3, 2023
1 parent 6e9b47c commit 333c7f2
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 11 deletions.
10 changes: 10 additions & 0 deletions assets/js/atomic/blocks/product-elements/image/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export const blockAttributes: BlockAttributes = {
type: 'boolean',
default: false,
},
width: {
type: 'string',
},
height: {
type: 'string',
},
scale: {
type: 'string',
default: 'cover',
},
};

export default blockAttributes;
31 changes: 26 additions & 5 deletions assets/js/atomic/blocks/product-elements/image/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import ProductSaleBadge from '../sale-badge/block';
import './style.scss';
import { BlockAttributes, ImageSizing } from './types';

const ImagePlaceholder = (): JSX.Element => {
const ImagePlaceholder = ( props ): JSX.Element => {
return (
<img
{ ...props }
src={ PLACEHOLDER_IMG_SRC }
alt=""
width={ undefined }
Expand All @@ -45,13 +46,19 @@ interface ImageProps {
loaded: boolean;
showFullSize: boolean;
fallbackAlt: string;
scale: string;
width?: string | undefined;
height?: string | undefined;
}

const Image = ( {
image,
loaded,
showFullSize,
fallbackAlt,
width,
scale,
height,
}: ImageProps ): JSX.Element => {
const { thumbnail, src, srcset, sizes, alt } = image || {};
const imageProps = {
Expand All @@ -61,13 +68,23 @@ const Image = ( {
...( showFullSize && { src, srcSet: srcset, sizes } ),
};

const imageStyles: Record< string, string | undefined > = {
height,
width,
objectFit: scale,
};

return (
<>
{ imageProps.src && (
/* eslint-disable-next-line jsx-a11y/alt-text */
<img data-testid="product-image" { ...imageProps } />
<img
style={ imageStyles }
data-testid="product-image"
{ ...imageProps }
/>
) }
{ ! image && <ImagePlaceholder /> }
{ ! image && <ImagePlaceholder style={ imageStyles } /> }
</>
);
};
Expand All @@ -81,6 +98,9 @@ export const Block = ( props: Props ): JSX.Element | null => {
showProductLink = true,
showSaleBadge,
saleBadgeAlign = 'right',
height,
width,
scale,
...restProps
} = props;
const styleProps = useStyleProps( props );
Expand All @@ -100,7 +120,6 @@ export const Block = ( props: Props ): JSX.Element | null => {
},
styleProps.className
) }
style={ styleProps.style }
>
<ImagePlaceholder />
</div>
Expand Down Expand Up @@ -134,7 +153,6 @@ export const Block = ( props: Props ): JSX.Element | null => {
},
styleProps.className
) }
style={ styleProps.style }
>
<ParentComponent { ...( showProductLink && anchorProps ) }>
{ !! showSaleBadge && (
Expand All @@ -148,6 +166,9 @@ export const Block = ( props: Props ): JSX.Element | null => {
image={ image }
loaded={ ! isLoading }
showFullSize={ imageSizing !== ImageSizing.THUMBNAIL }
width={ width }
height={ height }
scale={ scale }
/>
</ParentComponent>
</div>
Expand Down
20 changes: 17 additions & 3 deletions assets/js/atomic/blocks/product-elements/image/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
BLOCK_DESCRIPTION as description,
} from './constants';
import { BlockAttributes, ImageSizing } from './types';
import { ImageSizeSettings } from './image-size-settings';

type SaleBadgeAlignProps = 'left' | 'center' | 'right';

Expand All @@ -41,9 +42,16 @@ const Edit = ( {
setAttributes,
context,
}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => {
const { showProductLink, imageSizing, showSaleBadge, saleBadgeAlign } =
attributes;
const blockProps = useBlockProps();
const {
showProductLink,
imageSizing,
showSaleBadge,
saleBadgeAlign,
width,
height,
scale,
} = attributes;
const blockProps = useBlockProps( { style: { width, height } } );
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
const isBlockThemeEnabled = getSettingWithCoercion(
'is_block_theme_enabled',
Expand All @@ -59,6 +67,12 @@ const Edit = ( {
return (
<div { ...blockProps }>
<InspectorControls>
<ImageSizeSettings
scale={ scale }
width={ width }
height={ height }
setAttributes={ setAttributes }
/>
<PanelBody
title={ __( 'Content', 'woo-gutenberg-products-block' ) }
>
Expand Down
127 changes: 127 additions & 0 deletions assets/js/atomic/blocks/product-elements/image/image-size-settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockAttributes } from '@wordpress/blocks';
import {
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControl as ToggleGroupControl,
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToolsPanel as ToolsPanel,
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToolsPanelItem as ToolsPanelItem,
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';

interface ImageSizeSettingProps {
scale: string;
width: string | undefined;
height: string | undefined;
setAttributes: ( attrs: BlockAttributes ) => void;
}

const scaleHelp: Record< string, string > = {
cover: __(
'Image is scaled and cropped to fill the entire space without being distorted.',
'woo-gutenberg-products-block'
),
contain: __(
'Image is scaled to fill the space without clipping nor distorting.',
'woo-gutenberg-products-block'
),
fill: __(
'Image will be stretched and distorted to completely fill the space.',
'woo-gutenberg-products-block'
),
};

export const ImageSizeSettings = ( {
scale,
width,
height,
setAttributes,
}: ImageSizeSettingProps ) => {
return (
<ToolsPanel
className="wc-block-product-image__tools-panel"
label={ __( 'Image size', 'woo-gutenberg-products-block' ) }
>
<UnitControl
label={ __( 'Height', 'woo-gutenberg-products-block' ) }
onChange={ ( value: string ) => {
setAttributes( { height: value } );
} }
value={ height }
units={ [
{
value: 'px',
label: 'px',
},
] }
/>
<UnitControl
label={ __( 'Width', 'woo-gutenberg-products-block' ) }
onChange={ ( value: string ) => {
setAttributes( { width: value } );
} }
value={ width }
units={ [
{
value: 'px',
label: 'px',
},
] }
/>
{ height && (
<ToolsPanelItem
hasValue={ () => true }
label={ __( 'Scale', 'woo-gutenberg-products-block' ) }
>
<ToggleGroupControl
label={ __( 'Scale', 'woo-gutenberg-products-block' ) }
value={ scale }
help={ scaleHelp[ scale ] }
onChange={ ( value: string ) =>
setAttributes( {
scale: value,
} )
}
isBlock
>
<>
<ToggleGroupControlOption
value="cover"
label={ __(
'Cover',
'woo-gutenberg-products-block'
) }
/>
<ToggleGroupControlOption
value="contain"
label={ __(
'Contain',
'woo-gutenberg-products-block'
) }
/>
<ToggleGroupControlOption
value="fill"
label={ __(
'Fill',
'woo-gutenberg-products-block'
) }
/>
</>
</ToggleGroupControl>
</ToolsPanelItem>
) }
</ToolsPanel>
);
};
6 changes: 6 additions & 0 deletions assets/js/atomic/blocks/product-elements/image/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@

.wc-block-components-product-image {
margin: 0 0 $gap-small;
position: relative;
z-index: -1;
}

.wc-block-product-image__tools-panel .components-input-control {
margin-bottom: 8px;
}
1 change: 0 additions & 1 deletion assets/js/atomic/blocks/product-elements/image/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const supports = {
spacing: {
margin: true,
padding: true,
__experimentalSkipSerialization: true,
},
} ),
__experimentalSelector: '.wc-block-components-product-image',
Expand Down
6 changes: 6 additions & 0 deletions assets/js/atomic/blocks/product-elements/image/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@ export interface BlockAttributes {
imageSizing: ImageSizing;
// Whether or not be a children of Query Loop Block.
isDescendentOfQueryLoop: boolean;
// Height of the image.
height?: string;
// Width of the image.
width?: string;
// Image scaling method.
scale: 'cover' | 'contain' | 'fill';
}
2 changes: 1 addition & 1 deletion patterns/product-hero.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<div class="wp-block-columns has-base-color has-text-color has-background has-link-color" style="background-color:#6b7ba8;padding-left:20px">
<!-- wp:column {"width":"40%","style":{"spacing":{"padding":{"top":"0","right":"0","bottom":"0","left":"0"}}}} -->
<div class="wp-block-column" style="padding-top:0;padding-right:0;padding-bottom:0;padding-left:0;flex-basis:40%">
<!-- wp:post-featured-image {"height":"300px"} /-->
<!-- wp:woocommerce/product-image {"showSaleBadge":false,"isDescendentOfSingleProductBlock":true,"height":"300px"} /-->
</div>
<!-- /wp:column -->

Expand Down
12 changes: 11 additions & 1 deletion src/BlockTypes/ProductImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ private function parse_attributes( $attributes ) {
'imageSizing' => 'single',
'productId' => 'number',
'isDescendentOfQueryLoop' => 'false',
'scale' => 'cover',
);

return wp_parse_args( $attributes, $defaults );
Expand Down Expand Up @@ -147,18 +148,27 @@ private function render_anchor( $product, $on_sale_badge, $product_image, $attri
private function render_image( $product, $attributes ) {
$image_size = 'single' === $attributes['imageSizing'] ? 'woocommerce_single' : 'woocommerce_thumbnail';

$image_style = sprintf( 'max-width:none; height:%s; width:%s; object-fit:%s;', $attributes['height'] ?? '', $attributes['width'] ?? '', $attributes['scale'] ?? '' );

if ( ! $product->get_image_id() ) {
// The alt text is left empty on purpose, as it's considered a decorative image.
// More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/.
// Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494.
return wc_placeholder_img( $image_size, array( 'alt' => '' ) );
return wc_placeholder_img(
$image_size,
array(
'alt' => '',
'style' => $image_style,
)
);
}

return $product->get_image(
$image_size,
array(
'alt' => $product->get_title(),
'data-testid' => 'product-image',
'style' => $image_style,
)
);
}
Expand Down

0 comments on commit 333c7f2

Please sign in to comment.