Fix: Mini Cart block: divide contents into three inner blocks (https://github.com/woocommerce/woocommerce-blocks/pull/5386)

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
Tung Du 2021-12-20 14:57:55 +07:00 committed by GitHub
parent 4524799b5e
commit 50066455c0
25 changed files with 456 additions and 132 deletions

View File

@ -16,6 +16,7 @@ import type { TemplateArray } from '@wordpress/blocks';
* Internal dependencies * Internal dependencies
*/ */
import { useViewSwitcher, useForcedLayout } from '../shared'; import { useViewSwitcher, useForcedLayout } from '../shared';
import './editor.scss';
// Array of allowed block names. // Array of allowed block names.
const ALLOWED_BLOCKS = [ const ALLOWED_BLOCKS = [

View File

@ -0,0 +1,16 @@
.wp-block-woocommerce-mini-cart-contents {
max-width: 480px;
margin: 0 auto;
}
.wp-block-woocommerce-filled-mini-cart-contents-block {
.block-editor-block-list__layout {
display: flex;
flex-direction: column;
min-height: calc(100vh - 2 * $gap-largest);
}
}
.wp-block-woocommerce-mini-cart-products-table-block {
margin-bottom: auto;
}

View File

@ -36,6 +36,9 @@ const settings = {
reusable: false, reusable: false,
inserter: false, inserter: false,
__experimentalExposeControlsToChildren: true, __experimentalExposeControlsToChildren: true,
color: {
text: false,
},
}, },
attributes: { attributes: {
isPreview: { isPreview: {

View File

@ -1,11 +1,11 @@
/** /**
* External dependencies * External dependencies
*/ */
import { __ } from '@wordpress/i18n';
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { innerBlockAreas } from '@woocommerce/blocks-checkout'; import { innerBlockAreas } from '@woocommerce/blocks-checkout';
import type { TemplateArray } from '@wordpress/blocks'; import type { TemplateArray } from '@wordpress/blocks';
import { useEditorContext } from '@woocommerce/base-context'; import { EditorProvider, useEditorContext } from '@woocommerce/base-context';
import { previewCart } from '@woocommerce/resource-previews';
/** /**
* Internal dependencies * Internal dependencies
@ -18,16 +18,9 @@ export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
const { currentView } = useEditorContext(); const { currentView } = useEditorContext();
const defaultTemplate = ( [ const defaultTemplate = ( [
[ [ 'woocommerce/mini-cart-title-block', {} ],
'core/heading', [ 'woocommerce/mini-cart-products-table-block', {} ],
{ [ 'woocommerce/mini-cart-footer-block', {} ],
content: __(
'Filled mini cart content',
'woo-gutenberg-products-block'
),
level: 2,
},
],
].filter( Boolean ) as unknown ) as TemplateArray; ].filter( Boolean ) as unknown ) as TemplateArray;
useForcedLayout( { useForcedLayout( {
@ -42,11 +35,17 @@ export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
hidden={ hidden={
currentView !== 'woocommerce/filled-mini-cart-contents-block' currentView !== 'woocommerce/filled-mini-cart-contents-block'
} }
>
<EditorProvider
currentView={ currentView }
previewData={ { previewCart } }
> >
<InnerBlocks <InnerBlocks
template={ defaultTemplate }
allowedBlocks={ allowedBlocks } allowedBlocks={ allowedBlocks }
templateLock="insert" templateLock="insert"
/> />
</EditorProvider>
</div> </div>
); );
}; };

View File

@ -1,91 +1,20 @@
/** /**
* External dependencies * External dependencies
*/ */
import { __ } from '@wordpress/i18n'; import { useStoreCart } from '@woocommerce/base-context/hooks';
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';
/** const FilledMiniCartContentsBlock = ( {
* Internal dependencies children,
*/ }: {
import CartLineItemsTable from '../../../cart/cart-line-items-table';
const PaymentMethodIconsElement = (): JSX.Element => {
const { paymentMethods } = usePaymentMethods();
return (
<PaymentMethodIcons
icons={ getIconsFromPaymentMethods( paymentMethods ) }
/>
);
};
type FilledMiniCartContentsBlockProps = {
children: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[];
}; } ): JSX.Element | null => {
const { cartItems } = useStoreCart();
const FilledMiniCartContentsBlock = ( {}: FilledMiniCartContentsBlockProps ): JSX.Element | null => { if ( cartItems.length === 0 ) {
const { cartItems, cartIsLoading, cartTotals } = useStoreCart();
const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( cartTotals.total_items, 10 ) +
parseInt( cartTotals.total_items_tax, 10 )
: parseInt( cartTotals.total_items, 10 );
if ( cartIsLoading || cartItems.length === 0 ) {
return null; return null;
} }
return ( return <>{ children }</>;
<>
<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 FilledMiniCartContentsBlock; export default FilledMiniCartContentsBlock;

View File

@ -3,3 +3,6 @@
*/ */
import './empty-mini-cart-contents-block'; import './empty-mini-cart-contents-block';
import './filled-mini-cart-contents-block'; import './filled-mini-cart-contents-block';
import './mini-cart-title-block';
import './mini-cart-products-table-block';
import './mini-cart-footer-block';

View File

@ -0,0 +1,26 @@
{
"name": "woocommerce/mini-cart-footer-block",
"version": "1.0.0",
"title": "Mini Cart Footer",
"description": "Block that displays the footer of the Mini Cart block.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false
},
"attributes": {
"lock": {
"type": "object",
"default": {
"remove": true,
"move": true
}
}
},
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2
}

View File

@ -0,0 +1,73 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { TotalsItem } from '@woocommerce/blocks-checkout';
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
import {
usePaymentMethods,
useStoreCart,
} from '@woocommerce/base-context/hooks';
import PaymentMethodIcons from '@woocommerce/base-components/cart-checkout/payment-method-icons';
import { getIconsFromPaymentMethods } from '@woocommerce/base-utils';
import { getSetting } from '@woocommerce/settings';
import { CART_URL, CHECKOUT_URL } from '@woocommerce/block-settings';
import Button from '@woocommerce/base-components/button';
import { PaymentMethodDataProvider } from '@woocommerce/base-context';
const PaymentMethodIconsElement = (): JSX.Element => {
const { paymentMethods } = usePaymentMethods();
return (
<PaymentMethodIcons
icons={ getIconsFromPaymentMethods( paymentMethods ) }
/>
);
};
const Block = (): JSX.Element => {
const { cartTotals } = useStoreCart();
const subTotal = getSetting( 'displayCartPricesIncludingTax', false )
? parseInt( cartTotals.total_items, 10 ) +
parseInt( cartTotals.total_items_tax, 10 )
: parseInt( cartTotals.total_items, 10 );
return (
<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">
{ CART_URL && (
<Button
className="wc-block-mini-cart__footer-cart"
href={ CART_URL }
>
{ __( 'View my cart', 'woo-gutenberg-products-block' ) }
</Button>
) }
{ CHECKOUT_URL && (
<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 Block;

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { useBlockProps } from '@wordpress/block-editor';
import Noninteractive from '@woocommerce/base-components/noninteractive';
/**
* Internal dependencies
*/
import Block from './block';
export const Edit = (): JSX.Element => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<Noninteractive>
<Block />
</Noninteractive>
</div>
);
};

View File

@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { Icon, card } from '@woocommerce/icons';
import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import { Edit } from './edit';
import metadata from './block.json';
registerFeaturePluginBlockType( metadata, {
icon: {
src: (
<Icon
srcElement={ card }
className="wc-block-editor-components-block-icon"
/>
),
},
edit: Edit,
} );

View File

@ -0,0 +1,26 @@
{
"name": "woocommerce/mini-cart-products-table-block",
"version": "1.0.0",
"title": "Mini Cart Products Table",
"description": "Block that displays the products table of the Mini Cart block.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false
},
"attributes": {
"lock": {
"type": "object",
"default": {
"remove": true,
"move": true
}
}
},
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2
}

View File

@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { useStoreCart } from '@woocommerce/base-context/hooks';
/**
* Internal dependencies
*/
import CartLineItemsTable from '../../../cart/cart-line-items-table';
const Block = (): JSX.Element => {
const { cartItems, cartIsLoading } = useStoreCart();
return (
<div className="wc-block-mini-cart__items">
<CartLineItemsTable
lineItems={ cartItems }
isLoading={ cartIsLoading }
/>
</div>
);
};
export default Block;

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { useBlockProps } from '@wordpress/block-editor';
import Noninteractive from '@woocommerce/base-components/noninteractive';
/**
* Internal dependencies
*/
import Block from './block';
export const Edit = (): JSX.Element => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<Noninteractive>
<Block />
</Noninteractive>
</div>
);
};

View File

@ -0,0 +1,21 @@
/**
* External dependencies
*/
import { Icon, list } from '@woocommerce/icons';
import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import { Edit } from './edit';
import metadata from './block.json';
registerFeaturePluginBlockType( metadata, {
icon: (
<Icon
srcElement={ list }
className="wc-block-editor-components-block-icon"
/>
),
edit: Edit,
} );

View File

@ -0,0 +1,26 @@
{
"name": "woocommerce/mini-cart-title-block",
"version": "1.0.0",
"title": "Mini Cart Title",
"description": "Block that displays the title of the Mini Cart block.",
"category": "woocommerce",
"supports": {
"align": false,
"html": false,
"multiple": false,
"reusable": false,
"inserter": false
},
"attributes": {
"lock": {
"type": "object",
"default": {
"remove": true,
"move": true
}
}
},
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2
}

View File

@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { sprintf, _n, __ } from '@wordpress/i18n';
import { useStoreCart } from '@woocommerce/base-context/hooks';
/**
* Internal dependencies
*/
const Block = (): JSX.Element => {
const { cartItemsCount, cartIsLoading } = useStoreCart();
return (
<h2 className="wc-block-mini-cart__title">
{ cartIsLoading
? __( 'Your cart', 'woo-gutenberg-products-block' )
: sprintf(
/* translators: %d is the count of items in the cart. */
_n(
'Your cart (%d item)',
'Your cart (%d items)',
cartItemsCount,
'woo-gutenberg-products-block'
),
cartItemsCount
) }
</h2>
);
};
export default Block;

View File

@ -0,0 +1,19 @@
/**
* External dependencies
*/
import { useBlockProps } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
import Block from './block';
export const Edit = (): JSX.Element => {
const blockProps = useBlockProps();
return (
<div { ...blockProps }>
<Block />
</div>
);
};

View File

@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { Icon, bookmark } from '@woocommerce/icons';
import { registerFeaturePluginBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import { Edit } from './edit';
import metadata from './block.json';
registerFeaturePluginBlockType( metadata, {
icon: {
src: (
<Icon
srcElement={ bookmark }
className="wc-block-editor-components-block-icon"
/>
),
},
edit: Edit,
} );

View File

@ -9,6 +9,9 @@ import { lazy } from '@wordpress/element';
*/ */
import emptyMiniCartContentsMetadata from './empty-mini-cart-contents-block/block.json'; import emptyMiniCartContentsMetadata from './empty-mini-cart-contents-block/block.json';
import filledMiniCartMetadata from './filled-mini-cart-contents-block/block.json'; import filledMiniCartMetadata from './filled-mini-cart-contents-block/block.json';
import miniCartTitleMetadata from './mini-cart-title-block/block.json';
import miniCartProductsTableMetadata from './mini-cart-products-table-block/block.json';
import miniCartFooterMetadata from './mini-cart-footer-block/block.json';
// Modify webpack publicPath at runtime based on location of WordPress Plugin. // Modify webpack publicPath at runtime based on location of WordPress Plugin.
// eslint-disable-next-line no-undef,camelcase // eslint-disable-next-line no-undef,camelcase
@ -31,3 +34,30 @@ registerCheckoutBlock( {
) )
), ),
} ); } );
registerCheckoutBlock( {
metadata: miniCartTitleMetadata,
component: lazy( () =>
import(
/* webpackChunkName: "mini-cart-contents-block/title" */ './mini-cart-title-block/block'
)
),
} );
registerCheckoutBlock( {
metadata: miniCartProductsTableMetadata,
component: lazy( () =>
import(
/* webpackChunkName: "mini-cart-contents-block/products-table" */ './mini-cart-products-table-block/block'
)
),
} );
registerCheckoutBlock( {
metadata: miniCartFooterMetadata,
component: lazy( () =>
import(
/* webpackChunkName: "mini-cart-contents-block/footer" */ './mini-cart-footer-block/block'
)
),
} );

