rebase code on oct-10-2023

This commit is contained in:
Rachit Bhargava
2023-10-10 17:51:46 -04:00
parent b16ad94b69
commit 8f1a2c3a66
2197 changed files with 184921 additions and 35568 deletions
@@ -73,7 +73,7 @@ const ActiveAttributeFilters = ( {
const attributeLabel = attributeObject.label;
const filteringForPhpTemplate = getSettingWithCoercion(
'isRenderingPhpTemplate',
'is_rendering_php_template',
false,
isBoolean
);
@@ -59,7 +59,7 @@ const ActiveFiltersBlock = ( {
const isMounted = useIsMounted();
const componentHasMounted = isMounted();
const filteringForPhpTemplate = getSettingWithCoercion(
'isRenderingPhpTemplate',
'is_rendering_php_template',
false,
isBoolean
);
@@ -323,7 +323,7 @@ const ActiveFiltersBlock = ( {
);
const hasFilterableProducts = getSettingWithCoercion(
'hasFilterableProducts',
'has_filterable_products',
false,
isBoolean
);
@@ -160,6 +160,7 @@
height: 16px;
width: 16px;
line-height: 16px;
padding: 0;
margin: 0 0.5em 0 0;
color: currentColor;
@@ -72,19 +72,19 @@ const AttributeFilterBlock = ( {
getNotice?: GetNotice;
} ) => {
const hasFilterableProducts = getSettingWithCoercion(
'hasFilterableProducts',
'has_filterable_products',
false,
isBoolean
);
const filteringForPhpTemplate = getSettingWithCoercion(
'isRenderingPhpTemplate',
'is_rendering_php_template',
false,
isBoolean
);
const pageUrl = getSettingWithCoercion(
'pageUrl',
'page_url',
window.location.href,
isString
);
@@ -544,6 +544,9 @@ const AttributeFilterBlock = ( {
'single-selection': ! multiple,
'is-loading': isLoading,
} ) }
style={ {
borderStyle: 'none',
} }
suggestions={ displayedOptions
.filter(
( option ) =>
@@ -69,6 +69,7 @@ const Edit = ( {
}: EditProps ) => {
const {
attributeId,
className,
displayStyle,
heading,
headingLevel,
@@ -352,7 +353,6 @@ const Edit = ( {
href={ getAdminLink(
'edit.php?post_type=product&page=product_attributes'
) }
target="_top"
>
{ __( 'Add new attribute', 'woo-gutenberg-products-block' ) +
' ' }
@@ -362,7 +362,6 @@ const Edit = ( {
className="wc-block-attribute-filter__read_more_button"
isTertiary
href="https://docs.woocommerce.com/document/managing-product-taxonomies/"
target="_blank"
>
{ __( 'Learn more', 'woo-gutenberg-products-block' ) }
</Button>
@@ -420,7 +419,12 @@ const Edit = ( {
{ isEditing ? (
renderEditMode()
) : (
<div className={ classnames( 'wc-block-attribute-filter' ) }>
<div
className={ classnames(
className,
'wc-block-attribute-filter'
) }
>
{ heading && (
<BlockTitle
className="wc-block-attribute-filter__title"
@@ -3,6 +3,7 @@
*/
import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps } from '@wordpress/block-editor';
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
import { Icon, category } from '@wordpress/icons';
import classNames from 'classnames';
@@ -26,6 +27,13 @@ registerBlockType( metadata, {
},
supports: {
...metadata.supports,
...( isFeaturePluginBuild() && {
__experimentalBorder: {
radius: false,
color: true,
width: false,
},
} ),
},
attributes: {
...metadata.attributes,
@@ -1,24 +0,0 @@
const expressIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
stroke="#1E1E1E"
strokeLinejoin="round"
strokeWidth="1.5"
d="M18.25 12a6.25 6.25 0 1 1-12.5 0 6.25 6.25 0 0 1 12.5 0Z"
/>
<path fill="#1E1E1E" d="M10 3h4v3h-4z" />
<rect width="1.5" height="5" x="11.25" y="8" fill="#1E1E1E" rx=".75" />
<path
fill="#1E1E1E"
d="m15.7 4.816 1.66 1.078-1.114 1.718-1.661-1.078z"
/>
</svg>
);
export default expressIcon;
@@ -91,7 +91,7 @@ const CheckoutExpressPayment = () => {
headingLevel="2"
>
{ __(
'Express Checkout',
'Express checkout',
'woocommerce'
) }
</Title>
@@ -5,13 +5,18 @@ $border-radius: 5px;
margin: auto;
position: relative;
// nested class to avoid conflict with .editor-styles-wrapper ul
.wc-block-components-express-payment__event-buttons {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr));
grid-gap: 10px;
box-sizing: border-box;
width: 100%;
padding: 0;
margin: 0;
overflow: hidden;
text-align: center;
> li {
margin: 0;
width: 100%;
@@ -22,23 +27,18 @@ $border-radius: 5px;
}
}
}
@include breakpoint("<782px") {
.wc-block-components-express-payment__event-buttons {
grid-template-columns: 1fr;
}
}
}
.wc-block-components-express-payment--checkout {
/* stylelint-disable-next-line function-calc-no-unspaced-operator */
margin-top: calc($border-radius * 3);
.wc-block-components-express-payment__event-buttons {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(calc(33% - 10px), 1fr));
grid-gap: 10px;
@include breakpoint("<782px") {
grid-template-columns: 1fr;
}
}
.wc-block-components-express-payment__title-container {
display: flex;
flex-direction: row;
@@ -4,6 +4,7 @@
import { __ } from '@wordpress/i18n';
import { useEditorContext } from '@woocommerce/base-context';
import { CheckboxControl } from '@woocommerce/blocks-checkout';
import PropTypes from 'prop-types';
import { useSelect, useDispatch } from '@wordpress/data';
import { CHECKOUT_STORE_KEY, PAYMENT_STORE_KEY } from '@woocommerce/block-data';
@@ -22,14 +23,7 @@ import PaymentMethodErrorBoundary from './payment-method-error-boundary';
*
* @return {*} The rendered component.
*/
interface PaymentMethodCardProps {
showSaveOption: boolean;
children: React.ReactNode;
}
const PaymentMethodCard = ( {
children,
showSaveOption,
}: PaymentMethodCardProps ) => {
const PaymentMethodCard = ( { children, showSaveOption } ) => {
const { isEditor } = useEditorContext();
const { shouldSavePaymentMethod, customerId } = useSelect( ( select ) => {
const paymentMethodStore = select( PAYMENT_STORE_KEY );
@@ -50,7 +44,7 @@ const PaymentMethodCard = ( {
className="wc-block-components-payment-methods__save-card-info"
label={ __(
'Save payment information to my account for future purchases.',
'woo-gutenberg-products-block'
'woocommerce'
) }
checked={ shouldSavePaymentMethod }
onChange={ () =>
@@ -64,4 +58,9 @@ const PaymentMethodCard = ( {
);
};
PaymentMethodCard.propTypes = {
showSaveOption: PropTypes.bool,
children: PropTypes.node,
};
export default PaymentMethodCard;
@@ -0,0 +1,68 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import PropTypes from 'prop-types';
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
import { noticeContexts } from '@woocommerce/base-context';
class PaymentMethodErrorBoundary extends Component {
state = { errorMessage: '', hasError: false };
static getDerivedStateFromError( error ) {
return {
errorMessage: error.message,
hasError: true,
};
}
render() {
const { hasError, errorMessage } = this.state;
const { isEditor } = this.props;
if ( hasError ) {
let errorText = __(
'We are experiencing difficulties with this payment method. Please contact us for assistance.',
'woocommerce'
);
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
if ( errorMessage ) {
errorText = errorMessage;
} else {
errorText = __(
"There was an error with this payment method. Please verify it's configured correctly.",
'woocommerce'
);
}
}
const notices = [
{
id: '0',
content: errorText,
isDismissible: false,
status: 'error',
},
];
return (
<StoreNoticesContainer
additionalNotices={ notices }
context={ noticeContexts.PAYMENTS }
/>
);
}
return this.props.children;
}
}
PaymentMethodErrorBoundary.propTypes = {
isEditor: PropTypes.bool,
};
PaymentMethodErrorBoundary.defaultProps = {
isEditor: false,
};
export default PaymentMethodErrorBoundary;
@@ -1,52 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
import { noticeContexts } from '@woocommerce/base-context';
import { NoticeType } from '@woocommerce/types';
interface PaymentMethodErrorBoundaryProps {
isEditor: boolean;
children: React.ReactNode;
}
const PaymentMethodErrorBoundary = ( {
isEditor,
children,
}: PaymentMethodErrorBoundaryProps ) => {
const [ errorMessage ] = useState( '' );
const [ hasError ] = useState( false );
if ( hasError ) {
let errorText = __(
'We are experiencing difficulties with this payment method. Please contact us for assistance.',
'woo-gutenberg-products-block'
);
if ( isEditor || CURRENT_USER_IS_ADMIN ) {
if ( errorMessage ) {
errorText = errorMessage;
} else {
errorText = __(
"There was an error with this payment method. Please verify it's configured correctly.",
'woo-gutenberg-products-block'
);
}
}
const notices: NoticeType[] = [
{
id: '0',
content: errorText,
isDismissible: false,
status: 'error',
},
];
return (
<StoreNoticesContainer
additionalNotices={ notices }
context={ noticeContexts.PAYMENTS }
/>
);
}
return <>{ children }</>;
};
export default PaymentMethodErrorBoundary;
@@ -42,8 +42,8 @@ export const Edit = ( { attributes, setAttributes }: Props ): JSX.Element => {
onChange={ ( value ) =>
setAttributes( { columns: value } )
}
min={ getSetting( 'minColumns', 1 ) }
max={ getSetting( 'maxColumns', 6 ) }
min={ getSetting( 'min_columns', 1 ) }
max={ getSetting( 'max_columns', 6 ) }
/>
</PanelBody>
</InspectorControls>
@@ -2,7 +2,7 @@
"name": "woocommerce/cart-express-payment-block",
"version": "1.0.0",
"title": "Express Checkout",
"description": "Allow customers to breeze through with quick payment options.",
"description": "Provide an express payment option for your customers.",
"category": "woocommerce",
"supports": {
"align": false,
@@ -1,21 +1,19 @@
/**
* External dependencies
*/
import { Icon } from '@wordpress/icons';
import { Icon, payment } from '@wordpress/icons';
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { Edit, Save } from './edit';
import expressIcon from '../../../cart-checkout-shared/icon';
registerBlockType( 'woocommerce/cart-express-payment-block', {
icon: {
src: (
<Icon
style={ { fill: 'none' } } // this is needed for this particular svg
icon={ expressIcon }
icon={ payment }
className="wc-block-editor-components-block-icon"
/>
),
@@ -1,13 +1,7 @@
/**
* External dependencies
*/
import {
useMemo,
useEffect,
Fragment,
useState,
useCallback,
} from '@wordpress/element';
import { useMemo, useEffect, Fragment, useState } from '@wordpress/element';
import {
useCheckoutAddress,
useStoreEvents,
@@ -93,23 +87,6 @@ const Block = ( {
showApartmentField,
] ) as Record< keyof AddressFields, Partial< AddressField > >;
const onChangeAddress = useCallback(
( values: Partial< BillingAddress > ) => {
setBillingAddress( values );
if ( useBillingAsShipping ) {
setShippingAddress( values );
dispatchCheckoutEvent( 'set-shipping-address' );
}
dispatchCheckoutEvent( 'set-billing-address' );
},
[
dispatchCheckoutEvent,
setBillingAddress,
setShippingAddress,
useBillingAsShipping,
]
);
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
const noticeContext = useBillingAsShipping
? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ]
@@ -121,7 +98,14 @@ const Block = ( {
<AddressForm
id="billing"
type="billing"
onChange={ onChangeAddress }
onChange={ ( values: Partial< BillingAddress > ) => {
setBillingAddress( values );
if ( useBillingAsShipping ) {
setShippingAddress( values );
dispatchCheckoutEvent( 'set-shipping-address' );
}
dispatchCheckoutEvent( 'set-billing-address' );
} }
values={ billingAddress }
fields={
Object.keys(
@@ -2,7 +2,7 @@
"name": "woocommerce/checkout-express-payment-block",
"version": "1.0.0",
"title": "Express Checkout",
"description": "Allow customers to breeze through with quick payment options.",
"description": "Provide an express payment option for your customers.",
"category": "woocommerce",
"supports": {
"align": false,
@@ -1,21 +1,19 @@
/**
* External dependencies
*/
import { Icon } from '@wordpress/icons';
import { Icon, payment } from '@wordpress/icons';
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import expressIcon from '../../../cart-checkout-shared/icon';
import { Edit, Save } from './edit';
registerBlockType( 'woocommerce/checkout-express-payment-block', {
icon: {
src: (
<Icon
style={ { fill: 'none' } } // this is needed for this particular svg
icon={ expressIcon }
icon={ payment }
className="wc-block-editor-components-block-icon"
/>
),
@@ -15,15 +15,23 @@
.wc-block-checkout__shipping-fields,
.wc-block-checkout__billing-fields {
.wc-block-components-address-form {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-left: #{-$gap-small * 0.5};
margin-right: #{-$gap-small * 0.5};
&::after {
content: "";
clear: both;
display: block;
}
.wc-block-components-text-input,
.wc-block-components-country-input,
.wc-block-components-state-input {
flex: 0 0 calc(50% - #{$gap-small});
box-sizing: border-box;
float: left;
margin-left: #{$gap-small * 0.5};
margin-right: #{$gap-small * 0.5};
position: relative;
width: calc(50% - #{$gap-small});
&:nth-of-type(2),
&:first-of-type {
@@ -34,7 +42,11 @@
.wc-block-components-address-form__company,
.wc-block-components-address-form__address_1,
.wc-block-components-address-form__address_2 {
flex: 0 0 100%;
width: calc(100% - #{$gap-small});
}
.wc-block-components-checkbox {
clear: both;
}
}
}
@@ -2,13 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
useMemo,
useEffect,
Fragment,
useState,
useCallback,
} from '@wordpress/element';
import { useMemo, useEffect, Fragment, useState } from '@wordpress/element';
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
import {
useCheckoutAddress,
@@ -109,24 +103,6 @@ const Block = ( {
showApartmentField,
] ) as Record< keyof AddressFields, Partial< AddressField > >;
const onChangeAddress = useCallback(
( values: Partial< ShippingAddress > ) => {
setShippingAddress( values );
if ( useShippingAsBilling ) {
setBillingAddress( { ...values, email } );
dispatchCheckoutEvent( 'set-billing-address' );
}
dispatchCheckoutEvent( 'set-shipping-address' );
},
[
dispatchCheckoutEvent,
email,
setBillingAddress,
setShippingAddress,
useShippingAsBilling,
]
);
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
const noticeContext = useShippingAsBilling
? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ]
@@ -139,7 +115,14 @@ const Block = ( {
<AddressForm
id="shipping"
type="shipping"
onChange={ onChangeAddress }
onChange={ ( values: Partial< ShippingAddress > ) => {
setShippingAddress( values );
if ( useShippingAsBilling ) {
setBillingAddress( { ...values, email } );
dispatchCheckoutEvent( 'set-billing-address' );
}
dispatchCheckoutEvent( 'set-shipping-address' );
} }
values={ shippingAddress }
fields={
Object.keys(
@@ -8,15 +8,15 @@ import {
} from '@wordpress/blocks';
import { isWpVersion } from '@woocommerce/settings';
import { __, sprintf } from '@wordpress/i18n';
import {
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
PRODUCT_QUERY_VARIATION_NAME as productsVariationName,
} from '@woocommerce/blocks/product-query/constants';
/**
* Internal dependencies
*/
import {
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
} from '../product-query/constants';
import { VARIATION_NAME as productsVariationName } from '../product-query/variations/product-query';
import { createArchiveTitleBlock, createRowBlock } from './utils';
import { OnClickCallbackParameter, type InheritedAttributes } from './types';
@@ -9,15 +9,15 @@ import {
} from '@wordpress/blocks';
import { isWpVersion } from '@woocommerce/settings';
import { __, sprintf } from '@wordpress/i18n';
import {
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
PRODUCT_QUERY_VARIATION_NAME as productsVariationName,
} from '@woocommerce/blocks/product-query/constants';
/**
* Internal dependencies
*/
import {
INNER_BLOCKS_TEMPLATE as productsInnerBlocksTemplate,
QUERY_DEFAULT_ATTRIBUTES as productsQueryDefaultAttributes,
} from '../product-query/constants';
import { VARIATION_NAME as productsVariationName } from '../product-query/variations/product-query';
import { createArchiveTitleBlock, createRowBlock } from './utils';
import { OnClickCallbackParameter, type InheritedAttributes } from './types';
@@ -6,24 +6,36 @@ import { getTemplateDetailsBySlug } from '../utils';
describe( 'getTemplateDetailsBySlug', function () {
it( 'should return single-product object when given an exact match', () => {
expect(
getTemplateDetailsBySlug( 'single-product', TEMPLATES )
).toBeTruthy();
expect(
getTemplateDetailsBySlug( 'single-product', TEMPLATES )
).toStrictEqual( TEMPLATES[ 'single-product' ] );
} );
it( 'should return single-product object when given a partial match', () => {
expect(
getTemplateDetailsBySlug( 'single-product-hoodie', TEMPLATES )
).toBeTruthy();
expect(
getTemplateDetailsBySlug( 'single-product-hoodie', TEMPLATES )
).toStrictEqual( TEMPLATES[ 'single-product' ] );
} );
it( 'should return taxonomy-product object when given a partial match', () => {
expect(
getTemplateDetailsBySlug( 'taxonomy-product_tag', TEMPLATES )
).toBeTruthy();
expect(
getTemplateDetailsBySlug( 'taxonomy-product_tag', TEMPLATES )
).toStrictEqual( TEMPLATES[ 'taxonomy-product_tag' ] );
} );
it( 'should return taxonomy-product object when given an exact match', () => {
expect(
getTemplateDetailsBySlug( 'taxonomy-product_brands', TEMPLATES )
).toBeTruthy();
expect(
getTemplateDetailsBySlug( 'taxonomy-product_brands', TEMPLATES )
).toStrictEqual( TEMPLATES[ 'taxonomy-product' ] );
@@ -11,6 +11,7 @@ import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
import { crop } from '@wordpress/icons';
import { WP_REST_API_Category } from 'wp-types';
import { ProductResponseItem } from '@woocommerce/types';
import TextToolbarButton from '@woocommerce/editor-components/text-toolbar-button';
import type { ComponentType, Dispatch, SetStateAction } from 'react';
import type { BlockAlignment } from '@wordpress/blocks';
@@ -111,13 +112,13 @@ export const BlockControls = ( {
allowedTypes={ [ 'image' ] }
/>
{ backgroundImageId && mediaSrc ? (
<ToolbarButton
<TextToolbarButton
onClick={ () =>
setAttributes( { mediaId: 0, mediaSrc: '' } )
}
>
{ __( 'Reset', 'woo-gutenberg-products-block' ) }
</ToolbarButton>
</TextToolbarButton>
) : null }
</ToolbarGroup>
<ToolbarGroup
@@ -36,10 +36,6 @@ const CONTENT_CONFIG = {
'No product category is selected.',
'woo-gutenberg-products-block'
),
noSelectionButtonLabel: __(
'Select a category',
'woo-gutenberg-products-block'
),
};
const EDIT_MODE_CONFIG = {
@@ -36,10 +36,6 @@ const CONTENT_CONFIG = {
'No product is selected.',
'woo-gutenberg-products-block'
),
noSelectionButtonLabel: __(
'Select a product',
'woo-gutenberg-products-block'
),
};
const EDIT_MODE_CONFIG = {
@@ -64,7 +64,7 @@ export function register(
*/
minHeight: {
type: 'number',
default: getSetting( 'defaultHeight', 500 ),
default: getSetting( 'default_height', 500 ),
},
},
supports: {
@@ -100,7 +100,7 @@ export function register(
editMode: false,
hasParallax: false,
isRepeated: false,
height: getSetting( 'defaultHeight', 500 ),
height: getSetting( 'default_height', 500 ),
mediaSrc: '',
overlayColor: '#000000',
showDesc: true,
@@ -27,7 +27,6 @@ import {
interface WithFeaturedItemConfig extends GenericBlockUIConfig {
emptyMessage: string;
noSelectionButtonLabel: string;
}
export interface FeaturedItemRequiredAttributes {
@@ -45,7 +44,6 @@ export interface FeaturedItemRequiredAttributes {
overlayGradient: string;
showDesc: boolean;
showPrice: boolean;
editMode: boolean;
}
interface FeaturedCategoryRequiredAttributes
@@ -94,12 +92,7 @@ type FeaturedItemProps< T extends EditorBlock< T > > =
| ( T & FeaturedProductProps< T > );
export const withFeaturedItem =
( {
emptyMessage,
icon,
label,
noSelectionButtonLabel,
}: WithFeaturedItemConfig ) =>
( { emptyMessage, icon, label }: WithFeaturedItemConfig ) =>
< T extends EditorBlock< T > >( Component: ComponentType< T > ) =>
( props: FeaturedItemProps< T > ) => {
const [ isEditingImage ] = props.useEditingImage;
@@ -147,29 +140,13 @@ export const withFeaturedItem =
);
};
const renderNoItemButton = () => {
return (
<>
<p>{ emptyMessage }</p>
<div style={ { flexBasis: '100%', height: '0' } }></div>
<button
type="button"
className="components-button is-secondary"
onClick={ () => setAttributes( { editMode: true } ) }
>
{ noSelectionButtonLabel }
</button>
</>
);
};
const renderNoItem = () => (
<Placeholder
className={ className }
icon={ <Icon icon={ icon } /> }
label={ label }
>
{ isLoading ? <Spinner /> : renderNoItemButton() }
{ isLoading ? <Spinner /> : emptyMessage }
</Placeholder>
);
@@ -25,7 +25,7 @@ registerBlockType( metadata, {
...metadata.attributes,
columns: {
type: 'number',
default: getSetting( 'defaultColumns', 3 ),
default: getSetting( 'default_columns', 3 ),
},
},
@@ -32,8 +32,8 @@ export const HandpickedProductsInspectorControls = (
onChange={ ( value ) =>
setAttributes( { columns: value } )
}
min={ getSetting( 'minColumns', 1 ) }
max={ getSetting( 'maxColumns', 6 ) }
min={ getSetting( 'min_columns', 1 ) }
max={ getSetting( 'max_columns', 6 ) }
/>
<ToggleControl
label={ __(
@@ -1,65 +0,0 @@
{
"name": "woocommerce/mini-cart",
"version": "1.0.0",
"title": "Mini-Cart",
"icon": "miniCartAlt",
"description": "Display a button for shoppers to quickly view their cart.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"supports": {
"html": false,
"multiple": false,
"typography": {
"fontSize": true
}
},
"example": {
"attributes": {
"isPreview": true,
"className": "wc-block-mini-cart--preview"
}
},
"attributes": {
"isPreview": {
"type": "boolean",
"default": false
},
"miniCartIcon": {
"type": "string",
"default": "cart"
},
"addToCartBehaviour": {
"type": "string",
"default": "none"
},
"hasHiddenPrice": {
"type": "boolean",
"default": false
},
"cartAndCheckoutRenderStyle": {
"type": "string",
"default": "hidden"
},
"priceColor": {
"type": "object"
},
"priceColorValue": {
"type": "string"
},
"iconColor": {
"type": "object"
},
"iconColorValue": {
"type": "string"
},
"productCountColor": {
"type": "object"
},
"productCountColorValue": {
"type": "string"
}
},
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
@@ -41,7 +41,6 @@ import {
blockName,
attributes as miniCartContentsAttributes,
} from './mini-cart-contents/attributes';
import { defaultColorItem } from './utils/defaults';
type Props = BlockAttributes;
@@ -59,9 +58,9 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
miniCartIcon,
addToCartBehaviour = 'none',
hasHiddenPrice = false,
priceColor = defaultColorItem,
iconColor = defaultColorItem,
productCountColor = defaultColorItem,
priceColorValue,
iconColorValue,
productCountColorValue,
} = attributes;
const {
@@ -260,7 +259,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
{ ! hasHiddenPrice && (
<span
className="wc-block-mini-cart__amount"
style={ { color: priceColor.color } }
style={ { color: priceColorValue } }
>
{ formatPrice(
subTotal,
@@ -271,7 +270,7 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
{ taxLabel !== '' && subTotal !== 0 && ! hasHiddenPrice && (
<small
className="wc-block-mini-cart__tax-label"
style={ { color: priceColor.color } }
style={ { color: priceColorValue } }
>
{ taxLabel }
</small>
@@ -279,8 +278,8 @@ const MiniCartBlock = ( attributes: Props ): JSX.Element => {
<QuantityBadge
count={ cartItemsCount }
icon={ miniCartIcon }
iconColor={ iconColor }
productCountColor={ productCountColor }
iconColor={ iconColorValue }
productCountColor={ productCountColorValue }
/>
</button>
<Drawer
@@ -50,15 +50,9 @@ const renderMiniCartFrontend = () => {
miniCartIcon: el.dataset.miniCartIcon,
addToCartBehaviour: el.dataset.addToCartBehaviour || 'none',
hasHiddenPrice: el.dataset.hasHiddenPrice,
priceColor: el.dataset.priceColor
? JSON.parse( el.dataset.priceColor )
: {},
iconColor: el.dataset.iconColor
? JSON.parse( el.dataset.iconColor )
: {},
productCountColor: el.dataset.productCountColor
? JSON.parse( el.dataset.productCountColor )
: {},
priceColorValue: el.dataset.priceColorValue,
iconColorValue: el.dataset.iconColorValue,
productCountColorValue: el.dataset.productCountColorValue,
contents:
el.querySelector( '.wc-block-mini-cart__template-part' )
?.innerHTML ?? '',
@@ -1,7 +1,13 @@
/**
* External dependencies
*/
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import {
InspectorControls,
useBlockProps,
withColors,
__experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown,
__experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
} from '@wordpress/block-editor';
import { formatPrice } from '@woocommerce/price-format';
import {
PanelBody,
@@ -17,28 +23,28 @@ import Noninteractive from '@woocommerce/base-components/noninteractive';
import { isSiteEditorPage } from '@woocommerce/utils';
import type { ReactElement } from 'react';
import { select } from '@wordpress/data';
import classNames from 'classnames';
import { cartOutline, bag, bagAlt } from '@woocommerce/icons';
import { Icon } from '@wordpress/icons';
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
import { ColorPanel } from '@woocommerce/editor-components/color-panel';
import type { ColorPaletteOption } from '@woocommerce/editor-components/color-panel/types';
/**
* Internal dependencies
*/
import QuantityBadge from './quantity-badge';
import { defaultColorItem } from './utils/defaults';
import { migrateAttributesToColorPanel } from './utils/data';
import './editor.scss';
export interface Attributes {
interface Attributes {
miniCartIcon: 'cart' | 'bag' | 'bag-alt';
addToCartBehaviour: string;
hasHiddenPrice: boolean;
cartAndCheckoutRenderStyle: boolean;
priceColor: ColorPaletteOption;
iconColor: ColorPaletteOption;
productCountColor: ColorPaletteOption;
priceColor: string;
iconColor: string;
productCountColor: string;
priceColorValue: string;
iconColorValue: string;
productCountColorValue: string;
}
interface Props {
@@ -50,36 +56,33 @@ interface Props {
setProductCountColor: ( colorValue: string | undefined ) => void;
}
const Edit = ( { attributes, setAttributes }: Props ): ReactElement => {
const Edit = ( {
attributes,
setAttributes,
clientId,
setPriceColor,
setIconColor,
setProductCountColor,
}: Props ): ReactElement => {
const {
cartAndCheckoutRenderStyle,
addToCartBehaviour,
hasHiddenPrice,
priceColor = defaultColorItem,
iconColor = defaultColorItem,
productCountColor = defaultColorItem,
priceColorValue,
iconColorValue,
productCountColorValue,
miniCartIcon,
} = migrateAttributesToColorPanel( attributes );
} = attributes;
const miniCartColorAttributes = {
priceColor: {
label: __( 'Price', 'woo-gutenberg-products-block' ),
context: 'price-color',
},
iconColor: {
label: __( 'Icon', 'woo-gutenberg-products-block' ),
context: 'icon-color',
},
productCountColor: {
label: __( 'Product Count', 'woo-gutenberg-products-block' ),
context: 'product-count-color',
},
};
const blockProps = useBlockProps( {
className: 'wc-block-mini-cart',
const className = classNames( {
'wc-block-mini-cart': true,
'has-price-color': priceColorValue,
'has-icon-color': iconColorValue,
'has-product-count-color': productCountColorValue,
} );
const blockProps = useBlockProps( { className } );
const isSiteEditor = isSiteEditorPage( select( 'core/edit-site' ) );
const templatePartEditUri = getSetting(
@@ -89,6 +92,48 @@ const Edit = ( { attributes, setAttributes }: Props ): ReactElement => {
const productCount = 0;
const productTotal = 0;
const colorGradientSettings = useMultipleOriginColorsAndGradients();
const colorSettings = [
{
value: priceColorValue,
onChange: ( colorValue: string ) => {
setPriceColor( colorValue );
setAttributes( { priceColorValue: colorValue } );
},
label: __( 'Price', 'woo-gutenberg-products-block' ),
resetAllFilter: () => {
setPriceColor( undefined );
setAttributes( { priceColorValue: undefined } );
},
},
{
value: iconColorValue,
onChange: ( colorValue: string ) => {
setIconColor( colorValue );
setAttributes( { iconColorValue: colorValue } );
},
label: __( 'Icon', 'woo-gutenberg-products-block' ),
resetAllFilter: () => {
setIconColor( undefined );
setAttributes( { iconColorValue: undefined } );
},
},
{
value: productCountColorValue,
onChange: ( colorValue: string ) => {
setProductCountColor( colorValue );
setAttributes( { productCountColorValue: colorValue } );
},
label: __( 'Product count', 'woo-gutenberg-products-block' ),
resetAllFilter: () => {
setProductCountColor( undefined );
setAttributes( { productCountColorValue: undefined } );
},
},
];
return (
<div { ...blockProps }>
<InspectorControls>
@@ -242,21 +287,45 @@ const Edit = ( { attributes, setAttributes }: Props ): ReactElement => {
</BaseControl>
</PanelBody>
</InspectorControls>
<ColorPanel colorTypes={ miniCartColorAttributes } />
{ colorGradientSettings.hasColorsOrGradients && (
// @ts-to-do: Fix outdated InspectorControls type definitions in DefinitelyTyped and/or Gutenberg.
<InspectorControls group="color">
{ colorSettings.map(
( { onChange, label, value, resetAllFilter } ) => (
<ColorGradientSettingsDropdown
key={ `mini-cart-color-${ label }` }
__experimentalIsRenderedInSidebar
settings={ [
{
colorValue: value,
label,
onColorChange: onChange,
isShownByDefault: true,
resetAllFilter,
enableAlpha: true,
},
] }
panelId={ clientId }
{ ...colorGradientSettings }
/>
)
) }
</InspectorControls>
) }
<Noninteractive>
<button className="wc-block-mini-cart__button">
{ ! hasHiddenPrice && (
<span
className="wc-block-mini-cart__amount"
style={ { color: priceColor.color } }
style={ { color: priceColorValue } }
>
{ formatPrice( productTotal ) }
</span>
) }
<QuantityBadge
count={ productCount }
iconColor={ iconColor }
productCountColor={ productCountColor }
iconColor={ iconColorValue }
productCountColor={ productCountColorValue }
icon={ miniCartIcon }
/>
</button>
@@ -265,4 +334,16 @@ const Edit = ( { attributes, setAttributes }: Props ): ReactElement => {
);
};
export default Edit;
const miniCartColorAttributes = {
priceColor: 'price-color',
iconColor: 'icon-color',
productCountColor: 'product-count-color',
};
// @ts-expect-error: TypeScript doesn't resolve the shared React dependency and cannot resolve the type returned by `withColors`.
// Similar issue example: https://github.com/microsoft/TypeScript/issues/47663
const EditWithColors: JSX.Element = withColors( miniCartColorAttributes )(
Edit
);
export default EditWithColors;
@@ -1,30 +1,22 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { miniCartAlt } from '@woocommerce/icons';
import { Icon } from '@wordpress/icons';
import { registerBlockType } from '@wordpress/blocks';
import type { BlockConfiguration } from '@wordpress/blocks';
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
import { addFilter } from '@wordpress/hooks';
/**
* Internal dependencies
*/
import metadata from './block.json';
import edit from './edit';
import './style.scss';
const featurePluginSupport = {
...metadata.supports,
...( isFeaturePluginBuild() && {
typography: {
...metadata.supports.typography,
__experimentalFontFamily: true,
__experimentalFontWeight: true,
},
} ),
};
registerBlockType( metadata, {
const settings: BlockConfiguration = {
apiVersion: 2,
title: __( 'Mini-Cart', 'woo-gutenberg-products-block' ),
icon: {
src: (
<Icon
@@ -33,20 +25,81 @@ registerBlockType( metadata, {
/>
),
},
category: 'woocommerce',
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
description: __(
'Display a button for shoppers to quickly view their cart.',
'woo-gutenberg-products-block'
),
providesContext: {
priceColorValue: 'priceColorValue',
iconColorValue: 'iconColorValue',
productCountColorValue: 'productCountColorValue',
},
supports: {
...featurePluginSupport,
html: false,
multiple: false,
typography: {
fontSize: true,
...( isFeaturePluginBuild() && {
__experimentalFontFamily: true,
__experimentalFontWeight: true,
} ),
},
},
example: {
...metadata.example,
attributes: {
isPreview: true,
className: 'wc-block-mini-cart--preview',
},
},
attributes: {
...metadata.attributes,
isPreview: {
type: 'boolean',
default: false,
},
miniCartIcon: {
type: 'string',
default: 'cart',
},
addToCartBehaviour: {
type: 'string',
default: 'none',
},
hasHiddenPrice: {
type: 'boolean',
default: false,
},
cartAndCheckoutRenderStyle: {
type: 'string',
default: 'hidden',
},
priceColor: {
type: 'string',
},
priceColorValue: {
type: 'string',
},
iconColor: {
type: 'string',
},
iconColorValue: {
type: 'string',
},
productCountColor: {
type: 'string',
},
productCountColorValue: {
type: 'string',
},
},
edit,
save() {
return null;
},
} );
};
registerBlockType( 'woocommerce/mini-cart', settings );
// Remove the Mini Cart template part from the block inserter.
addFilter(
@@ -58,7 +111,7 @@ addFilter(
...blockSettings,
variations: blockSettings.variations.map(
( variation: { name: string } ) => {
if ( variation.name === 'mini-cart' ) {
if ( variation.name === 'instance_mini-cart' ) {
return {
...variation,
scope: [],
@@ -47,7 +47,18 @@ const Edit = ( {
}: Props ): ReactElement => {
const { currentView, width } = attributes;
const blockProps = useBlockProps();
const blockProps = useBlockProps( {
/**
* This is a workaround for the Site Editor to calculate the
* correct height of the Mini-Cart template part on the first load.
*
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5825
*/
style: {
minHeight: '100vh',
width,
},
} );
const defaultTemplate = [
[ 'woocommerce/filled-mini-cart-contents-block', {}, [] ],
@@ -88,12 +88,8 @@
}
}
/* Site Editor preview */
.block-editor-block-preview__content-iframe .editor-styles-wrapper {
.wp-block-woocommerce-mini-cart-contents,
.wp-block-woocommerce-filled-mini-cart-contents-block,
.wp-block-woocommerce-empty-mini-cart-contents-block {
height: 800px;
min-height: none;
}
.editor-styles-wrapper .wp-block-woocommerce-mini-cart-contents,
.editor-styles-wrapper .wp-block-woocommerce-filled-mini-cart-contents-block {
height: auto;
min-height: 500px;
}
@@ -29,7 +29,6 @@ const Block = ( {
<Button
className={ classNames(
className,
'wp-block-button__link',
'wc-block-mini-cart__shopping-button'
) }
variant={ getVariant( className, 'contained' ) }
@@ -8,13 +8,13 @@ import { Icon } from '@wordpress/icons';
* Internal dependencies
*/
import './style.scss';
import { IconType, ColorItem } from '.././types';
import { IconType } from '.././types';
interface Props {
count: number;
icon?: IconType;
iconColor: ColorItem | { color: undefined };
productCountColor: ColorItem | { color: undefined };
iconColor?: string;
productCountColor?: string;
}
const QuantityBadge = ( {
@@ -40,13 +40,13 @@ const QuantityBadge = ( {
<span className="wc-block-mini-cart__quantity-badge">
<Icon
className="wc-block-mini-cart__icon"
color={ iconColor.color }
color={ iconColor }
size={ 20 }
icon={ getIcon( icon ) }
/>
<span
className="wc-block-mini-cart__badge"
style={ { background: productCountColor.color } }
style={ { background: productCountColor } }
>
{ count > 0 ? count : '' }
</span>
@@ -5,12 +5,6 @@ import { CartResponseTotals } from '@woocommerce/types';
export type IconType = 'cart' | 'bag' | 'bag-alt' | undefined;
export interface ColorItem {
color: string;
name?: string;
slug?: string;
class?: string;
}
export interface BlockAttributes {
initialCartItemsCount?: number;
initialCartTotals?: CartResponseTotals;
@@ -21,7 +15,7 @@ export interface BlockAttributes {
miniCartIcon?: IconType;
addToCartBehaviour: string;
hasHiddenPrice: boolean;
priceColor: ColorItem;
iconColor: ColorItem;
productCountColor: ColorItem;
priceColorValue: string;
iconColorValue: string;
productCountColorValue: string;
}
@@ -12,12 +12,6 @@ import {
isBoolean,
} from '@woocommerce/types';
import { getSettingWithCoercion } from '@woocommerce/settings';
import type { ColorPaletteOption } from '@woocommerce/editor-components/color-panel/types';
/**
* Internal dependencies
*/
import { Attributes } from '../edit';
const getPrice = ( totals: CartResponseTotals, showIncludingTax: boolean ) => {
const currency = getCurrencyFromPriceResponse( totals );
@@ -158,45 +152,3 @@ export const getMiniCartTotalsFromServer = async (): Promise<
return undefined;
} );
};
interface MaybeInCompatibleAttributes
extends Omit<
Attributes,
'priceColor' | 'iconColor' | 'productCountColor'
> {
priceColorValue?: string;
iconColorValue?: string;
productCountColorValue?: string;
priceColor: Partial< ColorPaletteOption > | string;
iconColor: Partial< ColorPaletteOption > | string;
productCountColor: Partial< ColorPaletteOption > | string;
}
export function migrateAttributesToColorPanel(
attributes: MaybeInCompatibleAttributes
): Attributes {
const attrs = { ...attributes };
if ( attrs.priceColorValue && ! attrs.priceColor ) {
attrs.priceColor = {
color: attributes.priceColorValue as string,
};
delete attrs.priceColorValue;
}
if ( attrs.iconColorValue && ! attrs.iconColor ) {
attrs.iconColor = {
color: attributes.iconColorValue as string,
};
delete attrs.iconColorValue;
}
if ( attrs.productCountColorValue && ! attrs.productCountColor ) {
attrs.productCountColor = {
color: attributes.productCountColorValue as string,
};
delete attrs.productCountColorValue;
}
return <Attributes>attrs;
}
@@ -1,5 +0,0 @@
export const defaultColorItem = {
name: undefined,
color: undefined,
slug: undefined,
};
@@ -12,7 +12,6 @@ import {
getMiniCartTotalsFromLocalStorage,
getMiniCartTotalsFromServer,
updateTotals,
migrateAttributesToColorPanel,
} from '../data';
// This is a simplified version of the response of the Cart API endpoint.
@@ -207,34 +206,3 @@ describe( 'Mini-Cart frontend script when "the display prices during cart and ch
jest.restoreAllMocks();
} );
} );
const mockAttributes = {
miniCartIcon: 'cart',
addToCartBehaviour: 'inline',
hasHiddenPrice: false,
cartAndCheckoutRenderStyle: true,
priceColorValue: '#000000',
iconColorValue: '#ffffff',
productCountColorValue: '#ff0000',
};
describe( 'migrateAttributesToColorPanel tests', () => {
test( 'it correctly migrates attributes to color panel', () => {
const migratedAttributes =
migrateAttributesToColorPanel( mockAttributes );
expect( migratedAttributes ).toEqual( {
miniCartIcon: 'cart',
addToCartBehaviour: 'inline',
hasHiddenPrice: false,
cartAndCheckoutRenderStyle: true,
priceColor: {
color: '#000000',
},
iconColor: {
color: '#ffffff',
},
productCountColor: {
color: '#ff0000',
},
} );
} );
} );
@@ -11,6 +11,7 @@ import { useCallback, useState, useEffect } from '@wordpress/element';
import PriceSlider from '@woocommerce/base-components/price-slider';
import FilterTitlePlaceholder from '@woocommerce/base-components/filter-placeholder';
import { useDebouncedCallback } from 'use-debounce';
import PropTypes from 'prop-types';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
import { getSettingWithCoercion } from '@woocommerce/settings';
import { addQueryArgs, removeQueryArgs } from '@wordpress/url';
@@ -71,17 +72,6 @@ function formatPrice( value: unknown, minorUnit: number ) {
return Number( value ) * 10 ** minorUnit;
}
interface PriceFilterBlockProps {
/**
* The attributes for this block.
*/
attributes: Attributes;
/**
* Whether it's in the editor or frontend display.
*/
isEditor: boolean;
}
/**
* Component displaying a price filter.
*
@@ -92,16 +82,19 @@ interface PriceFilterBlockProps {
const PriceFilterBlock = ( {
attributes,
isEditor = false,
}: PriceFilterBlockProps ) => {
}: {
attributes: Attributes;
isEditor: boolean;
} ) => {
const setWrapperVisibility = useSetWraperVisibility();
const hasFilterableProducts = getSettingWithCoercion(
'hasFilterableProducts',
'has_filterable_products',
false,
isBoolean
);
const filteringForPhpTemplate = getSettingWithCoercion(
'isRenderingPhpTemplate',
'is_rendering_php_template',
false,
isBoolean
);
@@ -362,4 +355,15 @@ const PriceFilterBlock = ( {
);
};
PriceFilterBlock.propTypes = {
/**
* The attributes for this block.
*/
attributes: PropTypes.object.isRequired,
/**
* Whether it's in the editor or frontend display.
*/
isEditor: PropTypes.bool,
};
export default PriceFilterBlock;
@@ -136,7 +136,6 @@ export default function ( {
className="wc-block-price-slider__add-product-button"
isSecondary
href={ getAdminLink( 'post-new.php?post_type=product' ) }
target="_top"
>
{ __( 'Add new product', 'woo-gutenberg-products-block' ) +
' ' }
@@ -146,7 +145,6 @@ export default function ( {
className="wc-block-price-slider__read_more_button"
isTertiary
href="https://docs.woocommerce.com/document/managing-products/"
target="_blank"
>
{ __( 'Learn more', 'woo-gutenberg-products-block' ) }
</Button>
@@ -38,10 +38,10 @@ export const ProductBestSellersInspectorControls = (
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting( 'minColumns', 1 ) }
maxColumns={ getSetting( 'maxColumns', 6 ) }
minRows={ getSetting( 'minRows', 1 ) }
maxRows={ getSetting( 'maxRows', 6 ) }
minColumns={ getSetting( 'min_columns', 1 ) }
maxColumns={ getSetting( 'max_columns', 6 ) }
minRows={ getSetting( 'min_rows', 1 ) }
maxRows={ getSetting( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -31,6 +31,7 @@
margin: 0;
margin-right: 0.5em;
margin-left: -60px;
position: relative;
vertical-align: middle;
border: 1px solid #eee;
@@ -73,10 +73,10 @@ export const ProductsByCategoryInspectorControls = (
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting( 'minColumns', 1 ) }
maxColumns={ getSetting( 'maxColumns', 6 ) }
minRows={ getSetting( 'minRows', 1 ) }
maxRows={ getSetting( 'maxRows', 6 ) }
minColumns={ getSetting( 'min_columns', 1 ) }
maxColumns={ getSetting( 'max_columns', 6 ) }
minRows={ getSetting( 'min_rows', 1 ) }
maxRows={ getSetting( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -6,7 +6,7 @@
"title": "Product Collection",
"description": "Display a collection of products from your store.",
"category": "woocommerce",
"keywords": [ "WooCommerce", "Products (Beta)" ],
"keywords": [ "WooCommerce" ],
"textdomain": "woocommerce",
"attributes": {
"queryId": {
@@ -20,10 +20,6 @@
},
"displayLayout": {
"type": "object"
},
"displayUpgradeNotice": {
"type": "boolean",
"default": false
}
},
"providesContext": {
@@ -61,23 +61,17 @@ export const DEFAULT_ATTRIBUTES: Partial< ProductCollectionAttributes > = {
},
};
export const getDefaultQuery = (
currentQuery: ProductCollectionQuery
): ProductCollectionQuery => ( {
...currentQuery,
orderBy: DEFAULT_QUERY.orderBy as TProductCollectionOrderBy,
order: DEFAULT_QUERY.order as TProductCollectionOrder,
inherit: DEFAULT_QUERY.inherit,
} );
export const getDefaultDisplayLayout = () =>
DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout;
export const getDefaultSettings = (
currentAttributes: ProductCollectionAttributes
): Partial< ProductCollectionAttributes > => ( {
displayLayout: getDefaultDisplayLayout(),
query: getDefaultQuery( currentAttributes.query ),
displayLayout:
DEFAULT_ATTRIBUTES.displayLayout as ProductCollectionDisplayLayout,
query: {
...currentAttributes.query,
orderBy: DEFAULT_QUERY.orderBy as TProductCollectionOrderBy,
order: DEFAULT_QUERY.order as TProductCollectionOrder,
inherit: DEFAULT_QUERY.inherit,
},
} );
export const DEFAULT_FILTERS: Partial< ProductCollectionQuery > = {
@@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import ProductAttributeTermControl from '@woocommerce/editor-components/product-attribute-term-control';
import { AttributeMetadata } from '@woocommerce/types';
import { SearchListItem } from '@woocommerce/editor-components/search-list-control/types';
import { ADMIN_URL } from '@woocommerce/settings';
import {
@@ -15,15 +16,19 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionQuery } from '../types';
const EDIT_ATTRIBUTES_URL = `${ ADMIN_URL }edit.php?post_type=product&page=product_attributes`;
interface AttributesControlProps {
woocommerceAttributes?: AttributeMetadata[];
setQueryAttribute: ( value: Partial< ProductCollectionQuery > ) => void;
}
const AttributesControl = ( {
query,
woocommerceAttributes,
setQueryAttribute,
}: QueryControlProps ) => {
const woocommerceAttributes = query.woocommerceAttributes || [];
}: AttributesControlProps ) => {
const selectedAttributes = woocommerceAttributes?.map(
( { termId: id } ) => ( {
id,
@@ -13,7 +13,7 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionQuery } from '../types';
interface Author {
id: string;
@@ -27,6 +27,11 @@ interface AuthorsInfo {
names: string[];
}
interface AuthorControlProps {
value: string;
setQueryAttribute: ( value: Partial< ProductCollectionQuery > ) => void;
}
const AUTHORS_QUERY = {
who: 'authors',
per_page: -1,
@@ -63,8 +68,7 @@ const getIdByValue = (
if ( id ) return id;
};
function AuthorControl( { query, setQueryAttribute }: QueryControlProps ) {
const value = query.author;
function AuthorControl( { value, setQueryAttribute }: AuthorControlProps ) {
const { records: authorsList, error } = useEntityRecords< Author[] >(
'root',
'user',
@@ -2,6 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockEditProps } from '@wordpress/blocks';
import {
RangeControl,
// @ts-expect-error Using experimental features
@@ -12,26 +13,32 @@ import {
/**
* Internal dependencies
*/
import { DisplayLayoutControlProps } from '../types';
import { getDefaultDisplayLayout } from '../constants';
import {
ProductCollectionAttributes,
ProductCollectionDisplayLayout,
} from '../types';
import { getDefaultSettings } from '../constants';
const ColumnsControl = ( props: DisplayLayoutControlProps ) => {
const { type, columns } = props.displayLayout;
const ColumnsControl = (
props: BlockEditProps< ProductCollectionAttributes >
) => {
const { type, columns } = props.attributes.displayLayout;
const showColumnsControl = type === 'flex';
const defaultLayout = getDefaultDisplayLayout();
const defaultSettings = getDefaultSettings( props.attributes );
return showColumnsControl ? (
<ToolsPanelItem
label={ __( 'Columns', 'woo-gutenberg-products-block' ) }
hasValue={ () =>
defaultLayout?.columns !== columns ||
defaultLayout?.type !== type
defaultSettings.displayLayout?.columns !== columns ||
defaultSettings.displayLayout?.type !== type
}
isShownByDefault
onDeselect={ () => {
props.setAttributes( {
displayLayout: defaultLayout,
displayLayout:
defaultSettings.displayLayout as ProductCollectionDisplayLayout,
} );
} }
>
@@ -41,7 +48,7 @@ const ColumnsControl = ( props: DisplayLayoutControlProps ) => {
onChange={ ( value: number ) =>
props.setAttributes( {
displayLayout: {
...props.displayLayout,
...props.attributes.displayLayout,
columns: value,
},
} )
@@ -1,42 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { ToolbarGroup } from '@wordpress/components';
import { list, grid } from '@wordpress/icons';
/**
* Internal dependencies
*/
import {
DisplayLayoutControlProps,
ProductCollectionDisplayLayout,
} from '../types';
const DisplayLayoutControl = ( props: DisplayLayoutControlProps ) => {
const { type, columns } = props.displayLayout;
const setDisplayLayout = (
displayLayout: ProductCollectionDisplayLayout
) => {
props.setAttributes( { displayLayout } );
};
const displayLayoutControls = [
{
icon: list,
title: __( 'List view', 'woo-gutenberg-products-block' ),
onClick: () => setDisplayLayout( { type: 'list', columns } ),
isActive: type === 'list',
},
{
icon: grid,
title: __( 'Grid view', 'woo-gutenberg-products-block' ),
onClick: () => setDisplayLayout( { type: 'flex', columns } ),
isActive: type === 'flex',
},
];
return <ToolbarGroup controls={ displayLayoutControls } />;
};
export default DisplayLayoutControl;
@@ -15,7 +15,12 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionQuery } from '../types';
interface HandPickedProductsControlProps {
setQueryAttribute: ( value: Partial< ProductCollectionQuery > ) => void;
selectedProductIds?: string[] | undefined;
}
/**
* Returns:
@@ -50,10 +55,9 @@ function useProducts() {
}
const HandPickedProductsControl = ( {
query,
selectedProductIds,
setQueryAttribute,
}: QueryControlProps ) => {
const selectedProductIds = query.woocommerceHandPickedProducts;
}: HandPickedProductsControlProps ) => {
const { productsMap, productsList } = useProducts();
const onTokenChange = useCallback(
@@ -134,7 +138,6 @@ const HandPickedProductsControl = ( {
? [ __( 'Loading…', 'woo-gutenberg-products-block' ) ]
: selectedProductIds || []
}
__experimentalExpandOnFocus={ true }
/>
</ToolsPanelItem>
);
@@ -1,14 +1,10 @@
/**
* External dependencies
*/
import type { ElementType } from 'react';
import type { BlockEditProps } from '@wordpress/blocks';
import { InspectorControls, BlockControls } from '@wordpress/block-editor';
import { InspectorControls } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import { EditorBlock } from '@woocommerce/types';
import { addFilter } from '@wordpress/hooks';
import { ProductCollectionFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt';
import {
// @ts-expect-error Using experimental features
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@@ -18,23 +14,19 @@ import {
/**
* Internal dependencies
*/
import metadata from '../block.json';
import { ProductCollectionAttributes } from '../types';
import { setQueryAttribute } from '../utils';
import { DEFAULT_FILTERS, getDefaultSettings } from '../constants';
import UpgradeNotice from './upgrade-notice';
import ColumnsControl from './columns-control';
import InheritQueryControl from './inherit-query-control';
import OrderByControl from './order-by-control';
import OnSaleControl from './on-sale-control';
import { setQueryAttribute } from '../utils';
import { DEFAULT_FILTERS, getDefaultSettings } from '../constants';
import StockStatusControl from './stock-status-control';
import KeywordControl from './keyword-control';
import AttributesControl from './attributes-control';
import TaxonomyControls from './taxonomy-controls';
import HandPickedProductsControl from './hand-picked-products-control';
import AuthorControl from './author-control';
import DisplayLayoutControl from './display-layout-control';
import { replaceProductCollectionWithProducts } from '../../shared/scripts';
const ProductCollectionInspectorControls = (
props: BlockEditProps< ProductCollectionAttributes >
@@ -48,21 +40,8 @@ const ProductCollectionInspectorControls = (
[ props ]
);
const displayControlProps = {
setAttributes: props.setAttributes,
displayLayout: props.attributes.displayLayout,
};
const queryControlProps = {
setQueryAttribute: setQueryAttributeBind,
query,
};
return (
<InspectorControls>
<BlockControls>
<DisplayLayoutControl { ...displayControlProps } />
</BlockControls>
<ToolsPanel
label={ __( 'Settings', 'woo-gutenberg-products-block' ) }
resetAll={ () => {
@@ -72,10 +51,13 @@ const ProductCollectionInspectorControls = (
props.setAttributes( defaultSettings );
} }
>
<ColumnsControl { ...displayControlProps } />
<InheritQueryControl { ...queryControlProps } />
<ColumnsControl { ...props } />
<InheritQueryControl
setQueryAttribute={ setQueryAttributeBind }
query={ query }
/>
{ displayQueryControls ? (
<OrderByControl { ...queryControlProps } />
<OrderByControl { ...props } />
) : null }
</ToolsPanel>
@@ -90,50 +72,33 @@ const ProductCollectionInspectorControls = (
} }
className="wc-block-editor-product-collection-inspector-toolspanel__filters"
>
<OnSaleControl { ...queryControlProps } />
<StockStatusControl { ...queryControlProps } />
<HandPickedProductsControl { ...queryControlProps } />
<KeywordControl { ...queryControlProps } />
<AttributesControl { ...queryControlProps } />
<TaxonomyControls { ...queryControlProps } />
<AuthorControl { ...queryControlProps } />
<OnSaleControl { ...props } />
<StockStatusControl { ...props } />
<HandPickedProductsControl
setQueryAttribute={ setQueryAttributeBind }
selectedProductIds={
query.woocommerceHandPickedProducts
}
/>
<KeywordControl { ...props } />
<AttributesControl
woocommerceAttributes={
query.woocommerceAttributes || []
}
setQueryAttribute={ setQueryAttributeBind }
/>
<TaxonomyControls
setQueryAttribute={ setQueryAttributeBind }
query={ query }
/>
<AuthorControl
value={ query.author }
setQueryAttribute={ setQueryAttributeBind }
/>
</ToolsPanel>
) : null }
<ProductCollectionFeedbackPrompt />
</InspectorControls>
);
};
export default ProductCollectionInspectorControls;
const isProductCollection = (
block: EditorBlock< ProductCollectionAttributes >
) => block.name === metadata.name;
export const withUpgradeNoticeControls =
< T extends EditorBlock< T > >( BlockEdit: ElementType ) =>
( props: EditorBlock< ProductCollectionAttributes > ) => {
return isProductCollection( props ) ? (
<>
<InspectorControls>
{ props.attributes.displayUpgradeNotice && (
<UpgradeNotice
{ ...props }
revertMigration={
replaceProductCollectionWithProducts
}
/>
) }
</InspectorControls>
<BlockEdit { ...props } />
</>
) : (
<BlockEdit { ...props } />
);
};
addFilter(
'editor.BlockEdit',
'woocommerce/product-collection',
withUpgradeNoticeControls
);
@@ -67,7 +67,6 @@ const InheritQueryControl = ( {
} }
>
<ToggleControl
className="wc-block-product-collection__inherit-query-control"
label={ label }
help={ __(
'Toggle to use the global query context that is set with the current template, such as an archive or search. Disable to customize the settings independently.',
@@ -2,6 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockEditProps } from '@wordpress/blocks';
import { useEffect, useState } from '@wordpress/element';
import { useDebounce } from '@wordpress/compose';
import {
@@ -14,17 +15,18 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionAttributes } from '../types';
import { setQueryAttribute } from '../utils';
const KeywordControl = ( props: QueryControlProps ) => {
const { query, setQueryAttribute } = props;
const KeywordControl = (
props: BlockEditProps< ProductCollectionAttributes >
) => {
const { query } = props.attributes;
const [ querySearch, setQuerySearch ] = useState( query.search );
const onChangeDebounced = useDebounce( () => {
if ( query.search !== querySearch ) {
setQueryAttribute( {
search: querySearch,
} );
setQueryAttribute( props, { search: querySearch } );
}
}, 250 );
@@ -2,6 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockEditProps } from '@wordpress/blocks';
import {
ToggleControl,
// @ts-expect-error Using experimental features
@@ -12,10 +13,13 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionAttributes } from '../types';
import { setQueryAttribute } from '../utils';
const OnSaleControl = ( props: QueryControlProps ) => {
const { query, setQueryAttribute } = props;
const OnSaleControl = (
props: BlockEditProps< ProductCollectionAttributes >
) => {
const { query } = props.attributes;
return (
<ToolsPanelItem
@@ -23,7 +27,7 @@ const OnSaleControl = ( props: QueryControlProps ) => {
hasValue={ () => query.woocommerceOnSale === true }
isShownByDefault
onDeselect={ () => {
setQueryAttribute( {
setQueryAttribute( props, {
woocommerceOnSale: false,
} );
} }
@@ -35,7 +39,7 @@ const OnSaleControl = ( props: QueryControlProps ) => {
) }
checked={ query.woocommerceOnSale || false }
onChange={ ( woocommerceOnSale ) => {
setQueryAttribute( {
setQueryAttribute( props, {
woocommerceOnSale,
} );
} }
@@ -2,6 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockEditProps } from '@wordpress/blocks';
import {
SelectControl,
// @ts-expect-error Using experimental features
@@ -13,11 +14,11 @@ import {
* Internal dependencies
*/
import {
ProductCollectionAttributes,
TProductCollectionOrder,
TProductCollectionOrderBy,
QueryControlProps,
} from '../types';
import { getDefaultQuery } from '../constants';
import { getDefaultSettings } from '../constants';
const orderOptions = [
{
@@ -46,21 +47,27 @@ const orderOptions = [
},
];
const OrderByControl = ( props: QueryControlProps ) => {
const { query, setQueryAttribute } = props;
const { order, orderBy } = query;
const defaultQuery = getDefaultQuery( query );
const OrderByControl = (
props: BlockEditProps< ProductCollectionAttributes >
) => {
const { order, orderBy } = props.attributes.query;
const defaultSettings = getDefaultSettings( props.attributes );
return (
<ToolsPanelItem
label={ __( 'Order by', 'woo-gutenberg-products-block' ) }
hasValue={ () =>
order !== defaultQuery?.order ||
orderBy !== defaultQuery?.orderBy
order !== defaultSettings.query?.order ||
orderBy !== defaultSettings.query?.orderBy
}
isShownByDefault
onDeselect={ () => {
setQueryAttribute( defaultQuery );
props.setAttributes( {
query: {
...props.attributes.query,
...defaultSettings.query,
},
} );
} }
>
<SelectControl
@@ -69,9 +76,12 @@ const OrderByControl = ( props: QueryControlProps ) => {
label={ __( 'Order by', 'woo-gutenberg-products-block' ) }
onChange={ ( value ) => {
const [ newOrderBy, newOrder ] = value.split( '/' );
setQueryAttribute( {
order: newOrder as TProductCollectionOrder,
orderBy: newOrderBy as TProductCollectionOrderBy,
props.setAttributes( {
query: {
...props.attributes.query,
order: newOrder as TProductCollectionOrder,
orderBy: newOrderBy as TProductCollectionOrderBy,
},
} );
} }
/>
@@ -2,6 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { BlockEditProps } from '@wordpress/blocks';
import fastDeepEqual from 'fast-deep-equal/es6';
import {
FormTokenField,
@@ -13,7 +14,8 @@ import {
/**
* Internal dependencies
*/
import { QueryControlProps } from '../types';
import { ProductCollectionAttributes } from '../types';
import { setQueryAttribute } from '../utils';
import { STOCK_STATUS_OPTIONS, getDefaultStockStatuses } from '../constants';
/**
@@ -33,9 +35,10 @@ function getStockStatusIdByLabel( statusLabel: FormTokenField.Value ) {
)?.[ 0 ];
}
const StockStatusControl = ( props: QueryControlProps ) => {
const { query, setQueryAttribute } = props;
const StockStatusControl = (
props: BlockEditProps< ProductCollectionAttributes >
) => {
const { query } = props.attributes;
return (
<ToolsPanelItem
label={ __( 'Stock status', 'woo-gutenberg-products-block' ) }
@@ -46,7 +49,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
)
}
onDeselect={ () => {
setQueryAttribute( {
setQueryAttribute( props, {
woocommerceStockStatus: getDefaultStockStatuses(),
} );
} }
@@ -59,7 +62,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
.map( getStockStatusIdByLabel )
.filter( Boolean ) as string[];
setQueryAttribute( {
setQueryAttribute( props, {
woocommerceStockStatus,
} );
} }
@@ -1,66 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Notice, Button } from '@wordpress/components';
import { BlockEditProps } from '@wordpress/blocks';
import { createInterpolateElement } from '@wordpress/element';
/**
* Internal dependencies
*/
import { ProductCollectionAttributes } from '../types';
const UpgradeNotice = (
props: BlockEditProps< ProductCollectionAttributes > & {
revertMigration: () => void;
}
) => {
const { displayUpgradeNotice } = props.attributes;
const notice = createInterpolateElement(
__(
'Products (Beta) block was upgraded to <strongText />, an updated version with new features and simplified settings.',
'woo-gutenberg-products-block'
),
{
strongText: (
<strong>
{ __(
`Product Collection`,
'woo-gutenberg-products-block'
) }
</strong>
),
}
);
const buttonLabel = __(
'Revert to Products (Beta)',
'woo-gutenberg-products-block'
);
const handleRemove = () => {
// @todo: this logic needs to be extended to be hidden for all
// Product Collection blocks and whole store
props.setAttributes( {
displayUpgradeNotice: false,
} );
};
const handleClick = () => {
props.revertMigration();
};
return displayUpgradeNotice ? (
<Notice onRemove={ handleRemove }>
<>{ notice } </>
<br />
<br />
<Button variant="link" onClick={ handleClick }>
{ buttonLabel }
</Button>
</Notice>
) : null;
};
export default UpgradeNotice;
@@ -14,11 +14,10 @@ export interface ProductCollectionAttributes {
templateSlug: string;
displayLayout: ProductCollectionDisplayLayout;
tagName: string;
displayUpgradeNotice: boolean;
}
export interface ProductCollectionDisplayLayout {
type: 'flex' | 'list';
type: string;
columns: number;
}
@@ -62,12 +61,3 @@ export type TProductCollectionOrderBy =
| 'title'
| 'popularity'
| 'rating';
export type DisplayLayoutControlProps = {
displayLayout: ProductCollectionDisplayLayout;
setAttributes: ( attrs: Partial< ProductCollectionAttributes > ) => void;
};
export type QueryControlProps = {
query: ProductCollectionQuery;
setQueryAttribute: ( attrs: Partial< ProductCollectionQuery > ) => void;
};
@@ -1,74 +0,0 @@
/**
* External dependencies
*/
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { ProductGallerySettingsProps } from '../types';
export const ProductGalleryBlockSettings = ( {
attributes,
setAttributes,
}: ProductGallerySettingsProps ) => {
const { cropImages, hoverZoom, fullScreenOnClick } = attributes;
return (
<InspectorControls>
<PanelBody
title={ __( 'Media Settings', 'woo-gutenberg-products-block' ) }
>
<ToggleControl
label={ __(
'Crop images to fit',
'woo-gutenberg-products-block'
) }
help={ __(
'Images will be cropped to fit within a square space.',
'woo-gutenberg-products-block'
) }
checked={ cropImages }
onChange={ () =>
setAttributes( {
cropImages: ! cropImages,
} )
}
/>
<ToggleControl
label={ __(
'Zoom while hovering',
'woo-gutenberg-products-block'
) }
help={ __(
'While hovering the large image will zoom in by 30%.',
'woo-gutenberg-products-block'
) }
checked={ hoverZoom }
onChange={ () =>
setAttributes( {
hoverZoom: ! hoverZoom,
} )
}
/>
<ToggleControl
label={ __(
'Full-screen when clicked',
'woo-gutenberg-products-block'
) }
help={ __(
'Clicking on the large image will open a full-screen gallery experience.',
'woo-gutenberg-products-block'
) }
checked={ fullScreenOnClick }
onChange={ () =>
setAttributes( {
fullScreenOnClick: ! fullScreenOnClick,
} )
}
/>
</PanelBody>
</InspectorControls>
);
};
@@ -1,46 +1,17 @@
{
"$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": "woocommerce",
"providesContext": {
"thumbnailsPosition": "thumbnailsPosition",
"thumbnailsNumberOfThumbnails": "thumbnailsNumberOfThumbnails",
"productGalleryClientId": "productGalleryClientId"
},
"attributes": {
"thumbnailsPosition": {
"type": "string",
"default": "left"
},
"thumbnailsNumberOfThumbnails": {
"type": "number",
"default": 3
},
"productGalleryClientId": {
"type": "string",
"default": ""
},
"cropImages": {
"type": "boolean",
"default": false
},
"hoverZoom": {
"type": "boolean",
"default": false
},
"fullScreenOnClick": {
"type": "boolean",
"default": false
}
}
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
@@ -1,94 +1,25 @@
/**
* External dependencies
*/
import {
InnerBlocks,
InspectorControls,
useBlockProps,
} from '@wordpress/block-editor';
import { BlockEditProps, InnerBlockTemplate } from '@wordpress/blocks';
import { useEffect } from '@wordpress/element';
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import {
moveInnerBlocksToPosition,
updateGroupBlockType,
getInnerBlocksLockAttributes,
} from './utils';
import { ProductGalleryThumbnailsBlockSettings } from './inner-blocks/product-gallery-thumbnails/block-settings';
import { ProductGalleryBlockSettings } from './block-settings/index';
import type {
ProductGalleryThumbnailsBlockAttributes,
ProductGalleryBlockAttributes,
} 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<
ProductGalleryThumbnailsBlockAttributes & ProductGalleryBlockAttributes
> ) => {
export const Edit = (): JSX.Element => {
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>
<ProductGalleryThumbnailsBlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
context={ {
productGalleryClientId: clientId,
thumbnailsPosition: attributes.thumbnailsPosition,
thumbnailsNumberOfThumbnails:
attributes.thumbnailsNumberOfThumbnails,
} }
/>
</InspectorControls>
<InspectorControls>
<ProductGalleryBlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
/>
</InspectorControls>
<InnerBlocks
allowedBlocks={ [
'woocommerce/product-gallery-large-image',
'woocommerce/product-gallery-thumbnails',
] }
template={ TEMPLATE }
allowedBlocks={ [ 'woocommerce/product-gallery-large-image' ] }
templateLock={ false }
/>
</div>
);
};
export const Save = (): JSX.Element => {
return (
<div { ...useBlockProps.save() }>
<InnerBlocks.Content />
</div>
);
};
@@ -1,34 +1,35 @@
const Icon = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none">
<rect width="24" height="24" fill="none" rx="2" />
<path
d="M19 3H5C4.4 3 4 3.4 4 4V11C4 11.5 4.4 12 5 12H19C19.5 12 20 11.6 20 11V4C20 3.4 19.6 3 19 3ZM5.5 10.5V10.1L7.3 8.8L8.6 9.6C8.9 9.8 9.3 9.8 9.5 9.5L11 8.1L13.4 10.5H5.5ZM18.5 10.5H15.6L11.6 6.5C11.3 6.2 10.8 6.2 10.5 6.5L8.9 8L7.7 7.2C7.4 7 7.1 7 6.8 7.2L5.5 8.2V4.5H18.5V10.5Z"
fill="currentColor"
fill="#000"
d="M19 3H5c-.6 0-1 .4-1 1v7c0 .5.4 1 1 1h14c.5 0 1-.4 1-1V4c0-.6-.4-1-1-1ZM5.5 10.5v-.4l1.8-1.3 1.3.8c.3.2.7.2.9-.1L11 8.1l2.4 2.4H5.5Zm13 0h-2.9l-4-4c-.3-.3-.8-.3-1.1 0L8.9 8l-1.2-.8c-.3-.2-.6-.2-.9 0l-1.3 1V4.5h13v6Z"
/>
<mask id="a" fill="#fff">
<rect width="6" height="5.5" x="4" y="14.5" rx="1" />
</mask>
<rect
x="4.75"
y="15.5"
width="5"
height="4.5"
width="6"
height="5.5"
x="4"
y="14.5"
stroke="#000"
strokeWidth="3"
mask="url(#a)"
rx="1"
stroke="currentColor"
strokeWidth="1.5"
fill="none"
/>
<mask id="b" fill="#fff">
<rect width="6" height="5.5" x="11" y="14.5" rx="1" />
</mask>
<rect
x="12.25"
y="15.5"
width="5"
height="4.5"
width="6"
height="5.5"
x="11"
y="14.5"
stroke="#000"
strokeWidth="3"
mask="url(#b)"
rx="1"
stroke="currentColor"
strokeWidth="1.5"
fill="none"
/>
</svg>
);
@@ -7,12 +7,9 @@ import { isExperimentalBuild } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import { Edit } from './edit';
import { Save } from './save';
import { Edit, Save } from './edit';
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( {
@@ -26,6 +23,5 @@ if ( isExperimentalBuild() ) {
save: Save,
ancestor: [ 'woocommerce/single-product' ],
},
isAvailableOnPostEditor: true,
} );
}
@@ -1,13 +0,0 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-gallery-large-image",
"version": "1.0.0",
"title": "Large Image",
"description": "Display the Large Image of a product.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"usesContext": [ "postId" ],
"textdomain": "woocommerce",
"ancestor": [ "woocommerce/product-gallery" ]
}
@@ -1,39 +0,0 @@
/**
* External dependencies
*/
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',
} );
const Placeholder = () => {
return (
<div className="wc-block-editor-product-gallery-large-image">
<img
src={ `${ WC_BLOCKS_IMAGE_URL }block-placeholders/product-image-gallery.svg` }
alt="Placeholder"
/>
</div>
);
};
return (
<div { ...blockProps }>
<Disabled>
<Placeholder />
</Disabled>
</div>
);
};
export const Save = (): JSX.Element => {
return <div { ...useBlockProps.save() }></div>;
};
@@ -1,3 +0,0 @@
.wc-block-editor-product-gallery-large-image img {
max-width: 500px;
}
@@ -1,18 +0,0 @@
const Icon = () => (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.22448 1.5L1.5 6.81504V11.7072L5.12953 9.06066C5.38061 8.87758 5.71858 8.86829 5.97934 9.0373L8.90601 10.9342L12.4772 7.46225C12.7683 7.17925 13.2317 7.17925 13.5228 7.46225L16.5 10.3568V2C16.5 1.72386 16.2761 1.5 16 1.5H6.22448ZM1.5 13.5636V16C1.5 16.2761 1.72386 16.5 2 16.5H16C16.2761 16.5 16.5 16.2761 16.5 16V12.4032L16.4772 12.4266L13 9.04603L9.52279 12.4266C9.27191 12.6706 8.88569 12.7086 8.59206 12.5183L5.59643 10.5766L1.5 13.5636ZM0 2C0 0.89543 0.895431 0 2 0H16C17.1046 0 18 0.895431 18 2V16C18 17.1046 17.1046 18 16 18H2C0.89543 18 0 17.1046 0 16V2Z"
fill="currentColor"
/>
</svg>
);
export default Icon;
@@ -1,22 +0,0 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { isExperimentalBuild } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import icon from './icon';
import { Edit, Save } 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: Save,
} );
}
@@ -1,23 +0,0 @@
.woocommerce .wp-block-woocommerce-product-gallery-large-image {
position: relative;
// This is necessary to calculate the correct width of the gallery. https://www.lockedownseo.com/parent-div-100-height-child-floated-elements/#:~:text=Solution%20%232%3A%20Float%20Parent%20Container
clear: both;
span.onsale {
right: unset;
z-index: 1;
left: -1rem;
}
}
// This is necessary to calculate the correct width of the gallery. https://www.lockedownseo.com/parent-div-100-height-child-floated-elements/#:~:text=Solution%20%232%3A%20Float%20Parent%20Container
.woocommerce .wp-block-woocommerce-product-gallery-large-image::after {
clear: both;
content: "";
display: table;
}
.woocommerce .wp-block-woocommerce-product-gallery-large-image .woocommerce-product-gallery.images {
width: auto;
}
@@ -1,136 +0,0 @@
/**
* 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 { ProductGalleryThumbnailsSettingsProps } 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 ProductGalleryThumbnailsBlockSettings = ( {
context,
}: ProductGalleryThumbnailsSettingsProps ) => {
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>
);
};
@@ -1,21 +0,0 @@
{
"$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": "woocommerce",
"ancestor": [ "woocommerce/product-gallery" ],
"supports": {
"spacing": {
"margin": true,
"__experimentalDefaultControls": {
"margin": true
}
}
}
}
@@ -1,6 +0,0 @@
export enum ThumbnailsPosition {
OFF = 'off',
LEFT = 'left',
BOTTOM = 'bottom',
RIGHT = 'right',
}
@@ -1,61 +0,0 @@
/**
* 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 { ProductGalleryThumbnailsBlockSettings } from './block-settings';
import type {
ProductGalleryThumbnailsBlockAttributes,
Context,
} from '../../types';
import { ThumbnailsPosition } from './constants';
export const Edit = ( {
attributes,
setAttributes,
context,
}: BlockEditProps< ProductGalleryThumbnailsBlockAttributes > & 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>
) : null;
};
return (
<>
<div { ...blockProps }>
<InspectorControls>
<ProductGalleryThumbnailsBlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
context={ context }
/>
</InspectorControls>
<Disabled>
<Placeholder />
</Disabled>
</div>
</>
);
};
@@ -1,9 +0,0 @@
.wp-block-woocommerce-product-gallery-thumbnails {
.wc-block-editor-product-gallery-thumbnails {
img {
width: 100px;
height: 100px;
margin: 5px;
}
}
}
@@ -1,18 +0,0 @@
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;
@@ -1,24 +0,0 @@
/**
* 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;
},
} );
}
@@ -1,12 +0,0 @@
.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;
}
}
@@ -1,10 +0,0 @@
/**
* 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 } />;
};
@@ -1,45 +0,0 @@
/**
* 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;
}
export interface ProductGallerySettingsProps {
attributes: ProductGalleryBlockAttributes;
setAttributes: ( attributes: ProductGalleryBlockAttributes ) => void;
}
export interface ProductGalleryThumbnailsSettingsProps {
attributes: ProductGalleryThumbnailsBlockAttributes;
setAttributes: (
attributes: ProductGalleryThumbnailsBlockAttributes
) => void;
context: ProductGalleryThumbnailsBlockAttributes;
}
export interface Context {
context: {
thumbnailsPosition: ThumbnailsPosition;
thumbnailsNumberOfThumbnails: number;
productGalleryClientId: string;
};
}
@@ -1,179 +0,0 @@
/**
* 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
);
}
} );
};
@@ -46,10 +46,10 @@ export const ProductNewestBlock = ( {
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting( 'minColumns', 1 ) }
maxColumns={ getSetting( 'maxColumns', 6 ) }
minRows={ getSetting( 'minRows', 1 ) }
maxRows={ getSetting( 'maxRows', 6 ) }
minColumns={ getSetting( 'min_columns', 1 ) }
maxColumns={ getSetting( 'max_columns', 6 ) }
minRows={ getSetting( 'min_rows', 1 ) }
maxRows={ getSetting( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -47,10 +47,10 @@ export const ProductOnSaleInspectorControls = (
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting< number >( 'minColumns', 1 ) }
maxColumns={ getSetting< number >( 'maxColumns', 6 ) }
minRows={ getSetting< number >( 'minRows', 1 ) }
maxRows={ getSetting< number >( 'maxRows', 6 ) }
minColumns={ getSetting< number >( 'min_columns', 1 ) }
maxColumns={ getSetting< number >( 'max_columns', 6 ) }
minRows={ getSetting< number >( 'min_rows', 1 ) }
maxRows={ getSetting< number >( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -13,11 +13,6 @@ import { VARIATION_NAME as PRODUCT_TITLE_ID } from './variations/elements/produc
import { VARIATION_NAME as PRODUCT_TEMPLATE_ID } from './variations/elements/product-template';
import { ImageSizing } from '../../atomic/blocks/product-elements/image/types';
export const AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false;
export const MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION = false;
export const PRODUCT_QUERY_VARIATION_NAME = 'woocommerce/product-query';
export const EDIT_ATTRIBUTES_URL =
'/wp-admin/edit.php?post_type=product&page=product_attributes';
@@ -76,7 +71,7 @@ export const QUERY_DEFAULT_ATTRIBUTES: QueryBlockAttributes = {
// This is necessary to fix https://github.com/woocommerce/woocommerce-blocks/issues/9884.
const postTemplateHasSupportForGridView = getSettingWithCoercion(
'postTemplateHasSupportForGridView',
'post_template_has_support_for_grid_view',
false,
isBoolean
);
@@ -4,13 +4,11 @@
import type { ElementType } from 'react';
import { __ } from '@wordpress/i18n';
import { InspectorControls } from '@wordpress/block-editor';
import { useSelect, subscribe } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';
import { ProductQueryFeedbackPrompt } from '@woocommerce/editor-components/feedback-prompt';
import { EditorBlock, isNumber } from '@woocommerce/types';
import { EditorBlock } from '@woocommerce/types';
import { usePrevious } from '@woocommerce/base-hooks';
import { isWpVersion, getSettingWithCoercion } from '@woocommerce/settings';
import { ProductQueryBlockQuery } from '@woocommerce/blocks/product-query/types';
import {
FormTokenField,
ToggleControl,
@@ -39,14 +37,10 @@ import {
QUERY_DEFAULT_ATTRIBUTES,
QUERY_LOOP_ID,
STOCK_STATUS_OPTIONS,
AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION,
MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION,
} from './constants';
import { AttributesFilter } from './inspector-controls/attributes-filter';
import { PopularPresets } from './inspector-controls/popular-presets';
import { ProductSelector } from './inspector-controls/product-selector';
import { UpgradeNotice } from './inspector-controls/upgrade-notice';
import { replaceProductsWithProductCollection } from '../shared/scripts';
import './editor.scss';
@@ -126,18 +120,6 @@ export const WooInheritToggleControl = (
: props.attributes.query.inherit || false
}
onChange={ ( inherit ) => {
const inheritQuery: Partial< ProductQueryBlockQuery > = {
inherit,
};
if ( inherit ) {
inheritQuery.perPage = getSettingWithCoercion(
'loopShopPerPage',
12,
isNumber
);
}
if ( isCustomInheritGlobalQueryImplementationEnabled ) {
return setQueryAttribute( props, {
...QUERY_DEFAULT_ATTRIBUTES.query,
@@ -151,7 +133,7 @@ export const WooInheritToggleControl = (
setQueryAttribute( props, {
...props.defaultWooQueryParams,
...inheritQuery,
inherit,
// Restore the query object value before inherit was enabled.
...( inherit === false && {
...queryObjectBeforeInheritEnabled,
@@ -236,11 +218,6 @@ const ProductQueryControls = ( props: ProductQueryBlock ) => {
return (
<>
<InspectorControls>
{ MANUAL_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION && (
<UpgradeNotice
upgradeBlock={ replaceProductsWithProductCollection }
/>
) }
{ allowedControls?.includes( 'presets' ) && (
<PopularPresets { ...props } />
) }
@@ -289,16 +266,3 @@ export const withProductQueryControls =
};
addFilter( 'editor.BlockEdit', QUERY_LOOP_ID, withProductQueryControls );
if ( isWpVersion( '6.1', '>=' ) ) {
let unsubscribe: ( () => void ) | undefined;
if ( AUTO_REPLACE_PRODUCTS_WITH_PRODUCT_COLLECTION && ! unsubscribe ) {
unsubscribe = subscribe( () => {
replaceProductsWithProductCollection( () => {
if ( unsubscribe ) {
unsubscribe();
}
} );
}, 'core/block-editor' );
}
}
@@ -1,45 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Notice, Button } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
export const UpgradeNotice = ( props: { upgradeBlock: () => void } ) => {
const notice = createInterpolateElement(
__(
'Upgrade all Products (Beta) blocks on this page to <strongText /> for more features!',
'woo-gutenberg-products-block'
),
{
strongText: (
<strong>
{ __(
`Product Collection`,
'woo-gutenberg-products-block'
) }
</strong>
),
}
);
const buttonLabel = __(
'Upgrade to Product Collection',
'woo-gutenberg-products-block'
);
const handleClick = () => {
props.upgradeBlock();
};
return (
<Notice isDismissible={ false }>
<>{ notice }</>
<br />
<br />
<Button variant="link" onClick={ handleClick }>
{ buttonLabel }
</Button>
</Notice>
);
};
@@ -8,26 +8,23 @@ import {
import { Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { stacks } from '@woocommerce/icons';
import { isWpVersion, getSettingWithCoercion } from '@woocommerce/settings';
import { isWpVersion } from '@woocommerce/settings';
import { select, subscribe } from '@wordpress/data';
import {
QueryBlockAttributes,
ProductQueryBlockQuery,
} from '@woocommerce/blocks/product-query/types';
import { QueryBlockAttributes } from '@woocommerce/blocks/product-query/types';
import { isSiteEditorPage } from '@woocommerce/utils';
import { isNumber } from '@woocommerce/types';
/**
* Internal dependencies
*/
import {
PRODUCT_QUERY_VARIATION_NAME,
DEFAULT_ALLOWED_CONTROLS,
INNER_BLOCKS_TEMPLATE,
QUERY_DEFAULT_ATTRIBUTES,
QUERY_LOOP_ID,
} from '../constants';
export const VARIATION_NAME = 'woocommerce/product-query';
const ARCHIVE_PRODUCT_TEMPLATES = [
'woocommerce/woocommerce//archive-product',
'woocommerce/woocommerce//taxonomy-product_cat',
@@ -42,11 +39,11 @@ const registerProductsBlock = ( attributes: QueryBlockAttributes ) => {
'A block that displays a selection of products in your store.',
'woo-gutenberg-products-block'
),
name: PRODUCT_QUERY_VARIATION_NAME,
name: VARIATION_NAME,
/* translators: “Products“ is the name of the block. */
title: __( 'Products (Beta)', 'woo-gutenberg-products-block' ),
isActive: ( blockAttributes ) =>
blockAttributes.namespace === PRODUCT_QUERY_VARIATION_NAME,
blockAttributes.namespace === VARIATION_NAME,
icon: (
<Icon
icon={ stacks }
@@ -55,7 +52,7 @@ const registerProductsBlock = ( attributes: QueryBlockAttributes ) => {
),
attributes: {
...attributes,
namespace: PRODUCT_QUERY_VARIATION_NAME,
namespace: VARIATION_NAME,
},
// Gutenberg doesn't support this type yet, discussion here:
// https://github.com/WordPress/gutenberg/pull/43632
@@ -78,33 +75,16 @@ if ( isWpVersion( '6.1', '>=' ) ) {
}
if ( isSiteEditorPage( store ) ) {
const inherit =
ARCHIVE_PRODUCT_TEMPLATES.includes( currentTemplateId );
const inheritQuery: Partial< ProductQueryBlockQuery > = {
inherit,
};
if ( inherit ) {
inheritQuery.perPage = getSettingWithCoercion(
'loopShopPerPage',
12,
isNumber
);
}
const queryAttributes = {
...QUERY_DEFAULT_ATTRIBUTES,
query: {
...QUERY_DEFAULT_ATTRIBUTES.query,
...inheritQuery,
inherit:
ARCHIVE_PRODUCT_TEMPLATES.includes( currentTemplateId ),
},
};
unregisterBlockVariation(
QUERY_LOOP_ID,
PRODUCT_QUERY_VARIATION_NAME
);
unregisterBlockVariation( QUERY_LOOP_ID, VARIATION_NAME );
registerProductsBlock( queryAttributes );
}
@@ -46,7 +46,7 @@ export const BLOCK_ATTRIBUTES = {
};
const postTemplateHasSupportForGridView = getSettingWithCoercion(
'postTemplateHasSupportForGridView',
'post_template_has_support_for_grid_view',
false,
isBoolean
);
@@ -85,6 +85,7 @@ const ProductsByTagBlock = ( {
) }
initialOpen={ ! attributes.tags.length && ! isEditing }
>
{ /* @ts-expect-error ProductTagControl is yet to be converted to tsx*/ }
<ProductTagControl
selected={ attributes.tags }
onChange={ ( value = [] ) => {
@@ -108,22 +109,22 @@ const ProductsByTagBlock = ( {
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSettingWithCoercion(
'minColumns',
'min_columns',
1,
isNumber
) }
maxColumns={ getSettingWithCoercion(
'maxColumns',
'max_columns',
6,
isNumber
) }
minRows={ getSettingWithCoercion(
'minRows',
'min_rows',
6,
isNumber
) }
maxRows={ getSettingWithCoercion(
'maxRows',
'max_rows',
6,
isNumber
) }
@@ -202,21 +203,16 @@ const ProductsByTagBlock = ( {
'woo-gutenberg-products-block'
) }
<div className="wc-block-product-tag__selection">
{ /* @ts-expect-error ProductTagControl is yet to be converted to tsx*/ }
<ProductTagControl
selected={ currentAttributes.tags }
onChange={ ( value = [] ) => {
const ids = value.map( ( { id } ) => id );
setChangedAttributes( {
...changedAttributes,
tags: ids,
} );
setChangedAttributes( { tags: ids } );
} }
operator={ currentAttributes.tagOperator }
onOperatorChange={ ( value = 'any' ) =>
setChangedAttributes( {
...changedAttributes,
tagOperator: value,
} )
setChangedAttributes( { tagOperator: value } )
}
/>
<Button isPrimary onClick={ onDone }>
@@ -28,11 +28,11 @@ registerBlockType( metadata, {
...metadata.attributes,
columns: {
type: 'number',
default: getSetting( 'defaultColumns', 3 ),
default: getSetting( 'default_columns', 3 ),
},
rows: {
type: 'number',
default: getSetting( 'defaultRows', 3 ),
default: getSetting( 'default_rows', 3 ),
},
tags: {
type: 'array',
@@ -1,6 +1,6 @@
export interface ProductsByTagBlockProps {
attributes: {
tags: ( number | string )[];
tags: number[];
tagOperator: string;
columns: number;
rows: number;
@@ -17,8 +17,6 @@ import { Spinner } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';
import type { BlockEditProps } from '@wordpress/blocks';
import { ProductCollectionAttributes } from '@woocommerce/blocks/product-collection/types';
import { getSettingWithCoercion } from '@woocommerce/settings';
import { isNumber } from '@woocommerce/types';
const ProductTemplateInnerBlocks = () => {
const innerBlocksProps = useInnerBlocksProps(
@@ -104,11 +102,6 @@ const ProductTemplateEdit = ( {
const [ { page } ] = queryContext;
const [ activeBlockContextId, setActiveBlockContextId ] = useState();
const postType = 'product';
const loopShopPerPage = getSettingWithCoercion(
'loopShopPerPage',
12,
isNumber
);
const { products, blocks } = useSelect(
( select ) => {
const { getEntityRecords, getTaxonomies } = select( coreStore );
@@ -128,7 +121,6 @@ const ProductTemplateEdit = ( {
slug: templateSlug.replace( 'category-', '' ),
} );
const query: Record< string, unknown > = {
postType,
offset: perPage ? perPage * ( page - 1 ) + offset : 0,
order,
orderby: orderBy,
@@ -179,7 +171,6 @@ const ProductTemplateEdit = ( {
if ( templateCategory ) {
query.categories = templateCategory[ 0 ]?.id;
}
query.per_page = loopShopPerPage;
}
return {
products: getEntityRecords( 'postType', postType, {
@@ -47,10 +47,10 @@ export const ProductTopRatedBlock = ( {
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting( 'minColumns', 1 ) }
maxColumns={ getSetting( 'maxColumns', 6 ) }
minRows={ getSetting( 'minRows', 1 ) }
maxRows={ getSetting( 'maxRows', 6 ) }
minColumns={ getSetting( 'min_columns', 1 ) }
maxColumns={ getSetting( 'max_columns', 6 ) }
minRows={ getSetting( 'min_rows', 1 ) }
maxRows={ getSetting( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -25,11 +25,11 @@ registerBlockType( metadata, {
...metadata.attributes,
columns: {
type: 'number',
default: getSetting( 'defaultColumns', 3 ),
default: getSetting( 'default_columns', 3 ),
},
rows: {
type: 'number',
default: getSetting( 'defaultRows', 3 ),
default: getSetting( 'default_rows', 3 ),
},
stockStatus: {
type: 'array',
@@ -42,10 +42,10 @@ export const ProductsByAttributeInspectorControls = (
rows={ rows }
alignButtons={ alignButtons }
setAttributes={ setAttributes }
minColumns={ getSetting( 'minColumns', 1 ) }
maxColumns={ getSetting( 'maxColumns', 6 ) }
minRows={ getSetting( 'minRows', 1 ) }
maxRows={ getSetting( 'maxRows', 6 ) }
minColumns={ getSetting( 'min_columns', 1 ) }
maxColumns={ getSetting( 'max_columns', 6 ) }
minRows={ getSetting( 'min_rows', 1 ) }
maxRows={ getSetting( 'max_rows', 6 ) }
/>
</PanelBody>
<PanelBody
@@ -9,8 +9,8 @@ import { getSetting } from '@woocommerce/settings';
import { DEFAULT_PRODUCT_LIST_LAYOUT } from '../base-utils';
export default {
columns: getSetting( 'defaultColumns', 3 ),
rows: getSetting( 'defaultRows', 3 ),
columns: getSetting( 'default_columns', 3 ),
rows: getSetting( 'default_rows', 3 ),
alignButtons: false,
contentVisibility: {
orderBy: true,

Some files were not shown because too many files have changed in this diff Show More