* WIP Product Gallery: Add the Thumbnails block

* Product Gallery Thumbnails: Add block settings

* Add template for the Product Gallery block

* Add template for the Product Gallery block. Add the rest of the files.

* Product Gallery Thumbnails: Add context settings sharing between the Product Gallery and Thumbnails block.

* Product Gallery Thumbnails: Add UI functionality and frontend functionality. Add settings for the Thumbnails in both places - Product Gallery and the Thumbnails block.

* Product Gallery Thumbnails: Move the static template ouside of the component

* Make sure the context is set before accesing the array values

* Product Gallery Thumbnails: Move the setGroupAttributes() function outside of the component

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Update the Features Flags and Experimental Interfaces doc

* Product Gallery Thumbnails: Fix TS error

* Product Gallery Thumbnails: Remove unused stylesheet

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Remove unused context and fix the thumbnails bottom position styling on the frontend.

* Product Gallery Thumbnails: Allow the user to move the horizontal thumbnails above the large image and don't overwrite that automatically

* Product Gallery Thumbnails: Add code comments and remove the incorrect conditional check when moving thumbnails up and down

* Product Gallery Thumbnails: Fix the eslint dependency error

* Product Gallery Thumbnails: Refactor Product Gallery edit code and move the logic to a utils file

* Product Gallery Thumbnails: Update the utils file

* Product Gallery Thumbnails: Update the utils file. Fix comment indentation

* Product Gallery Thumbnails: Fix undefined variable html when only 1 product image is set

* Product Gallery: Rename clientId to productGalleryClientId

* Product Gallery Thumbnails: Combine the useEffect code having the same dependencies

* Product Gallery Thumbnails: Combine all useEffect code together

* Product Gallery Thumbnails: Add a ThumbnailsPosition enum

* Product Gallery Thumbnails: Update the thumbnailsPosition to an enum

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Add missing dependency

* Product Gallery Thumbnails: Uppercase the enum and fix the thumbnails position bug when initially adding the Product Gallery block

* Product Gallery: Add crop, zoom and full-screen settings

* Product Gallery Thumbnails: Replace ts-ignore with ts-expect-error

* Product Gallery Thumbnails: Replace ts-ignore with ts-expect-error

* Product Gallery Thumbnails: Revert back to ts-ignore

* Revert "Product Gallery: Add crop, zoom and full-screen settings"

This reverts commit 840654197619e2611029b81990493387ae0b543d.
This commit is contained in:
Daniel Dudzic 2023-08-02 09:09:51 +02:00 committed by GitHub
parent 76e68dddfd
commit d6e4dd5eb3
25 changed files with 837 additions and 19 deletions

View File

@ -1,17 +1,34 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-gallery",
"version": "1.0.0",
"title": "Product Gallery",
"description": "Showcase your products relevant images and media.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"supports": {
"align": true,
"multiple": false
},
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId", "postType", "queryId" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
"providesContext": {
"thumbnailsPosition": "thumbnailsPosition",
"thumbnailsNumberOfThumbnails": "thumbnailsNumberOfThumbnails",
"productGalleryClientId": "productGalleryClientId"
},
"attributes": {
"thumbnailsPosition": {
"type": "string",
"default": "left"
},
"thumbnailsNumberOfThumbnails": {
"type": "number",
"default": 3
},
"productGalleryClientId": {
"type": "string",
"default": ""
}
}
}

View File

