initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,176 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useState, useEffect, useRef } from '@wordpress/element';
import { dispatch } from '@wordpress/data';
import { translateJQueryEventToNative } from '@woocommerce/base-utils';
import { useStoreCart } from '@woocommerce/base-context/hooks';
import Drawer from '@woocommerce/base-components/drawer';
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
import {
formatPrice,
getCurrencyFromPriceResponse,
} from '@woocommerce/price-format';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import CartLineItemsTable from '../cart/full-cart/cart-line-items-table';
import './style.scss';
interface MiniCartBlockProps {
isPlaceholderOpen?: boolean;
}
const MiniCartBlock = ( {
isPlaceholderOpen = false,
}: MiniCartBlockProps ): JSX.Element => {
const {
cartItems,
cartItemsCount,
cartIsLoading,
cartTotals,
} = useStoreCart();
const [ isOpen, setIsOpen ] = useState< boolean >( isPlaceholderOpen );
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 >(
isPlaceholderOpen
);
useEffect( () => {
const openMiniCartAndRefreshData = ( e ) => {
const eventDetail = e.detail;
if ( ! eventDetail || ! eventDetail.preserveCartData ) {
dispatch( storeKey ).invalidateResolutionForStore();
}
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',
openMiniCartAndRefreshData
);
return () => {
removeJQueryAddedToCartEvent();
document.body.removeEventListener(
'wc-blocks_added_to_cart',
openMiniCartAndRefreshData
);
};
}, [] );
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 )
: cartTotals.total_items;
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 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>
) : (
<CartLineItemsTable
lineItems={ cartItems }
isLoading={ cartIsLoading }
/>
);
return (
<>
<button
className="wc-block-mini-cart__button"
onClick={ () => {
if ( ! isOpen ) {
setIsOpen( true );
setSkipSlideIn( false );
}
} }
aria-label={ ariaLabel }
>
{ sprintf(
/* translators: %d is the count of items in the cart. */
_n(
'%d item',
'%d items',
cartItemsCount,
'woo-gutenberg-products-block'
),
cartItemsCount
) }
</button>
<Drawer
className={ classNames(
'wc-block-mini-cart__drawer',
'is-mobile',
{
'is-loading': cartIsLoading,
}
) }
title={
cartIsLoading
? __( 'Your cart', 'woo-gutenberg-products-block' )
: sprintf(
/* translators: %d is the count of items in the cart. */
_n(
'Your cart (%d item)',
'Your cart (%d items)',
cartItemsCount,
'woo-gutenberg-products-block'
),
cartItemsCount
)
}
isOpen={ isOpen }
onClose={ () => {
setIsOpen( false );
} }
slideIn={ ! skipSlideIn }
>
{ contents }
</Drawer>
</>
);
};
export default MiniCartBlock;

View File

@ -0,0 +1,52 @@
/**
* External dependencies
*/
import { renderFrontend } from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import withMiniCartConditionalHydration from './with-mini-cart-conditional-hydration';
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: withMiniCartConditionalHydration( MiniCartBlock ),
getProps: ( el: HTMLElement ) => ( {
isDataOutdated: el.dataset.isDataOutdated,
isPlaceholderOpen: el.dataset.isPlaceholderOpen === 'true',
} ),
} );
// Refocus previously focused button if drawer is not open.
if (
focusedMiniCartBlock instanceof HTMLElement &&
! focusedMiniCartBlock.dataset.isPlaceholderOpen
) {
const innerButton = focusedMiniCartBlock.querySelector(
'.wc-block-mini-cart__button'
);
if ( innerButton instanceof HTMLElement ) {
innerButton.focus();
}
}
};
renderMiniCartFrontend();

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { _n, sprintf } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import type { ReactElement } from 'react';
const MiniCartBlock = (): ReactElement => {
const blockProps = useBlockProps( {
className: 'wc-block-mini-cart',
} );
const productCount = 0;
return (
<div { ...blockProps }>
<button className="wc-block-mini-cart__button">
{ sprintf(
/* translators: %d is the number of products in the cart. */
_n(
'%d product',
'%d products',
productCount,
'woo-gutenberg-products-block'
),
productCount
) }
</button>
</div>
);
};
export default MiniCartBlock;

View File

@ -0,0 +1,130 @@
/**
* 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;
}
// eslint-disable-next-line @wordpress/no-global-event-listener
window.onload = () => {
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 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 showContents = () => {
if ( ! wasLoadScriptsCalled ) {
loadScripts();
}
document.body.removeEventListener(
'wc-blocks_added_to_cart',
// eslint-disable-next-line @typescript-eslint/no-use-before-define
showContentsAndUpdate
);
miniCartBlock.dataset.isPlaceholderOpen = 'true';
miniCartDrawerPlaceholderOverlay.classList.add(
'wc-block-components-drawer__screen-overlay--with-slide-in'
);
miniCartDrawerPlaceholderOverlay.classList.remove(
'wc-block-components-drawer__screen-overlay--is-hidden'
);
removeJQueryAddedToCartEvent();
};
const showContentsAndUpdate = () => {
miniCartBlock.dataset.isDataOutdated = 'true';
showContents();
};
miniCartButton.addEventListener( 'mouseover', loadScripts );
miniCartButton.addEventListener( 'focus', loadScripts );
miniCartButton.addEventListener( 'click', showContents );
// 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',
showContentsAndUpdate
);
}
} );
};

View File

@ -0,0 +1,50 @@
/**
* 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', 'woo-gutenberg-products-block' ),
icon: {
src: <Icon srcElement={ cart } />,
foreground: '#96588a',
},
category: 'woocommerce',
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
description: __(
'Display a mini cart widget.',
'woo-gutenberg-products-block'
),
supports: {
html: false,
multiple: false,
},
example: {
attributes: {
isPreview: true,
},
},
attributes: {
isPreview: {
type: 'boolean',
default: false,
save: false,
},
},
edit,
save() {
return null;
},
};
registerExperimentalBlockType( 'woocommerce/mini-cart', settings );

View File

@ -0,0 +1,8 @@
.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;
}

View File

@ -0,0 +1,34 @@
/**
* External dependencies
*/
import {
withStoreCartApiHydration,
withRestApiHydration,
} from '@woocommerce/block-hocs';
interface MiniCartBlockInterface {
// Signals whether the cart data is outdated. That happens when
// opening the mini cart after adding a product to the cart.
isDataOutdated?: boolean;
// Signals that the HTML placeholder drawer has been opened. Needed
// to know whether we have to skip the slide in animation.
isPlaceholderOpen?: boolean;
}
// Custom HOC to conditionally hydrate API data depending on the isDataOutdated
// prop.
export default (
OriginalComponent: ( component: MiniCartBlockInterface ) => JSX.Element
) => {
return ( {
isDataOutdated,
...props
}: MiniCartBlockInterface ): JSX.Element => {
const Component = isDataOutdated
? OriginalComponent
: withStoreCartApiHydration(
withRestApiHydration( OriginalComponent )
);
return <Component { ...props } />;
};
};