Product Gallery Thumbnails: remove settings from container block (#54799)

* Remove thumbnails settings from container block

* Migrate Thumbnails from context usage to local attributes

* Cleanup types

* Adjust frontend

* Fix the frontend gallery

* Fix types

* Fix types

* Remove provide context

* Remove occurrances of productGalleryClientId

* Update block references and block manifest

* Rename thumbnailsNumberOfThumbnails to numberOfThumbnails

* Remove attribute from gallery template

* Remove thumbnails editor styles

* Simplify Thumbnails edit file

* Remove thumbnails position manipulation from parent

* Remove utils manipulating positions

* WIP: Simplify styles

* Some stylingimprovements

* Update block manifest

* Bring back some of the old styles

* Fix styles

* Remove Thumbnails position attribute and corresponding code

* Remove leftover exports

* Continue adjusting styles

* Fix frontend by attaching new classes

* Remove unnecessary variables and fix merge

* Add changelog

* Fix lint

* Remove tests related to positioning settings

* Fix E2E

* Update blocks references and manifest

* Fix test

* Address code review

* Update blocks manifest

* Fix typo

* Limit the number of thumbnails if there's not enough images

* Fix PHP error

* Fix lint
This commit is contained in:
Karol Manijak 2025-01-31 13:35:13 +01:00 committed by GitHub
parent c9ee77c785
commit 67348ccaf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 356 additions and 875 deletions

View File

@ -152,6 +152,61 @@ Displays an on-sale badge if the product is on-sale.
- **Supports:**
- **Attributes:** isDescendentOfQueryLoop, isDescendentOfSingleProductTemplate, productId
## Product Summary - woocommerce/product-summary
Display a short description about a product.
- **Name:** woocommerce/product-summary
- **Category:**
- **Ancestor:**
- **Parent:**
- **Supports:**
- **Attributes:** isDescendantOfAllProducts, isDescendentOfQueryLoop, isDescendentOfSingleProductBlock, isDescendentOfSingleProductTemplate, linkText, productId, showDescriptionIfEmpty, showLink, summaryLength
## Accordion Group - woocommerce/accordion-group
A group of headers and associated expandable content.
- **Name:** woocommerce/accordion-group
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin, padding), ~~html~~
- **Attributes:** allowedBlocks, autoclose, iconPosition
## Accordion Header - woocommerce/accordion-header
Accordion header.
- **Name:** woocommerce/accordion-header
- **Category:** woocommerce
- **Ancestor:**
- **Parent:** woocommerce/accordion-item
- **Supports:** anchor, border, color (background, gradient, text), interactivity, layout, shadow, spacing (margin, padding), typography (fontSize, textAlign), ~~align~~
- **Attributes:** icon, iconPosition, level, levelOptions, openByDefault, textAlignment, title
## Accordion - woocommerce/accordion-item
A single accordion that displays a header and expandable content.
- **Name:** woocommerce/accordion-item
- **Category:** woocommerce
- **Ancestor:**
- **Parent:** woocommerce/accordion-group
- **Supports:** align (full, wide), color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin)
- **Attributes:** openByDefault
## Accordion Panel - woocommerce/accordion-panel
Accordion Panel
- **Name:** woocommerce/accordion-panel
- **Category:** woocommerce
- **Ancestor:**
- **Parent:** woocommerce/accordion-item
- **Supports:** border, color (background, gradient, text), interactivity, layout, shadow, spacing (blockGap, margin, padding), typography (fontSize, lineHeight)
- **Attributes:** allowedBlocks, isSelected, openByDefault, templateLock
## Active Filters Controls - woocommerce/active-filters
Display the currently active filters.
@ -163,6 +218,61 @@ Display the currently active filters.
- **Supports:** color (text, ~~background~~), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~
- **Attributes:** displayStyle, headingLevel
## Add to Cart with Options (Experimental) - woocommerce/add-to-cart-with-options
Create an "Add To Cart" composition by using blocks
- **Name:** woocommerce/add-to-cart-with-options
- **Category:** woocommerce-product-elements
- **Ancestor:**
- **Parent:**
- **Supports:** interactivity
- **Attributes:** isDescendentOfSingleProductBlock
## Grouped Product Selector (Experimental) - woocommerce/add-to-cart-with-options-grouped-product-selector
Display a group of products that can be added to the cart.
- **Name:** woocommerce/add-to-cart-with-options-grouped-product-selector
- **Category:** woocommerce-product-elements
- **Ancestor:** woocommerce/add-to-cart-with-options
- **Parent:**
- **Supports:**
- **Attributes:**
## Grouped Product Selector Item Template (Experimental) - woocommerce/add-to-cart-with-options-grouped-product-selector-item
A list item template that represents a child product within the Grouped Product Selector block.
- **Name:** woocommerce/add-to-cart-with-options-grouped-product-selector-item
- **Category:** woocommerce-product-elements
- **Ancestor:** woocommerce/add-to-cart-with-options-grouped-product-selector
- **Parent:**
- **Supports:** ~~inserter~~
- **Attributes:**
## Quantity Selector (Experimental) - woocommerce/add-to-cart-with-options-quantity-selector
Display an input field to select the number of products to add to cart.
- **Name:** woocommerce/add-to-cart-with-options-quantity-selector
- **Category:** woocommerce-product-elements
- **Ancestor:** woocommerce/add-to-cart-with-options
- **Parent:**
- **Supports:**
- **Attributes:** quantitySelectorStyle
## Variation Selector (Experimental) - woocommerce/add-to-cart-with-options-variation-selector
Display a dropdown to select a variation to add to cart.
- **Name:** woocommerce/add-to-cart-with-options-variation-selector
- **Category:** woocommerce-product-elements
- **Ancestor:** woocommerce/add-to-cart-with-options
- **Parent:**
- **Supports:**
- **Attributes:**
## Filter by Attribute Controls - woocommerce/attribute-filter
Enable customers to filter the product grid by selecting one or more attributes, such as color.
@ -414,7 +524,7 @@ Enable customers to change the sorting order of the products.
- **Ancestor:**
- **Parent:**
- **Supports:** color (text, ~~background~~), typography (fontSize)
- **Attributes:** fontSize
- **Attributes:** fontSize, useLabel
## Checkout - woocommerce/checkout
@ -524,7 +634,7 @@ Shows cart items.
- **Ancestor:**
- **Parent:** woocommerce/checkout-order-summary-block
- **Supports:** ~~align~~, ~~html~~, ~~lock~~, ~~multiple~~, ~~reusable~~
- **Attributes:** className, lock
- **Attributes:** className, disableProductDescriptions, lock
## Coupon Form - woocommerce/checkout-order-summary-coupon-form-block
@ -699,8 +809,8 @@ Renders classic WooCommerce shortcodes.
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** color (text, ~~background~~), ~~inserter~~
- **Attributes:** color, storeOnly
- **Supports:** color (background, text), ~~inserter~~
- **Attributes:** color, comingSoonPatternId, storeOnly
## Customer account - woocommerce/customer-account
@ -946,7 +1056,7 @@ Display the order confirmation billing section.
## Account Creation - woocommerce/order-confirmation-create-account
Allow customers to create an account after their purchase. Configure this feature in your store settings.
Allow customers to create an account after their purchase.
- **Name:** woocommerce/order-confirmation-create-account
- **Category:** woocommerce
@ -1120,6 +1230,17 @@ The contents of this block will display when there are no products found.
- **Supports:** align, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~
- **Attributes:**
## Blockified Product Details - woocommerce/blockified-product-details
Display a product's description, attributes, and reviews
- **Name:** woocommerce/blockified-product-details
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:**
- **Attributes:**
## Add to Cart with Options - woocommerce/add-to-cart-form
Display a button so the customer can add a product to their cart. Options will also be displayed depending on product type. e.g. quantity, variation.
@ -1139,7 +1260,7 @@ Let shoppers filter products displayed on the page.
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** align, color (background, button, heading, text, ~~enableContrastChecker~~), inserter, interactivity, multiple, typography (fontSize)
- **Supports:** align, color (background, button, heading, text, ~~enableContrastChecker~~), inserter, interactivity, layout (default, ~~allowEditing~~), multiple, spacing (blockGap), typography (fontSize)
- **Attributes:** overlayButtonType, overlayIcon, overlayIconSize
## Active (Experimental) - woocommerce/product-filter-active
@ -1150,8 +1271,8 @@ Display the currently active filters.
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filters
- **Parent:**
- **Supports:** color (text, ~~background~~), interactivity
- **Attributes:** displayStyle
- **Supports:** interactivity, spacing (margin, padding, ~~blockGap~~)
- **Attributes:**
## Attribute (Experimental) - woocommerce/product-filter-attribute
@ -1162,7 +1283,7 @@ Enable customers to filter the product grid by selecting one or more attributes,
- **Ancestor:** woocommerce/product-filters
- **Parent:**
- **Supports:** color (text, ~~background~~), interactivity, spacing (blockGap, margin, padding), typography (fontSize, lineHeight)
- **Attributes:** attributeId, clearButton, displayStyle, hideEmpty, isPreview, queryType, selectType, showCounts, sortOrder
- **Attributes:** attributeId, displayStyle, hideEmpty, isPreview, queryType, selectType, showCounts, sortOrder
## List - woocommerce/product-filter-checkbox-list
@ -1192,32 +1313,10 @@ Allows shoppers to reset this filter.
- **Name:** woocommerce/product-filter-clear-button
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filter
- **Ancestor:** woocommerce/product-filter,woocommerce/product-filter-attribute,woocommerce/product-filter-price,woocommerce/product-filter-rating,woocommerce/product-filter-status,woocommerce/product-filter-active
- **Parent:**
- **Supports:** interactivity, ~~inserter~~
- **Attributes:**
## Product Filters Overlay (Experimental) - woocommerce/product-filters-overlay
Display product filters in an overlay on top of a page.
- **Name:** woocommerce/product-filters-overlay
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** align, color (background, text), dimensions (), layout (allowCustomContentAndWideSize), spacing (blockGap, padding), typography (), ~~inserter~~, ~~multiple~~
- **Attributes:** overlayPosition, overlayStyle, style
## Overlay Navigation (Experimental) - woocommerce/product-filters-overlay-navigation
Display overlay navigation controls.
- **Name:** woocommerce/product-filters-overlay-navigation
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filters-overlay,woocommerce/product-filters
- **Parent:**
- **Supports:** align (center, left, right), color (background, text), inserter, interactivity, layout (default, ~~allowEditing~~), position (sticky), spacing (blockGap, margin, padding), typography (fontSize, lineHeight)
- **Attributes:** align, buttonStyle, iconSize, navigationStyle, overlayIcon, overlayMode, style, triggerType
- **Supports:** inserter, interactivity
- **Attributes:** clearType
## Price (Experimental) - woocommerce/product-filter-price
@ -1238,8 +1337,8 @@ A slider helps shopper choose a price range.
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filter-price
- **Parent:**
- **Supports:** ~~html~~
- **Attributes:** inlineInput, showInputFields
- **Supports:** color (~~background~~, ~~enableContrastChecker~~, ~~text~~), ~~html~~
- **Attributes:** customSlider, customSliderHandle, customSliderHandleBorder, inlineInput, showInputFields, slider, sliderHandle, sliderHandleBorder
## Rating (Experimental) - woocommerce/product-filter-rating
@ -1252,6 +1351,17 @@ Enable customers to filter the product collection by rating.
- **Supports:** color (text, ~~background~~), interactivity
- **Attributes:** className, isPreview, minRating, showCounts
## Chips - woocommerce/product-filter-removable-chips
Display removable active filters as chips.
- **Name:** woocommerce/product-filter-removable-chips
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-filter-active
- **Parent:**
- **Supports:** layout (default, ~~allowInheriting~~, ~~allowJustification~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~)
- **Attributes:** chipBackground, chipBorder, chipText, customChipBackground, customChipBorder, customChipText
## Status (Experimental) - woocommerce/product-filter-status
Let shoppers filter products by choosing stock status.
@ -1261,7 +1371,7 @@ Let shoppers filter products by choosing stock status.
- **Ancestor:** woocommerce/product-filters
- **Parent:**
- **Supports:** color (text, ~~background~~), interactivity, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** clearButton, displayStyle, hideEmpty, isPreview, showCounts
- **Attributes:** displayStyle, hideEmpty, isPreview, showCounts
## Product Gallery (Beta) - woocommerce/product-gallery
@ -1269,10 +1379,10 @@ Showcase your products relevant images and media.
- **Name:** woocommerce/product-gallery
- **Category:** woocommerce
- **Ancestor:**
- **Ancestor:** woocommerce/single-product
- **Parent:**
- **Supports:** align, interactivity, ~~multiple~~
- **Attributes:** cropImages, fullScreenOnClick, hoverZoom, mode, nextPreviousButtonsPosition, pagerDisplayMode, productGalleryClientId, thumbnailsNumberOfThumbnails, thumbnailsPosition
- **Supports:** align, interactivity
- **Attributes:** cropImages, fullScreenOnClick, hoverZoom
## Large Image - woocommerce/product-gallery-large-image
@ -1298,13 +1408,13 @@ Display next and previous buttons.
## Pager - woocommerce/product-gallery-pager
Display the gallery pager.
Display the gallery pager in format "current image/total images".
- **Name:** woocommerce/product-gallery-pager
- **Category:** woocommerce
- **Ancestor:** woocommerce/product-gallery
- **Parent:**
- **Supports:**
- **Supports:** color (background, text), spacing (margin, padding), typography (fontSize, lineHeight, textAlign)
- **Attributes:**
## Thumbnails - woocommerce/product-gallery-thumbnails
@ -1316,7 +1426,7 @@ Display the Thumbnails of a product.
- **Ancestor:** woocommerce/product-gallery
- **Parent:**
- **Supports:** spacing (margin)
- **Attributes:**
- **Attributes:** numberOfThumbnails
## Newest Products - woocommerce/product-new
@ -1403,7 +1513,7 @@ Enable customers to filter the product grid by rating.
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** color, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~
- **Supports:** color (background, button, text), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~
- **Attributes:** className, displayStyle, isPreview, selectType, showCounts, showFilterButton
## Single Product - woocommerce/single-product
@ -1425,7 +1535,7 @@ Enable customers to filter the product grid by stock status.
- **Category:** woocommerce
- **Ancestor:**
- **Parent:**
- **Supports:** color, ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~
- **Supports:** color (background, button, text), ~~html~~, ~~inserter~~, ~~lock~~, ~~multiple~~
- **Attributes:** className, displayStyle, headingLevel, isPreview, selectType, showCounts, showFilterButton
## Store Notices - woocommerce/store-notices