@ -1,25 +1,82 @@
/**
* External dependencies
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
import {
InnerBlocks,
InspectorControls,
useBlockProps,
} from '@wordpress/block-editor';
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
import { useEffect } from '@wordpress/element';
export const Edit = (): JSX.Element => {
/**
* Internal dependencies
*/
import {
moveInnerBlocksToPosition,
updateGroupBlockType,
getInnerBlocksLockAttributes,
} from './utils';
import { BlockSettings } from './inner-blocks/product-gallery-thumbnails/block-settings';
import type { BlockAttributes } from './types';
const TEMPLATE: InnerBlockTemplate[] = [
[
'core/group',
{ layout: { type: 'flex' } },
[
[
'woocommerce/product-gallery-thumbnails',
getInnerBlocksLockAttributes( 'lock' ),
],
[
'woocommerce/product-gallery-large-image',
getInnerBlocksLockAttributes( 'lock' ),
],
],
],
];
export const Edit = ( {
clientId,
attributes,
setAttributes,
}: BlockEditProps< BlockAttributes > ) => {
const blockProps = useBlockProps();
// Update the Group block type when the thumbnailsPosition attribute changes.
updateGroupBlockType( attributes, clientId );
useEffect( () => {
setAttributes( {
...attributes,
productGalleryClientId: clientId,
} );
// Move the Thumbnails block to the correct above or below the Large Image based on the thumbnailsPosition attribute.
moveInnerBlocksToPosition( attributes, clientId );
}, [ setAttributes, attributes, clientId ] );
return (
<div { ...blockProps }>
<InspectorControls>
<BlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
context={ {
productGalleryClientId: clientId,
thumbnailsPosition: attributes.thumbnailsPosition,
thumbnailsNumberOfThumbnails:
attributes.thumbnailsNumberOfThumbnails,
} }
/>
</InspectorControls>
<InnerBlocks
allowedBlocks={ [ 'woocommerce/product-gallery-large-image' ] }
templateLock={ false }
allowedBlocks={ [
'woocommerce/product-gallery-large-image',
'woocommerce/product-gallery-thumbnails',
] }
template={ TEMPLATE }
/>
</div>
);
};
export const Save = (): JSX.Element => {
return (
<div { ...useBlockProps.save() }>
<InnerBlocks.Content />
</div>
);
};

View File

