* 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
This commit is contained in:
Albert Juhé Lluveras 2021-11-19 12:47:48 +01:00 committed by GitHub
parent 65233c4e86
commit bbaa4d8798
17 changed files with 456 additions and 208 deletions

View File

@ -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`

View File

@ -60,7 +60,7 @@ export const usePaymentMethodDataContext = (): PaymentMethodDataContextType => {
export const PaymentMethodDataProvider = ( {
children,
}: {
children: React.ReactChildren;
children: React.ReactNode;
} ): JSX.Element => {
const {
isProcessing: checkoutIsProcessing,

View File

@ -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.
@ -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

View File

@ -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[] ) => {

View File

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

View File

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

View File

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

View File

@ -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 (
<PaymentMethodIcons
icons={ getIconsFromPaymentMethods( paymentMethods ) }
/>
);
};
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 ? (
<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>
</>
);
return (
<>
<button
@ -236,7 +188,9 @@ const MiniCartBlock = ( {
} }
slideIn={ ! skipSlideIn }
>
{ contents }
<div ref={ contentsRef }>
<RawHTML>{ contents }</RawHTML>
</div>
</Drawer>
</>
);

View File

@ -41,6 +41,9 @@ const renderMiniCartFrontend = () => {
isInitiallyOpen: el.dataset.isInitiallyOpen === 'true',
colorClassNames,
style: el.dataset.style ? JSON.parse( el.dataset.style ) : {},
contents: el.querySelector(
'.wc-block-mini-cart__template-part'
)?.innerHTML,
};
},
} );

View File

@ -23,7 +23,10 @@ import { defaultCartState } from '../../../../data/default-states';
const MiniCartBlock = ( props ) => (
<SlotFillProvider>
<Block { ...props } />
<Block
contents='<div class="wc-block-mini-cart-contents"></div>'
{ ...props }
/>
</SlotFillProvider>
);
@ -47,7 +50,7 @@ const mockFullCart = () => {
} );
};
describe( 'Testing cart', () => {
describe( 'Testing Mini Cart', () => {
beforeEach( async () => {
mockFullCart();
// need to clear the store resolution state between tests.
@ -93,10 +96,12 @@ describe( 'Testing cart', () => {
} );
await waitForElementToBeRemoved( () =>
screen.queryByLabelText( /3 items/i )
screen.queryByLabelText( /3 items in cart/i )
);
await waitFor( () =>
expect( screen.getByLabelText( /0 items/i ) ).toBeInTheDocument()
expect(
screen.getByLabelText( /0 items in cart/i )
).toBeInTheDocument()
);
} );
@ -107,16 +112,18 @@ describe( 'Testing cart', () => {
mockFullCart();
// eslint-disable-next-line no-undef
const removedFromCartEvent = new Event( 'wc-blocks_added_to_cart' );
const addedToCartEvent = new Event( 'wc-blocks_added_to_cart' );
act( () => {
document.body.dispatchEvent( removedFromCartEvent );
document.body.dispatchEvent( addedToCartEvent );
} );
await waitForElementToBeRemoved( () =>
screen.queryByLabelText( /0 items/i )
screen.queryByLabelText( /0 items in cart/i )
);
await waitFor( () =>
expect( screen.getAllByLabelText( /3 items/i ).length > 0 )
expect(
screen.getByLabelText( /3 items in cart/i )
).toBeInTheDocument()
);
} );
} );

View File

@ -50,6 +50,10 @@ const blocks = {
customDir: 'cart-checkout/mini-cart',
isExperimental: true,
},
'mini-cart-contents': {
customDir: 'cart-checkout/mini-cart-contents',
isExperimental: true,
},
'single-product': {
isExperimental: true,
},

View File

@ -17,6 +17,13 @@ class BlockTemplatesController {
*/
private $templates_directory;
/**
* Holds the path for the directory where the block template parts will be kept.
*
* @var string
*/
private $template_parts_directory;
/**
* Directory name of the block template directory.
*
@ -24,11 +31,19 @@ class BlockTemplatesController {
*/
const TEMPLATES_DIR_NAME = 'block-templates';
/**
* Directory name of the block template parts directory.
*
* @var string
*/
const TEMPLATE_PARTS_DIR_NAME = 'block-template-parts';
/**
* Constructor.
*/
public function __construct() {
$this->templates_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATES_DIR_NAME;
$this->templates_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATES_DIR_NAME;
$this->template_parts_directory = plugin_dir_path( __DIR__ ) . 'templates/' . self::TEMPLATE_PARTS_DIR_NAME;
$this->init();
}
@ -116,11 +131,11 @@ class BlockTemplatesController {
list( , $slug ) = $template_name_parts;
// If this blocks template doesn't exist then we should just skip the function and let Gutenberg handle it.
if ( ! $this->block_template_is_available( $slug ) ) {
if ( ! $this->block_template_is_available( $slug, $template_type ) ) {
return $template;
}
$available_templates = $this->get_block_templates( array( $slug ) );
$available_templates = $this->get_block_templates( array( $slug ), $template_type );
return ( is_array( $available_templates ) && count( $available_templates ) > 0 )
? BlockTemplateUtils::gutenberg_build_template_result_from_file( $available_templates[0], $available_templates[0]->type )
: $template;
@ -135,13 +150,13 @@ class BlockTemplatesController {
* @return array
*/
public function add_block_templates( $query_result, $query, $template_type ) {
if ( ! function_exists( 'gutenberg_supports_block_templates' ) || ! gutenberg_supports_block_templates() || 'wp_template' !== $template_type ) {
if ( ! function_exists( 'gutenberg_supports_block_templates' ) || ! gutenberg_supports_block_templates() ) {
return $query_result;
}
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
$template_files = $this->get_block_templates( $slugs );
$template_files = $this->get_block_templates( $slugs, $template_type );
// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
foreach ( $template_files as $template_file ) {
@ -172,7 +187,7 @@ class BlockTemplatesController {
// It would be custom if the template was modified in the editor, so if it's not custom we can load it from
// the filesystem.
if ( 'custom' !== $template_file->source ) {
$template = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, 'wp_template' );
$template = BlockTemplateUtils::gutenberg_build_template_result_from_file( $template_file, $template_type );
} else {
$template_file->title = BlockTemplateUtils::convert_slug_to_title( $template_file->slug );
$query_result[] = $template_file;
@ -242,12 +257,13 @@ class BlockTemplatesController {
* Gets the templates saved in the database.
*
* @param array $slugs An array of slugs to retrieve templates for.
* @param array $template_type wp_template or wp_template_part.
*
* @return int[]|\WP_Post[] An array of found templates.
*/
public function get_block_templates_from_db( $slugs = array() ) {
public function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) {
$check_query_args = array(
'post_type' => 'wp_template',
'post_type' => $template_type,
'posts_per_page' => -1,
'no_found_rows' => true,
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
@ -278,16 +294,25 @@ class BlockTemplatesController {
*
* @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
* @param array $already_found_templates Templates that have already been found, these are customised templates that are loaded from the database.
* @param array $template_type wp_template or wp_template_part.
*
* @return array Templates from the WooCommerce blocks plugin directory.
*/
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates ) {
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $this->templates_directory );
public function get_block_templates_from_woocommerce( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
$directory = $this->get_templates_directory( $template_type );
$template_files = BlockTemplateUtils::gutenberg_get_template_paths( $directory );
$templates = array();
if ( 'wp_template_part' === $template_type ) {
$dir_name = self::TEMPLATE_PARTS_DIR_NAME;
} else {
$dir_name = self::TEMPLATES_DIR_NAME;
}
foreach ( $template_files as $template_file ) {
$template_slug = substr(
$template_file,
strpos( $template_file, self::TEMPLATES_DIR_NAME . DIRECTORY_SEPARATOR ) + 1 + strlen( self::TEMPLATES_DIR_NAME ),
strpos( $template_file, $dir_name . DIRECTORY_SEPARATOR ) + 1 + strlen( $dir_name ),
-5
);
@ -318,7 +343,7 @@ class BlockTemplatesController {
'slug' => $template_slug,
'id' => 'woocommerce//' . $template_slug,
'path' => $template_file,
'type' => 'wp_template',
'type' => $template_type,
'theme' => 'woocommerce',
'source' => 'woocommerce',
'title' => BlockTemplateUtils::convert_slug_to_title( $template_slug ),
@ -334,14 +359,31 @@ class BlockTemplatesController {
* Get and build the block template objects from the block template files.
*
* @param array $slugs An array of slugs to retrieve templates for.
* @param array $template_type wp_template or wp_template_part.
*
* @return array
*/
public function get_block_templates( $slugs = array() ) {
$templates_from_db = $this->get_block_templates_from_db( $slugs );
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db );
public function get_block_templates( $slugs = array(), $template_type = 'wp_template' ) {
$templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
$templates_from_woo = $this->get_block_templates_from_woocommerce( $slugs, $templates_from_db, $template_type );
return array_merge( $templates_from_db, $templates_from_woo );
}
/**
* Gets the directory where templates of a specific template type can be found.
*
* @param array $template_type wp_template or wp_template_part.
*
* @return string
*/
protected function get_templates_directory( $template_type = 'wp_template' ) {
if ( 'wp_template_part' === $template_type ) {
return $this->template_parts_directory;
}
return $this->templates_directory;
}
/**
* Check if the theme has a template. So we know if to load our own in or not.
*
@ -357,16 +399,19 @@ class BlockTemplatesController {
* Checks whether a block template with that name exists in Woo Blocks
*
* @param string $template_name Template to check.
* @param array $template_type wp_template or wp_template_part.
*
* @return boolean
*/
public function block_template_is_available( $template_name ) {
public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
if ( ! $template_name ) {
return false;
}
$directory = $this->get_templates_directory( $template_type ) . '/' . $template_name . '.html';
return is_readable(
$this->templates_directory . '/' . $template_name . '.html'
) || $this->get_block_templates( array( $template_name ) );
$directory
) || $this->get_block_templates( array( $template_name ), $template_type );
}
/**

View File

@ -302,6 +302,13 @@ class MiniCart extends AbstractBlock {
</div>';
}
$part = 'mini-cart';
$template_part = gutenberg_get_block_template( get_stylesheet() . '//' . $part, 'wp_template_part' );
$template_part_contents = '';
if ( $template_part && ! empty( $template_part->content ) ) {
$template_part_contents = do_blocks( $template_part->content );
}
return '<div class="' . $wrapper_classes . '">
<button class="wc-block-mini-cart__button ' . $classes . '" aria-label="' . esc_attr( $aria_label ) . '" style="' . $style . '">' . $button_html . '</button>
<div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
@ -311,81 +318,13 @@ class MiniCart extends AbstractBlock {
<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>'
. $this->get_cart_contents_markup( $cart_contents ) .
'</div>
</div>
<div class="wc-block-mini-cart__template-part">'
. $template_part_contents .
'</div>
</div>
</div>
</div>
</div>';
}
/**
* Render the markup of the Cart contents.
*
* @param array $cart_contents Array of contents in the cart.
*
* @return string The HTML markup.
*/
protected function get_cart_contents_markup( $cart_contents ) {
// Force mobile styles.
return '<table class="wc-block-cart-items">
<thead>
<tr class="wc-block-cart-items__header">
<th class="wc-block-cart-items__header-image"><span /></th>
<th class="wc-block-cart-items__header-product"><span /></th>
<th class="wc-block-cart-items__header-total"><span /></th>
</tr>
</thead>
<tbody>' . implode( array_map( array( $this, 'get_cart_item_markup' ), $cart_contents ) ) . '</tbody>
</table>';
}
/**
* Render the skeleton of a Cart item.
*
* @return string The skeleton HTML markup.
*/
protected function get_cart_item_markup() {
return '<tr class="wc-block-cart-items__row">
<td class="wc-block-cart-item__image">
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
</td>
<td class="wc-block-cart-item__product">
<div class="wc-block-components-product-name"></div>
<div class="wc-block-components-product-price"></div>
<div class="wc-block-components-product-metadata"></div>
<div class="wc-block-cart-item__quantity">
<div class="wc-block-components-quantity-selector">
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus"></button>
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus"></button>
</div>
<button class="wc-block-cart-item__remove-link"></button>
</div>
</td>
<td class="wc-block-cart-item__total">
<div class="wc-block-cart-item__total-price-and-sale-badge-wrapper">
<div class="wc-block-components-product-price"></div>
</div>
</td>
</tr>';
}
/**
* Get the supports array for this block type.
*
* @see $this->register_block_type()
* @return string;
*/
protected function get_block_type_supports() {
return array_merge(
parent::get_block_type_supports(),
array(
'html' => false,
'multiple' => false,
'color' => true,
'__experimentalSelector' => '.wc-block-mini-cart__button, .wc-block-mini-cart__badge',
)
);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Assets;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
/**
* Mini Cart class.
*
* @internal
*/
class MiniCartContents extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'mini-cart-contents';
/**
* Get the editor script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return array|string;
*/
protected function get_block_type_editor_script( $key = null ) {
$script = [
'handle' => 'wc-' . $this->block_name . '-block',
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
'dependencies' => [ 'wc-blocks' ],
];
return $key ? $script[ $key ] : $script;
}
/**
* Get the frontend script handle for this block type.
*
* @param string $key Data to get, or default to everything.
*
* @return null
*/
protected function get_block_type_script( $key = null ) {
// The frontend script is a dependency of the Mini Cart block so it's
// already lazy-loaded.
return null;
}
/**
* Render the markup for the Mini Cart contents block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
*
* @return string Rendered block type output.
*/
protected function render( $attributes, $content ) {
if ( is_admin() || WC()->is_rest_api_request() ) {
// In the editor we will display the placeholder, so no need to
// print the markup.
return '';
}
return '<div class="wc-block-mini-cart-contents"></div>';
}
}

View File

@ -185,6 +185,7 @@ final class BlockTypesController {
if ( Package::feature()->is_experimental_build() ) {
$block_types[] = 'SingleProduct';
$block_types[] = 'MiniCart';
$block_types[] = 'MiniCartContents';
}
/**

View File

@ -109,6 +109,12 @@ class BlockTemplateUtils {
$template->has_theme_file = $has_theme_file;
$template->is_custom = true;
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
if ( 'wp_template_part' === $post->post_type ) {
$type_terms = get_the_terms( $post, 'wp_template_part_area' );
if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
$template->area = $type_terms[0]->name;
}
}
return $template;
}
@ -137,6 +143,13 @@ class BlockTemplateUtils {
$template->has_theme_file = true;
$template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
$template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
if ( 'wp_template_part' === $template_type ) {
if ( 'mini-cart' === $template_file->slug ) {
$template->area = 'mini-cart';
} else {
$template->area = 'uncategorized';
}
}
return $template;
}

View File

@ -0,0 +1 @@
<!-- wp:woocommerce/mini-cart-contents /-->