View File

@ -19,7 +19,7 @@ import {
useEffect, useEffect,
useState, useState,
} from '@wordpress/element'; } from '@wordpress/element';
import { sprintf, _n, __ } from '@wordpress/i18n'; import { sprintf, _n } from '@wordpress/i18n';
import classnames from 'classnames'; import classnames from 'classnames';
/** /**
* Internal dependencies * Internal dependencies
@ -183,27 +183,18 @@ const MiniCartBlock = ( {
'is-loading': cartIsLoading, 'is-loading': cartIsLoading,
} }
) } ) }
title={ title=""
cartIsLoading
? __( 'Your cart', 'woo-gutenberg-products-block' )
: sprintf(
/* translators: %d is the count of items in the cart. */
_n(
'Your cart (%d item)',
'Your cart (%d items)',
cartItemsCount,
'woo-gutenberg-products-block'
),
cartItemsCount
)
}
isOpen={ isOpen } isOpen={ isOpen }
onClose={ () => { onClose={ () => {
setIsOpen( false ); setIsOpen( false );
} } } }
slideIn={ ! skipSlideIn } slideIn={ ! skipSlideIn }
> >
<div ref={ contentsRef }> <div
className="wc-block-mini-cart__template-part"
ref={ contentsRef }
>
{ /* @todo The `div` wrapper of RawHTML isn't removed on the front end. */ }
<RawHTML>{ contents }</RawHTML> <RawHTML>{ contents }</RawHTML>
</div> </div>
</Drawer> </Drawer>