@ -7,17 +7,21 @@ import { isExperimentalBuild } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import { Edit, Save } from './edit';
import { Edit } from './edit';
import { Save } from './save';
import metadata from './block.json';
import icon from './icon';
import './inner-blocks/product-gallery-large-image';
import './inner-blocks/product-gallery-thumbnails';
if ( isExperimentalBuild() ) {
registerBlockSingleProductTemplate( {
blockName: metadata.name,
// @ts-expect-error: `metadata` currently does not have a type definition in WordPress core
blockMetadata: metadata,
blockSettings: {
icon,
// @ts-expect-error `edit` can be extended to include other attributes
edit: Edit,
save: Save,
ancestor: [ 'woocommerce/single-product' ],

View File

@ -5,6 +5,11 @@ import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
import { useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
/**
* Internal dependencies
*/
import './editor.scss';
export const Edit = (): JSX.Element => {
const blockProps = useBlockProps( {
className: 'wc-block-editor-product-gallery_large-image',

View File

@ -0,0 +1,3 @@
.wc-block-editor-product-gallery-large-image img {
max-width: 500px;
}

View File

@ -0,0 +1,134 @@
/**
* External dependencies
*/
import {
InspectorControls,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import {
thumbnailsPositionLeft,
thumbnailsPositionBottom,
thumbnailsPositionRight,
} from '@woocommerce/icons';
import { useDispatch } from '@wordpress/data';
import {
PanelBody,
RangeControl,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - Ignoring because `__experimentalToggleGroupControlOption` is not yet in the type definitions.
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions.
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControl as ToggleGroupControl,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import { ThumbnailsPosition } from '../constants';
import type { ThumbnailsSettingProps } from '../../../types';
const positionHelp: Record< ThumbnailsPosition, string > = {
[ ThumbnailsPosition.OFF ]: __(
'No thumbnails will be displayed.',
'woo-gutenberg-products-block'
),
[ ThumbnailsPosition.LEFT ]: __(
'A strip of small images will appear to the left of the main gallery image.',
'woo-gutenberg-products-block'
),
[ ThumbnailsPosition.BOTTOM ]: __(
'A strip of small images will appear below the main gallery image.',
'woo-gutenberg-products-block'
),
[ ThumbnailsPosition.RIGHT ]: __(
'A strip of small images will appear to the right of the main gallery image.',
'woo-gutenberg-products-block'
),
};
export const BlockSettings = ( { context }: ThumbnailsSettingProps ) => {
const maxNumberOfThumbnails = 8;
const minNumberOfThumbnails = 2;
const { productGalleryClientId } = context;
// @ts-expect-error @wordpress/block-editor/store types not provided
const { updateBlockAttributes } = useDispatch( blockEditorStore );
return (
<InspectorControls>
<PanelBody
title={ __( 'Settings', 'woo-gutenberg-products-block' ) }
>
<ToggleGroupControl
className="wc-block-editor-product-gallery-thumbnails__position-toggle"
isBlock={ true }
label={ __( 'Thumbnails', 'woo-gutenberg-products-block' ) }
value={ context.thumbnailsPosition }
help={
positionHelp[
context.thumbnailsPosition as ThumbnailsPosition
]
}
onChange={ ( value: string ) =>
updateBlockAttributes( productGalleryClientId, {
thumbnailsPosition: value,
} )
}
>
<ToggleGroupControlOption
value={ ThumbnailsPosition.OFF }
label={ __( 'Off', 'woo-gutenberg-products-block' ) }
/>
<ToggleGroupControlOption
value={ ThumbnailsPosition.LEFT }
label={
<Icon size={ 32 } icon={ thumbnailsPositionLeft } />
}
/>
<ToggleGroupControlOption
value={ ThumbnailsPosition.BOTTOM }
label={
<Icon
size={ 32 }
icon={ thumbnailsPositionBottom }
/>
}
/>
<ToggleGroupControlOption
value={ ThumbnailsPosition.RIGHT }
label={
<Icon
size={ 32 }
icon={ thumbnailsPositionRight }
/>
}
/>
</ToggleGroupControl>
{ context.thumbnailsPosition !== ThumbnailsPosition.OFF && (
<RangeControl
label={ __(
'Number of Thumbnails',
'woo-gutenberg-products-block'
) }
value={ context.thumbnailsNumberOfThumbnails }
onChange={ ( value: number ) =>
updateBlockAttributes( productGalleryClientId, {
thumbnailsNumberOfThumbnails: value,
} )
}
help={ __(
'Choose how many thumbnails (2-8) will display. If more images exist, a “View all” button will display.',
'woo-gutenberg-products-block'
) }
max={ maxNumberOfThumbnails }
min={ minNumberOfThumbnails }
/>
) }
</PanelBody>
</InspectorControls>
);
};

View File

@ -0,0 +1,21 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-gallery-thumbnails",
"version": "1.0.0",
"title": "Thumbnails",
"description": "Display the Thumbnails of a product.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId", "thumbnailsPosition", "thumbnailsNumberOfThumbnails", "productGalleryClientId" ],
"textdomain": "woo-gutenberg-products-block",
"ancestor": [ "woocommerce/product-gallery" ],
"supports": {
"spacing": {
"margin": true,
"__experimentalDefaultControls": {
"margin": true
}
}
}
}

View File

@ -0,0 +1,6 @@
export enum ThumbnailsPosition {
OFF = 'off',
LEFT = 'left',
BOTTOM = 'bottom',
RIGHT = 'right',
}

View File

@ -0,0 +1,64 @@
/**
* External dependencies
*/
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import type { BlockEditProps } from '@wordpress/blocks';
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import './editor.scss';
import { BlockSettings } from './block-settings';
import type { BlockAttributes, Context } from '../../types';
import { ThumbnailsPosition } from './constants';
export const Edit = ( {
attributes,
setAttributes,
context,
}: BlockEditProps< BlockAttributes > & Context ) => {
const blockProps = useBlockProps();
const Placeholder = () => {
return (
<>
{ context.thumbnailsPosition !== ThumbnailsPosition.OFF && (
<div className="wc-block-editor-product-gallery-thumbnails">
{ [
...Array(
context.thumbnailsNumberOfThumbnails
).keys(),
].map( ( index ) => {
return (
<img
key={ index }
src={ `${ WC_BLOCKS_IMAGE_URL }block-placeholders/product-image-gallery.svg` }
alt="Placeholder"
/>
);
} ) }
</div>
) }
</>
);
};
return (
<>
<div { ...blockProps }>
<InspectorControls>
<BlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
context={ context }
/>
</InspectorControls>
<Disabled>
<Placeholder />
</Disabled>
</div>
</>
);
};

View File

@ -0,0 +1,9 @@
.wp-block-woocommerce-product-gallery-thumbnails {
.wc-block-editor-product-gallery-thumbnails {
img {
width: 100px;
height: 100px;
margin: 5px;
}
}
}

View File

@ -0,0 +1,18 @@
const Icon = () => (
<svg
width="19"
height="19"
viewBox="0 0 19 19"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.375 1.5H1.625C1.55596 1.5 1.5 1.55596 1.5 1.625V9.87895L4.35871 8.33965C4.5696 8.22609 4.82204 8.22009 5.03808 8.3235L7.42329 9.46513L10.3126 7.39076C10.574 7.20308 10.926 7.20308 11.1874 7.39076L13.5 9.05108V1.625C13.5 1.55596 13.444 1.5 13.375 1.5ZM13.5 10.8976L10.75 8.92328L7.93741 10.9426C7.71497 11.1023 7.42319 11.1281 7.1762 11.0098L4.73428 9.84105L1.5 11.5826V13.375C1.5 13.444 1.55596 13.5 1.625 13.5H13.375C13.444 13.5 13.5 13.444 13.5 13.375V10.8976ZM1.625 0C0.727537 0 0 0.727538 0 1.625V13.375C0 14.2725 0.727538 15 1.625 15H13.375C14.2725 15 15 14.2725 15 13.375V1.625C15 0.727537 14.2725 0 13.375 0H1.625ZM17.25 5V16C17.25 16.6909 16.6909 17.25 16.0011 17.25H3V18.75H16.0011C17.5204 18.75 18.75 17.5183 18.75 16V5H17.25Z"
fill="#1E1E1E"
/>
</svg>
);
export default Icon;

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { isExperimentalBuild } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import icon from './icon';
import { Edit } from './edit';
import metadata from './block.json';
import './style.scss';
if ( isExperimentalBuild() ) {
// @ts-expect-error: `metadata` currently does not have a type definition in WordPress core
registerBlockType( metadata, {
icon,
edit: Edit,
save() {
return null;
},
} );
}

View File

@ -0,0 +1,12 @@
.woocommerce {
.is-vertical .wc-block-components-product-gallery-thumbnails {
display: flex;
flex-direction: row;
}
.wc-block-components-product-gallery-thumbnails .woocommerce-product-gallery__image {
width: 100px;
height: 100px;
margin: 5px;
}
}

View File

@ -0,0 +1,10 @@
/**
* External dependencies
*/
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
export const Save = (): JSX.Element => {
const blockProps = useBlockProps.save();
const innerBlocksProps = useInnerBlocksProps.save( blockProps );
return <div { ...innerBlocksProps } />;
};

View File

@ -0,0 +1,30 @@
/**
* Internal dependencies
*/
import { ThumbnailsPosition } from './inner-blocks/product-gallery-thumbnails/constants';
export interface BlockAttributes {
thumbnailsPosition: ThumbnailsPosition;
thumbnailsNumberOfThumbnails: number;
productGalleryClientId: string;
}
export interface Context {
context: {
thumbnailsPosition: ThumbnailsPosition;
thumbnailsNumberOfThumbnails: number;
productGalleryClientId: string;
};
}
export interface ProductGalleryBlockEditProps {
clientId: string;
attributes: BlockAttributes;
setAttributes: ( newAttributes: BlockAttributes ) => void;
}
export interface ThumbnailsSettingProps {
attributes: BlockAttributes;
context: BlockAttributes;
setAttributes: ( attributes: BlockAttributes ) => void;
}

View File

@ -0,0 +1,179 @@
/**
* External dependencies
*/
import { store as blockEditorStore } from '@wordpress/block-editor';
import { BlockAttributes } from '@wordpress/blocks';
import { select, dispatch } from '@wordpress/data';
/**
* Generates layout attributes based on the position of thumbnails.
*
* @param {string} thumbnailsPosition - The position of thumbnails ('bottom' or other values).
* @return {{type: string, orientation?: string, flexWrap?: string}} - An object representing layout attributes.
*/
export const getGroupLayoutAttributes = (
thumbnailsPosition: string
): { type: string; orientation?: string; flexWrap?: string } => {
switch ( thumbnailsPosition ) {
case 'bottom':
// Stack
return { type: 'flex', orientation: 'vertical' };
default:
// Row
return { type: 'flex', flexWrap: 'nowrap' };
}
};
/**
* Returns inner block lock attributes based on provided action.
*
* @param {string} action - The action to take on the inner blocks ('lock' or 'unlock').
* @return {{lock: {move?: boolean, remove?: boolean}}} - An object representing lock attributes for inner blocks.
*/
export const getInnerBlocksLockAttributes = (
action: string
): { lock: { move?: boolean; remove?: boolean } } => {
switch ( action ) {
case 'lock':
return { lock: { move: true, remove: true } };
case 'unlock':
return { lock: {} };
default:
return { lock: {} };
}
};
/**
* Updates block attributes based on provided attributes.
*
* @param {BlockAttributes} attributesToUpdate - The new attributes to set on the block.
* @param {BlockAttributes | undefined} block - The block object to update.
*/
export const updateBlockAttributes = (
attributesToUpdate: BlockAttributes,
block: BlockAttributes | undefined
): void => {
if ( block !== undefined ) {
const updatedBlock = {
...block,
attributes: {
...block.attributes,
...attributesToUpdate,
},
};
dispatch( 'core/block-editor' ).updateBlock(
block.clientId,
updatedBlock
);
}
};
/**
* Moves inner blocks to a position based on provided attributes.
*
* @param {BlockAttributes} attributes - The attributes of the parent block.
* @param {string} clientId - The clientId of the parent block.
*/
export const moveInnerBlocksToPosition = (
attributes: BlockAttributes,
clientId: string
): void => {
const parentBlock = select( 'core/block-editor' ).getBlock( clientId );
if ( parentBlock?.name === 'woocommerce/product-gallery' ) {
const groupBlock = parentBlock.innerBlocks.find(
( innerBlock ) => innerBlock.name === 'core/group'
);
if ( groupBlock ) {
const largeImageBlock = groupBlock.innerBlocks.find(
( innerBlock ) =>
innerBlock.name ===
'woocommerce/product-gallery-large-image'
);
const thumbnailsBlock = groupBlock.innerBlocks.find(
( innerBlock ) =>
innerBlock.name === 'woocommerce/product-gallery-thumbnails'
);
const thumbnailsIndex = groupBlock.innerBlocks.findIndex(
( innerBlock ) =>
innerBlock.name === 'woocommerce/product-gallery-thumbnails'
);
const largeImageIndex = groupBlock.innerBlocks.findIndex(
( innerBlock ) =>
innerBlock.name ===
'woocommerce/product-gallery-large-image'
);
if ( thumbnailsIndex !== -1 && largeImageIndex !== -1 ) {
updateBlockAttributes(
getInnerBlocksLockAttributes( 'unlock' ),
thumbnailsBlock
);
updateBlockAttributes(
getInnerBlocksLockAttributes( 'unlock' ),
largeImageBlock
);
const { thumbnailsPosition } = attributes;
const clientIdToMove =
groupBlock.innerBlocks[ thumbnailsIndex ].clientId;
if (
thumbnailsPosition === 'bottom' ||
thumbnailsPosition === 'right'
) {
// @ts-expect-error - Ignoring because `moveBlocksDown` is not yet in the type definitions.
dispatch( blockEditorStore ).moveBlocksDown(
[ clientIdToMove ],
groupBlock.clientId
);
} else {
// @ts-expect-error - Ignoring because `moveBlocksUp` is not yet in the type definitions.
dispatch( blockEditorStore ).moveBlocksUp(
[ clientIdToMove ],
groupBlock.clientId
);
}
updateBlockAttributes(
getInnerBlocksLockAttributes( 'lock' ),
thumbnailsBlock
);
updateBlockAttributes(
getInnerBlocksLockAttributes( 'lock' ),
largeImageBlock
);
}
}
}
};
/**
* Updates the type of group block based on provided attributes.
*
* @param {BlockAttributes} attributes - The attributes of the parent block.
* @param {string} clientId - The clientId of the parent block.
*/
export const updateGroupBlockType = (
attributes: BlockAttributes,
clientId: string
): void => {
const block = select( 'core/block-editor' ).getBlock( clientId );
block?.innerBlocks.forEach( ( innerBlock ) => {
if ( innerBlock.name === 'core/group' ) {
updateBlockAttributes(
{
layout: getGroupLayoutAttributes(
attributes.thumbnailsPosition
),
},
innerBlock
);
}
} );
};

View File

@ -18,6 +18,9 @@ export { default as productDetails } from './library/product-details';
export { default as productMeta } from './library/product-meta';
export { default as removeCart } from './library/remove-cart';
export { default as stacks } from './library/stacks';
export { default as thumbnailsPositionLeft } from './library/thumbnails-position-left';
export { default as thumbnailsPositionBottom } from './library/thumbnails-position-bottom';
export { default as thumbnailsPositionRight } from './library/thumbnails-position-right';
export { default as thumbUp } from './library/thumb-up';
export { default as toggle } from './library/toggle';
export { default as totals } from './library/totals';

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const thumbnailsPositionBottom = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path
d="M19 3H5C3.9 3 3 3.9 3 5V12C3 13.1 3.9 14 5 14H19C20.1 14 21 13.1 21 12V5C21 3.9 20.1 3 19 3ZM5 4.5H19C19.3 4.5 19.5 4.7 19.5 5V8.4L16.5 5.5C16.2 5.2 15.7 5.2 15.5 5.5L11.9 9L9 7C8.7 6.8 8.4 6.8 8.2 7L4.6 9.6V5C4.5 4.7 4.7 4.5 5 4.5ZM19 12.5H5C4.7 12.5 4.5 12.3 4.5 12V11.6L8.6 8.6L11.6 10.5C11.9 10.7 12.3 10.7 12.5 10.4L16 7L19.5 10.4V12C19.5 12.3 19.3 12.5 19 12.5Z"
fill="currentColor"
/>
<rect
x="6.25"
y="15.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
<rect
x="13.25"
y="15.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
</SVG>
);
export default thumbnailsPositionBottom;

View File

@ -0,0 +1,45 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const thumbnailsPositionLeft = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24" fill="none">
<g clipPath="url(#clip0_420_11645)">
<path
d="M22.5 3H10.5C9.4 3 8.5 3.9 8.5 5V19C8.5 20.1 9.4 21 10.5 21H22.5C23.6 21 24.5 20.1 24.5 19V5C24.5 3.9 23.6 3 22.5 3ZM10.5 4.5H22.5C22.8 4.5 23 4.7 23 5V13.4L21 10.5C20.7 10.2 20.2 10.2 20 10.5L16.4 14L13.5 12C13.2 11.8 12.9 11.8 12.7 12L10.1 14.6V5C10 4.7 10.2 4.5 10.5 4.5ZM22.5 19.5H10.5C10.2 19.5 10 19.3 10 19V16.6L13.1 13.6L16.1 15.5C16.4 15.7 16.8 15.7 17 15.4L20.5 12L23 15.4V19C23 19.3 22.8 19.5 22.5 19.5Z"
fill="currentColor"
/>
<rect
x="1.25"
y="3.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
<rect
x="1.25"
y="10.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_420_11645">
<rect
width="24"
height="24"
fill="white"
transform="translate(0.5)"
/>
</clipPath>
</defs>
</SVG>
);
export default thumbnailsPositionLeft;

View File

@ -0,0 +1,45 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const thumbnailsPositionRight = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24" fill="none">
<g clipPath="url(#clip0_420_11656)">
<path
d="M14.5 3H2.5C1.4 3 0.5 3.9 0.5 5V19C0.5 20.1 1.4 21 2.5 21H14.5C15.6 21 16.5 20.1 16.5 19V5C16.5 3.9 15.6 3 14.5 3ZM2.5 4.5H14.5C14.8 4.5 15 4.7 15 5V13.4L13 10.5C12.7 10.2 12.2 10.2 12 10.5L8.4 14L5.5 12C5.2 11.8 4.9 11.8 4.7 12L2.1 14.6V5C2 4.7 2.2 4.5 2.5 4.5ZM14.5 19.5H2.5C2.2 19.5 2 19.3 2 19V16.6L5.1 13.6L8.1 15.5C8.4 15.7 8.8 15.7 9 15.4L12.5 12L15 15.4V19C15 19.3 14.8 19.5 14.5 19.5Z"
fill="currentColor"
/>
<rect
x="19.25"
y="3.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
<rect
x="19.25"
y="10.75"
width="4.5"
height="4.5"
rx="1.25"
stroke="currentColor"
strokeWidth="1.5"
/>
</g>
<defs>
<clipPath id="clip0_420_11656">
<rect
width="24"
height="24"
fill="currentColor"
transform="translate(0.5)"
/>
</clipPath>
</defs>
</SVG>
);
export default thumbnailsPositionRight;

View File

@ -54,6 +54,10 @@ const blocks = {
customDir: 'product-gallery/inner-blocks/product-gallery-large-image',
isExperimental: true,
},
'product-gallery-thumbnails': {
customDir: 'product-gallery/inner-blocks/product-gallery-thumbnails',
isExperimental: true,
},
'product-new': {},
'product-on-sale': {},
'product-query': {

View File

@ -37,6 +37,7 @@ The majority of our feature flagging is blocks, this is a list of them:
- Product Collection ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/17007169ea5f61e36903d7ca79902794cbb45100/src/BlockTypesController.php#L228) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/17007169ea5f61e36903d7ca79902794cbb45100/bin/webpack-entries.js#L71-L73)).
- Product Gallery ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/e3fe996251b270d45ecc73207ea4ad587c2dbc78/src/BlockTypesController.php#L232) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/e3fe996251b270d45ecc73207ea4ad587c2dbc78/bin/webpack-entries.js#L50-L52C3)).
- Product Gallery Thumbnails ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/04af396b9aec5a915ad98188eded53e723a051d3/src/BlockTypesController.php#L234) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/04af396b9aec5a915ad98188eded53e723a051d3/bin/webpack-entries.js#L57-L60)).
- Product Template ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/17007169ea5f61e36903d7ca79902794cbb45100/src/BlockTypesController.php#L229) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/17007169ea5f61e36903d7ca79902794cbb45100/bin/webpack-entries.js#L74-L76)).
- Product Average Rating ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/1111e2fb9d6f5074df96a444b99e2fc00e4eb8d1/src/BlockTypesController.php#L229) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/1111e2fb9d6f5074df96a444b99e2fc00e4eb8d1/bin/webpack-entries.js#L68-L70))
- Product Rating Stars ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/BlockTypesController.php#L230) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/bin/webpack-entries.js#L68-L70))

View File

@ -25,7 +25,7 @@ class ProductGalleryLargeImage extends AbstractBlock {
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
return [ 'postId' ];
}
/**

View File

@ -0,0 +1,93 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductGalleryLargeImage class.
*/
class ProductGalleryThumbnails extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-gallery-thumbnails';
/**
* It isn't necessary register block assets because it is a server side block.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition' ];
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( isset( $block->context['thumbnailsPosition'] ) && '' !== $block->context['thumbnailsPosition'] && 'off' !== $block->context['thumbnailsPosition'] ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
$post_thumbnail_id = $product->get_image_id();
$html = '';
if ( $product ) {
$attachment_ids = $product->get_gallery_image_ids();
if ( $attachment_ids && $post_thumbnail_id ) {
$html .= wc_get_gallery_image_html( $post_thumbnail_id, true );
$number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3;
$thumbnails_count = 1;
foreach ( $attachment_ids as $attachment_id ) {
if ( $thumbnails_count >= $number_of_thumbnails ) {
break;
}
/**
* Filter the HTML markup for a single product image thumbnail in the gallery.
*
* @param string $thumbnail_html The HTML markup for the thumbnail.
* @param int $attachment_id The attachment ID of the thumbnail.
*
* @since 7.9.0
*/
$html .= apply_filters( 'woocommerce_single_product_image_thumbnail_html', wc_get_gallery_image_html( $attachment_id ), $attachment_id ); // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped
$thumbnails_count++;
}
}
return sprintf(
'<div class="wc-block-components-product-gallery-thumbnails %1$s" style="%2$s">
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$html
);
}
}
}
}

View File

@ -231,6 +231,7 @@ final class BlockTypesController {
$block_types[] = 'ProductTemplate';
$block_types[] = 'ProductGallery';
$block_types[] = 'ProductGalleryLargeImage';
$block_types[] = 'ProductGalleryThumbnails';
}
/**