Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Mini Cart as template part #5025

Merged
merged 21 commits into from Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -36,7 +36,7 @@ const setUp = (): void => {
const addListeners = (): void => {
setUp();

if ( ! window.wcBlocksStoreCartListeners.count ) {
if ( window.wcBlocksStoreCartListeners.count === 0 ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is not directly related to this PR, but still wanted to add it. 🙂

count is going to be an integer, so it's more precise to compare it to 0 than to check if it's falsy.

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