/** * 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 { isBoolean, isString } from '@woocommerce/types'; import { RawHTML, unmountComponentAtNode, useCallback, useEffect, 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; transparentButton: boolean; colorClassNames?: string; style?: Record< string, Record< string, string > >; contents: string; } const MiniCartBlock = ( { isInitiallyOpen = false, colorClassNames, style, contents = '', }: Props ): JSX.Element => { const { cartItemsCount, cartIsLoading, cartTotals } = useStoreCart(); 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( () => { 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 = () => { 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 ); }; }, [] ); const showIncludingTax = getSettingWithCoercion( 'displayCartPricesIncludingTax', false, isBoolean ); const taxLabel = getSettingWithCoercion( 'taxLabel', '', isString ); 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 ( <> { if ( ! isOpen ) { setIsOpen( true ); setSkipSlideIn( false ); } } } aria-label={ ariaLabel } > { formatPrice( subTotal, getCurrencyFromPriceResponse( cartTotals ) ) } { taxLabel !== '' && subTotal !== 0 && ( { taxLabel } ) } { setIsOpen( false ); } } slideIn={ ! skipSlideIn } > { /* @todo The `div` wrapper of RawHTML isn't removed on the front end. */ } { contents } > ); }; export default MiniCartBlock;