Ensure validation of fields occurs when collapsing fields (https://github.com/woocommerce/woocommerce-blocks/pull/11199)

* Ensure validation of fields occurs when collapsing fields

* update click for edit button

* turn off pointer events when hidden

* Add visibility rule
This commit is contained in:
Mike Jolley 2023-10-10 22:07:58 +01:00 committed by GitHub
parent 41724e9400
commit 75bac91787
7 changed files with 121 additions and 78 deletions

View File

@ -1,12 +1,17 @@
/** /**
* External dependencies * External dependencies
*/ */
import { CSSTransition, TransitionGroup } from 'react-transition-group'; import classnames from 'classnames';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import './style.scss'; import './style.scss';
/**
* Wrapper for address fields which handles the edit/preview transition. Form fields are always rendered so that
* validation can occur.
*/
export const AddressWrapper = ( { export const AddressWrapper = ( {
isEditing = false, isEditing = false,
addressCard, addressCard,
@ -16,25 +21,22 @@ export const AddressWrapper = ( {
addressCard: () => JSX.Element; addressCard: () => JSX.Element;
addressForm: () => JSX.Element; addressForm: () => JSX.Element;
} ): JSX.Element | null => { } ): JSX.Element | null => {
const wrapperClasses = classnames(
'wc-block-components-address-address-wrapper',
{
'is-editing': isEditing,
}
);
return ( return (
<TransitionGroup className="address-fade-transition-wrapper"> <div className={ wrapperClasses }>
{ ! isEditing && ( <div className="wc-block-components-address-card-wrapper">
<CSSTransition
timeout={ 300 }
classNames="address-fade-transition"
>
{ addressCard() } { addressCard() }
</CSSTransition> </div>
) } <div className="wc-block-components-address-form-wrapper">
{ isEditing && (
<CSSTransition
timeout={ 300 }
classNames="address-fade-transition"
>
{ addressForm() } { addressForm() }
</CSSTransition> </div>
) } </div>
</TransitionGroup>
); );
}; };

View File

@ -1,25 +1,32 @@
.address-fade-transition-wrapper { .wc-block-components-address-address-wrapper {
position: relative; position: relative;
}
.address-fade-transition-enter { .wc-block-components-address-card-wrapper,
.wc-block-components-address-form-wrapper {
transition: all 300ms ease-in-out;
width: 100%;
}
&.is-editing {
.wc-block-components-address-form-wrapper {
opacity: 1;
}
.wc-block-components-address-card-wrapper {
opacity: 0; opacity: 0;
} visibility: hidden;
.address-fade-transition-enter-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
.address-fade-transition-enter-done {
opacity: 1;
}
.address-fade-transition-exit {
opacity: 1;
position: absolute; position: absolute;
top: 0; top: 0;
} }
.address-fade-transition-exit-active { }
opacity: 0;
transition: opacity 300ms ease-out; &:not(.is-editing) {
} .wc-block-components-address-form-wrapper {
.address-fade-transition-done {
opacity: 0; opacity: 0;
visibility: hidden;
height: 0;
}
.wc-block-components-address-card-wrapper {
opacity: 1;
}
}
} }

View File

@ -80,10 +80,6 @@ const Block = ( {
const noticeContext = useBillingAsShipping const noticeContext = useBillingAsShipping
? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ] ? [ noticeContexts.BILLING_ADDRESS, noticeContexts.SHIPPING_ADDRESS ]
: [ noticeContexts.BILLING_ADDRESS ]; : [ noticeContexts.BILLING_ADDRESS ];
const hasAddress = !! (
billingAddress.address_1 &&
( billingAddress.first_name || billingAddress.last_name )
);
return ( return (
<> <>
@ -93,7 +89,6 @@ const Block = ( {
addressFieldsConfig={ addressFieldsConfig } addressFieldsConfig={ addressFieldsConfig }
showPhoneField={ showPhoneField } showPhoneField={ showPhoneField }
requirePhoneField={ requirePhoneField } requirePhoneField={ requirePhoneField }
hasAddress={ hasAddress }
forceEditing={ forceEditing } forceEditing={ forceEditing }
/> />
</WrapperComponent> </WrapperComponent>

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState, useCallback } from '@wordpress/element'; import { useState, useCallback, useEffect } from '@wordpress/element';
import { AddressForm } from '@woocommerce/base-components/cart-checkout'; import { AddressForm } from '@woocommerce/base-components/cart-checkout';
import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context';
import type { import type {
@ -9,6 +9,8 @@ import type {
AddressField, AddressField,
AddressFields, AddressFields,
} from '@woocommerce/settings'; } from '@woocommerce/settings';
import { useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
/** /**
* Internal dependencies * Internal dependencies
@ -21,13 +23,11 @@ const CustomerAddress = ( {
addressFieldsConfig, addressFieldsConfig,
showPhoneField, showPhoneField,
requirePhoneField, requirePhoneField,
hasAddress,
forceEditing = false, forceEditing = false,
}: { }: {
addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >; addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >;
showPhoneField: boolean; showPhoneField: boolean;
requirePhoneField: boolean; requirePhoneField: boolean;
hasAddress: boolean;
forceEditing?: boolean; forceEditing?: boolean;
} ) => { } ) => {
const { const {
@ -40,8 +40,34 @@ const CustomerAddress = ( {
useBillingAsShipping, useBillingAsShipping,
} = useCheckoutAddress(); } = useCheckoutAddress();
const { dispatchCheckoutEvent } = useStoreEvents(); const { dispatchCheckoutEvent } = useStoreEvents();
const hasAddress = !! (
billingAddress.address_1 &&
( billingAddress.first_name || billingAddress.last_name )
);
const [ editing, setEditing ] = useState( ! hasAddress || forceEditing ); const [ editing, setEditing ] = useState( ! hasAddress || forceEditing );
// Forces editing state if store has errors.
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
const store = select( VALIDATION_STORE_KEY );
return {
hasValidationErrors: store.hasValidationErrors(),
invalidProps: Object.keys( billingAddress )
.filter( ( key ) => {
return (
store.getValidationError( 'billing_' + key ) !==
undefined
);
} )
.filter( Boolean ),
};
} );
useEffect( () => {
if ( invalidProps.length > 0 && editing === false ) {
setEditing( true );
}
}, [ editing, hasValidationErrors, invalidProps.length ] );
const addressFieldKeys = Object.keys( const addressFieldKeys = Object.keys(
defaultAddressFields defaultAddressFields
) as ( keyof AddressFields )[]; ) as ( keyof AddressFields )[];

View File

@ -104,7 +104,6 @@ const Block = ( {
addressFieldsConfig={ addressFieldsConfig } addressFieldsConfig={ addressFieldsConfig }
showPhoneField={ showPhoneField } showPhoneField={ showPhoneField }
requirePhoneField={ requirePhoneField } requirePhoneField={ requirePhoneField }
hasAddress={ hasAddress }
/> />
</WrapperComponent> </WrapperComponent>
{ hasAddress && ( { hasAddress && (

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState, useCallback } from '@wordpress/element'; import { useState, useCallback, useEffect } from '@wordpress/element';
import { AddressForm } from '@woocommerce/base-components/cart-checkout'; import { AddressForm } from '@woocommerce/base-components/cart-checkout';
import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context';
import type { import type {
@ -9,6 +9,8 @@ import type {
AddressField, AddressField,
AddressFields, AddressFields,
} from '@woocommerce/settings'; } from '@woocommerce/settings';
import { useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
/** /**
* Internal dependencies * Internal dependencies
@ -21,12 +23,10 @@ const CustomerAddress = ( {
addressFieldsConfig, addressFieldsConfig,
showPhoneField, showPhoneField,
requirePhoneField, requirePhoneField,
hasAddress,
}: { }: {
addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >; addressFieldsConfig: Record< keyof AddressFields, Partial< AddressField > >;
showPhoneField: boolean; showPhoneField: boolean;
requirePhoneField: boolean; requirePhoneField: boolean;
hasAddress: boolean;
} ) => { } ) => {
const { const {
defaultAddressFields, defaultAddressFields,
@ -37,8 +37,34 @@ const CustomerAddress = ( {
useShippingAsBilling, useShippingAsBilling,
} = useCheckoutAddress(); } = useCheckoutAddress();
const { dispatchCheckoutEvent } = useStoreEvents(); const { dispatchCheckoutEvent } = useStoreEvents();
const hasAddress = !! (
shippingAddress.address_1 &&
( shippingAddress.first_name || shippingAddress.last_name )
);
const [ editing, setEditing ] = useState( ! hasAddress ); const [ editing, setEditing ] = useState( ! hasAddress );
// Forces editing state if store has errors.
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
const store = select( VALIDATION_STORE_KEY );
return {
hasValidationErrors: store.hasValidationErrors(),
invalidProps: Object.keys( shippingAddress )
.filter( ( key ) => {
return (
store.getValidationError( 'shipping_' + key ) !==
undefined
);
} )
.filter( Boolean ),
};
} );
useEffect( () => {
if ( invalidProps.length > 0 && editing === false ) {
setEditing( true );
}
}, [ editing, hasValidationErrors, invalidProps.length ] );
const addressFieldKeys = Object.keys( const addressFieldKeys = Object.keys(
defaultAddressFields defaultAddressFields
) as ( keyof AddressFields )[]; ) as ( keyof AddressFields )[];

View File

@ -126,34 +126,22 @@ export class CheckoutPage {
} }
async editBillingDetails() { async editBillingDetails() {
const editButton = await this.page const editButton = this.page.locator(
.locator(
'.wc-block-checkout__billing-fields .wc-block-components-address-card__edit' '.wc-block-checkout__billing-fields .wc-block-components-address-card__edit'
) );
.isVisible();
if ( editButton ) { if ( await editButton.isVisible() ) {
await this.page await editButton.click();
.locator(
'.wc-block-checkout__billing-fields .wc-block-components-address-card__edit'
)
.click();
} }
} }
async editShippingDetails() { async editShippingDetails() {
const editButton = await this.page const editButton = this.page.locator(
.locator(
'.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit' '.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit'
) );
.isVisible();
if ( editButton ) { if ( await editButton.isVisible() ) {
await this.page await editButton.click();
.locator(
'.wc-block-checkout__shipping-fields .wc-block-components-address-card__edit'
)
.click();
} }
} }