Update Order Summary Design (#45767)

* Add border around order summary on cart & checkout

* Group subtotal, discount, fees, taxes and shiping block

* Move the coupon form in order summary

* Move the coupon form in order summary

* Manage state externally in Panel component and refactor Coupon form to use the Panel component

* Remove descriptions from order summary items

* increase font weight of order summary title

* Tidy up design for desktop and add separator back in for blocks on checkout order summary

* Remove border around order summary on mobile

* Revert "Move the coupon form in order summary"

This reverts commit 4a8044cdcf.

* Change heading styles for cart

* Change font weight to 500 of order summary heading on checkout block

* Remove padding and border between order summary totals items

* Refactor css for cart & checkout totals to work in the editor

* Adjust cart totals heading in the editor

* Last adjustment to checkout totals style to work in editor

* Add changelog

* Change the cursor to pointer for the panel component

* remove unused short and full description from OrderSummaryItem

* Fix failing e2e tests

* Fix lint issues

* Vertically align order summary title in the editor

* Fix e2e tests

* Fix linting issues

* Fix unit tests

* Remove changes from woocommerce.php

* Fix checkout block test

* fix eslint errors in checkout block test

---------

Co-authored-by: Niels Lange <info@nielslange.de>
This commit is contained in:
Alex Florisca 2024-04-25 06:03:31 +01:00 committed by GitHub
parent d8486e579c
commit 7dc8dd63ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 141 additions and 122 deletions

View File

@ -41,8 +41,6 @@ const OrderSummaryItem = ( { cartItem }: OrderSummaryProps ): JSX.Element => {
permalink,
prices,
quantity,
short_description: shortDescription,
description: fullDescription,
item_data: itemData,
variation,
totals,
@ -175,8 +173,6 @@ const OrderSummaryItem = ( { cartItem }: OrderSummaryProps ): JSX.Element => {
)
) }
<ProductMetadata
shortDescription={ shortDescription }
fullDescription={ fullDescription }
itemData={ itemData }
variation={ variation }
/>

View File

@ -1,5 +1,7 @@
.wc-block-components-order-summary {
.wc-block-components-order-summary__button-text {
font-weight: 500;
}
.wc-block-components-panel__button {
padding-top: 0;
margin-top: 0;
@ -40,7 +42,6 @@
.wc-block-components-product-metadata {
@include font-size(regular);
}
}
.wc-block-components-order-summary-item__image,
@ -104,12 +105,10 @@
padding-top: $gap-smallest;
}
.wc-block-components-order-summary-item__individual-prices {
display: block;
padding-top: $gap-smaller;
}
}
.is-medium,

View File

