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
*/
import { useViewSwitcher, useForcedLayout } from '../shared';
import './editor.scss';
// Array of allowed block names.
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,
inserter: false,
__experimentalExposeControlsToChildren: true,
color: {
text: false,
},
},
attributes: {
isPreview: {

View File

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

View File

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

View File

@ -3,3 +3,6 @@
*/
import './empty-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 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.
// 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,
useState,
} from '@wordpress/element';
import { sprintf, _n, __ } from '@wordpress/i18n';
import { sprintf, _n } from '@wordpress/i18n';
import classnames from 'classnames';
/**
* Internal dependencies
@ -183,27 +183,18 @@ const MiniCartBlock = ( {
'is-loading': cartIsLoading,
}
) }
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
)
}
title=""
isOpen={ isOpen }
onClose={ () => {
setIsOpen( false );
} }
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>
</div>
</Drawer>

View File

@ -54,14 +54,14 @@
font-size: 1rem;
.components-modal__content {
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
position: relative;
}
.components-modal__header {
margin: $gap 0;
position: absolute;
top: $gap-largest;
right: $gap;
}
.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 {
border-top: 1px solid $gray-300;
margin-bottom: -$gap-largest;

View File

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

View File

@ -20,7 +20,7 @@ export enum innerBlockAreas {
CART_TOTALS = 'woocommerce/cart-totals-block',
MINI_CART = 'woocommerce/mini-cart-contents',
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 > {

View File

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

View File

@ -1,13 +1,19 @@
<!-- 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-filled-mini-cart-contents-block"><!-- wp:heading -->
<h2 id="filled-mini-cart-content">Filled mini cart content</h2>
<!-- /wp:heading --></div>
<!-- /wp:woocommerce/filled-mini-cart-contents-block -->
<div class="wp-block-woocommerce-mini-cart-contents">
<!-- wp:woocommerce/filled-mini-cart-contents-block -->
<div class="wp-block-woocommerce-filled-mini-cart-contents-block">
<!-- 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/empty-mini-cart-contents-block -->
<div class="wp-block-woocommerce-empty-mini-cart-contents-block"><!-- wp:heading -->
<h2 id="empty-mini-cart-content">Empty mini cart content</h2>
<!-- /wp:heading --></div>
<!-- /wp:woocommerce/empty-mini-cart-contents-block --></div>
<!-- wp:woocommerce/empty-mini-cart-contents-block -->
<div class="wp-block-woocommerce-empty-mini-cart-contents-block">
<!-- wp:heading -->
<h2 id="empty-mini-cart-content">Empty mini cart content</h2>
<!-- /wp:heading -->
</div>
<!-- /wp:woocommerce/empty-mini-cart-contents-block -->
</div>
<!-- /wp:woocommerce/mini-cart-contents -->