View File

@ -54,14 +54,14 @@
font-size: 1rem; font-size: 1rem;
.components-modal__content { .components-modal__content {
box-sizing: border-box; padding: 0;
display: flex; position: relative;
flex-direction: column;
height: 100%;
} }
.components-modal__header { .components-modal__header {
margin: $gap 0; position: absolute;
top: $gap-largest;
right: $gap;
} }
.wc-block-mini-cart__items { .wc-block-mini-cart__items {
@ -76,6 +76,21 @@
} }
} }
// @todo Review the class naming convention for Mini Cart inner blocks.
.wp-block-woocommerce-mini-cart-contents {
background: #fff;
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
padding: $gap-largest $gap;
}
.wc-block-mini-cart__title {
@include font-size(large);
margin-top: 0;
}
.wc-block-mini-cart__footer { .wc-block-mini-cart__footer {
border-top: 1px solid $gray-300; border-top: 1px solid $gray-300;
margin-bottom: -$gap-largest; margin-bottom: -$gap-largest;

View File

@ -71,7 +71,6 @@ describe( 'Testing Mini Cart', () => {
fireEvent.click( screen.getByLabelText( /items/i ) ); fireEvent.click( screen.getByLabelText( /items/i ) );
} ); } );
expect( screen.getByText( /Your cart/i ) ).toBeInTheDocument();
expect( fetchMock ).toHaveBeenCalledTimes( 1 ); expect( fetchMock ).toHaveBeenCalledTimes( 1 );
// ["`select` control in `@wordpress/data-controls` is deprecated. Please use built-in `resolveSelect` control in `@wordpress/data` instead."] // ["`select` control in `@wordpress/data-controls` is deprecated. Please use built-in `resolveSelect` control in `@wordpress/data` instead."]
expect( console ).toHaveWarned(); expect( console ).toHaveWarned();
@ -86,7 +85,6 @@ describe( 'Testing Mini Cart', () => {
fireEvent.click( screen.getByLabelText( /items/i ) ); fireEvent.click( screen.getByLabelText( /items/i ) );
} ); } );
expect( screen.getByText( /0 items/i ) ).toBeInTheDocument();
expect( fetchMock ).toHaveBeenCalledTimes( 1 ); expect( fetchMock ).toHaveBeenCalledTimes( 1 );
} ); } );

