Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Add Mini Cart block in experimental builds #4510

Merged
merged 13 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions assets/js/base/utils/lazy-load-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
interface lazyLoadScriptParams {
handle: string;
src: string;
version?: string;
after?: string;
before?: string;
translations?: string;
}

/**
* Appends a `<script>` tag to the document body based on the src and handle
* parameters. In addition, it appends additional script tags to load the code
* needed for translations and any before and after inline scripts. See these
* documentation pages for more information:
*
* https://developer.wordpress.org/reference/functions/wp_set_script_translations/
* https://developer.wordpress.org/reference/functions/wp_add_inline_script/
*/
const lazyLoadScript = ( {
handle,
src,
version,
after,
before,
translations,
}: lazyLoadScriptParams ): Promise< void > => {
return new Promise( ( resolve, reject ) => {
// Append script translations if they doesn't exist yet in the page.
if ( translations ) {
const handleTranslationsElements = document.querySelectorAll(
`#${ handle }-js-translations`
);
if ( handleTranslationsElements.length === 0 ) {
const handleTranslations = document.createElement( 'script' );
handleTranslations.innerHTML = translations;
handleTranslations.id = `${ handle }-js-translations`;
document.body.appendChild( handleTranslations );
}
}
// Append before inline script if it doesn't exist yet in the page.
if ( before ) {
const handleBeforeScriptElements = document.querySelectorAll(
`#${ handle }-js-before`
);
if ( handleBeforeScriptElements.length === 0 ) {
const handleBeforeScript = document.createElement( 'script' );
handleBeforeScript.innerHTML = before;
handleBeforeScript.id = `${ handle }-js-before`;
document.body.appendChild( handleBeforeScript );
}
}

// Append script.
const handleScriptElements = document.querySelectorAll(
`#${ handle }-js`
);
if ( handleScriptElements.length > 0 ) {
resolve();
} else {
const handleScript = document.createElement( 'script' );
handleScript.src = version ? `${ src }?${ version }` : src;
handleScript.id = `${ handle }-js`;
handleScript.onerror = reject;
handleScript.onload = () => {
// Append after inline script if it doesn't exist yet in the page.
if ( after ) {
const handleAfterScriptElements = document.querySelectorAll(
`#${ handle }-js-after`
);
if ( handleAfterScriptElements.length === 0 ) {
const handleAfterScript = document.createElement(
'script'
);
handleAfterScript.innerHTML = after;
handleAfterScript.id = `${ handle }-js-after`;
document.body.appendChild( handleAfterScript );
}
}
resolve();
};
document.body.appendChild( handleScript );
}
} );
};

export default lazyLoadScript;
30 changes: 30 additions & 0 deletions assets/js/base/utils/preload-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
interface preloadScriptParams {
handle: string;
src: string;
version?: string;
}

/**
* Appends a `<link>` tag to the document head to preload a script based on the
* src and handle parameters.
*/
const preloadScript = ( {
handle,
src,
version,
}: preloadScriptParams ): void => {
const handleScriptElements = document.querySelectorAll(
`#${ handle }-js, #${ handle }-js-prefetch`
);

if ( handleScriptElements.length === 0 ) {
const prefetchLink = document.createElement( 'link' );
prefetchLink.href = version ? `${ src }?${ version }` : src;
prefetchLink.rel = 'preload';
prefetchLink.as = 'script';
prefetchLink.id = `${ handle }-js-prefetch`;
document.head.appendChild( prefetchLink );
}
};

