* 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:
Rua Haszard 2020-01-10 11:50:14 +13:00 committed by GitHub
parent 24fba4880b
commit 0763655d42
16 changed files with 427 additions and 7 deletions

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,3 @@
.wc-block-quantity-selector__input {
width: 60px;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,4 @@
.block-editor__container .wc-block-cart-item__product img {
max-width: $cart-image-width;
}

View File

@ -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 />

View File

@ -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;
}
}

View File

@ -1,3 +1,7 @@
.wp-block-woocommerce-cart.is-loading {
display: none;
}
.wp-block-woocommerce-cart {
margin-bottom: 3em;
}

View File

@ -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

View File

@ -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',
},
},
];

View File

@ -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

View File

@ -132,6 +132,10 @@
"path": "./build/*frontend*.js",
"maxSize": "60 kB"
},
{
"path": "./build/cart-frontend.js",
"maxSize": "100 kB"
},
{
"path": "./build/*.css",
"maxSize": "50kB"