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