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

View File

@@ -73,7 +73,7 @@ const ActiveAttributeFilters = ( {
const attributeLabel = attributeObject.label;
const filteringForPhpTemplate = getSettingWithCoercion(
'isRenderingPhpTemplate',
'is_rendering_php_template',
false,
isBoolean
);

View File

@@ -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
);

View File

@@ -160,6 +160,7 @@
height: 16px;
width: 16px;
line-height: 16px;
padding: 0;
margin: 0 0.5em 0 0;
color: currentColor;

View File

@@ -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 ) =>

View File

@@ -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"

View File

@@ -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,

View File

@@ -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;

View File

@@ -91,7 +91,7 @@ const CheckoutExpressPayment = () => {
headingLevel="2"
>
{ __(
'Express Checkout',
'Express checkout',
'woocommerce'
) }
</Title>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>

View File

@@ -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,

View File

@@ -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"
/>
),

View File

@@ -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(

View File

@@ -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,

View File

@@ -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"
/>
),

View File

@@ -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;
}
}
}

View File

@@ -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(

View File

@@ -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';

View File

@@ -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';

View File

@@ -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' ] );

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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,

View File

@@ -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>
);

View File

@@ -25,7 +25,7 @@ registerBlockType( metadata, {
...metadata.attributes,
columns: {
type: 'number',
default: getSetting( 'defaultColumns', 3 ),
default: getSetting( 'default_columns', 3 ),
},
},

View File

@@ -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={ __(

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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 ?? '',

View File

@@ -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;

View File

@@ -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: [],

View File

@@ -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', {}, [] ],

View File

@@ -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;
}

View File

@@ -29,7 +29,6 @@ const Block = ( {
<Button
className={ classNames(
className,
'wp-block-button__link',
'wc-block-mini-cart__shopping-button'
) }
variant={ getVariant( className, 'contained' ) }

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,5 +0,0 @@
export const defaultColorItem = {
name: undefined,
color: undefined,
slug: undefined,
};

View File

@@ -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',
},
} );
} );
} );

View File

@@ -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;

View File

@@ -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>

View File

@@ -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

View File

@@ -31,6 +31,7 @@
margin: 0;
margin-right: 0.5em;
margin-left: -60px;
position: relative;
vertical-align: middle;
border: 1px solid #eee;

View File

@@ -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

View File

@@ -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": {

View File

@@ -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 > = {

View File

@@ -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,

View File

@@ -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',

View File

@@ -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,
},
} )

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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
);

View File

@@ -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.',

View File

@@ -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 );

View File

@@ -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,
} );
} }

View File

@@ -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,
},
} );
} }
/>

View File

@@ -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,
} );
} }

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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>
);
};

View File

@@ -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"
}

View File

@@ -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>
);
};

View File

@@ -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>
);

View File

@@ -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,
} );
}

View File

@@ -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" ]
}

View File

@@ -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>;
};

View File

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

View File

@@ -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;

View File

@@ -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,
} );
}

View File

@@ -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;
}

View File

@@ -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>
);
};

View File

@@ -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
}
}
}
}

View File

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

View File

@@ -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>
</>
);
};

View File

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

View File

@@ -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;

View File

@@ -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;
},
} );
}

View File

@@ -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;
}
}

View File

@@ -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 } />;
};

View File

@@ -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;
};
}

View File

@@ -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
);
}
} );
};

View File

@@ -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

View File

@@ -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

View File

@@ -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
);

View File

@@ -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' );
}
}

View File

@@ -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>
);
};

View File

@@ -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 );
}

View File

@@ -46,7 +46,7 @@ export const BLOCK_ATTRIBUTES = {
};
const postTemplateHasSupportForGridView = getSettingWithCoercion(
'postTemplateHasSupportForGridView',
'post_template_has_support_for_grid_view',
false,
isBoolean
);

View File

@@ -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 }>

View File

@@ -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',

View File

@@ -1,6 +1,6 @@
export interface ProductsByTagBlockProps {
attributes: {
tags: ( number | string )[];
tags: number[];
tagOperator: string;
columns: number;
rows: number;

View File

@@ -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, {

View File

@@ -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

View File

@@ -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',

View File

@@ -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

View File

@@ -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