export default preloadScript;
6 changes: 3 additions & 3 deletions assets/js/base/utils/render-frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const isElementInsideWrappers = ( el, wrappers ) => {
const renderBlockInContainers = ( {
Block,
containers,
getProps = () => {},
getErrorBoundaryProps = () => {},
getProps = () => ( {} ),
getErrorBoundaryProps = () => ( {} ),
} ) => {
if ( containers.length === 0 ) {
return;
Expand All @@ -49,7 +49,7 @@ const renderBlockInContainers = ( {
const errorBoundaryProps = getErrorBoundaryProps( el, i );
const attributes = {
...el.dataset,
...props.attributes,
...( props.attributes || {} ),
};
el.classList.remove( 'is-loading' );

Expand Down
81 changes: 39 additions & 42 deletions assets/js/blocks/cart-checkout/cart/full-cart/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ table.wc-block-cart-items {
}

// Loading placeholder state.
.wc-block-cart--is-loading {
.wc-block-cart--is-loading,
.wc-block-mini-cart-items--is-loading {
th span,
h2 span {
@include placeholder();
Expand All @@ -97,47 +98,43 @@ table.wc-block-cart-items {
h2 span {
min-width: 33%;
}
.wc-block-cart-items {
.wc-block-cart-items__row {
.wc-block-cart-item__price,
.wc-block-cart-item__individual-price,
.wc-block-cart-item__product-metadata,
.wc-block-cart-item__image > *,
.wc-block-components-quantity-selector {
@include placeholder();
}
.wc-block-cart-item__product-name {
@include placeholder();
@include force-content();
min-width: 84px;
display: inline-block;
}
.wc-block-cart-item__product-metadata {
margin-top: 0.25em;
min-width: 8em;
}
.wc-block-cart-item__remove-link {
visibility: hidden;
}
.wc-block-cart-item__image a {
display: block;
}
.wc-block-cart-item__individual-price {
@include force-content();
max-width: 3em;
display: block;
margin-top: 0.25em;
}
.wc-block-cart-item__total {
> span,
> div {
display: none;
}
.wc-block-cart-item__price {
@include force-content();
display: block;
}
}
.wc-block-cart-item__price,
.wc-block-cart-item__individual-price,
.wc-block-cart-item__product-metadata,
.wc-block-cart-item__image > *,
.wc-block-components-quantity-selector {
@include placeholder();
}
.wc-block-cart-item__product-name {
@include placeholder();
@include force-content();
min-width: 84px;
display: inline-block;
}
.wc-block-cart-item__product-metadata {
margin-top: 0.25em;
min-width: 8em;
}
.wc-block-cart-item__remove-link {
visibility: hidden;
}
.wc-block-cart-item__image a {
display: block;
}
.wc-block-cart-item__individual-price {
@include force-content();
max-width: 3em;
display: block;
margin-top: 0.25em;
}
.wc-block-cart-item__total {
> span,
> div {
display: none;
}
.wc-block-cart-item__price {
@include force-content();
display: block;
}
}
.wc-block-cart__sidebar .components-card {
Expand Down
37 changes: 37 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { renderFrontend } from '@woocommerce/base-utils';
import { useStoreCart } from '@woocommerce/base-context/hooks';
import {
withStoreCartApiHydration,
withRestApiHydration,
} from '@woocommerce/block-hocs';

/**
* Internal dependencies
*/
import CartLineItemsTable from '../cart/full-cart/cart-line-items-table';

const MiniCartContents = () => {
const { cartItems, cartIsLoading } = useStoreCart();

if ( cartItems.length === 0 ) {
return <>{ __( 'Cart is empty', 'woo-gutenberg-products-block' ) }</>;
}

return (
<CartLineItemsTable
lineItems={ cartItems }
isLoading={ cartIsLoading }
/>
);
};

renderFrontend( {
selector: '.wc-block-mini-cart__contents',
Block: withStoreCartApiHydration(
withRestApiHydration( MiniCartContents )
),
} );
33 changes: 33 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/edit.tsx
Original file line number Diff line number Diff line change
@@ -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;
75 changes: 75 additions & 0 deletions assets/js/blocks/cart-checkout/mini-cart/frontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* External dependencies
*/
import { getSetting } from '@woocommerce/settings';
import preloadScript from '@woocommerce/base-utils/preload-script';
import lazyLoadScript from '@woocommerce/base-utils/lazy-load-script';

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

if ( miniCartBlocks.length === 0 ) {
return;
}

const dependencies = getSetting(
'mini_cart_block_frontend_dependencies',
{}
) as Record< string, dependencyData >;
Aljullu marked this conversation as resolved.
Show resolved Hide resolved

// Preload scripts
for ( const dependencyHandle in dependencies ) {
const dependency = dependencies[ dependencyHandle ];
preloadScript( {
handle: dependencyHandle,
...dependency,
} );
}

miniCartBlocks.forEach( ( miniCartBlock ) => {
const miniCartButton = miniCartBlock.querySelector(
'.wc-block-mini-cart__button'
);
const miniCartContents = miniCartBlock.querySelector(
'.wc-block-mini-cart__contents'
);

if ( ! miniCartButton || ! miniCartContents ) {
// Markup is not correct, abort.
return;
}

const showContents = async () => {
miniCartContents.removeAttribute( 'hidden' );
ralucaStan marked this conversation as resolved.
Show resolved Hide resolved

// Load scripts
for ( const dependencyHandle in dependencies ) {
const dependency = dependencies[ dependencyHandle ];
await lazyLoadScript( {
handle: dependencyHandle,
...dependency,
} );
}
};
const hideContents = () =>
miniCartContents.setAttribute( 'hidden', 'true' );

miniCartButton.addEventListener( 'mouseover', showContents );
miniCartButton.addEventListener( 'mouseleave', hideContents );

miniCartContents.addEventListener( 'mouseover', showContents );
miniCartContents.addEventListener( 'mouseleave', hideContents );

miniCartButton.addEventListener( 'focus', showContents );
miniCartButton.addEventListener( 'blur', hideContents );
} );
};
Loading