Skip to content

Commit

Permalink
Mini Cart as template part (woocommerce#5025)
Browse files Browse the repository at this point in the history
* Fix wrong event prefix in doc comment

* Make className prop in CartLineItemsTableProps optional

* Mini Cart as template part

* Remove BlockTemplatePartsController and instead use BlockTemplatesController

* Remove old code

* Clean up frontend rendering

* Update tests

* Improve if clause

* Fix wrong tests title

* Fix wrong variable name

* Make sure Mini Cart contents block is unmounted whem mini cart closes or unmounts

* Remove unnecessary waitFor

* Fix PaymentMethodDataProvider wrong children type

* TypeScript fixes

* Make comment shorter

* Remove test code

* Fix contant unmounts of Mini Cart contents block

* Fix wrong template_type passed

* Set Template part area to 'uncategorized'

* Set Template part area to the correct value

* Move template dir check outside loop
  • Loading branch information
Aljullu authored and jonny-bull committed Dec 16, 2021
1 parent 31f7401 commit 1541be6
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 208 deletions.
Expand Up @@ -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`
Expand Down
Expand Up @@ -60,7 +60,7 @@ export const usePaymentMethodDataContext = (): PaymentMethodDataContextType => {
export const PaymentMethodDataProvider = ( {
children,
}: {
children: React.ReactChildren;
children: React.ReactNode;
} ): JSX.Element => {
const {
isProcessing: checkoutIsProcessing,
Expand Down
48 changes: 39 additions & 9 deletions assets/js/base/utils/render-frontend.js
Expand Up @@ -53,17 +53,47 @@ const renderBlockInContainers = ( {
};
el.classList.remove( 'is-loading' );

render(
<BlockErrorBoundary { ...errorBoundaryProps }>
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
<Block { ...props } attributes={ attributes } />
</Suspense>
</BlockErrorBoundary>,
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(
<BlockErrorBoundary { ...errorBoundaryProps }>
<Suspense fallback={ <div className="wc-block-placeholder" /> }>
<Block { ...props } attributes={ attributes } />
</Suspense>
</BlockErrorBoundary>,
container
);
};

/**
* Renders the block frontend in the elements matched by the selector which are
* outside the wrapper elements.
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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[] ) => {
Expand Down
104 changes: 104 additions & 0 deletions 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 (
<PaymentMethodIcons
icons={ getIconsFromPaymentMethods( paymentMethods ) }
/>
);
};

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 ? (
<div
className="wc-block-mini-cart__empty-cart"
tabIndex={ -1 }
ref={ emptyCartRef }
>
{ __( 'Cart is empty', 'woo-gutenberg-products-block' ) }
</div>
) : (
<>
<div className="wc-block-mini-cart__items">
<CartLineItemsTable
lineItems={ cartItems }
isLoading={ cartIsLoading }
/>
</div>
<div className="wc-block-mini-cart__footer">
<TotalsItem
className="wc-block-mini-cart__footer-subtotal"
currency={ getCurrencyFromPriceResponse( cartTotals ) }
label={ __( 'Subtotal', 'woo-gutenberg-products-block' ) }
value={ subTotal }
description={ __(
'Shipping, taxes, and discounts calculated at checkout.',
'woo-gutenberg-products-block'
) }
/>
<div className="wc-block-mini-cart__footer-actions">
<Button
className="wc-block-mini-cart__footer-cart"
href={ CART_URL }
>
{ __( 'View my cart', 'woo-gutenberg-products-block' ) }
</Button>
<Button
className="wc-block-mini-cart__footer-checkout"
href={ CHECKOUT_URL }
>
{ __(
'Go to checkout',
'woo-gutenberg-products-block'
) }
</Button>
</div>
<PaymentMethodDataProvider>
<PaymentMethodIconsElement />
</PaymentMethodDataProvider>
</div>
</>
);
};

export default MiniCartContentsBlock;
17 changes: 17 additions & 0 deletions 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 (
<div { ...blockProps }>
<p>Editing the mini cart contents</p>
</div>
);
};

export default Edit;
62 changes: 62 additions & 0 deletions 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: <Icon srcElement={ cart } />,
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 );

0 comments on commit 1541be6

Please sign in to comment.