diff --git a/assets/js/base/context/hooks/cart/use-store-cart-event-listeners.ts b/assets/js/base/context/hooks/cart/use-store-cart-event-listeners.ts
index 51896c843f5..a092913fad7 100644
--- a/assets/js/base/context/hooks/cart/use-store-cart-event-listeners.ts
+++ b/assets/js/base/context/hooks/cart/use-store-cart-event-listeners.ts
@@ -36,7 +36,7 @@ const setUp = (): void => {
const addListeners = (): void => {
setUp();
- if ( ! window.wcBlocksStoreCartListeners.count ) {
+ if ( window.wcBlocksStoreCartListeners.count === 0 ) {
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
'added_to_cart',
`wc-blocks_added_to_cart`
diff --git a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx
index 919185a2a66..c8c6da01254 100644
--- a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx
+++ b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx
@@ -60,7 +60,7 @@ export const usePaymentMethodDataContext = (): PaymentMethodDataContextType => {
export const PaymentMethodDataProvider = ( {
children,
}: {
- children: React.ReactChildren;
+ children: React.ReactNode;
} ): JSX.Element => {
const {
isProcessing: checkoutIsProcessing,
diff --git a/assets/js/base/utils/render-frontend.js b/assets/js/base/utils/render-frontend.js
index 1e75c68262f..7063352357e 100644
--- a/assets/js/base/utils/render-frontend.js
+++ b/assets/js/base/utils/render-frontend.js
@@ -53,17 +53,47 @@ const renderBlockInContainers = ( {
};
el.classList.remove( 'is-loading' );
- render(
-
- }>
-
-
- ,
- el
- );
+ renderBlock( {
+ Block,
+ container: el,
+ props,
+ attributes,
+ errorBoundaryProps,
+ } );
} );
};
+/**
+ * Renders a block component in a single `container` node.
+ *
+ * @param {Object} props Render props.
+ * @param {Function} props.Block React component to use as a
+ * replacement.
+ * @param {Node} props.container Container to replace with
+ * the Block component.
+ * @param {Object} [props.attributes] Attributes object for the
+ * block.
+ * @param {Object} [props.props] Props object for the block.
+ * @param {Object} [props.errorBoundaryProps] Props object for the error
+ * boundary.
+ */
+export const renderBlock = ( {
+ Block,
+ container,
+ attributes = {},
+ props = {},
+ errorBoundaryProps = {},
+} ) => {
+ render(
+
+ }>
+
+
+ ,
+ container
+ );
+};
+
/**
* Renders the block frontend in the elements matched by the selector which are
* outside the wrapper elements.
@@ -141,7 +171,7 @@ const renderBlockInsideWrapper = ( {
* Renders the block frontend on page load. If the block is contained inside a
* wrapper element that should be excluded from initial load, it adds the
* appropriate event listeners to render the block when the
- * `blocks_render_blocks_frontend` event is triggered.
+ * `wc-blocks_render_blocks_frontend` event is triggered.
*
* @param {Object} props Render props.
* @param {Function} props.Block React component to use as a
diff --git a/assets/js/blocks/cart-checkout/cart/cart-line-items-table/index.tsx b/assets/js/blocks/cart-checkout/cart/cart-line-items-table/index.tsx
index 986d36c3748..e9a22366255 100644
--- a/assets/js/blocks/cart-checkout/cart/cart-line-items-table/index.tsx
+++ b/assets/js/blocks/cart-checkout/cart/cart-line-items-table/index.tsx
@@ -19,7 +19,7 @@ const placeholderRows = [ ...Array( 3 ) ].map( ( _x, i ) => (
interface CartLineItemsTableProps {
lineItems: CartResponseItem[];
isLoading: boolean;
- className: string;
+ className?: string;
}
const setRefs = ( lineItems: CartResponseItem[] ) => {
diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/block.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/block.tsx
new file mode 100644
index 00000000000..76b161733c7
--- /dev/null
+++ b/assets/js/blocks/cart-checkout/mini-cart-contents/block.tsx
@@ -0,0 +1,104 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useEffect, useRef } from '@wordpress/element';
+import {
+ usePaymentMethods,
+ useStoreCart,
+} from '@woocommerce/base-context/hooks';
+import { TotalsItem } from '@woocommerce/blocks-checkout';
+import { CART_URL, CHECKOUT_URL } from '@woocommerce/block-settings';
+import Button from '@woocommerce/base-components/button';
+import { PaymentMethodDataProvider } from '@woocommerce/base-context';
+import { getIconsFromPaymentMethods } from '@woocommerce/base-utils';
+import PaymentMethodIcons from '@woocommerce/base-components/cart-checkout/payment-method-icons';
+import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
+import { getSetting } from '@woocommerce/settings';
+
+/**
+ * Internal dependencies
+ */
+import CartLineItemsTable from '../cart/cart-line-items-table';
+
+const PaymentMethodIconsElement = (): JSX.Element => {
+ const { paymentMethods } = usePaymentMethods();
+ return (
+
+ );
+};
+
+const MiniCartContentsBlock = (): JSX.Element => {
+ const { cartItems, cartIsLoading, cartTotals } = useStoreCart();
+ const emptyCartRef = useRef< HTMLDivElement | null >( null );
+
+ const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
+ ? parseInt( cartTotals.total_items, 10 ) +
+ parseInt( cartTotals.total_items_tax, 10 )
+ : parseInt( cartTotals.total_items, 10 );
+
+ useEffect( () => {
+ // If the cart has been completely emptied, move focus to empty cart
+ // element.
+ if ( ! cartIsLoading && cartItems.length === 0 ) {
+ if ( emptyCartRef.current instanceof HTMLElement ) {
+ emptyCartRef.current.focus();
+ }
+ }
+ }, [ cartIsLoading, cartItems.length, emptyCartRef ] );
+
+ return ! cartIsLoading && cartItems.length === 0 ? (
+
+ { __( 'Cart is empty', 'woo-gutenberg-products-block' ) }
+
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default MiniCartContentsBlock;
diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx
new file mode 100644
index 00000000000..68ee465cc79
--- /dev/null
+++ b/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx
@@ -0,0 +1,17 @@
+/**
+ * External dependencies
+ */
+import type { ReactElement } from 'react';
+import { useBlockProps } from '@wordpress/block-editor';
+
+const Edit = (): ReactElement => {
+ const blockProps = useBlockProps();
+
+ return (
+
+
Editing the mini cart contents
+
+ );
+};
+
+export default Edit;
diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx
new file mode 100644
index 00000000000..b35ec0a3b5b
--- /dev/null
+++ b/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx
@@ -0,0 +1,62 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { Icon, cart } from '@woocommerce/icons';
+import { registerExperimentalBlockType } from '@woocommerce/block-settings';
+
+/**
+ * Internal dependencies
+ */
+import edit from './edit';
+
+const settings = {
+ apiVersion: 2,
+ title: __( 'Mini Cart Contents', 'woo-gutenberg-products-block' ),
+ icon: {
+ src: ,
+ foreground: '#7f54b3',
+ },
+ category: 'woocommerce',
+ keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
+ description: __(
+ 'Display a mini cart widget.',
+ 'woo-gutenberg-products-block'
+ ),
+ supports: {
+ align: false,
+ html: false,
+ multiple: false,
+ reusable: false,
+ inserter: false,
+ },
+ attributes: {
+ lock: {
+ type: 'object',
+ default: {
+ remove: true,
+ move: true,
+ },
+ },
+ },
+ example: {
+ attributes: {
+ isPreview: true,
+ },
+ },
+ attributes: {
+ isPreview: {
+ type: 'boolean',
+ default: false,
+ save: false,
+ },
+ },
+
+ edit,
+
+ save() {
+ return null;
+ },
+};
+
+registerExperimentalBlockType( 'woocommerce/mini-cart-contents', settings );
diff --git a/assets/js/blocks/cart-checkout/mini-cart/block.tsx b/assets/js/blocks/cart-checkout/mini-cart/block.tsx
index 31086d5eba8..d181ee3c363 100644
--- a/assets/js/blocks/cart-checkout/mini-cart/block.tsx
+++ b/assets/js/blocks/cart-checkout/mini-cart/block.tsx
@@ -3,69 +3,89 @@
*/
import classnames from 'classnames';
import { __, _n, sprintf } from '@wordpress/i18n';
-import { useState, useEffect, useRef } from '@wordpress/element';
import {
+ RawHTML,
+ useState,
+ useEffect,
+ useRef,
+ unmountComponentAtNode,
+} from '@wordpress/element';
+import {
+ renderBlock,
translateJQueryEventToNative,
- getIconsFromPaymentMethods,
} from '@woocommerce/base-utils';
-import {
- useStoreCart,
- usePaymentMethods,
-} from '@woocommerce/base-context/hooks';
+import { useStoreCart } from '@woocommerce/base-context/hooks';
import Drawer from '@woocommerce/base-components/drawer';
import {
formatPrice,
getCurrencyFromPriceResponse,
} from '@woocommerce/price-format';
import { getSetting } from '@woocommerce/settings';
-import { TotalsItem } from '@woocommerce/blocks-checkout';
-import PaymentMethodIcons from '@woocommerce/base-components/cart-checkout/payment-method-icons';
-import { CART_URL, CHECKOUT_URL } from '@woocommerce/block-settings';
-import Button from '@woocommerce/base-components/button';
-import { PaymentMethodDataProvider } from '@woocommerce/base-context';
/**
* Internal dependencies
*/
-import CartLineItemsTable from '../cart/cart-line-items-table';
import QuantityBadge from './quantity-badge';
+import MiniCartContentsBlock from '../mini-cart-contents/block';
import './style.scss';
-const PaymentMethodIconsElement = (): JSX.Element => {
- const { paymentMethods } = usePaymentMethods();
- return (
-
- );
-};
-
interface Props {
isInitiallyOpen?: boolean;
transparentButton: boolean;
colorClassNames?: string;
style?: Record< string, Record< string, string > >;
+ contents: string;
}
const MiniCartBlock = ( {
isInitiallyOpen = false,
colorClassNames,
style,
+ contents = '',
}: Props ): JSX.Element => {
- const {
- cartItems,
- cartItemsCount,
- cartIsLoading,
- cartTotals,
- } = useStoreCart();
+ const { cartItemsCount, cartIsLoading, cartTotals } = useStoreCart();
const [ isOpen, setIsOpen ] = useState< boolean >( isInitiallyOpen );
- const emptyCartRef = useRef< HTMLDivElement | null >( null );
// We already rendered the HTML drawer placeholder, so we want to skip the
// slide in animation.
const [ skipSlideIn, setSkipSlideIn ] = useState< boolean >(
isInitiallyOpen
);
+ const contentsRef = useRef() as React.MutableRefObject< HTMLDivElement >;
+
+ useEffect( () => {
+ if ( contentsRef.current instanceof Element ) {
+ const container = contentsRef.current.querySelector(
+ '.wc-block-mini-cart-contents'
+ );
+ if ( ! container ) {
+ return;
+ }
+ if ( isOpen ) {
+ renderBlock( {
+ Block: MiniCartContentsBlock,
+ container,
+ } );
+ } else {
+ unmountComponentAtNode( container );
+ }
+ }
+ }, [ isOpen ] );
+
+ useEffect( () => {
+ return () => {
+ const contentsNode = contentsRef.current as unknown;
+ if ( contentsNode instanceof Element ) {
+ const container = contentsNode.querySelector(
+ '.wc-block-mini-cart-contents'
+ );
+ if ( container ) {
+ unmountComponentAtNode( container );
+ }
+ }
+ };
+ }, [] );
+
useEffect( () => {
const openMiniCart = () => {
setSkipSlideIn( false );
@@ -93,16 +113,6 @@ const MiniCartBlock = ( {
};
}, [] );
- useEffect( () => {
- // If the cart has been completely emptied, move focus to empty cart
- // element.
- if ( isOpen && ! cartIsLoading && cartItems.length === 0 ) {
- if ( emptyCartRef.current instanceof HTMLElement ) {
- emptyCartRef.current.focus();
- }
- }
- }, [ isOpen, cartIsLoading, cartItems.length, emptyCartRef ] );
-
const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( cartTotals.total_items, 10 ) +
parseInt( cartTotals.total_items_tax, 10 )
@@ -125,64 +135,6 @@ const MiniCartBlock = ( {
color: style?.color?.text,
};
- const contents =
- ! cartIsLoading && cartItems.length === 0 ? (
-
- { __( 'Cart is empty', 'woo-gutenberg-products-block' ) }
-
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-
return (
<>