View File

@ -20,7 +20,7 @@ export enum innerBlockAreas {
CART_TOTALS = 'woocommerce/cart-totals-block', CART_TOTALS = 'woocommerce/cart-totals-block',
MINI_CART = 'woocommerce/mini-cart-contents', MINI_CART = 'woocommerce/mini-cart-contents',
EMPTY_MINI_CART = 'woocommerce/empty-mini-cart-contents-block', EMPTY_MINI_CART = 'woocommerce/empty-mini-cart-contents-block',
FILLED_MINI_CART = 'woocommerce/filled-mini-cart-content-block', FILLED_MINI_CART = 'woocommerce/filled-mini-cart-contents-block',
} }
interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > { interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > {

View File

@ -397,9 +397,7 @@ class MiniCart extends AbstractBlock {
<div class="components-modal__frame wc-block-components-drawer"> <div class="components-modal__frame wc-block-components-drawer">
<div class="components-modal__content"> <div class="components-modal__content">
<div class="components-modal__header"> <div class="components-modal__header">
<div class="components-modal__header-heading-container"> <div class="components-modal__header-heading-container"></div>
<h1 id="components-modal-header-1" class="components-modal__header-heading">' . wp_kses_post( $title ) . '</h1>
</div>
</div> </div>
<div class="wc-block-mini-cart__template-part">' <div class="wc-block-mini-cart__template-part">'
. wp_kses_post( $template_part_contents ) . . wp_kses_post( $template_part_contents ) .

View File

@ -1,13 +1,19 @@
<!-- wp:woocommerce/mini-cart-contents --> <!-- wp:woocommerce/mini-cart-contents -->
<div class="wp-block-woocommerce-mini-cart-contents"><!-- wp:woocommerce/filled-mini-cart-contents-block --> <div class="wp-block-woocommerce-mini-cart-contents">
<div class="wp-block-woocommerce-filled-mini-cart-contents-block"><!-- wp:heading --> <!-- wp:woocommerce/filled-mini-cart-contents-block -->
<h2 id="filled-mini-cart-content">Filled mini cart content</h2> <div class="wp-block-woocommerce-filled-mini-cart-contents-block">
<!-- /wp:heading --></div> <!-- wp:woocommerce/mini-cart-title-block /-->
<!-- wp:woocommerce/mini-cart-products-table-block /-->
<!-- wp:woocommerce/mini-cart-footer-block /-->
</div>
<!-- /wp:woocommerce/filled-mini-cart-contents-block --> <!-- /wp:woocommerce/filled-mini-cart-contents-block -->
<!-- wp:woocommerce/empty-mini-cart-contents-block --> <!-- wp:woocommerce/empty-mini-cart-contents-block -->
<div class="wp-block-woocommerce-empty-mini-cart-contents-block"><!-- wp:heading --> <div class="wp-block-woocommerce-empty-mini-cart-contents-block">
<!-- wp:heading -->
<h2 id="empty-mini-cart-content">Empty mini cart content</h2> <h2 id="empty-mini-cart-content">Empty mini cart content</h2>
<!-- /wp:heading --></div> <!-- /wp:heading -->
<!-- /wp:woocommerce/empty-mini-cart-contents-block --></div> </div>
<!-- /wp:woocommerce/empty-mini-cart-contents-block -->
</div>
<!-- /wp:woocommerce/mini-cart-contents --> <!-- /wp:woocommerce/mini-cart-contents -->