View File

@ -79,7 +79,7 @@
"post_title": "Blocks reference",
"menu_title": "Blocks Reference",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/building-a-woo-store/block-references.md",
"hash": "fee3ebe3c18bb39f0a8e9e1f43fe8153b4b5664913cd50b6e3c078ede151601b",
"hash": "0200411990f2cf1bdb9b0240380779d6777d2e60a7d1920fec1181d6b4281e0e",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/building-a-woo-store/block-references.md",
"id": "1fbe91d7fa4fafaf35f0297e4cee1e7958756aed"
},
@ -451,7 +451,7 @@
"menu_title": "Free shipping customizations",
"tags": "code-snippets",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/code-snippets/free_shipping_customization.md",
"hash": "3cf4bf7ecf4ea4138591ee6fead8268f48619e28ecb2c3b10a8cf7f6ad68ed09",
"hash": "0f5793a00b7e2db6d0862ff4130951ebdd1845bd77237d8e94dc4e63f4fe70e9",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/code-snippets/free_shipping_customization.md",
"id": "cac6f1ccd661588e9a5fa7405643e9c6d4da388e"
},
@ -1915,5 +1915,5 @@
"categories": []
}
],
"hash": "010c87899f8432c25051b7e14b4262763197d04ffa0dc581be95945454acacb3"
"hash": "541fa48f03c824c6b66c26ea761ad690aba0eda3084e9e28b0a8a5d26f5ffbc7"
}

