first commit
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { renderParentBlock } from '@woocommerce/atomic-utils';
|
||||
import Drawer from '@woocommerce/base-components/drawer';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils';
|
||||
import { getRegisteredBlockComponents } from '@woocommerce/blocks-registry';
|
||||
import {
|
||||
formatPrice,
|
||||
getCurrencyFromPriceResponse,
|
||||
} from '@woocommerce/price-format';
|
||||
import { getSettingWithCoercion } from '@woocommerce/settings';
|
||||
import {
|
||||
CartResponseTotals,
|
||||
isBoolean,
|
||||
isString,
|
||||
isCartResponseTotals,
|
||||
isNumber,
|
||||
} from '@woocommerce/types';
|
||||
import {
|
||||
unmountComponentAtNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { sprintf, _n } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import QuantityBadge from './quantity-badge';
|
||||
import { MiniCartContentsBlock } from './mini-cart-contents/block';
|
||||
import './style.scss';
|
||||
import { blockName } from './mini-cart-contents/attributes';
|
||||
|
||||
interface Props {
|
||||
isInitiallyOpen?: boolean;
|
||||
colorClassNames?: string;
|
||||
style?: Record< string, Record< string, string > >;
|
||||
contents: string;
|
||||
addToCartBehaviour: string;
|
||||
}
|
||||
|
||||
const MiniCartBlock = ( {
|
||||
isInitiallyOpen = false,
|
||||
colorClassNames,
|
||||
style,
|
||||
contents = '',
|
||||
addToCartBehaviour = 'none',
|
||||
}: Props ): JSX.Element => {
|
||||
const {
|
||||
cartItemsCount: cartItemsCountFromApi,
|
||||
cartIsLoading,
|
||||
cartTotals: cartTotalsFromApi,
|
||||
} = useStoreCart();
|
||||
|
||||
const isFirstLoadingCompleted = useRef( cartIsLoading );
|
||||
|
||||
useEffect( () => {
|
||||
if ( isFirstLoadingCompleted.current && ! cartIsLoading ) {
|
||||
isFirstLoadingCompleted.current = false;
|
||||
}
|
||||
}, [ cartIsLoading, isFirstLoadingCompleted ] );
|
||||
|
||||
const [ isOpen, setIsOpen ] = useState< boolean >( isInitiallyOpen );
|
||||
// We already rendered the HTML drawer placeholder, so we want to skip the
|
||||
// slide in animation.
|
||||
const [ skipSlideIn, setSkipSlideIn ] =
|
||||
useState< boolean >( isInitiallyOpen );
|
||||
const [ contentsNode, setContentsNode ] = useState< HTMLDivElement | null >(
|
||||
null
|
||||
);
|
||||
|
||||
const contentsRef = useCallback( ( node ) => {
|
||||
setContentsNode( node );
|
||||
}, [] );
|
||||
|
||||
useEffect( () => {
|
||||
const body = document.querySelector( 'body' );
|
||||
if ( body ) {
|
||||
if ( isOpen ) {
|
||||
Object.assign( body.style, { overflow: 'hidden' } );
|
||||
} else {
|
||||
Object.assign( body.style, { overflow: '' } );
|
||||
}
|
||||
}
|
||||
}, [ isOpen ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( contentsNode instanceof Element ) {
|
||||
const container = contentsNode.querySelector(
|
||||
'.wp-block-woocommerce-mini-cart-contents'
|
||||
);
|
||||
if ( ! container ) {
|
||||
return;
|
||||
}
|
||||
if ( isOpen ) {
|
||||
renderParentBlock( {
|
||||
Block: MiniCartContentsBlock,
|
||||
blockName,
|
||||
selector: '.wp-block-woocommerce-mini-cart-contents',
|
||||
blockMap: getRegisteredBlockComponents( blockName ),
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if ( contentsNode instanceof Element && isOpen ) {
|
||||
const container = contentsNode.querySelector(
|
||||
'.wp-block-woocommerce-mini-cart-contents'
|
||||
);
|
||||
if ( container ) {
|
||||
unmountComponentAtNode( container );
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [ isOpen, contentsNode ] );
|
||||
|
||||
useEffect( () => {
|
||||
const openMiniCart = () => {
|
||||
if ( addToCartBehaviour === 'open_drawer' ) {
|
||||
setSkipSlideIn( false );
|
||||
setIsOpen( true );
|
||||
}
|
||||
};
|
||||
|
||||
// Make it so we can read jQuery events triggered by WC Core elements.
|
||||
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
|
||||
'added_to_cart',
|
||||
'wc-blocks_added_to_cart'
|
||||
);
|
||||
|
||||
document.body.addEventListener(
|
||||
'wc-blocks_added_to_cart',
|
||||
openMiniCart
|
||||
);
|
||||
|
||||
return () => {
|
||||
removeJQueryAddedToCartEvent();
|
||||
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_added_to_cart',
|
||||
openMiniCart
|
||||
);
|
||||
};
|
||||
}, [ addToCartBehaviour ] );
|
||||
|
||||
const showIncludingTax = getSettingWithCoercion(
|
||||
'displayCartPricesIncludingTax',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
const preFetchedCartTotals =
|
||||
getSettingWithCoercion< CartResponseTotals | null >(
|
||||
'cartTotals',
|
||||
null,
|
||||
isCartResponseTotals
|
||||
);
|
||||
|
||||
const preFetchedCartItemsCount = getSettingWithCoercion< number >(
|
||||
'cartItemsCount',
|
||||
0,
|
||||
isNumber
|
||||
);
|
||||
|
||||
const taxLabel = getSettingWithCoercion( 'taxLabel', '', isString );
|
||||
|
||||
const cartTotals =
|
||||
! isFirstLoadingCompleted.current || preFetchedCartTotals === null
|
||||
? cartTotalsFromApi
|
||||
: preFetchedCartTotals;
|
||||
|
||||
const cartItemsCount = ! isFirstLoadingCompleted.current
|
||||
? cartItemsCountFromApi
|
||||
: preFetchedCartItemsCount;
|
||||
|
||||
const subTotal = showIncludingTax
|
||||
? parseInt( cartTotals.total_items, 10 ) +
|
||||
parseInt( cartTotals.total_items_tax, 10 )
|
||||
: parseInt( cartTotals.total_items, 10 );
|
||||
|
||||
const ariaLabel = sprintf(
|
||||
/* translators: %1$d is the number of products in the cart. %2$s is the cart total */
|
||||
_n(
|
||||
'%1$d item in cart, total price of %2$s',
|
||||
'%1$d items in cart, total price of %2$s',
|
||||
cartItemsCount,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
cartItemsCount,
|
||||
formatPrice( subTotal, getCurrencyFromPriceResponse( cartTotals ) )
|
||||
);
|
||||
|
||||
const colorStyle = {
|
||||
backgroundColor: style?.color?.background,
|
||||
color: style?.color?.text,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={ `wc-block-mini-cart__button ${ colorClassNames }` }
|
||||
style={ colorStyle }
|
||||
onClick={ () => {
|
||||
if ( ! isOpen ) {
|
||||
setIsOpen( true );
|
||||
setSkipSlideIn( false );
|
||||
}
|
||||
} }
|
||||
aria-label={ ariaLabel }
|
||||
>
|
||||
<span className="wc-block-mini-cart__amount">
|
||||
{ formatPrice(
|
||||
subTotal,
|
||||
getCurrencyFromPriceResponse( cartTotals )
|
||||
) }
|
||||
</span>
|
||||
{ taxLabel !== '' && subTotal !== 0 && (
|
||||
<small className="wc-block-mini-cart__tax-label">
|
||||
{ taxLabel }
|
||||
</small>
|
||||
) }
|
||||
<QuantityBadge count={ cartItemsCount } />
|
||||
</button>
|
||||
<Drawer
|
||||
className={ classnames(
|
||||
'wc-block-mini-cart__drawer',
|
||||
'is-mobile',
|
||||
{
|
||||
'is-loading': cartIsLoading,
|
||||
}
|
||||
) }
|
||||
title=""
|
||||
isOpen={ isOpen }
|
||||
onClose={ () => {
|
||||
setIsOpen( false );
|
||||
} }
|
||||
slideIn={ ! skipSlideIn }
|
||||
>
|
||||
<div
|
||||
className="wc-block-mini-cart__template-part"
|
||||
ref={ contentsRef }
|
||||
dangerouslySetInnerHTML={ { __html: contents } }
|
||||
></div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MiniCartBlock;
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { renderFrontend } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import MiniCartBlock from './block';
|
||||
import './style.scss';
|
||||
|
||||
const renderMiniCartFrontend = () => {
|
||||
// Check if button is focused. In that case, we want to refocus it after we
|
||||
// replace it with the React equivalent.
|
||||
let focusedMiniCartBlock: HTMLElement | null = null;
|
||||
/* eslint-disable @wordpress/no-global-active-element */
|
||||
if (
|
||||
document.activeElement &&
|
||||
document.activeElement.classList.contains(
|
||||
'wc-block-mini-cart__button'
|
||||
) &&
|
||||
document.activeElement.parentNode instanceof HTMLElement
|
||||
) {
|
||||
focusedMiniCartBlock = document.activeElement.parentNode;
|
||||
}
|
||||
/* eslint-enable @wordpress/no-global-active-element */
|
||||
|
||||
renderFrontend( {
|
||||
selector: '.wc-block-mini-cart',
|
||||
Block: MiniCartBlock,
|
||||
getProps: ( el ) => {
|
||||
let colorClassNames = '';
|
||||
const button = el.querySelector( '.wc-block-mini-cart__button' );
|
||||
if ( button !== null ) {
|
||||
colorClassNames = button.classList
|
||||
.toString()
|
||||
.replace( 'wc-block-mini-cart__button', '' );
|
||||
}
|
||||
return {
|
||||
isDataOutdated: el.dataset.isDataOutdated,
|
||||
isInitiallyOpen: el.dataset.isInitiallyOpen === 'true',
|
||||
colorClassNames,
|
||||
style: el.dataset.style ? JSON.parse( el.dataset.style ) : {},
|
||||
addToCartBehaviour: el.dataset.addToCartBehaviour || 'none',
|
||||
contents:
|
||||
el.querySelector( '.wc-block-mini-cart__template-part' )
|
||||
?.innerHTML ?? '',
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
// Refocus previously focused button if drawer is not open.
|
||||
if (
|
||||
focusedMiniCartBlock instanceof HTMLElement &&
|
||||
! focusedMiniCartBlock.dataset.isInitiallyOpen
|
||||
) {
|
||||
const innerButton = focusedMiniCartBlock.querySelector(
|
||||
'.wc-block-mini-cart__button'
|
||||
);
|
||||
if ( innerButton instanceof HTMLElement ) {
|
||||
innerButton.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderMiniCartFrontend();
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import type { ReactElement } from 'react';
|
||||
import { formatPrice } from '@woocommerce/price-format';
|
||||
import { CartCheckoutCompatibilityNotice } from '@woocommerce/editor-components/compatibility-notices';
|
||||
import { PanelBody, ExternalLink, SelectControl } from '@wordpress/components';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import QuantityBadge from './quantity-badge';
|
||||
|
||||
interface Attributes {
|
||||
addToCartBehaviour: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
attributes: Attributes;
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
}
|
||||
|
||||
const Edit = ( { attributes, setAttributes }: Props ): ReactElement => {
|
||||
const { addToCartBehaviour } = attributes;
|
||||
const blockProps = useBlockProps( {
|
||||
className: `wc-block-mini-cart`,
|
||||
} );
|
||||
|
||||
const templatePartEditUri = getSetting(
|
||||
'templatePartEditUri',
|
||||
''
|
||||
) as string;
|
||||
|
||||
const productCount = 0;
|
||||
const productTotal = 0;
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InspectorControls>
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Mini Cart Settings',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<SelectControl
|
||||
label={ __(
|
||||
'Add-to-Cart behaviour',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
value={ addToCartBehaviour }
|
||||
onChange={ ( value ) => {
|
||||
setAttributes( { addToCartBehaviour: value } );
|
||||
} }
|
||||
help={ __(
|
||||
'Select what happens when a customer adds a product to the cart.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
options={ [
|
||||
{
|
||||
value: 'none',
|
||||
label: __(
|
||||
'Do nothing',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'open_drawer',
|
||||
label: __(
|
||||
'Open cart drawer',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
},
|
||||
] }
|
||||
/>
|
||||
</PanelBody>
|
||||
{ templatePartEditUri && (
|
||||
<PanelBody
|
||||
title={ __(
|
||||
'Template settings',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
>
|
||||
<p>
|
||||
{ __(
|
||||
'Edit the appearance of your empty and filled mini cart contents.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</p>
|
||||
<ExternalLink href={ templatePartEditUri }>
|
||||
{ __(
|
||||
'Edit Mini Cart template part',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</ExternalLink>
|
||||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Noninteractive>
|
||||
<button className="wc-block-mini-cart__button">
|
||||
<span className="wc-block-mini-cart__amount">
|
||||
{ formatPrice( productTotal ) }
|
||||
</span>
|
||||
<QuantityBadge count={ productCount } />
|
||||
</button>
|
||||
</Noninteractive>
|
||||
<CartCheckoutCompatibilityNotice blockName="mini-cart" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import preloadScript from '@woocommerce/base-utils/preload-script';
|
||||
import lazyLoadScript from '@woocommerce/base-utils/lazy-load-script';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils/legacy-events';
|
||||
|
||||
interface dependencyData {
|
||||
src: string;
|
||||
version?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
translations?: string;
|
||||
}
|
||||
|
||||
window.addEventListener( 'load', () => {
|
||||
const miniCartBlocks = document.querySelectorAll( '.wc-block-mini-cart' );
|
||||
let wasLoadScriptsCalled = false;
|
||||
|
||||
if ( miniCartBlocks.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dependencies = getSetting(
|
||||
'mini_cart_block_frontend_dependencies',
|
||||
{}
|
||||
) as Record< string, dependencyData >;
|
||||
|
||||
// Preload scripts
|
||||
for ( const dependencyHandle in dependencies ) {
|
||||
const dependency = dependencies[ dependencyHandle ];
|
||||
preloadScript( {
|
||||
handle: dependencyHandle,
|
||||
...dependency,
|
||||
} );
|
||||
}
|
||||
|
||||
// Make it so we can read jQuery events triggered by WC Core elements.
|
||||
const removeJQueryAddingToCartEvent = translateJQueryEventToNative(
|
||||
'adding_to_cart',
|
||||
'wc-blocks_adding_to_cart'
|
||||
);
|
||||
const removeJQueryAddedToCartEvent = translateJQueryEventToNative(
|
||||
'added_to_cart',
|
||||
'wc-blocks_added_to_cart'
|
||||
);
|
||||
const removeJQueryRemovedFromCartEvent = translateJQueryEventToNative(
|
||||
'removed_from_cart',
|
||||
'wc-blocks_removed_from_cart'
|
||||
);
|
||||
|
||||
const loadScripts = async () => {
|
||||
// Ensure we only call loadScripts once.
|
||||
if ( wasLoadScriptsCalled ) {
|
||||
return;
|
||||
}
|
||||
wasLoadScriptsCalled = true;
|
||||
|
||||
// Remove adding to cart event handler.
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_adding_to_cart',
|
||||
loadScripts
|
||||
);
|
||||
removeJQueryAddingToCartEvent();
|
||||
|
||||
// Lazy load scripts.
|
||||
for ( const dependencyHandle in dependencies ) {
|
||||
const dependency = dependencies[ dependencyHandle ];
|
||||
await lazyLoadScript( {
|
||||
handle: dependencyHandle,
|
||||
...dependency,
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener( 'wc-blocks_adding_to_cart', loadScripts );
|
||||
|
||||
miniCartBlocks.forEach( ( miniCartBlock, i ) => {
|
||||
if ( ! ( miniCartBlock instanceof HTMLElement ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const miniCartButton = miniCartBlock.querySelector(
|
||||
'.wc-block-mini-cart__button'
|
||||
);
|
||||
const miniCartDrawerPlaceholderOverlay = miniCartBlock.querySelector(
|
||||
'.wc-block-components-drawer__screen-overlay'
|
||||
);
|
||||
|
||||
if ( ! miniCartButton || ! miniCartDrawerPlaceholderOverlay ) {
|
||||
// Markup is not correct, abort.
|
||||
return;
|
||||
}
|
||||
|
||||
const loadContents = () => {
|
||||
if ( ! wasLoadScriptsCalled ) {
|
||||
loadScripts();
|
||||
}
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_added_to_cart',
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
openDrawerWithRefresh
|
||||
);
|
||||
document.body.removeEventListener(
|
||||
'wc-blocks_removed_from_cart',
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
loadContentsWithRefresh
|
||||
);
|
||||
removeJQueryAddedToCartEvent();
|
||||
removeJQueryRemovedFromCartEvent();
|
||||
};
|
||||
|
||||
const openDrawer = () => {
|
||||
miniCartBlock.dataset.isInitiallyOpen = 'true';
|
||||
|
||||
miniCartDrawerPlaceholderOverlay.classList.add(
|
||||
'wc-block-components-drawer__screen-overlay--with-slide-in'
|
||||
);
|
||||
miniCartDrawerPlaceholderOverlay.classList.remove(
|
||||
'wc-block-components-drawer__screen-overlay--is-hidden'
|
||||
);
|
||||
|
||||
loadContents();
|
||||
};
|
||||
|
||||
const openDrawerWithRefresh = () => {
|
||||
miniCartBlock.dataset.isDataOutdated = 'true';
|
||||
openDrawer();
|
||||
};
|
||||
|
||||
const loadContentsWithRefresh = () => {
|
||||
miniCartBlock.dataset.isDataOutdated = 'true';
|
||||
miniCartBlock.dataset.isInitiallyOpen = 'false';
|
||||
loadContents();
|
||||
};
|
||||
|
||||
miniCartButton.addEventListener( 'mouseover', loadScripts );
|
||||
miniCartButton.addEventListener( 'focus', loadScripts );
|
||||
miniCartButton.addEventListener( 'click', openDrawer );
|
||||
|
||||
// There might be more than one Mini Cart block in the page. Make sure
|
||||
// only one opens when adding a product to the cart.
|
||||
if ( i === 0 ) {
|
||||
document.body.addEventListener(
|
||||
'wc-blocks_added_to_cart',
|
||||
openDrawerWithRefresh
|
||||
);
|
||||
document.body.addEventListener(
|
||||
'wc-blocks_removed_from_cart',
|
||||
loadContentsWithRefresh
|
||||
);
|
||||
}
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { cart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import type { BlockConfiguration } from '@wordpress/blocks';
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit from './edit';
|
||||
|
||||
const settings: BlockConfiguration = {
|
||||
apiVersion: 2,
|
||||
title: __( 'Mini Cart', 'woo-gutenberg-products-block' ),
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ cart }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
category: 'woocommerce',
|
||||
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
|
||||
description: __(
|
||||
'Display a mini cart widget.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
supports: {
|
||||
html: false,
|
||||
multiple: false,
|
||||
color: true,
|
||||
typography: {
|
||||
fontSize: true,
|
||||
...( isFeaturePluginBuild() && {
|
||||
__experimentalFontFamily: true,
|
||||
} ),
|
||||
},
|
||||
},
|
||||
example: {
|
||||
attributes: {
|
||||
isPreview: true,
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
isPreview: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
save: false,
|
||||
},
|
||||
addToCartBehaviour: {
|
||||
type: 'string',
|
||||
default: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
edit,
|
||||
|
||||
save() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
registerBlockType( 'woocommerce/mini-cart', settings );
|
||||
@@ -0,0 +1 @@
|
||||
export const blockName = 'woocommerce/mini-cart-contents';
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './inner-blocks/register-components';
|
||||
|
||||
type MiniCartContentsBlockProps = {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
};
|
||||
|
||||
export const MiniCartContentsBlock = ( {
|
||||
children,
|
||||
}: MiniCartContentsBlockProps ): JSX.Element => {
|
||||
return <>{ children }</>;
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable jsdoc/check-alignment */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { ReactElement } from 'react';
|
||||
import {
|
||||
useBlockProps,
|
||||
InnerBlocks,
|
||||
BlockControls,
|
||||
} from '@wordpress/block-editor';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { filledCart, removeCart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { EditorProvider } from '@woocommerce/base-context';
|
||||
import type { TemplateArray } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useViewSwitcher, useForcedLayout } from '../../cart-checkout-shared';
|
||||
import { MiniCartInnerBlocksStyle } from './inner-blocks-style';
|
||||
import './editor.scss';
|
||||
|
||||
// Array of allowed block names.
|
||||
const ALLOWED_BLOCKS = [
|
||||
'woocommerce/filled-mini-cart-contents-block',
|
||||
'woocommerce/empty-mini-cart-contents-block',
|
||||
];
|
||||
|
||||
const views = [
|
||||
{
|
||||
view: 'woocommerce/filled-mini-cart-contents-block',
|
||||
label: __( 'Filled Mini Cart', 'woo-gutenberg-products-block' ),
|
||||
icon: <Icon icon={ filledCart } />,
|
||||
},
|
||||
{
|
||||
view: 'woocommerce/empty-mini-cart-contents-block',
|
||||
label: __( 'Empty Mini Cart', 'woo-gutenberg-products-block' ),
|
||||
icon: <Icon icon={ removeCart } />,
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
const Edit = ( { clientId }: Props ): ReactElement => {
|
||||
const blockProps = useBlockProps( {
|
||||
/**
|
||||
* This is a workaround for the Site Editor to calculate the
|
||||
* correct height of the Mini Cart template part on the first load.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5825
|
||||
*/
|
||||
style: {
|
||||
minHeight: '100vh',
|
||||
},
|
||||
} );
|
||||
|
||||
const defaultTemplate = [
|
||||
[ 'woocommerce/filled-mini-cart-contents-block', {}, [] ],
|
||||
[ 'woocommerce/empty-mini-cart-contents-block', {}, [] ],
|
||||
] as TemplateArray;
|
||||
|
||||
const { currentView, component: ViewSwitcherComponent } = useViewSwitcher(
|
||||
clientId,
|
||||
views
|
||||
);
|
||||
|
||||
useForcedLayout( {
|
||||
clientId,
|
||||
registeredBlocks: ALLOWED_BLOCKS,
|
||||
defaultTemplate,
|
||||
} );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<EditorProvider currentView={ currentView }>
|
||||
<BlockControls>{ ViewSwitcherComponent }</BlockControls>
|
||||
<InnerBlocks
|
||||
allowedBlocks={ ALLOWED_BLOCKS }
|
||||
template={ defaultTemplate }
|
||||
templateLock={ false }
|
||||
/>
|
||||
</EditorProvider>
|
||||
<MiniCartInnerBlocksStyle style={ blockProps.style } />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
.editor-styles-wrapper .wp-block-woocommerce-mini-cart-contents {
|
||||
max-width: 480px;
|
||||
/* We need to override the margin top here to simulate the layout of
|
||||
the mini cart contents on the front end. */
|
||||
margin: 0 auto !important;
|
||||
|
||||
.wp-block-woocommerce-empty-mini-cart-contents-block[hidden],
|
||||
.wp-block-woocommerce-filled-mini-cart-contents-block[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-filled-mini-cart-contents-block > .block-editor-inner-blocks > .block-editor-block-list__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-mini-cart-items-block {
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
margin-bottom: $gap;
|
||||
padding: 0 $gap;
|
||||
|
||||
> .block-editor-inner-blocks > .block-editor-block-list__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Temporary fix after the appender button was positioned absolute
|
||||
// See https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5742#issuecomment-1032804168
|
||||
.block-list-appender {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-mini-cart-products-table-block {
|
||||
margin-bottom: auto;
|
||||
margin-top: $gap;
|
||||
}
|
||||
|
||||
h2.wc-block-mini-cart__title {
|
||||
@include font-size(larger);
|
||||
margin: $gap-largest $gap 0;
|
||||
}
|
||||
|
||||
table.wc-block-cart-items {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.block-editor-button-block-appender {
|
||||
box-shadow: inset 0 0 0 1px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-empty-mini-cart-contents-block {
|
||||
min-height: 100vh;
|
||||
overflow-y: unset;
|
||||
padding: 0;
|
||||
|
||||
> .block-editor-inner-blocks {
|
||||
box-sizing: border-box;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
padding: $gap-largest $gap $gap;
|
||||
}
|
||||
|
||||
// Temporary fix after the appender button was positioned absolute
|
||||
// See https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5742#issuecomment-1032804168
|
||||
.block-list-appender {
|
||||
margin-top: $gap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__shopping-button a {
|
||||
color: currentColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { cart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import type { BlockConfiguration } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import edit, { Save as save } from './edit';
|
||||
import { blockName } from './attributes';
|
||||
import './inner-blocks';
|
||||
|
||||
const settings: BlockConfiguration = {
|
||||
apiVersion: 2,
|
||||
title: __( 'Mini Cart Contents', 'woo-gutenberg-products-block' ),
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ cart }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
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,
|
||||
color: {
|
||||
link: true,
|
||||
},
|
||||
lock: false,
|
||||
},
|
||||
attributes: {
|
||||
isPreview: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
save: false,
|
||||
},
|
||||
lock: {
|
||||
type: 'object',
|
||||
default: {
|
||||
remove: true,
|
||||
move: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
example: {
|
||||
attributes: {
|
||||
isPreview: true,
|
||||
},
|
||||
},
|
||||
edit,
|
||||
save,
|
||||
};
|
||||
|
||||
registerBlockType( blockName, settings );
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* This is a workaround to style inner blocks using the color
|
||||
* settings of the Mini Cart Contents block. It's possible to get
|
||||
* the Mini Cart Contents block's attributes inside the inner blocks
|
||||
* components, but we have 4 out of 7 inner blocks that inherit
|
||||
* style from the Mini Cart Contents block, so we need to apply the
|
||||
* styles here to avoid duplication.
|
||||
*
|
||||
* We only use this hack for the Site Editor. On the frontend, we
|
||||
* manipulate the style using block attributes and inject the CSS
|
||||
* via `wp_add_inline_style()` function.
|
||||
*/
|
||||
export const MiniCartInnerBlocksStyle = ( {
|
||||
style,
|
||||
}: {
|
||||
style: Record< string, unknown >;
|
||||
} ): JSX.Element => {
|
||||
const innerStyles = [
|
||||
{
|
||||
selector:
|
||||
'.wc-block-mini-cart__footer .wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout',
|
||||
properties: [
|
||||
{
|
||||
property: 'color',
|
||||
value: style.backgroundColor,
|
||||
},
|
||||
{
|
||||
property: 'background-color',
|
||||
value: style.color,
|
||||
},
|
||||
{
|
||||
property: 'border-color',
|
||||
value: style.color,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
.map( ( { selector, properties } ) => {
|
||||
const rules = properties
|
||||
.filter( ( { value } ) => value )
|
||||
.map( ( { property, value } ) => `${ property }: ${ value };` )
|
||||
.join( '' );
|
||||
|
||||
if ( rules ) return `${ selector } { ${ rules } }`;
|
||||
return '';
|
||||
} )
|
||||
.join( '' )
|
||||
.trim();
|
||||
|
||||
if ( ! innerStyles ) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <style>{ innerStyles } </style>;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getBlockTypes } from '@wordpress/blocks';
|
||||
|
||||
const EXCLUDED_BLOCKS: readonly string[] = [
|
||||
'woocommerce/mini-cart',
|
||||
'woocommerce/checkout',
|
||||
'woocommerce/cart',
|
||||
'woocommerce/single-product',
|
||||
'woocommerce/cart-totals-block',
|
||||
'woocommerce/checkout-fields-block',
|
||||
'core/post-template',
|
||||
'core/comment-template',
|
||||
'core/query-pagination',
|
||||
'core/comments-query-loop',
|
||||
'core/post-comments-form',
|
||||
'core/post-comments-link',
|
||||
'core/post-comments-count',
|
||||
'core/comments-pagination',
|
||||
'core/post-navigation-link',
|
||||
'core/button',
|
||||
];
|
||||
|
||||
export const getMiniCartAllowedBlocks = (): string[] =>
|
||||
getBlockTypes()
|
||||
.filter( ( block ) => {
|
||||
if ( EXCLUDED_BLOCKS.includes( block.name ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude child blocks of EXCLUDED_BLOCKS.
|
||||
if (
|
||||
block.parent &&
|
||||
block.parent.filter( ( value ) =>
|
||||
EXCLUDED_BLOCKS.includes( value )
|
||||
).length > 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} )
|
||||
.map( ( { name } ) => name );
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "woocommerce/empty-mini-cart-contents-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Empty Mini Cart Contents",
|
||||
"description": "Contains blocks that are displayed when the mini cart is empty.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/mini-cart-contents" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
|
||||
import { useEditorContext } from '@woocommerce/base-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getMiniCartAllowedBlocks } from '../allowed-blocks';
|
||||
|
||||
export const Edit = (): JSX.Element => {
|
||||
const blockProps = useBlockProps();
|
||||
const { currentView } = useEditorContext();
|
||||
|
||||
return (
|
||||
<div
|
||||
{ ...blockProps }
|
||||
hidden={
|
||||
currentView !== 'woocommerce/empty-mini-cart-contents-block'
|
||||
}
|
||||
>
|
||||
<InnerBlocks
|
||||
allowedBlocks={ getMiniCartAllowedBlocks() }
|
||||
renderAppender={ InnerBlocks.ButtonBlockAppender }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
type EmptyMiniCartContentsBlockProps = {
|
||||
children: JSX.Element | JSX.Element[];
|
||||
className: string;
|
||||
};
|
||||
|
||||
const EmptyMiniCartContentsBlock = ( {
|
||||
children,
|
||||
className,
|
||||
}: EmptyMiniCartContentsBlockProps ): JSX.Element | null => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
|
||||
const elementRef = useRef< HTMLDivElement >( null );
|
||||
|
||||
useEffect( () => {
|
||||
if ( cartItems.length === 0 && ! cartIsLoading ) {
|
||||
elementRef.current?.focus();
|
||||
}
|
||||
}, [ cartItems, cartIsLoading ] );
|
||||
|
||||
if ( cartIsLoading || cartItems.length > 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div tabIndex={ -1 } ref={ elementRef } className={ className }>
|
||||
<div className="wc-block-mini-cart__empty-cart-wrapper">
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyMiniCartContentsBlock;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { removeCart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ removeCart }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "woocommerce/filled-mini-cart-contents-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Filled Mini Cart Contents",
|
||||
"description": "Contains blocks that are displayed when the mini cart has products.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/mini-cart-contents" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const Block = ( { children }: { children: JSX.Element } ): JSX.Element => {
|
||||
return <>{ children }</>;
|
||||
};
|
||||
|
||||
export default Block;
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import type { TemplateArray } from '@wordpress/blocks';
|
||||
import { EditorProvider, useEditorContext } from '@woocommerce/base-context';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
useForcedLayout,
|
||||
getAllowedBlocks,
|
||||
} from '../../../../cart-checkout-shared';
|
||||
|
||||
export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
|
||||
const blockProps = useBlockProps();
|
||||
const allowedBlocks = getAllowedBlocks( innerBlockAreas.FILLED_MINI_CART );
|
||||
const { currentView } = useEditorContext();
|
||||
|
||||
const defaultTemplate = [
|
||||
[ 'woocommerce/mini-cart-title-block', {} ],
|
||||
[ 'woocommerce/mini-cart-items-block', {} ],
|
||||
[ 'woocommerce/mini-cart-footer-block', {} ],
|
||||
].filter( Boolean ) as unknown as TemplateArray;
|
||||
|
||||
useForcedLayout( {
|
||||
clientId,
|
||||
registeredBlocks: allowedBlocks,
|
||||
defaultTemplate,
|
||||
} );
|
||||
|
||||
return (
|
||||
<div
|
||||
{ ...blockProps }
|
||||
hidden={
|
||||
currentView !== 'woocommerce/filled-mini-cart-contents-block'
|
||||
}
|
||||
>
|
||||
<EditorProvider
|
||||
currentView={ currentView }
|
||||
previewData={ { previewCart } }
|
||||
>
|
||||
<InnerBlocks
|
||||
template={ defaultTemplate }
|
||||
allowedBlocks={ allowedBlocks }
|
||||
templateLock="insert"
|
||||
/>
|
||||
</EditorProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
|
||||
type FilledMiniCartContentsBlockProps = {
|
||||
children: JSX.Element;
|
||||
className: string;
|
||||
};
|
||||
|
||||
const FilledMiniCartContentsBlock = ( {
|
||||
children,
|
||||
className,
|
||||
}: FilledMiniCartContentsBlockProps ): JSX.Element | null => {
|
||||
const { cartItems } = useStoreCart();
|
||||
|
||||
if ( cartItems.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={ className }>{ children }</div>;
|
||||
};
|
||||
|
||||
export default FilledMiniCartContentsBlock;
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { filledCart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ filledCart }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './empty-mini-cart-contents-block';
|
||||
import './filled-mini-cart-contents-block';
|
||||
import './mini-cart-title-block';
|
||||
import './mini-cart-items-block';
|
||||
import './mini-cart-products-table-block';
|
||||
import './mini-cart-footer-block';
|
||||
import './mini-cart-shopping-button-block';
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"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,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const PaymentMethodIconsElement = (): JSX.Element => {
|
||||
const { paymentMethods } = usePaymentMethods();
|
||||
return (
|
||||
<PaymentMethodIcons
|
||||
icons={ getIconsFromPaymentMethods( paymentMethods ) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Block = ( { className }: Props ): 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={ classNames( 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 }
|
||||
variant="outlined"
|
||||
>
|
||||
{ __( '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;
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return <div { ...useBlockProps.save() }></div>;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, payment } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ payment }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "woocommerce/mini-cart-items-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Mini Cart Items",
|
||||
"description": "Contains the products table and other custom blocks of filled mini cart.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": false,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
|
||||
import type { TemplateArray } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getMiniCartAllowedBlocks } from '../allowed-blocks';
|
||||
|
||||
export const Edit = (): JSX.Element => {
|
||||
const blockProps = useBlockProps( {
|
||||
className: 'wc-block-mini-cart__items',
|
||||
} );
|
||||
|
||||
const defaultTemplate = [
|
||||
[ 'woocommerce/mini-cart-products-table-block', {} ],
|
||||
].filter( Boolean ) as unknown as TemplateArray;
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InnerBlocks
|
||||
template={ defaultTemplate }
|
||||
renderAppender={ InnerBlocks.ButtonBlockAppender }
|
||||
templateLock={ false }
|
||||
allowedBlocks={ getMiniCartAllowedBlocks() }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return (
|
||||
<div { ...useBlockProps.save() }>
|
||||
<InnerBlocks.Content />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
|
||||
type MiniCartItemsBlockProps = {
|
||||
children: JSX.Element;
|
||||
className: string;
|
||||
};
|
||||
|
||||
const Block = ( {
|
||||
children,
|
||||
className,
|
||||
}: MiniCartItemsBlockProps ): JSX.Element => {
|
||||
return (
|
||||
<div className={ classNames( className, 'wc-block-mini-cart__items' ) }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Block;
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, grid } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ grid }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"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,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/mini-cart-items-block" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CartLineItemsTable from '../../../../cart/cart-line-items-table';
|
||||
|
||||
type MiniCartContentsBlockProps = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
const Block = ( { className }: MiniCartContentsBlockProps ): JSX.Element => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
return (
|
||||
<div
|
||||
className={ classNames(
|
||||
className,
|
||||
'wc-block-mini-cart__products-table'
|
||||
) }
|
||||
>
|
||||
<CartLineItemsTable
|
||||
lineItems={ cartItems }
|
||||
isLoading={ cartIsLoading }
|
||||
className="wc-block-mini-cart-items"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Block;
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return <div { ...useBlockProps.save() }></div>;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, list } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: (
|
||||
<Icon icon={ list } className="wc-block-editor-components-block-icon" />
|
||||
),
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "woocommerce/mini-cart-shopping-button-block",
|
||||
"version": "1.0.0",
|
||||
"title": "Mini Cart Shopping Button",
|
||||
"description": "Block that displays the shopping button for the Mini Cart block.",
|
||||
"category": "woocommerce",
|
||||
"supports": {
|
||||
"align": false,
|
||||
"html": false,
|
||||
"multiple": false,
|
||||
"reusable": false,
|
||||
"inserter": true
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": false,
|
||||
"move": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/empty-mini-cart-contents-block" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { SHOP_URL } from '@woocommerce/block-settings';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
type MiniCartShoppingButtonBlockProps = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
const Block = ( {
|
||||
className,
|
||||
}: MiniCartShoppingButtonBlockProps ): JSX.Element | null => {
|
||||
if ( ! SHOP_URL ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classNames(
|
||||
className,
|
||||
'wc-block-mini-cart__shopping-button'
|
||||
) }
|
||||
>
|
||||
<a href={ SHOP_URL }>
|
||||
{ __( 'Start shopping', 'woo-gutenberg-products-block' ) }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Block;
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return <div { ...useBlockProps.save() }></div>;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, button } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ button }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"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,
|
||||
"lock": false
|
||||
},
|
||||
"attributes": {
|
||||
"lock": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": [ "woocommerce/filled-mini-cart-contents-block" ],
|
||||
"textdomain": "woo-gutenberg-products-block",
|
||||
"apiVersion": 2
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { sprintf, _n, __ } from '@wordpress/i18n';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
type MiniCartTitleBlockProps = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
const Block = ( { className }: MiniCartTitleBlockProps ): JSX.Element => {
|
||||
const { cartItemsCount, cartIsLoading } = useStoreCart();
|
||||
return (
|
||||
<h2 className={ classNames( 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;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
|
||||
export const Save = (): JSX.Element => {
|
||||
return <div { ...useBlockProps.save() }></div>;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Icon, heading } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ heading }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
edit: Edit,
|
||||
save: Save,
|
||||
} );
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { WC_BLOCKS_BUILD_URL } from '@woocommerce/block-settings';
|
||||
import { registerCheckoutBlock } from '@woocommerce/blocks-checkout';
|
||||
import { lazy } from '@wordpress/element';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
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';
|
||||
import miniCartItemsMetadata from './mini-cart-items-block/block.json';
|
||||
import miniCartShoppingButtonMetadata from './mini-cart-shopping-button-block/block.json';
|
||||
|
||||
// Modify webpack publicPath at runtime based on location of WordPress Plugin.
|
||||
// eslint-disable-next-line no-undef,camelcase
|
||||
__webpack_public_path__ = WC_BLOCKS_BUILD_URL;
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: filledMiniCartMetadata,
|
||||
component: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "mini-cart-contents-block/filled-cart" */ './filled-mini-cart-contents-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: emptyMiniCartContentsMetadata,
|
||||
component: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "mini-cart-contents-block/empty-cart" */ './empty-mini-cart-contents-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: miniCartTitleMetadata,
|
||||
component: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "mini-cart-contents-block/title" */ './mini-cart-title-block/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: miniCartItemsMetadata,
|
||||
component: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "mini-cart-contents-block/items" */ './mini-cart-items-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
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'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: miniCartShoppingButtonMetadata,
|
||||
component: lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "mini-cart-contents-block/shopping-button" */ './mini-cart-shopping-button-block/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { miniCart } from '@woocommerce/icons';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
interface Props {
|
||||
count: number;
|
||||
colorClassNames?: string;
|
||||
style?: Record< string, string | undefined >;
|
||||
}
|
||||
|
||||
const QuantityBadge = ( {
|
||||
count,
|
||||
colorClassNames,
|
||||
style,
|
||||
}: Props ): JSX.Element => {
|
||||
return (
|
||||
<span className="wc-block-mini-cart__quantity-badge">
|
||||
<Icon
|
||||
className="wc-block-mini-cart__icon"
|
||||
size={ 20 }
|
||||
icon={ miniCart }
|
||||
/>
|
||||
<span
|
||||
className={ `wc-block-mini-cart__badge ${ colorClassNames }` }
|
||||
style={ style }
|
||||
>
|
||||
{ count }
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuantityBadge;
|
||||
@@ -0,0 +1,34 @@
|
||||
.wc-block-mini-cart__quantity-badge {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__badge {
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: 0.15em solid;
|
||||
border-radius: 1em;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
font-size: 0.875em;
|
||||
font-weight: 600;
|
||||
height: math.div(em(20px), 0.875);
|
||||
justify-content: center;
|
||||
margin-left: math.div(em(-10px), 0.875);
|
||||
min-width: math.div(em(20px), 0.875);
|
||||
padding: 0 em($gap-smallest);
|
||||
transform: translateY(-50%);
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__icon {
|
||||
display: block;
|
||||
height: em(24px);
|
||||
width: em(24px);
|
||||
|
||||
html[dir="rtl"] & {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
.wc-block-mini-cart {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__button {
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 400;
|
||||
padding: em($gap-small) em($gap-smaller);
|
||||
|
||||
&:hover:not([disabled]) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__amount {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__tax-label {
|
||||
margin-right: em($gap-smaller);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.wc-block-mini-cart__amount {
|
||||
display: initial;
|
||||
font-weight: 600;
|
||||
margin-right: $gap-smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-open .wc-block-mini-cart__button {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Reset font size so it doesn't depend on drawer's ancestors.
|
||||
.wc-block-mini-cart__drawer {
|
||||
font-size: 1rem;
|
||||
|
||||
.components-modal__content {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.components-modal__header {
|
||||
position: absolute;
|
||||
top: $gap-largest;
|
||||
right: $gap;
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-mini-cart-contents {
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-empty-mini-cart-contents-block,
|
||||
.wp-block-woocommerce-filled-mini-cart-contents-block {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-empty-mini-cart-contents-block {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-filled-mini-cart-contents-block {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-empty-mini-cart-contents-block .wc-block-mini-cart__empty-cart-wrapper {
|
||||
overflow-y: auto;
|
||||
padding: $gap-largest $gap $gap;
|
||||
}
|
||||
|
||||
h2.wc-block-mini-cart__title {
|
||||
@include font-size(larger);
|
||||
margin: $gap-largest $gap 0;
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
padding: $gap $gap 0;
|
||||
|
||||
.wc-block-mini-cart__products-table {
|
||||
margin-bottom: auto;
|
||||
margin-right: -$gap;
|
||||
padding-right: $gap;
|
||||
|
||||
.wc-block-cart-items__row {
|
||||
padding-top: $gap-smaller;
|
||||
padding-bottom: $gap-smaller;
|
||||
|
||||
&:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__footer {
|
||||
border-top: 1px solid $gray-300;
|
||||
padding: $gap-large $gap;
|
||||
|
||||
.wc-block-components-totals-item.wc-block-mini-cart__footer-subtotal {
|
||||
font-weight: 600;
|
||||
margin-bottom: $gap;
|
||||
|
||||
.wc-block-components-totals-item__description {
|
||||
display: none;
|
||||
font-size: 0.75em;
|
||||
font-weight: 400;
|
||||
|
||||
@media only screen and (min-width: 480px) {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__footer-actions {
|
||||
display: flex;
|
||||
gap: $gap;
|
||||
|
||||
.wc-block-mini-cart__footer-cart.wc-block-components-button {
|
||||
box-shadow: inset 0 0 0 1px currentColor;
|
||||
color: currentColor;
|
||||
display: none;
|
||||
flex-grow: 1;
|
||||
font-weight: 600;
|
||||
|
||||
@media only screen and (min-width: 480px) {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__footer-checkout {
|
||||
flex-grow: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-payment-method-icons {
|
||||
margin-top: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-mini-cart__shopping-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
a {
|
||||
border: 2px solid;
|
||||
color: currentColor;
|
||||
font-weight: 600;
|
||||
padding: $gap-small $gap-large;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $gray-900;
|
||||
border-color: $gray-900;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from '@testing-library/react';
|
||||
import { previewCart } from '@woocommerce/resource-previews';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
import { default as fetchMock } from 'jest-fetch-mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from '../block';
|
||||
import { defaultCartState } from '../../../data/default-states';
|
||||
|
||||
const MiniCartBlock = ( props ) => (
|
||||
<SlotFillProvider>
|
||||
<Block
|
||||
contents='<div class="wc-block-mini-cart-contents"></div>'
|
||||
{ ...props }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
||||
const mockEmptyCart = () => {
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/v1\/cart/ ) ) {
|
||||
return Promise.resolve(
|
||||
JSON.stringify( defaultCartState.cartData )
|
||||
);
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
};
|
||||
|
||||
const mockFullCart = () => {
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/v1\/cart/ ) ) {
|
||||
return Promise.resolve( JSON.stringify( previewCart ) );
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
};
|
||||
|
||||
describe( 'Testing Mini Cart', () => {
|
||||
beforeEach( () => {
|
||||
act( () => {
|
||||
mockFullCart();
|
||||
// need to clear the store resolution state between tests.
|
||||
dispatch( storeKey ).invalidateResolutionForStore();
|
||||
dispatch( storeKey ).receiveCart( defaultCartState.cartData );
|
||||
} );
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
fetchMock.resetMocks();
|
||||
} );
|
||||
|
||||
it( 'opens Mini Cart drawer when clicking on button', async () => {
|
||||
render( <MiniCartBlock /> );
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
userEvent.click( screen.getByLabelText( /items/i ) );
|
||||
|
||||
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
|
||||
} );
|
||||
|
||||
it( 'renders empty cart if there are no items in the cart', async () => {
|
||||
mockEmptyCart();
|
||||
render( <MiniCartBlock /> );
|
||||
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
userEvent.click( screen.getByLabelText( /items/i ) );
|
||||
|
||||
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
|
||||
} );
|
||||
|
||||
it( 'updates contents when removed from cart event is triggered', async () => {
|
||||
render( <MiniCartBlock /> );
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
|
||||
mockEmptyCart();
|
||||
// eslint-disable-next-line no-undef
|
||||
const removedFromCartEvent = new Event( 'wc-blocks_removed_from_cart' );
|
||||
act( () => {
|
||||
document.body.dispatchEvent( removedFromCartEvent );
|
||||
} );
|
||||
|
||||
await waitForElementToBeRemoved( () =>
|
||||
screen.queryByLabelText( /3 items in cart/i )
|
||||
);
|
||||
await waitFor( () =>
|
||||
expect(
|
||||
screen.getByLabelText( /0 items in cart/i )
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'updates contents when added to cart event is triggered', async () => {
|
||||
mockEmptyCart();
|
||||
render( <MiniCartBlock /> );
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
|
||||
mockFullCart();
|
||||
// eslint-disable-next-line no-undef
|
||||
const addedToCartEvent = new Event( 'wc-blocks_added_to_cart' );
|
||||
act( () => {
|
||||
document.body.dispatchEvent( addedToCartEvent );
|
||||
} );
|
||||
|
||||
await waitForElementToBeRemoved( () =>
|
||||
screen.queryByLabelText( /0 items in cart/i )
|
||||
);
|
||||
await waitFor( () =>
|
||||
expect(
|
||||
screen.getByLabelText( /3 items in cart/i )
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user