Cart block: line-items front end initial work (https://github.com/woocommerce/woocommerce-blocks/pull/1333)
* render block on front end, add `Shopping cart` heading (baby steps) * fake data for editing full cart + show line count in header * add note about core/html using `is-active` class for toggle state * reinstate work-in-progress full cart component (lost in rebase) * reinstate full cart from master * component for full cart title & item count + margin tweaks: - add margin between main cart & sidebar - add margin after cart block * add cart items sample data + factor sample product image to module * use sample cart data for item count * basic table of cart line items (no styling) * prettification * show images for cart line items + initial table styling * cart quantity selector component (work in progress) * use state for cart product quantity, allow incr/decr from UI (WIP) * replace WIP custom quantity control with number edit (temporary) * correctly format cart line item total price * align cart item columns with headings + indent image on desktop * tweak css for cart line item padding on mobile so it's more explicit * show cart line item full price if discounted * add placeholder for cart remove item link * switch cart table to flex layout (was table)… This will allow us to move things around for mobile/responsive layout. * only show cart items table header on desktop * more cart items styling - row borders, appropriate padding + + move image width to variable + fix class name plurality for row (item not items) * use standard $gap instead of 1em for padding/margins * responsive (mobile) layout for cart line items: - shift line $ total to bottom right - stack quantity selector in product info column * remove extraneous cart table padding on mobile * comment about unused styles for quantity selector component * add follow up issue for todo * remove inappropriate href * render srcset & sizes for cart line item product image * remove todo comment * switch back to table markup for cart items (in progress): - table is more semantic, associates headers with columns * cart line items column widths - product column is larger (60%) * reinstate table row borders * bottom-align line item price on mobile * cart contents heading should be H2 + prettify * remove unused QuantitySelector code/styles, rename main class in line with BEM * defaults for QuantitySelector props * variable/property name tidies - match conventions/API * fix bug: line total price is only bottom-align on small screen * move QuantitySelector to root of components, intended to be generally useful * use lineItem directly for cart, specify shape in PropTypes * rename cart components to align with "line item" rather than "product" * rejig class names to better align with new component names & BEM style * show cart item image correct size: - use single column for product image and info, with flex container - specify image width (rem instead of px) * fix safari issue - cart product images displaying vertically stretched * shift product name left margin from image, to account for no-image case * experiment: bump bundlewatch size limit for cart temporarily: - our fake data inline image is heavy - when we switch to real API we will no longer need it * fix issue introduced when moving margin from image to details div: - product details needs margin on left (not right) * fix react props issues: - explicitly destructure image props for srcSet (vs srcset) - use API key field for line item key instead of id, fix duplicate test id - CartLineItemsTable takes an array of lineItems (incorrect PropTypes) * remove redundant divs + use conventional `null` (when no full price) * override editor styles to ensure cart product image is correct size * move cart items editor style override to editor.css * add an explicit readable heading for cart heading to match visual layout
This commit is contained in:
parent
24fba4880b
commit
0763655d42
|
@ -17,3 +17,6 @@ $block-spacing: 4px; // Vertical space between blocks.
|
|||
$block-side-ui-width: 28px; // Width of the movers/drag handle UI.
|
||||
$block-side-ui-clearance: 2px; // Space between movers/drag handle UI, and block.
|
||||
$block-container-side-padding: $block-side-ui-width + $block-padding + 2 * $block-side-ui-clearance; // Total space left and right of the block footprint.
|
||||
|
||||
// Cart block
|
||||
$cart-image-width: 5rem;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
const QuantitySelector = ( {
|
||||
className,
|
||||
quantity = 1,
|
||||
onChange = () => null,
|
||||
} ) => {
|
||||
const classes = classNames(
|
||||
'wc-block-quantity-selector__input',
|
||||
className
|
||||
);
|
||||
|
||||
// For now just use a regular number edit. (temporary)
|
||||
// @todo https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/1521
|
||||
return (
|
||||
<input
|
||||
className={ classes }
|
||||
type="number"
|
||||
step="1"
|
||||
min="0"
|
||||
value={ quantity }
|
||||
onChange={ ( event ) => onChange( event.target.value ) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
QuantitySelector.propTypes = {
|
||||
className: PropTypes.string,
|
||||
quantity: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default QuantitySelector;
|
|
@ -0,0 +1,3 @@
|
|||
.wc-block-quantity-selector__input {
|
||||
width: 60px;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
import QuantitySelector from '@woocommerce/base-components/quantity-selector';
|
||||
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
|
||||
import { getCurrency } from '@woocommerce/base-utils';
|
||||
|
||||
const CartLineItemRow = ( { lineItem } ) => {
|
||||
const { name, images, quantity, totals } = lineItem;
|
||||
const { line_total: total, line_subtotal: subtotal } = totals;
|
||||
|
||||
const imageProps = {};
|
||||
if ( images && images.length ) {
|
||||
imageProps.src = lineItem.images[ 0 ].src || '';
|
||||
imageProps.alt = lineItem.images[ 0 ].alt || '';
|
||||
imageProps.srcSet = lineItem.images[ 0 ].srcset || '';
|
||||
imageProps.sizes = lineItem.images[ 0 ].sizes || '';
|
||||
}
|
||||
|
||||
const [ lineQuantity, setLineQuantity ] = useState( quantity );
|
||||
const currency = getCurrency();
|
||||
|
||||
const isDiscounted = subtotal !== total;
|
||||
const fullPrice = isDiscounted ? (
|
||||
<div className="wc-block-cart-item__full-price">
|
||||
<FormattedMonetaryAmount currency={ currency } value={ subtotal } />
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// We use this in two places so we can stack the quantity selector under
|
||||
// product info on smaller screens.
|
||||
const quantitySelector = ( className ) => {
|
||||
return (
|
||||
<QuantitySelector
|
||||
className={ className }
|
||||
quantity={ lineQuantity }
|
||||
onChange={ setLineQuantity }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="wc-block-cart-item__product">
|
||||
<div className="wc-block-cart-item__product-wrapper">
|
||||
<img { ...imageProps } alt={ imageProps.alt } />
|
||||
<div className="wc-block-cart-item__product-details">
|
||||
{ name }
|
||||
{ quantitySelector(
|
||||
'wc-block-cart-item__quantity-stacked'
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="wc-block-cart-item__quantity">
|
||||
<div>
|
||||
{ quantitySelector() }
|
||||
<div className="wc-block-cart-item__remove-link">
|
||||
{ __( 'Remove item', 'woo-gutenberg-products-block' ) }
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="wc-block-cart-item__total">
|
||||
{ fullPrice }
|
||||
<FormattedMonetaryAmount
|
||||
currency={ currency }
|
||||
value={ total }
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
CartLineItemRow.propTypes = {
|
||||
lineItem: PropTypes.shape( {
|
||||
name: PropTypes.string.isRequired,
|
||||
images: PropTypes.array.isRequired,
|
||||
quantity: PropTypes.number.isRequired,
|
||||
totals: PropTypes.shape( {
|
||||
line_subtotal: PropTypes.string.isRequired,
|
||||
line_total: PropTypes.string.isRequired,
|
||||
} ).isRequired,
|
||||
} ),
|
||||
};
|
||||
|
||||
export default CartLineItemRow;
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CartLineItemRow from './cart-line-item-row';
|
||||
|
||||
const CartLineItemsTable = ( { lineItems = [] } ) => {
|
||||
const products = lineItems.map( ( lineItem ) => (
|
||||
<CartLineItemRow key={ lineItem.key } lineItem={ lineItem } />
|
||||
) );
|
||||
|
||||
return (
|
||||
<table className="wc-block-cart-items">
|
||||
<thead>
|
||||
<tr className="wc-block-cart-items__header">
|
||||
<th className="wc-block-cart-items__header-product">
|
||||
{ __( 'Product', 'woo-gutenberg-products-block' ) }
|
||||
</th>
|
||||
<th className="wc-block-cart-items__header-quantity">
|
||||
{ __( 'Quantity', 'woo-gutenberg-products-block' ) }
|
||||
</th>
|
||||
<th className="wc-block-cart-items__header-total">
|
||||
{ __( 'Total', 'woo-gutenberg-products-block' ) }
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{ products }</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
CartLineItemsTable.propTypes = {
|
||||
lineItems: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
key: PropTypes.string.isRequired,
|
||||
} )
|
||||
),
|
||||
};
|
||||
|
||||
export default CartLineItemsTable;
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf, _n } from '@wordpress/i18n';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const CartLineItemsTitle = ( {
|
||||
title = __( 'Shopping cart', 'woo-gutenberg-products-block' ),
|
||||
itemCount = 1,
|
||||
} ) => {
|
||||
const itemCountHeading = sprintf(
|
||||
_n( '%d item', '%d items', itemCount, 'woo-gutenberg-products-block' ),
|
||||
itemCount
|
||||
);
|
||||
const readableHeading = `${ title } – ${ itemCountHeading }`;
|
||||
|
||||
return (
|
||||
<h2 aria-label={ readableHeading }>
|
||||
<span>{ title } </span>
|
||||
<span className="wc-block-cart__item-count">
|
||||
{ itemCountHeading }
|
||||
</span>
|
||||
</h2>
|
||||
);
|
||||
};
|
||||
|
||||
CartLineItemsTitle.propTypes = {
|
||||
title: PropTypes.string,
|
||||
itemCount: PropTypes.number,
|
||||
};
|
||||
|
||||
export default CartLineItemsTitle;
|
|
@ -0,0 +1,4 @@
|
|||
.block-editor__container .wc-block-cart-item__product img {
|
||||
max-width: $cart-image-width;
|
||||
}
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { previewCartItems } from '@woocommerce/resource-previews';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CheckoutButton from './checkout-button';
|
||||
import CartLineItemsTitle from './cart-line-items-title';
|
||||
import CartLineItemsTable from './cart-line-items-table';
|
||||
|
||||
import './style.scss';
|
||||
import './editor.scss';
|
||||
|
||||
/**
|
||||
* Component that renders the Cart block when user has something in cart aka "full".
|
||||
|
@ -11,9 +20,8 @@ const Cart = () => {
|
|||
return (
|
||||
<div className="wc-block-cart">
|
||||
<div className="wc-block-cart__main">
|
||||
<span>
|
||||
Cart block <b>full state</b> coming soon…
|
||||
</span>
|
||||
<CartLineItemsTitle itemCount={ previewCartItems.length } />
|
||||
<CartLineItemsTable lineItems={ previewCartItems } />
|
||||
</div>
|
||||
<div className="wc-block-cart__sidebar">
|
||||
<CheckoutButton />
|
||||
|
|
|
@ -1,6 +1,72 @@
|
|||
.wc-block-cart__sidebar {
|
||||
border: 1px solid $core-grey-light-600;
|
||||
border-width: 1px 0;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
.wc-block-cart__item-count {
|
||||
float: right;
|
||||
}
|
||||
|
||||
table.wc-block-cart-items th,
|
||||
table.wc-block-cart-items td {
|
||||
// Override Storefront theme gray table background.
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
table.wc-block-cart-items th {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
table.wc-block-cart-items td {
|
||||
border-top: 1px solid $core-grey-light-600;
|
||||
padding: $gap 0;
|
||||
}
|
||||
table.wc-block-cart-items {
|
||||
border-bottom: 1px solid $core-grey-light-600;
|
||||
}
|
||||
|
||||
.wc-block-cart-items__header {
|
||||
display: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wc-block-cart-items__header-total,
|
||||
.wc-block-cart-item__total {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__product-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
// Fixes a Safari-specific issue - product images display stretched vertically (full image height).
|
||||
// https://stackoverflow.com/questions/57516373/image-stretching-in-flexbox-in-safari
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__product img {
|
||||
max-width: $cart-image-width;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__product-details {
|
||||
margin-left: $gap;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__quantity {
|
||||
display: none;
|
||||
}
|
||||
.wc-block-cart-item__quantity-stacked {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__total {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__full-price {
|
||||
color: $core-grey-light-800;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
@include breakpoint( ">782px" ) {
|
||||
|
@ -10,6 +76,7 @@
|
|||
|
||||
.wc-block-cart__main {
|
||||
flex-grow: 4;
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
.wc-block-cart__sidebar {
|
||||
|
@ -19,4 +86,32 @@
|
|||
flex-grow: 1;
|
||||
padding: $gap $gap-large $gap-largest;
|
||||
}
|
||||
|
||||
.wc-block-cart-items {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.wc-block-cart-items__header {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.wc-block-cart-items__header-product {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__quantity {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__quantity-stacked {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__product img {
|
||||
margin-left: $gap;
|
||||
}
|
||||
|
||||
.wc-block-cart-item__total {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.wp-block-woocommerce-cart.is-loading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-cart {
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# TextToolbarButton
|
||||
|
||||
TextToolbarButton is used in Toolbar for text buttons which show isToggled state.
|
||||
TextToolbarButton is used in Toolbar for text buttons which show `isToggled` state.
|
||||
|
||||
Note: Gutenberg core has `ToolbarGroup` and `ToolbarButton` in progress. When these are available this component may not be needed.
|
||||
Notes:
|
||||
|
||||
- Gutenberg core has `ToolbarGroup` and `ToolbarButton` in progress. When these are available this component may not be needed.
|
||||
- Gutenberg [core `html` block uses regular `Button` in toolbar](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/html/edit.js), and sets `is-active` class to trigger "active" styling when button is toggled on.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import productPicture from './product-image';
|
||||
|
||||
// Sample data for cart block line items.
|
||||
// This closely resembles the data returned from the Store API cart/items endpoint.
|
||||
// https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/master/src/RestApi/StoreApi#cart-items-api
|
||||
export const previewCartItems = [
|
||||
{
|
||||
key: '1',
|
||||
id: 1,
|
||||
quantity: 2,
|
||||
name: __( 'Beanie', 'woo-gutenberg-products-block' ),
|
||||
sku: 'woo-beanie',
|
||||
permalink: 'https://example.org',
|
||||
images: [
|
||||
{
|
||||
id: 10,
|
||||
src: productPicture,
|
||||
thumbnail: productPicture,
|
||||
srcset: '',
|
||||
sizes: '',
|
||||
name: '',
|
||||
alt: '',
|
||||
},
|
||||
],
|
||||
variation: [],
|
||||
totals: {
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
line_subtotal: '1299',
|
||||
line_subtotal_tax: '0',
|
||||
line_total: '1299',
|
||||
line_total_tax: '0',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
id: 2,
|
||||
quantity: 1,
|
||||
name: __( 'Cap', 'woo-gutenberg-products-block' ),
|
||||
sku: 'woo-cap',
|
||||
permalink: 'https://example.org',
|
||||
images: [
|
||||
{
|
||||
id: 11,
|
||||
src: productPicture,
|
||||
thumbnail: productPicture,
|
||||
srcset: '',
|
||||
sizes: '',
|
||||
name: '',
|
||||
alt: '',
|
||||
},
|
||||
],
|
||||
variation: [],
|
||||
totals: {
|
||||
currency_code: 'USD',
|
||||
currency_symbol: '$',
|
||||
currency_minor_unit: 2,
|
||||
currency_decimal_separator: '.',
|
||||
currency_thousand_separator: ',',
|
||||
currency_prefix: '$',
|
||||
currency_suffix: '',
|
||||
line_subtotal: '1600',
|
||||
line_subtotal_tax: '0',
|
||||
line_total: '1400',
|
||||
line_total_tax: '0',
|
||||
},
|
||||
},
|
||||
];
|
|
@ -1,4 +1,5 @@
|
|||
export { previewProducts } from './products';
|
||||
export { previewCartItems } from './cart-items';
|
||||
export { previewReviews } from './reviews';
|
||||
export { gridBlockPreview } from './grid-block';
|
||||
export { previewCategories } from './categories';
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -132,6 +132,10 @@
|
|||
"path": "./build/*frontend*.js",
|
||||
"maxSize": "60 kB"
|
||||
},
|
||||
{
|
||||
"path": "./build/cart-frontend.js",
|
||||
"maxSize": "100 kB"
|
||||
},
|
||||
{
|
||||
"path": "./build/*.css",
|
||||
"maxSize": "50kB"
|
||||
|
|
Loading…
Reference in New Issue