View File

@ -9,30 +9,14 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import type { ProductGallerySettingsProps } from '../types';
import { ProductGalleryThumbnailsBlockSettings } from '../inner-blocks/product-gallery-thumbnails/block-settings';
export const ProductGalleryBlockSettings = ( {
attributes,
setAttributes,
context,
}: ProductGallerySettingsProps ) => {
const { cropImages, hoverZoom, fullScreenOnClick } = attributes;
const {
productGalleryClientId,
thumbnailsNumberOfThumbnails,
thumbnailsPosition,
} = context;
return (
<InspectorControls>
<PanelBody title={ __( 'Gallery Navigation', 'woocommerce' ) }>
<ProductGalleryThumbnailsBlockSettings
context={ {
productGalleryClientId,
thumbnailsNumberOfThumbnails,
thumbnailsPosition,
} }
/>
</PanelBody>
<PanelBody title={ __( 'Media Settings', 'woocommerce' ) }>
<ToggleControl
label={ __( 'Crop images to fit', 'woocommerce' ) }

View File

@ -14,27 +14,12 @@
"textdomain": "woocommerce",
"usesContext": [ "postId" ],
"providesContext": {
"thumbnailsPosition": "thumbnailsPosition",
"thumbnailsNumberOfThumbnails": "thumbnailsNumberOfThumbnails",
"productGalleryClientId": "productGalleryClientId",
"hoverZoom": "hoverZoom",
"fullScreenOnClick": "fullScreenOnClick",
"cropImages": "cropImages"
},
"ancestor": [ "woocommerce/single-product" ],
"attributes": {
"thumbnailsPosition": {
"type": "string",
"default": "left"
},
"thumbnailsNumberOfThumbnails": {
"type": "number",
"default": 3
},
"productGalleryClientId": {
"type": "string",
"default": ""
},
"cropImages": {
"type": "boolean",
"default": false

View File

@ -8,14 +8,12 @@ import {
useInnerBlocksProps,
} from '@wordpress/block-editor';
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { ProductGalleryBlockSettings } from './block-settings/index';
import type { ProductGalleryAttributes } from './types';
import { moveInnerBlocksToPosition } from './utils';
import type { ProductGalleryBlockAttributes } from './types';
const TEMPLATE: InnerBlockTemplate[] = [
[
@ -88,33 +86,17 @@ const TEMPLATE: InnerBlockTemplate[] = [
];
export const Edit = ( {
clientId,
attributes,
setAttributes,
}: BlockEditProps< ProductGalleryAttributes > ) => {
}: BlockEditProps< ProductGalleryBlockAttributes > ) => {
const blockProps = useBlockProps();
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>
<ProductGalleryBlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
context={ {
productGalleryClientId: clientId,
thumbnailsPosition: attributes.thumbnailsPosition,
thumbnailsNumberOfThumbnails:
attributes.thumbnailsNumberOfThumbnails,
} }
/>
</InspectorControls>
<InnerBlocks

View File

@ -6,13 +6,8 @@
"title": "Next/Previous Buttons",
"description": "Display next and previous buttons.",
"category": "woocommerce",
"keywords": [
"WooCommerce"
],
"usesContext": [
"productGalleryClientId",
"postId"
],
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId" ],
"textdomain": "woocommerce",
"supports": {
"layout": {
@ -26,7 +21,5 @@
"allowInheriting": false
}
},
"ancestor": [
"woocommerce/product-gallery-large-image"
]
}
"ancestor": [ "woocommerce/product-gallery-large-image" ]
}

View File

@ -1,109 +1,38 @@
/**
* External dependencies
*/
import { store as blockEditorStore } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import {
thumbnailsPositionLeft,
thumbnailsPositionBottom,
thumbnailsPositionRight,
} from '@woocommerce/icons';
import { useDispatch } from '@wordpress/data';
import {
Icon,
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';
import { RangeControl } from '@wordpress/components';
/**
* Internal dependencies
*/
import { ThumbnailsPosition } from '../constants';
import type { ProductGalleryThumbnailsSettingsProps } from '../../../types';
const positionHelp: Record< ThumbnailsPosition, string > = {
[ ThumbnailsPosition.LEFT ]: __(
'A strip of small images will appear to the left of the main gallery image.',
'woocommerce'
),
[ ThumbnailsPosition.BOTTOM ]: __(
'A strip of small images will appear below the main gallery image.',
'woocommerce'
),
[ ThumbnailsPosition.RIGHT ]: __(
'A strip of small images will appear to the right of the main gallery image.',
'woocommerce'
),
};
import type { ProductGalleryThumbnailsSettingsProps } from '../types';
export const ProductGalleryThumbnailsBlockSettings = ( {
context,
attributes,
setAttributes,
}: ProductGalleryThumbnailsSettingsProps ) => {
const maxNumberOfThumbnails = 8;
const minNumberOfThumbnails = 3;
const { productGalleryClientId } = context;
const { updateBlockAttributes } = useDispatch( blockEditorStore );
const { numberOfThumbnails } = attributes;
return (
<>
<ToggleGroupControl
className="wc-block-editor-product-gallery-thumbnails__position-toggle"
isBlock
label={ __( 'Thumbnails', 'woocommerce' ) }
value={ context.thumbnailsPosition }
help={
positionHelp[
context.thumbnailsPosition as ThumbnailsPosition
]
}
onChange={ ( value: string ) =>
updateBlockAttributes( productGalleryClientId, {
thumbnailsPosition: value,
} )
}
>
<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>
<RangeControl
label={ __( 'Number of Thumbnails', 'woocommerce' ) }
value={ context.thumbnailsNumberOfThumbnails }
onChange={ ( value: number ) =>
updateBlockAttributes( productGalleryClientId, {
thumbnailsNumberOfThumbnails: Math.round( value ),
} )
}
help={ __(
'Choose how many thumbnails (3-8) will display. If more images exist, a “View all” button will display.',
'woocommerce'
) }
max={ maxNumberOfThumbnails }
min={ minNumberOfThumbnails }
step={ 1 }
/>
</>
<RangeControl
label={ __( 'Number of Thumbnails', 'woocommerce' ) }
value={ numberOfThumbnails }
onChange={ ( value: number ) =>
setAttributes( {
numberOfThumbnails: Math.round( value ),
} )
}
help={ __(
'Choose how many thumbnails (3-8) will display. If more images exist, a “View all” button will display.',
'woocommerce'
) }
max={ maxNumberOfThumbnails }
min={ minNumberOfThumbnails }
step={ 1 }
/>
);
};

View File

@ -6,20 +6,16 @@
"title": "Thumbnails",
"description": "Display the Thumbnails of a product.",
"category": "woocommerce",
"keywords": [
"WooCommerce"
],
"usesContext": [
"postId",
"thumbnailsPosition",
"thumbnailsNumberOfThumbnails",
"productGalleryClientId",
"cropImages"
],
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId", "cropImages" ],
"textdomain": "woocommerce",
"ancestor": [
"woocommerce/product-gallery"
],
"ancestor": [ "woocommerce/product-gallery" ],
"attributes": {
"numberOfThumbnails": {
"type": "number",
"default": 3
}
},
"supports": {
"spacing": {
"margin": true,
@ -28,4 +24,4 @@
}
}
}
}
}

View File

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

View File

@ -2,66 +2,50 @@
* External dependencies
*/
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { Disabled, PanelBody } from '@wordpress/components';
import { PanelBody } from '@wordpress/components';
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
import clsx from 'clsx';
import type { BlockEditProps } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import './editor.scss';
import { ProductGalleryThumbnailsBlockSettings } from './block-settings';
import type { ProductGalleryContext } from '../../types';
import type { ProductGalleryThumbnailsBlockAttributes } from './types';
interface EditProps {
context: ProductGalleryContext;
}
export const Edit = ( { context }: EditProps ) => {
export const Edit = ( {
attributes,
setAttributes,
}: BlockEditProps< ProductGalleryThumbnailsBlockAttributes > ) => {
const blockProps = useBlockProps( {
className: clsx(
'wc-block-product-gallery-thumbnails',
`wc-block-product-gallery-thumbnails--number-of-thumbnails-${ context.thumbnailsNumberOfThumbnails }`,
`wc-block-product-gallery-thumbnails--position-${ context.thumbnailsPosition }`
),
className: `wc-block-product-gallery-thumbnails wc-block-product-gallery-thumbnails--number-of-thumbnails-${ attributes.numberOfThumbnails }`,
} );
const Placeholder = () => {
return (
<div className="wc-block-editor-product-gallery-thumbnails">
{ [
...Array( context.thumbnailsNumberOfThumbnails ).keys(),
].map( ( index ) => {
return (
<div { ...blockProps }>
<InspectorControls>
<PanelBody>
<ProductGalleryThumbnailsBlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
/>
</PanelBody>
</InspectorControls>
{ [ ...Array( attributes.numberOfThumbnails ).keys() ].map(
( index ) => {
return (
<div
className="wc-block-product-gallery-thumbnails__thumbnail"
key={ index }
>
<img
className="wc-block-product-gallery-thumbnails__image"
src={ `${ WC_BLOCKS_IMAGE_URL }block-placeholders/product-image-gallery.svg` }
alt="Placeholder"
alt=""
/>
</div>
);
} ) }
</div>
);
};
return (
<>
<div { ...blockProps }>
<InspectorControls>
<PanelBody>
<ProductGalleryThumbnailsBlockSettings
context={ context }
/>
</PanelBody>
</InspectorControls>
<Disabled>
<Placeholder />
</Disabled>
</div>
</>
}
) }
</div>
);
};

View File

@ -1,30 +0,0 @@
$thumbnails: ".wc-block-editor-product-gallery-thumbnails";
$thumbnails-gap: 15px;
#{$thumbnails} {
display: flex;
.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});
}
}

View File

@ -0,0 +1,10 @@
export type ProductGalleryThumbnailsBlockAttributes = {
numberOfThumbnails: number;
};
export type ProductGalleryThumbnailsSettingsProps = {
attributes: ProductGalleryThumbnailsBlockAttributes;
setAttributes: (
attributes: Partial< ProductGalleryThumbnailsBlockAttributes >
) => void;
};

View File

@ -1,17 +1,9 @@
$admin-bar-height: 32px;
$gallery: ".wc-block-product-gallery";
$large-image: "#{$gallery}-large-image";
$thumbnails: "#{$gallery}-thumbnails";
$next-previous: "#{$large-image}-next-previous";
$next-previous-left-off: "#{$next-previous}-left--off";
$next-previous-right-off: "#{$next-previous}-right--off";
$outside-image-offset: 30px;
$outside-image-max-width: calc(100% - (2 * $outside-image-offset));
$thumbnails-gap: 15px;
$default-number-of-thumbnails: 3;
$thumbnails-border-width: 1px;
$dialog-padding: 20px;
// Large Image
#{$large-image} {
.wc-block-product-gallery-large-image {
width: 100%;
height: fit-content;
position: relative;
@ -84,8 +76,7 @@ $default-number-of-thumbnails: 3;
}
}
// Next/Previous Buttons
#{$next-previous} {
.wc-block-product-gallery-large-image-next-previous {
display: flex;
align-items: flex-end;
flex-direction: row;
@ -125,12 +116,6 @@ $default-number-of-thumbnails: 3;
width: 100%;
}
// Next/Previous Buttons Off Setting
#{$next-previous-left-off},
#{$next-previous-right-off} {
display: none;
}
// Next/Previous Buttons Inside the Image Settings
.wc-block-product-gallery-large-image-next-previous-left--inside-image {
margin-left: 15px;
@ -157,100 +142,45 @@ $default-number-of-thumbnails: 3;
}
}
// Thumbnails
#{$thumbnails} {
.wc-block-product-gallery-thumbnails {
display: flex;
gap: $thumbnails-gap;
img {
&__image {
cursor: pointer;
height: auto;
width: auto;
max-width: 100%;
}
#{$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 {
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;
object-fit: contain;
}
&__thumbnail {
border: $thumbnails-border-width solid rgba($color: #000, $alpha: 0.1);
display: flex;
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;
}
}
justify-content: center;
align-items: center;
position: relative;
aspect-ratio: 1 / 1;
}
.wc-block-product-gallery-thumbnails__thumbnail__overlay {
&__thumbnail__overlay {
container-type: inline-size;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
cursor: pointer;
color: #fff;
background-color: rgba(0, 0, 0, 0.4);
top: 0;
width: 100%;
height: 100%;
line-height: 1.5;
.wc-block-product-gallery-thumbnails__thumbnail__remaining-thumbnails-count {
font-size: 1.2rem;
font-weight: 700;
@ -260,11 +190,6 @@ $default-number-of-thumbnails: 3;
text-decoration: underline;
}
.wc-block-product-gallery-thumbnails__thumbnail__remaining-thumbnails-count,
.wc-block-product-gallery-thumbnails__thumbnail__view-all {
color: #fff;
}
@container (width < 70px) {
.wc-block-product-gallery-thumbnails__thumbnail__view-all {
display: none;
@ -301,7 +226,34 @@ $default-number-of-thumbnails: 3;
}
}
$dialog-padding: 20px;
// Automattically react to the Group container settings.
.is-nowrap .wc-block-product-gallery-thumbnails,
.is-horizontal.is-nowrap .wc-block-product-gallery-thumbnails {
flex-direction: column;
@for $i from 3 through 8 {
&.wc-block-product-gallery-thumbnails--number-of-thumbnails-#{$i} {
$gap-width: $thumbnails-gap * ($i - 1);
$border-width: $i * $thumbnails-border-width * 2;
flex-basis: calc((100% - #{$gap-width} - #{$border-width}) / $i);
}
}
}
.is-vertical .wc-block-product-gallery-thumbnails,
.is-horizontal .wc-block-product-gallery-thumbnails {
flex-direction: row;
@for $i from 3 through 8 {
&.wc-block-product-gallery-thumbnails--number-of-thumbnails-#{$i} {
$gap-width: $thumbnails-gap * ($i - 1);
$border-width: $i * $thumbnails-border-width * 2;
flex: 0 0 calc((100% - #{$gap-width} - #{$border-width}) / $i);
}
}
}
body.wc-block-product-gallery-dialog-open {
overflow: hidden;

View File

@ -1,60 +1,12 @@
/**
* Internal dependencies
*/
import { ThumbnailsPosition } from './inner-blocks/product-gallery-thumbnails/constants';
export interface ProductGalleryBlockAttributes {
cropImages?: boolean;
hoverZoom?: boolean;
fullScreenOnClick?: boolean;
}
export interface ProductGalleryThumbnailsBlockAttributes {
thumbnailsPosition: ThumbnailsPosition;
thumbnailsNumberOfThumbnails: number;
productGalleryClientId: string;
}
export interface ProductGalleryBlockEditProps {
clientId: string;
attributes: ProductGalleryThumbnailsBlockAttributes;
setAttributes: (
newAttributes: ProductGalleryThumbnailsBlockAttributes
) => void;
cropImages: boolean;
hoverZoom: boolean;
fullScreenOnClick: boolean;
}
export interface ProductGallerySettingsProps {
attributes: ProductGalleryBlockAttributes;
setAttributes: ( attributes: ProductGalleryBlockAttributes ) => void;
context: ProductGalleryContext;
setAttributes: (
attributes: Partial< ProductGalleryBlockAttributes >
) => void;
}
export interface ProductGalleryThumbnailsSettingsProps {
context: ProductGalleryThumbnailsContext;
}
export type ProductGalleryContext = {
thumbnailsPosition: ThumbnailsPosition;
thumbnailsNumberOfThumbnails: number;
productGalleryClientId: string;
};
export type ProductGalleryPagerContext = Pick<
ProductGalleryContext,
'productGalleryClientId'
>;
export type ProductGalleryLargeImageNextPreviousContext = Pick<
ProductGalleryContext,
'productGalleryClientId'
>;
export type ProductGalleryThumbnailsContext = Pick<
ProductGalleryContext,
| 'productGalleryClientId'
| 'thumbnailsPosition'
| 'thumbnailsNumberOfThumbnails'
>;
export type ProductGalleryAttributes = ProductGalleryThumbnailsBlockAttributes &
ProductGalleryBlockAttributes;

View File

@ -1,178 +0,0 @@
/**
* External dependencies
*/
import { BlockAttributes } from '@wordpress/blocks';
import { select, dispatch } from '@wordpress/data';
import { findBlock } from '@woocommerce/utils';
/**
* Internal dependencies
*/
import { ThumbnailsPosition } from './inner-blocks/product-gallery-thumbnails/constants';
/**
* 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' };
case 'off':
// Stack
return { type: 'flex', orientation: 'vertical' };
default:
// Row
return { type: 'flex', flexWrap: 'nowrap' };
}
};
/**
* 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
);
}
};
/**
* Sets the layout of group block based on the thumbnails' position.
*
* @param {ThumbnailsPosition} thumbnailsPosition - The position of thumbnails.
* @param {string} clientId - The client ID of the block to update.
*/
const setGroupBlockLayoutByThumbnailsPosition = (
thumbnailsPosition: ThumbnailsPosition,
clientId: string
): void => {
const block = select( 'core/block-editor' ).getBlock( clientId );
block?.innerBlocks.forEach( ( innerBlock ) => {
if (
innerBlock.name === 'core/group' &&
innerBlock.attributes.metadata.name === 'Gallery Area'
) {
updateBlockAttributes(
{
layout: getGroupLayoutAttributes( thumbnailsPosition ),
},
innerBlock
);
}
} );
};
/**
* 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 { getBlock, getBlockRootClientId, getBlockIndex } =
select( 'core/block-editor' );
const productGalleryBlock = getBlock( clientId );
if ( productGalleryBlock?.name === 'woocommerce/product-gallery' ) {
const { moveBlockToPosition } = dispatch( 'core/block-editor' );
const previousLayout = productGalleryBlock.innerBlocks.length
? productGalleryBlock.innerBlocks[ 0 ].attributes.layout
: null;
const thumbnailsBlock = findBlock( {
blocks: [ productGalleryBlock ],
findCondition( block ) {
return block.name === 'woocommerce/product-gallery-thumbnails';
},
} );
const largeImageParentBlock = findBlock( {
blocks: [ productGalleryBlock ],
findCondition( block ) {
return Boolean(
block.innerBlocks?.find(
( innerBlock ) =>
innerBlock.name ===
'woocommerce/product-gallery-large-image'
)
);
},
} );
const largeImageParentBlockIndex = getBlockIndex(
largeImageParentBlock?.clientId || ''
);
const thumbnailsBlockIndex = getBlockIndex(
thumbnailsBlock?.clientId || ''
);
if (
largeImageParentBlock &&
thumbnailsBlock &&
largeImageParentBlockIndex !== -1 &&
thumbnailsBlockIndex !== -1
) {
const { thumbnailsPosition } = attributes;
setGroupBlockLayoutByThumbnailsPosition(
thumbnailsPosition,
clientId
);
setGroupBlockLayoutByThumbnailsPosition(
thumbnailsPosition,
productGalleryBlock.innerBlocks[ 0 ].clientId
);
if ( previousLayout ) {
const orientation =
getGroupLayoutAttributes( thumbnailsPosition ).orientation;
updateBlockAttributes(
{
layout: { ...previousLayout, orientation },
},
productGalleryBlock.innerBlocks[ 0 ]
);
}
if (
( ( thumbnailsPosition === 'bottom' ||
thumbnailsPosition === 'right' ) &&
thumbnailsBlockIndex < largeImageParentBlockIndex ) ||
( thumbnailsPosition === 'left' &&
thumbnailsBlockIndex > largeImageParentBlockIndex )
) {
moveBlockToPosition(
thumbnailsBlock.clientId,
getBlockRootClientId( thumbnailsBlock.clientId ) ||
undefined,
getBlockRootClientId( largeImageParentBlock.clientId ) ||
undefined,
largeImageParentBlockIndex
);
}
}
}
};

View File

@ -31,9 +31,6 @@ export { default as removeCart } from './library/remove-cart';
export { default as sparkles } from './library/sparkles';
export { default as stacks } from './library/stacks';
export { default as thumbUp } from './library/thumb-up';
export { default as thumbnailsPositionBottom } from './library/thumbnails-position-bottom';
export { default as thumbnailsPositionLeft } from './library/thumbnails-position-left';
export { default as thumbnailsPositionRight } from './library/thumbnails-position-right';
export { default as toggle } from './library/toggle';
export { default as totals } from './library/totals';
export { default as woo } from './library/woo';

View File

@ -1,33 +0,0 @@
/**
* 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

@ -1,45 +0,0 @@
/**
* 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

@ -1,45 +0,0 @@
/**
* 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

@ -72,74 +72,14 @@ test.describe( 'Product Gallery Thumbnails block', () => {
} );
test.describe( 'settings', () => {
for ( const position of [ 'left', 'bottom', 'right' ] ) {
test( `positions thumbnails to the ${ position }`, async ( {
page,
editor,
} ) => {
const layoutClass = {
left: 'left-of',
bottom: 'below',
right: 'right-of',
}[ position ];
await test.step( 'in editor', async () => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
await editor.selectBlocks( productGalleryBlock );
await editor.openDocumentSettingsSidebar();
await page
.getByLabel( 'Editor settings' )
.locator( `button[data-value="${ position }"]` )
.click();
await expect(
productGalleryBlock.locator(
`[data-type="woocommerce/product-gallery-thumbnails"]:${ layoutClass }(
[data-type="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
await editor.saveSiteEditorEntities( {
isOnlyCurrentEntityDirty: true,
} );
} );
await test.step( 'in frontend', async () => {
await page.goto( '/product/v-neck-t-shirt/' );
const productGalleryBlock = page.locator(
'[data-block-name="woocommerce/product-gallery"]'
);
await expect(
productGalleryBlock.locator(
'[data-block-name="woocommerce/product-gallery-thumbnails"]'
)
).toBeVisible();
await expect(
productGalleryBlock.locator(
`[data-block-name="woocommerce/product-gallery-thumbnails"]:${ layoutClass }(
[data-block-name="woocommerce/product-gallery-large-image"]
)`
)
).toBeVisible();
} );
} );
}
test( 'rounds the number of thumbnails to integer', async ( {
page,
editor,
} ) => {
const productGalleryBlock = editor.canvas.locator(
'[data-type="woocommerce/product-gallery"]'
);
const thumbnailsBlock =
editor.canvas.getByLabel( 'Block: Thumbnails' );
await editor.selectBlocks( productGalleryBlock );
await editor.selectBlocks( thumbnailsBlock );
await editor.openDocumentSettingsSidebar();
const numberOfThumbnailInput = page
@ -151,7 +91,7 @@ test.describe( 'Product Gallery Thumbnails block', () => {
await numberOfThumbnailInput.fill( '4.2' );
await page.keyboard.press( 'Enter' );
const numberOfThumbnailsOnScreen = productGalleryBlock.locator(
const numberOfThumbnailsOnScreen = thumbnailsBlock.locator(
'.wc-block-product-gallery-thumbnails__thumbnail'
);

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Product Gallery (Beta): Remove Thumbnails position settings and rely on Core's layour blocks

View File

@ -115,7 +115,6 @@ class ProductGallery extends AbstractBlock {
$classname_single_image = 'is-single-product-gallery-image';
}
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
$product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
$product_gallery_first_image_id = reset( $product_gallery_first_image );
@ -131,7 +130,7 @@ class ProductGallery extends AbstractBlock {
array(
'selectedImageNumber' => 1,
'isDialogOpen' => false,
'visibleImagesIds' => ProductGalleryUtils::get_product_gallery_image_ids( $product, $number_of_thumbnails, true ),
'visibleImagesIds' => ProductGalleryUtils::get_product_gallery_image_ids( $product, null, true ),
'dialogVisibleImagesIds' => ProductGalleryUtils::get_product_gallery_image_ids( $product, null, false ),
'productId' => $product_id,
'elementThatTriggeredDialogOpening' => null,

View File

@ -29,15 +29,6 @@ class ProductGalleryLargeImageNextPrevious extends AbstractBlock {
return null;
}
/**
* Register the context
*
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'productGalleryClientId' ];
}
/**
* Include and render the block.
*

View File

@ -39,7 +39,7 @@ class ProductGalleryThumbnails extends AbstractBlock {
* @return string[]
*/
protected function get_block_type_uses_context() {
return [ 'productGalleryClientId', 'postId', 'thumbnailsNumberOfThumbnails', 'thumbnailsPosition', 'mode', 'cropImages' ];
return [ 'postId', 'mode', 'cropImages' ];
}
/**
@ -120,7 +120,7 @@ class ProductGalleryThumbnails extends AbstractBlock {
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! isset( $block->context ) || ! isset( $block->context['thumbnailsPosition'] ) || '' === $block->context['thumbnailsPosition'] ) {
if ( ! isset( $block->context ) ) {
return '';
}
@ -146,57 +146,66 @@ class ProductGalleryThumbnails extends AbstractBlock {
$crop_images = $block->context['cropImages'] ?? false;
$product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'full', array(), 'wc-block-product-gallery-thumbnails__thumbnail', $crop_images );
if ( $product_gallery_images && count( $product_gallery_images ) > 1 ) {
$html = '';
$number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) && is_numeric( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3;
$thumbnails_count = 1;
if ( ! $product_gallery_images || count( $product_gallery_images ) <= 1 ) {
return '';
}
foreach ( $product_gallery_images as $product_gallery_image_html ) {
// Limit the number of thumbnails only in the standard mode (and not in dialog).
if ( $this->limit_thumbnails( $thumbnails_count, $number_of_thumbnails ) ) {
break;
}
$html = '';
$default_number_of_thumbnails = 3;
$number_of_thumbnails = isset( $attributes['numberOfThumbnails'] ) && is_numeric( $attributes['numberOfThumbnails'] ) ? $attributes['numberOfThumbnails'] : $default_number_of_thumbnails;
$number_of_images = count( $product_gallery_images );
// If the number of thumbnails is greater than the number of images, set the number of thumbnails to the number of images.
// But not less than than 3 (default number of thumbnails).
$thumbnails_layout = max( min( $number_of_images, $number_of_thumbnails ), $default_number_of_thumbnails );
$number_of_thumbnails_class = 'wc-block-product-gallery-thumbnails--number-of-thumbnails-' . $thumbnails_layout;
$thumbnails_count = 1;
$remaining_thumbnails_count = count( $product_gallery_images ) - $number_of_thumbnails;
// Display view all if this is the last visible thumbnail and there are more images.
if ( $this->should_display_view_all( $thumbnails_count, $product_gallery_images, $number_of_thumbnails ) ) {
$product_gallery_image_html = $this->inject_view_all( $product_gallery_image_html, $this->generate_view_all_html( $remaining_thumbnails_count ) );
}
$processor = new \WP_HTML_Tag_Processor( $product_gallery_image_html );
if ( $processor->next_tag( 'img' ) ) {
$processor->set_attribute( 'data-wc-on--keydown', 'actions.onThumbnailKeyDown' );
$processor->set_attribute( 'tabindex', '0' );
$processor->set_attribute(
'data-wc-on--click',
'actions.selectImage'
);
$html .= $processor->get_updated_html();
} else {
$html .= $product_gallery_image_html;
}
++$thumbnails_count;
foreach ( $product_gallery_images as $product_gallery_image_html ) {
// Limit the number of thumbnails only in the standard mode (and not in dialog).
if ( $this->limit_thumbnails( $thumbnails_count, $number_of_thumbnails ) ) {
break;
}
$allowed_html = wp_kses_allowed_html( 'post' );
$allowed_html['img']['tabindex'] = true;
$remaining_thumbnails_count = $number_of_images - $number_of_thumbnails;
return sprintf(
'<div class="wc-block-product-gallery-thumbnails wp-block-woocommerce-product-gallery-thumbnails %1$s" style="%2$s" data-wc-interactive=\'%4$s\'>
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
wp_kses(
$html,
$allowed_html
),
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP )
);
// Display view all if this is the last visible thumbnail and there are more images.
if ( $this->should_display_view_all( $thumbnails_count, $product_gallery_images, $number_of_thumbnails ) ) {
$product_gallery_image_html = $this->inject_view_all( $product_gallery_image_html, $this->generate_view_all_html( $remaining_thumbnails_count ) );
}
$processor = new \WP_HTML_Tag_Processor( $product_gallery_image_html );
if ( $processor->next_tag( 'img' ) ) {
$processor->add_class( 'wc-block-product-gallery-thumbnails__image' );
$processor->set_attribute( 'data-wc-on--keydown', 'actions.onThumbnailKeyDown' );
$processor->set_attribute( 'tabindex', '0' );
$processor->set_attribute(
'data-wc-on--click',
'actions.selectImage'
);
$html .= $processor->get_updated_html();
} else {
$html .= $product_gallery_image_html;
}
++$thumbnails_count;
}
$allowed_html = wp_kses_allowed_html( 'post' );
$allowed_html['img']['tabindex'] = true;
return sprintf(
'<div class="wc-block-product-gallery-thumbnails wp-block-woocommerce-product-gallery-thumbnails %1$s" style="%2$s" data-wc-interactive=\'%4$s\'>
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] . ' ' . $number_of_thumbnails_class ),
esc_attr( $classes_and_styles['styles'] ),
wp_kses(
$html,
$allowed_html
),
wp_json_encode( array( 'namespace' => 'woocommerce/product-gallery' ), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP )
);
}
}