Cart i2: Render filled and empty Carts on frontend (https://github.com/woocommerce/woocommerce-blocks/pull/4802)
* WIP getting to work on frontend * restore frontend.tsx * fix layout * remove unit tests living where they shouldn't be living * remove skeleton * support emtpy cart in frontend * remove extra todo * use fragment instead of div * Add empty cart event * Remove extra fragment
This commit is contained in:
parent
27600b3309
commit
b6167bc179
|
@ -13,7 +13,7 @@ interface DispatchedEventProperties {
|
|||
// See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail
|
||||
detail?: unknown;
|
||||
// Element that dispatches the event. By default, the body.
|
||||
element?: HTMLElement;
|
||||
element?: Element | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useEffect, RawHTML } from '@wordpress/element';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import LoadingMask from '@woocommerce/base-components/loading-mask';
|
||||
import { ValidationContextProvider } from '@woocommerce/base-context';
|
||||
import {
|
||||
dispatchEvent,
|
||||
translateJQueryEventToNative,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
import { translateJQueryEventToNative } from '@woocommerce/base-utils';
|
||||
import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreSnackbarNoticesProvider,
|
||||
CartProvider,
|
||||
} from '@woocommerce/base-context/providers';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import FullCart from './full-cart';
|
||||
const reloadPage = () => void window.location.reload( true );
|
||||
|
||||
const EmptyCart = ( { content } ) => {
|
||||
useEffect( () => {
|
||||
dispatchEvent( 'wc-blocks_render_blocks_frontend', {
|
||||
element: document.body.querySelector(
|
||||
'.wp-block-woocommerce-cart'
|
||||
),
|
||||
} );
|
||||
}, [] );
|
||||
return <RawHTML>{ content }</RawHTML>;
|
||||
const Cart = ( { children } ) => {
|
||||
const { cartIsLoading } = useStoreCart();
|
||||
|
||||
return (
|
||||
<LoadingMask showSpinner={ true } isLoading={ cartIsLoading }>
|
||||
<ValidationContextProvider>{ children }</ValidationContextProvider>
|
||||
</LoadingMask>
|
||||
);
|
||||
};
|
||||
|
||||
const Block = ( { emptyCart, attributes, scrollToTop } ) => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
|
||||
const ScrollOnError = ( { scrollToTop } ) => {
|
||||
useEffect( () => {
|
||||
const invalidateCartData = () => {
|
||||
dispatch( storeKey ).invalidateResolutionForStore();
|
||||
|
@ -72,19 +72,32 @@ const Block = ( { emptyCart, attributes, scrollToTop } ) => {
|
|||
};
|
||||
}, [ scrollToTop ] );
|
||||
|
||||
return (
|
||||
<>
|
||||
{ ! cartIsLoading && cartItems.length === 0 ? (
|
||||
<EmptyCart content={ emptyCart } />
|
||||
) : (
|
||||
<LoadingMask showSpinner={ true } isLoading={ cartIsLoading }>
|
||||
<ValidationContextProvider>
|
||||
<FullCart attributes={ attributes } />
|
||||
</ValidationContextProvider>
|
||||
</LoadingMask>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const Block = ( { attributes, children, scrollToTop } ) => (
|
||||
<BlockErrorBoundary
|
||||
header={ __( 'Something went wrong…', 'woo-gutenberg-products-block' ) }
|
||||
text={ __(
|
||||
'The cart has encountered an unexpected error. If the error persists, please get in touch with us for help.',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
button={
|
||||
<button className="wc-block-button" onClick={ reloadPage }>
|
||||
{ __( 'Reload the page', 'woo-gutenberg-products-block' ) }
|
||||
</button>
|
||||
}
|
||||
showErrorMessage={ CURRENT_USER_IS_ADMIN }
|
||||
>
|
||||
<StoreSnackbarNoticesProvider context="wc/cart">
|
||||
<StoreNoticesProvider context="wc/cart">
|
||||
<SlotFillProvider>
|
||||
<CartProvider>
|
||||
<Cart attributes={ attributes }>{ children }</Cart>
|
||||
<ScrollOnError scrollToTop={ scrollToTop } />
|
||||
</CartProvider>
|
||||
</SlotFillProvider>
|
||||
</StoreNoticesProvider>
|
||||
</StoreSnackbarNoticesProvider>
|
||||
</BlockErrorBoundary>
|
||||
);
|
||||
export default withScrollToTop( Block );
|
||||
|
|
|
@ -5,25 +5,27 @@ import {
|
|||
withStoreCartApiHydration,
|
||||
withRestApiHydration,
|
||||
} from '@woocommerce/block-hocs';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
StoreNoticesProvider,
|
||||
StoreSnackbarNoticesProvider,
|
||||
CartProvider,
|
||||
} from '@woocommerce/base-context/providers';
|
||||
import { SlotFillProvider } from '@woocommerce/blocks-checkout';
|
||||
import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings';
|
||||
import {
|
||||
renderFrontend,
|
||||
getValidBlockAttributes,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { getValidBlockAttributes } from '@woocommerce/base-utils';
|
||||
import { Children, cloneElement, isValidElement } from '@wordpress/element';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
import { useValidation } from '@woocommerce/base-context/hooks';
|
||||
import { getRegisteredBlockComponents } from '@woocommerce/blocks-registry';
|
||||
|
||||
import { renderParentBlock } from '@woocommerce/atomic-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block.js';
|
||||
import { blockAttributes } from './attributes';
|
||||
import './inner-blocks/register-components';
|
||||
import Block from './block';
|
||||
import { blockName, blockAttributes } from './attributes';
|
||||
|
||||
const reloadPage = () => void window.location.reload( true );
|
||||
/**
|
||||
* Wrapper component to supply API data and show empty cart view as needed.
|
||||
*
|
||||
|
@ -45,30 +47,36 @@ const CartFrontend = ( props ) => {
|
|||
|
||||
const getProps = ( el ) => {
|
||||
return {
|
||||
emptyCart: el.innerHTML,
|
||||
attributes: getValidBlockAttributes( blockAttributes, el.dataset ),
|
||||
};
|
||||
};
|
||||
|
||||
const getErrorBoundaryProps = () => {
|
||||
return {
|
||||
header: __( 'Something went wrong…', 'woo-gutenberg-products-block' ),
|
||||
text: __(
|
||||
'The cart has encountered an unexpected error. If the error persists, please get in touch with us for help.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
showErrorMessage: CURRENT_USER_IS_ADMIN,
|
||||
button: (
|
||||
<button className="wc-block-button" onClick={ reloadPage }>
|
||||
{ __( 'Reload the page', 'woo-gutenberg-products-block' ) }
|
||||
</button>
|
||||
attributes: getValidBlockAttributes(
|
||||
blockAttributes,
|
||||
!! el ? el.dataset : {}
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
renderFrontend( {
|
||||
selector: '.wp-block-woocommerce-cart-i2',
|
||||
const Wrapper = ( { children } ) => {
|
||||
// we need to pluck out receiveCart.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { extensions, receiveCart, ...cart } = useStoreCart();
|
||||
const validation = useValidation();
|
||||
return Children.map( children, ( child ) => {
|
||||
if ( isValidElement( child ) ) {
|
||||
const componentProps = {
|
||||
extensions,
|
||||
cart,
|
||||
validation,
|
||||
};
|
||||
return cloneElement( child, componentProps );
|
||||
}
|
||||
return child;
|
||||
} );
|
||||
};
|
||||
|
||||
renderParentBlock( {
|
||||
Block: withStoreCartApiHydration( withRestApiHydration( CartFrontend ) ),
|
||||
blockName,
|
||||
selector: '.wp-block-woocommerce-cart-i2',
|
||||
getProps,
|
||||
getErrorBoundaryProps,
|
||||
blockMap: getRegisteredBlockComponents( blockName ),
|
||||
blockWrapper: Wrapper,
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { dispatchEvent } from '@woocommerce/base-utils';
|
||||
|
||||
const FrontendBlock = ( {
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
} ): JSX.Element | null => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
useEffect( () => {
|
||||
dispatchEvent( 'wc-blocks_render_blocks_frontend', {
|
||||
element: document.body.querySelector(
|
||||
'.wp-block-woocommerce-cart'
|
||||
),
|
||||
} );
|
||||
}, [] );
|
||||
if ( ! cartIsLoading && cartItems.length === 0 ) {
|
||||
return <>{ children }</>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default FrontendBlock;
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
|
||||
const FrontendBlock = ( {
|
||||
children,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
} ): JSX.Element | null => {
|
||||
const { cartItems, cartIsLoading } = useStoreCart();
|
||||
// @todo pass attributes to inner most blocks.
|
||||
const hasDarkControls = false;
|
||||
if ( cartIsLoading || cartItems.length >= 1 ) {
|
||||
return (
|
||||
<SidebarLayout
|
||||
className={ classnames( 'wc-block-cart', {
|
||||
'has-dark-controls': hasDarkControls,
|
||||
} ) }
|
||||
>
|
||||
{ children }
|
||||
</SidebarLayout>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default FrontendBlock;
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { lazy } from '@wordpress/element';
|
||||
import { WC_BLOCKS_BUILD_URL } from '@woocommerce/block-settings';
|
||||
import { registerCheckoutBlock } from '@woocommerce/blocks-checkout';
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import filledCartMetadata from './filled-cart-block/block.json';
|
||||
import emptyCartMetadata from './empty-cart-block/block.json';
|
||||
import cartItemsMetadata from './cart-items-block/block.json';
|
||||
import cartExpressPaymentMetadata from './cart-express-payment-block/block.json';
|
||||
import cartLineItemsMetadata from './cart-line-items-block/block.json';
|
||||
import cartOrderSummaryMetadata from './cart-order-summary-block/block.json';
|
||||
import cartTotalsMetadata from './cart-totals-block/block.json';
|
||||
import cartProceedToCheckoutMetadata from './proceed-to-checkout-block/block.json';
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: filledCartMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/filled-cart" */ './filled-cart-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
registerCheckoutBlock( {
|
||||
metadata: emptyCartMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/empty-cart" */ './empty-cart-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartItemsMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/items" */ './cart-items-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartLineItemsMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/line-items" */ './cart-line-items-block/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartTotalsMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/totals" */ './cart-totals-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartOrderSummaryMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/order-summary" */ './cart-order-summary-block/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartExpressPaymentMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/express-payment" */ './cart-express-payment-block/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerCheckoutBlock( {
|
||||
metadata: cartProceedToCheckoutMetadata,
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "cart-blocks/checkout-button" */ './proceed-to-checkout-block/frontend'
|
||||
)
|
||||
),
|
||||
} );
|
File diff suppressed because it is too large
Load Diff
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen, waitFor } 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';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from '../block';
|
||||
import { defaultCartState } from '../../../../data/default-states';
|
||||
import { allSettings } from '../../../../settings/shared/settings-init';
|
||||
|
||||
const CartBlock = ( props ) => (
|
||||
<SlotFillProvider>
|
||||
<Block { ...props } />
|
||||
</SlotFillProvider>
|
||||
);
|
||||
describe( 'Testing cart', () => {
|
||||
beforeEach( async () => {
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/cart/ ) ) {
|
||||
return Promise.resolve( JSON.stringify( previewCart ) );
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
// need to clear the store resolution state between tests.
|
||||
await dispatch( storeKey ).invalidateResolutionForStore();
|
||||
await dispatch( storeKey ).receiveCart( defaultCartState.cartData );
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
fetchMock.resetMocks();
|
||||
} );
|
||||
|
||||
it( 'renders cart if there are items in the cart', async () => {
|
||||
render(
|
||||
<CartBlock
|
||||
emptyCart={ null }
|
||||
attributes={ {
|
||||
isShippingCalculatorEnabled: false,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect(
|
||||
screen.getByText( /Proceed to Checkout/i )
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
|
||||
// ["`select` control in `@wordpress/data-controls` is deprecated. Please use built-in `resolveSelect` control in `@wordpress/data` instead."]
|
||||
expect( console ).toHaveWarned();
|
||||
} );
|
||||
|
||||
it( 'Contains a Taxes section if Core options are set to show it', async () => {
|
||||
allSettings.displayCartPricesIncludingTax = false;
|
||||
// The criteria for showing the Taxes section is:
|
||||
// Display prices during basket and checkout: 'Excluding tax'.
|
||||
const { container } = render(
|
||||
<CartBlock
|
||||
emptyCart={ null }
|
||||
attributes={ {
|
||||
isShippingCalculatorEnabled: false,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
it( 'Shows individual tax lines if the store is set to do so', async () => {
|
||||
allSettings.displayCartPricesIncludingTax = false;
|
||||
allSettings.displayItemizedTaxes = true;
|
||||
// The criteria for showing the lines in the Taxes section is:
|
||||
// Display prices during basket and checkout: 'Excluding tax'.
|
||||
// Display tax totals: 'Itemized';
|
||||
const { container } = render(
|
||||
<CartBlock
|
||||
emptyCart={ null }
|
||||
attributes={ {
|
||||
isShippingCalculatorEnabled: false,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
it( 'Shows rate percentages after tax lines if the block is set to do so', async () => {
|
||||
allSettings.displayCartPricesIncludingTax = false;
|
||||
allSettings.displayItemizedTaxes = true;
|
||||
// The criteria for showing the lines in the Taxes section is:
|
||||
// Display prices during basket and checkout: 'Excluding tax'.
|
||||
// Display tax totals: 'Itemized';
|
||||
const { container } = render(
|
||||
<CartBlock
|
||||
emptyCart={ null }
|
||||
attributes={ {
|
||||
showRateAfterTaxName: true,
|
||||
isShippingCalculatorEnabled: false,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
it( 'renders empty cart if there are no items in the cart', async () => {
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/cart/ ) ) {
|
||||
return Promise.resolve(
|
||||
JSON.stringify( defaultCartState.cartData )
|
||||
);
|
||||
}
|
||||
return Promise.resolve( '' );
|
||||
} );
|
||||
render(
|
||||
<CartBlock
|
||||
emptyCart={ '<div>Empty Cart</div>' }
|
||||
attributes={ {
|
||||
isShippingCalculatorEnabled: false,
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect( screen.getByText( /Empty Cart/i ) ).toBeInTheDocument();
|
||||
expect( fetchMock ).toHaveBeenCalledTimes( 1 );
|
||||
} );
|
||||
|
||||
it( 'renders correct cart line subtotal when currency has 0 decimals', async () => {
|
||||
fetchMock.mockResponse( ( req ) => {
|
||||
if ( req.url.match( /wc\/store\/cart/ ) ) {
|
||||
const cart = {
|
||||
...previewCart,
|
||||
// Make it so there is only one item to simplify things.
|
||||
items: [
|
||||
{
|
||||
...previewCart.items[ 0 ],
|
||||
totals: {
|
||||
...previewCart.items[ 0 ].totals,
|
||||
// Change price format so there are no decimals.
|
||||
currency_minor_unit: 0,
|
||||
currency_prefix: '',
|
||||
currency_suffix: '€',
|
||||
line_subtotal: '16',
|
||||
line_total: '18',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return Promise.resolve( JSON.stringify( cart ) );
|
||||
}
|
||||
} );
|
||||
render( <CartBlock emptyCart={ null } attributes={ {} } /> );
|
||||
|
||||
await waitFor( () => expect( fetchMock ).toHaveBeenCalled() );
|
||||
expect( screen.getAllByRole( 'cell' )[ 1 ] ).toHaveTextContent( '16€' );
|
||||
} );
|
||||
} );
|
|
@ -13,6 +13,7 @@ export enum innerBlockAreas {
|
|||
BILLING_ADDRESS = 'woocommerce/checkout-billing-address-block',
|
||||
SHIPPING_METHODS = 'woocommerce/checkout-shipping-methods-block',
|
||||
PAYMENT_METHODS = 'woocommerce/checkout-payment-methods-block',
|
||||
CART = 'woocommerce/cart-i2',
|
||||
EMPTY_CART = 'woocommerce/empty-cart-block',
|
||||
FILLED_CART = 'woocommerce/filled-cart-block',
|
||||
CART_ITEMS = 'woocommerce/cart-items-block',
|
||||
|
|
|
@ -74,7 +74,7 @@ class CartI2 extends AbstractBlock {
|
|||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
return $this->inject_html_data_attributes( $content . $this->get_skeleton(), $attributes );
|
||||
return $this->inject_html_data_attributes( $content, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,85 +162,4 @@ class CartI2 extends AbstractBlock {
|
|||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/cart' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render skeleton markup for the cart block.
|
||||
*/
|
||||
protected function get_skeleton() {
|
||||
return '
|
||||
<div class="wc-block-skeleton wc-block-components-sidebar-layout wc-block-cart wc-block-cart--is-loading wc-block-cart--skeleton hidden" aria-hidden="true">
|
||||
<div class="wc-block-components-main wc-block-cart__main">
|
||||
<h2 class="wc-block-components-title"><span></span></h2>
|
||||
<table class="wc-block-cart-items">
|
||||
<thead>
|
||||
<tr class="wc-block-cart-items__header">
|
||||
<th class="wc-block-cart-items__header-image"><span /></th>
|
||||
<th class="wc-block-cart-items__header-product"><span /></th>
|
||||
<th class="wc-block-cart-items__header-total"><span /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="wc-block-components-sidebar wc-block-cart__sidebar">
|
||||
<div class="components-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
' . $this->get_skeleton_inline_script();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue