/** * External dependencies */ import { renderParentBlock } from '@woocommerce/atomic-utils'; import Drawer from '@woocommerce/base-components/drawer'; import { useStoreCart } from '@woocommerce/base-context/hooks'; import { getValidBlockAttributes, translateJQueryEventToNative, } from '@woocommerce/base-utils'; import { getRegisteredBlockComponents } from '@woocommerce/blocks-registry'; import { formatPrice, getCurrencyFromPriceResponse, } from '@woocommerce/price-format'; import { getSettingWithCoercion } from '@woocommerce/settings'; import { isBoolean, isString, isCartResponseTotals, isNumber, } from '@woocommerce/types'; import { unmountComponentAtNode, useCallback, useEffect, useRef, useState, } from '@wordpress/element'; import { sprintf, _n } from '@wordpress/i18n'; import clsx from 'clsx'; /** * Internal dependencies */ import type { BlockAttributes } from './types'; import QuantityBadge from './quantity-badge'; import { MiniCartContentsBlock } from './mini-cart-contents/block'; import './style.scss'; import { blockName, attributes as miniCartContentsAttributes, } from './mini-cart-contents/attributes'; import { defaultColorItem } from './utils/defaults'; type Props = BlockAttributes; function getScrollbarWidth() { return window.innerWidth - document.documentElement.clientWidth; } const MiniCartBlock = ( attributes: Props ): JSX.Element => { const { initialCartItemsCount, initialCartTotals, isInitiallyOpen = false, colorClassNames, contents = '', miniCartIcon, addToCartBehaviour = 'none', hasHiddenPrice = true, priceColor = defaultColorItem, iconColor = defaultColorItem, productCountColor = defaultColorItem, productCountVisibility = 'greater_than_zero', } = attributes; const { cartItemsCount: cartItemsCountFromApi, cartIsLoading, cartTotals: cartTotalsFromApi, } = useStoreCart(); const cartIsLoadingForTheFirstTime = useRef( cartIsLoading ); useEffect( () => { if ( cartIsLoadingForTheFirstTime.current && ! cartIsLoading ) { cartIsLoadingForTheFirstTime.current = false; } }, [ cartIsLoading, cartIsLoadingForTheFirstTime ] ); useEffect( () => { if ( ! cartIsLoading && isCartResponseTotals( cartTotalsFromApi ) && isNumber( cartItemsCountFromApi ) ) { // Save server data to local storage, so we can re-fetch it faster // on the next page load. localStorage.setItem( 'wc-blocks_mini_cart_totals', JSON.stringify( { totals: cartTotalsFromApi, itemsCount: cartItemsCountFromApi, } ) ); } } ); 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 ) { const scrollBarWidth = getScrollbarWidth(); if ( isOpen ) { Object.assign( body.style, { overflow: 'hidden', paddingRight: scrollBarWidth + 'px', } ); } else { Object.assign( body.style, { overflow: '', paddingRight: 0 } ); } } }, [ 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, getProps: ( el: Element ) => { return { attributes: getValidBlockAttributes( miniCartContentsAttributes, /* eslint-disable @typescript-eslint/no-explicit-any */ ( el instanceof HTMLElement ? el.dataset : {} ) as any ), }; }, 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 taxLabel = getSettingWithCoercion( 'taxLabel', '', isString ); const cartTotals = cartIsLoadingForTheFirstTime.current && isCartResponseTotals( initialCartTotals ) ? initialCartTotals : cartTotalsFromApi; const cartItemsCount = cartIsLoadingForTheFirstTime.current && isNumber( initialCartItemsCount ) ? initialCartItemsCount : cartItemsCountFromApi; const subTotal = showIncludingTax ? parseInt( cartTotals.total_items, 10 ) + parseInt( cartTotals.total_items_tax, 10 ) : parseInt( cartTotals.total_items, 10 ); const ariaLabel = hasHiddenPrice ? sprintf( /* translators: %1$d is the number of products in the cart. */ _n( '%1$d item in cart', '%1$d items in cart', cartItemsCount, 'woocommerce' ), cartItemsCount ) : 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, 'woocommerce' ), cartItemsCount, formatPrice( subTotal, getCurrencyFromPriceResponse( cartTotals ) ) ); return ( <> { setIsOpen( false ); } } slideIn={ ! skipSlideIn } >
); }; export default MiniCartBlock;