@ -9,10 +9,10 @@ import { withInstanceId } from '@wordpress/compose';
import {
ValidatedTextInput,
ValidationInputError,
Panel,
} from '@woocommerce/blocks-components';
import { useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import classnames from 'classnames';
import type { MouseEvent, MouseEventHandler } from 'react';
/**
@ -46,28 +46,16 @@ export const TotalsCoupon = ( {
displayCouponForm = false,
}: TotalsCouponProps ): JSX.Element => {
const [ couponValue, setCouponValue ] = useState( '' );
const [ isCouponFormHidden, setIsCouponFormHidden ] = useState(
! displayCouponForm
);
const [ isCouponFormVisible, setIsCouponFormVisible ] =
useState( displayCouponForm );
const textInputId = `wc-block-components-totals-coupon__input-${ instanceId }`;
const formWrapperClass = classnames(
'wc-block-components-totals-coupon__content',
{
'screen-reader-text': isCouponFormHidden,
}
);
const { validationErrorId } = useSelect( ( select ) => {
const store = select( VALIDATION_STORE_KEY );
return {
validationErrorId: store.getValidationErrorId( textInputId ),
};
} );
const handleCouponAnchorClick: MouseEventHandler< HTMLAnchorElement > = (
e: MouseEvent< HTMLAnchorElement >
) => {
e.preventDefault();
setIsCouponFormHidden( false );
};
const handleCouponSubmit: MouseEventHandler< HTMLButtonElement > = (
e: MouseEvent< HTMLButtonElement >
) => {
@ -76,73 +64,64 @@ export const TotalsCoupon = ( {
onSubmit( couponValue )?.then( ( result ) => {
if ( result ) {
setCouponValue( '' );
setIsCouponFormHidden( true );
setIsCouponFormVisible( false );
}
} );
} else {
setCouponValue( '' );
setIsCouponFormHidden( true );
setIsCouponFormVisible( true );
}
};
return (
<div className="wc-block-components-totals-coupon">
{ isCouponFormHidden ? (
<a
role="button"
href="#wc-block-components-totals-coupon__form"
className="wc-block-components-totals-coupon-link"
aria-label={ __( 'Add a coupon', 'woocommerce' ) }
onClick={ handleCouponAnchorClick }
>
{ __( 'Add a coupon', 'woocommerce' ) }
</a>
) : (
<LoadingMask
screenReaderLabel={ __(
'Applying coupon…',
'woocommerce'
) }
isLoading={ isLoading }
showSpinner={ false }
>
<div className={ formWrapperClass }>
<form
className="wc-block-components-totals-coupon__form"
id="wc-block-components-totals-coupon__form"
>
<ValidatedTextInput
id={ textInputId }
errorId="coupon"
className="wc-block-components-totals-coupon__input"
label={ __( 'Enter code', 'woocommerce' ) }
value={ couponValue }
ariaDescribedBy={ validationErrorId }
onChange={ ( newCouponValue ) => {
setCouponValue( newCouponValue );
} }
focusOnMount={ true }
validateOnMount={ false }
showError={ false }
/>
<Button
className="wc-block-components-totals-coupon__button"
disabled={ isLoading || ! couponValue }
showSpinner={ isLoading }
onClick={ handleCouponSubmit }
type="submit"
>
{ __( 'Apply', 'woocommerce' ) }
</Button>
</form>
<ValidationInputError
propertyName="coupon"
elementId={ textInputId }
<Panel
className="wc-block-components-totals-coupon"
initialOpen={ isCouponFormVisible }
hasBorder={ false }
title={ __( 'Add a coupon', 'woocommerce' ) }
state={ [ isCouponFormVisible, setIsCouponFormVisible ] }
>
<LoadingMask
screenReaderLabel={ __( 'Applying coupon…', 'woocommerce' ) }
isLoading={ isLoading }
showSpinner={ false }
>
<div className="wc-block-components-totals-coupon__content">
<form
className="wc-block-components-totals-coupon__form"
id="wc-block-components-totals-coupon__form"
>
<ValidatedTextInput
id={ textInputId }
errorId="coupon"
className="wc-block-components-totals-coupon__input"
label={ __( 'Enter code', 'woocommerce' ) }
value={ couponValue }
ariaDescribedBy={ validationErrorId }
onChange={ ( newCouponValue ) => {
setCouponValue( newCouponValue );
} }
focusOnMount={ true }
validateOnMount={ false }
showError={ false }
/>
</div>
</LoadingMask>
) }
</div>
<Button
className="wc-block-components-totals-coupon__button"
disabled={ isLoading || ! couponValue }
showSpinner={ isLoading }
onClick={ handleCouponSubmit }
type="submit"
>
{ __( 'Apply', 'woocommerce' ) }
</Button>
</form>
<ValidationInputError
propertyName="coupon"
elementId={ textInputId }
/>
</div>
</LoadingMask>
</Panel>
);
};

View File

@ -1,5 +1,4 @@
.wc-block-components-totals-coupon {
.wc-block-components-panel__button {
margin-top: 0;
padding-top: 0;
@ -22,7 +21,6 @@
}
.wc-block-components-totals-coupon__button {
height: em(48px);
flex-shrink: 0;
margin-left: $gap-smaller;
padding-left: $gap-large;
@ -39,4 +37,3 @@
flex-direction: column;
position: relative;
}

View File

@ -35,3 +35,12 @@ body.wc-lock-selected-block--remove {
box-sizing: border-box;
}
}
.wc-block-cart {
.wc-block-cart__totals-title {
textarea {
font-weight: 700; // Ensure correct font-weight in site-editor.
vertical-align: middle;
}
}
}

View File

@ -1,6 +1,7 @@
.wp-block-woocommerce-cart-order-summary-heading-block {
textarea {
text-align: right;
text-align: left;
padding-left: $gap;
text-transform: uppercase;
line-height: 1;
height: auto;

View File

@ -9,6 +9,7 @@ import { registerBlockType } from '@wordpress/blocks';
* Internal dependencies
*/
import { Edit, Save } from './edit';
import './style.scss';
registerBlockType( 'woocommerce/cart-order-summary-totals-block', {
icon: {

View File

@ -0,0 +1,8 @@
.wp-block-woocommerce-cart-order-summary-totals-block {
border-top: 1px solid $universal-border-light;
padding-bottom: $gap;
.wc-block-components-totals-wrapper {
padding-bottom: 0;
border: 0;
}
}

View File

@ -105,7 +105,8 @@
table.wc-block-cart-items {
margin: 0;
}
.wc-block-components-shipping-rates-control .wc-block-components-radio-control__option {
.wc-block-components-shipping-rates-control
.wc-block-components-radio-control__option {
padding: 0 0 0 em($gap-huge);
}
}
@ -132,16 +133,12 @@
.wc-block-cart__totals-title {
@include text-heading();
@include font-size( smaller );
@include font-size(smaller);
display: block;
font-weight: 700;
padding: $gap-smaller $gap $gap-smaller 0;
text-align: right;
text-align: left;
text-transform: uppercase;
textarea {
font-weight: 700; // Ensure correct font-weight in site-editor.
}
}
.wc-block-components-sidebar {
@ -227,7 +224,7 @@
}
// Skeleton is shown before mobile classes are appended.
@media only screen and ( max-width: 700px ) {
@media only screen and (max-width: 700px) {
.wp-block-woocommerce-cart.is-loading {
padding-top: $gap;

View File

@ -210,12 +210,6 @@ describe( 'Checkout Order Summary', () => {
it( 'Renders the standard preview items in the sidebar', async () => {
const { container } = render( <Block showRateAfterTaxName={ true } /> );
expect(
await findByText( container, 'Warm hat for winter' )
).toBeInTheDocument();
expect(
await findByText( container, 'Lightweight baseball cap' )
).toBeInTheDocument();
// Checking if variable product is rendered.
expect(

View File

@ -9,6 +9,7 @@ import { registerBlockType } from '@wordpress/blocks';
* Internal dependencies
*/
import { Edit, Save } from './edit';
import './style.scss';
registerBlockType( 'woocommerce/checkout-order-summary-totals-block', {
icon: {

View File

@ -0,0 +1,8 @@
.wp-block-woocommerce-checkout-order-summary-totals-block {
border-top: 1px solid $universal-border-light;
padding-bottom: $gap;
.wc-block-components-totals-wrapper {
padding-bottom: 0;
border: 0;
}
}

View File

@ -11,6 +11,17 @@
}
}
.is-large {
.wp-block-woocommerce-checkout-order-summary-block {
border: 1px solid $universal-border-light;
border-radius: 5px;
.wc-block-components-totals-wrapper:first-of-type {
border-top: 0;
}
}
}
.wp-block-woocommerce-checkout.is-loading {
display: flex;
flex-wrap: wrap;
@ -101,7 +112,8 @@
// For Twenty Twenty we need to increase specificity a bit more.
.theme-twentytwenty .wp-block-woocommerce-checkout.is-loading {
.wp-block-woocommerce-checkout-totals-block .wc-block-components-panel > h2 {
.wp-block-woocommerce-checkout-totals-block
.wc-block-components-panel > h2 {
@include font-size(large);
@include reset-box();
}

View File

@ -18,6 +18,7 @@ export interface PanelProps {
hasBorder?: boolean;
title: ReactNode;
titleTag?: keyof JSX.IntrinsicElements;
state?: [ boolean, React.Dispatch< React.SetStateAction< boolean > > ];
}
const Panel = ( {
@ -27,8 +28,13 @@ const Panel = ( {
hasBorder = false,
title,
titleTag: TitleTag = 'div',
state,
}: PanelProps ): ReactElement => {
const [ isOpen, setIsOpen ] = useState< boolean >( initialOpen );
let [ isOpen, setIsOpen ] = useState< boolean >( initialOpen );
// If state is managed externally, we override the internal state.
if ( Array.isArray( state ) && state.length === 2 ) {
[ isOpen, setIsOpen ] = state;
}
return (
<div

View File

@ -58,6 +58,16 @@ export default {
},
},
},
state: {
control: 'text',
description:
'A tuple of a state variable and a setter method for managing opening/closing state externally.',
table: {
type: {
summary: 'array',
},
},
},
},
} as Meta< PanelProps >;

View File

@ -38,6 +38,7 @@
@include reset-typography();
background: transparent;
box-shadow: none;
cursor: pointer;
}
> .wc-block-components-panel__button-icon {
@ -59,7 +60,8 @@
}
// Extra classes for specificity.
.theme-twentytwentyone.theme-twentytwentyone.theme-twentytwentyone .wc-block-components-panel__button {
.theme-twentytwentyone.theme-twentytwentyone.theme-twentytwentyone
.wc-block-components-panel__button {
background-color: inherit;
color: inherit;
}

View File

@ -21,10 +21,8 @@
padding: 0;
> * > * {
border-bottom: 1px solid $universal-border-light;
padding: $gap 0;
// Removes the border for the slot inserted
&::after {
border-width: 0;

View File

@ -132,7 +132,7 @@ test.describe( 'Cart performance', () => {
'button.wc-block-components-quantity-selector__button--plus'
)
.waitFor();
await page.click( '.wc-block-components-totals-coupon-link' );
await page.click( '.wc-block-components-totals-coupon button' );
const timesForResponse = [];
let i = 3;

View File

@ -45,7 +45,7 @@ test.describe( 'Shopper → Coupon', () => {
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
await frontendUtils.goToCart();
await page.getByLabel( 'Add a coupon' ).click();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' );
await page.getByRole( 'button', { name: 'Apply' } ).click();
@ -60,7 +60,7 @@ test.describe( 'Shopper → Coupon', () => {
).toBeHidden();
await frontendUtils.goToCheckout();
await page.getByLabel( 'Add a coupon' ).click();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' );
await page.getByRole( 'button', { name: 'Apply' } ).click();
@ -94,7 +94,7 @@ test.describe( 'Shopper → Coupon', () => {
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
await frontendUtils.goToCheckout();
await page.getByLabel( 'Add a coupon' ).click();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' );
await page.getByRole( 'button', { name: 'Apply' } ).click();
@ -110,7 +110,7 @@ test.describe( 'Shopper → Coupon', () => {
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
await frontendUtils.goToCheckout();
await page.getByLabel( 'Add a coupon' ).click();
await page.getByRole( 'button', { name: 'Add a coupon' } ).click();
await page.getByLabel( 'Enter code' ).fill( 'single-use-coupon' );
await page.getByRole( 'button', { name: 'Apply' } ).click();

View File

@ -65,7 +65,7 @@ test.describe( 'Shopper → Translations', () => {
await expect( page.getByText( 'Totalen winkelwagen' ) ).toBeVisible();
await expect(
page.getByLabel( 'Een waardebon toevoegen' )
page.getByRole( 'button', { name: 'Een waardebon toevoegen' } )
).toBeVisible();
await expect(

View File

@ -0,0 +1,4 @@
Significance: patch
Type: tweak
Update the order summary on Cart & Checkout with some minor visual changes

View File

@ -277,9 +277,6 @@ test.describe( 'Checkout Block page', () => {
'.wc-block-components-order-summary-item__individual-price'
)
).toContainText( `$${ singleProductSalePrice }` );
await expect(
page.locator( '.wc-block-components-product-metadata__description' )
).toContainText( simpleProductDesc );
await expect(
page.locator(
'.wc-block-components-totals-footer-item > .wc-block-components-totals-item__value'
@ -335,7 +332,7 @@ test.describe( 'Checkout Block page', () => {
page,
} ) => {
await page.goto( `/shop/?add-to-cart=${ productId }`, {
waitUntil: 'networkidle',
waitUntil: 'networkidle', // eslint-disable-line playwright/no-networkidle
} );
await page.goto( checkoutBlockPageSlug );
await expect(
@ -371,7 +368,7 @@ test.describe( 'Checkout Block page', () => {
// go again to the checkout to verify details
await page.goto( `/shop/?add-to-cart=${ productId }`, {
waitUntil: 'networkidle',
waitUntil: 'networkidle', // eslint-disable-line playwright/no-networkidle
} );
await page.goto( checkoutBlockPageSlug );
await expect(
@ -443,7 +440,7 @@ test.describe( 'Checkout Block page', () => {
page,
} ) => {
await page.goto( `/shop/?add-to-cart=${ productId }`, {
waitUntil: 'networkidle',
waitUntil: 'networkidle', // eslint-disable-line playwright/no-networkidle
} );
await page.goto( checkoutBlockPageSlug );
await expect(
@ -470,7 +467,7 @@ test.describe( 'Checkout Block page', () => {
page,
} ) => {
await page.goto( `/shop/?add-to-cart=${ productId }`, {
waitUntil: 'networkidle',
waitUntil: 'networkidle', // eslint-disable-line playwright/no-networkidle
} );
await page.goto( checkoutBlockPageSlug );
await expect(
@ -660,7 +657,7 @@ test.describe( 'Checkout Block page', () => {
// click to log in and make sure you are on the same page after logging in
await page.locator( 'text=Log in.' ).click();
await page.waitForLoadState( 'networkidle' );
await page.waitForLoadState( 'networkidle' ); // eslint-disable-line playwright/no-networkidle
await page
.locator( 'input[name="username"]' )
.fill( customer.username );
@ -668,7 +665,7 @@ test.describe( 'Checkout Block page', () => {
.locator( 'input[name="password"]' )
.fill( customer.password );
await page.locator( 'text=Log in' ).click();
await page.waitForLoadState( 'networkidle' );
await page.waitForLoadState( 'networkidle' ); // eslint-disable-line playwright/no-networkidle
await expect(
page.getByRole( 'heading', { name: checkoutBlockPageTitle } )
).toBeVisible();
@ -753,7 +750,7 @@ test.describe( 'Checkout Block page', () => {
test( 'can create an account during checkout', async ( { page } ) => {
await page.goto( `/shop/?add-to-cart=${ productId }`, {
waitUntil: 'networkidle',
waitUntil: 'networkidle', // eslint-disable-line playwright/no-networkidle
} );
await page.goto( checkoutBlockPageSlug );
await expect(
@ -801,7 +798,7 @@ test.describe( 'Checkout Block page', () => {
// sign in as admin to confirm account creation
await page.goto( 'wp-admin/users.php' );
await page.waitForLoadState( 'networkidle' );
await page.waitForLoadState( 'networkidle' ); // eslint-disable-line playwright/no-networkidle
await page.locator( 'input[name="log"]' ).fill( admin.username );
await page.locator( 'input[name="pwd"]' ).fill( admin.password );
await page.locator( 'text=Log in' ).click();