Checkout: Collapsible Order Summary in mobile view (#52253)
This commit is contained in:
parent
f5d5caa3f0
commit
601e14a253
|
@ -54,6 +54,7 @@
|
||||||
"@wordpress/data",
|
"@wordpress/data",
|
||||||
"@wordpress/data-controls",
|
"@wordpress/data-controls",
|
||||||
"@wordpress/date",
|
"@wordpress/date",
|
||||||
|
"@wordpress/editor",
|
||||||
"@wordpress/dependency-extraction-webpack-plugin",
|
"@wordpress/dependency-extraction-webpack-plugin",
|
||||||
"@wordpress/deprecated",
|
"@wordpress/deprecated",
|
||||||
"@wordpress/dom",
|
"@wordpress/dom",
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { __ } from '@wordpress/i18n';
|
|
||||||
import { useContainerWidthContext } from '@woocommerce/base-context';
|
import { useContainerWidthContext } from '@woocommerce/base-context';
|
||||||
import { Panel } from '@woocommerce/blocks-components';
|
|
||||||
import type { CartItem } from '@woocommerce/types';
|
import type { CartItem } from '@woocommerce/types';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -26,15 +25,10 @@ const OrderSummary = ( {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<div
|
||||||
className="wc-block-components-order-summary"
|
className={ clsx( 'wc-block-components-order-summary', {
|
||||||
initialOpen={ isLarge }
|
'is-large': isLarge,
|
||||||
hasBorder={ false }
|
} ) }
|
||||||
title={
|
|
||||||
<span className="wc-block-components-order-summary__button-text">
|
|
||||||
{ __( 'Order summary', 'woocommerce' ) }
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div className="wc-block-components-order-summary__content">
|
<div className="wc-block-components-order-summary__content">
|
||||||
{ cartItems.map( ( cartItem ) => {
|
{ cartItems.map( ( cartItem ) => {
|
||||||
|
@ -46,7 +40,7 @@ const OrderSummary = ( {
|
||||||
);
|
);
|
||||||
} ) }
|
} ) }
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
.wc-block-components-order-summary {
|
.wc-block-components-order-summary {
|
||||||
|
// Compensate for removing Panel.
|
||||||
|
padding: 0 $gap;
|
||||||
|
|
||||||
.wc-block-components-order-summary__button-text {
|
.wc-block-components-order-summary__button-text {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
gap: $gap-smaller;
|
gap: $gap-smaller;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.wc-block-components-text-input.wc-block-components-totals-coupon__input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.wc-block-components-totals-coupon__input,
|
.wc-block-components-totals-coupon__input,
|
||||||
.wc-block-components-totals-coupon__button {
|
.wc-block-components-totals-coupon__button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -20,8 +20,11 @@ import { previewCart } from '@woocommerce/resource-previews';
|
||||||
*/
|
*/
|
||||||
import { useStoreEvents } from '../use-store-events';
|
import { useStoreEvents } from '../use-store-events';
|
||||||
import type { ShippingData } from './types';
|
import type { ShippingData } from './types';
|
||||||
|
import { useEditorContext } from '../../providers';
|
||||||
|
|
||||||
export const useShippingData = (): ShippingData => {
|
export const useShippingData = (): ShippingData => {
|
||||||
|
const { isEditor } = useEditorContext();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
shippingRates,
|
shippingRates,
|
||||||
needsShipping,
|
needsShipping,
|
||||||
|
@ -29,32 +32,37 @@ export const useShippingData = (): ShippingData => {
|
||||||
isLoadingRates,
|
isLoadingRates,
|
||||||
isCollectable,
|
isCollectable,
|
||||||
isSelectingRate,
|
isSelectingRate,
|
||||||
} = useSelect( ( select ) => {
|
} = useSelect(
|
||||||
const isEditor = !! select( 'core/editor' );
|
( select ) => {
|
||||||
const store = select( storeKey );
|
const store = select( storeKey );
|
||||||
const rates = isEditor
|
const rates = isEditor
|
||||||
? previewCart.shipping_rates
|
? previewCart.shipping_rates
|
||||||
: store.getShippingRates();
|
: store.getShippingRates();
|
||||||
return {
|
return {
|
||||||
shippingRates: rates,
|
shippingRates: rates,
|
||||||
needsShipping: isEditor
|
needsShipping: isEditor
|
||||||
? previewCart.needs_shipping
|
? previewCart.needs_shipping
|
||||||
: store.getNeedsShipping(),
|
: store.getNeedsShipping(),
|
||||||
hasCalculatedShipping: isEditor
|
hasCalculatedShipping: isEditor
|
||||||
? previewCart.has_calculated_shipping
|
? previewCart.has_calculated_shipping
|
||||||
: store.getHasCalculatedShipping(),
|
: store.getHasCalculatedShipping(),
|
||||||
isLoadingRates: isEditor ? false : store.isCustomerDataUpdating(),
|
isLoadingRates: isEditor
|
||||||
isCollectable: rates.every(
|
? false
|
||||||
( { shipping_rates: packageShippingRates } ) =>
|
: store.isCustomerDataUpdating(),
|
||||||
packageShippingRates.find( ( { method_id: methodId } ) =>
|
isCollectable: rates.every(
|
||||||
hasCollectableRate( methodId )
|
( { shipping_rates: packageShippingRates } ) =>
|
||||||
)
|
packageShippingRates.find(
|
||||||
),
|
( { method_id: methodId } ) =>
|
||||||
isSelectingRate: isEditor
|
hasCollectableRate( methodId )
|
||||||
? false
|
)
|
||||||
: store.isShippingRateBeingSelected(),
|
),
|
||||||
};
|
isSelectingRate: isEditor
|
||||||
} );
|
? false
|
||||||
|
: store.isShippingRateBeingSelected(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ isEditor ]
|
||||||
|
);
|
||||||
|
|
||||||
// set selected rates on ref so it's always current.
|
// set selected rates on ref so it's always current.
|
||||||
const selectedRates = useRef< Record< string, string > >( {} );
|
const selectedRates = useRef< Record< string, string > >( {} );
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-components-address-form__address_2-toggle {
|
.wc-block-components-address-form__address_2-toggle {
|
||||||
|
display: inline-block;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
|
@ -7,6 +7,13 @@ import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||||
import { TotalsFooterItem } from '@woocommerce/base-components/cart-checkout';
|
import { TotalsFooterItem } from '@woocommerce/base-components/cart-checkout';
|
||||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { useId, useState } from '@wordpress/element';
|
||||||
|
import { Icon } from '@wordpress/components';
|
||||||
|
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
|
||||||
|
import { useContainerWidthContext } from '@woocommerce/base-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
|
@ -21,9 +28,29 @@ export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
|
||||||
const blockProps = useBlockProps();
|
const blockProps = useBlockProps();
|
||||||
const { cartTotals } = useStoreCart();
|
const { cartTotals } = useStoreCart();
|
||||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||||
|
const totalPrice = parseInt( cartTotals.total_price, 10 );
|
||||||
const allowedBlocks = getAllowedBlocks(
|
const allowedBlocks = getAllowedBlocks(
|
||||||
innerBlockAreas.CHECKOUT_ORDER_SUMMARY
|
innerBlockAreas.CHECKOUT_ORDER_SUMMARY
|
||||||
);
|
);
|
||||||
|
const { isLarge } = useContainerWidthContext();
|
||||||
|
const [ isOpen, setIsOpen ] = useState( false );
|
||||||
|
const ariaControlsId = useId();
|
||||||
|
|
||||||
|
const orderSummaryProps = ! isLarge
|
||||||
|
? {
|
||||||
|
role: 'button',
|
||||||
|
onClick: () => setIsOpen( ! isOpen ),
|
||||||
|
'aria-expanded': isOpen,
|
||||||
|
'aria-controls': ariaControlsId,
|
||||||
|
tabIndex: 0,
|
||||||
|
onKeyDown: ( event: React.KeyboardEvent ) => {
|
||||||
|
if ( event.key === 'Enter' || event.key === ' ' ) {
|
||||||
|
setIsOpen( ! isOpen );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
const defaultTemplate = [
|
const defaultTemplate = [
|
||||||
[ 'woocommerce/checkout-order-summary-cart-items-block', {}, [] ],
|
[ 'woocommerce/checkout-order-summary-cart-items-block', {}, [] ],
|
||||||
[ 'woocommerce/checkout-order-summary-coupon-form-block', {}, [] ],
|
[ 'woocommerce/checkout-order-summary-coupon-form-block', {}, [] ],
|
||||||
|
@ -38,17 +65,48 @@ export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...blockProps }>
|
<div { ...blockProps }>
|
||||||
<InnerBlocks
|
<div
|
||||||
allowedBlocks={ allowedBlocks }
|
className="wc-block-components-checkout-order-summary__title"
|
||||||
template={ defaultTemplate }
|
{ ...orderSummaryProps }
|
||||||
/>
|
>
|
||||||
<div className="wc-block-components-totals-wrapper">
|
<p
|
||||||
<TotalsFooterItem
|
className="wc-block-components-checkout-order-summary__title-text"
|
||||||
currency={ totalsCurrency }
|
role="heading"
|
||||||
values={ cartTotals }
|
>
|
||||||
/>
|
{ __( 'Order summary', 'woocommerce' ) }
|
||||||
|
</p>
|
||||||
|
{ ! isLarge && (
|
||||||
|
<>
|
||||||
|
<FormattedMonetaryAmount
|
||||||
|
currency={ totalsCurrency }
|
||||||
|
value={ totalPrice }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon icon={ isOpen ? chevronUp : chevronDown } />
|
||||||
|
</>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={ clsx(
|
||||||
|
'wc-block-components-checkout-order-summary__content',
|
||||||
|
{
|
||||||
|
'is-open': isOpen,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
id={ ariaControlsId }
|
||||||
|
>
|
||||||
|
<InnerBlocks
|
||||||
|
allowedBlocks={ allowedBlocks }
|
||||||
|
template={ defaultTemplate }
|
||||||
|
/>
|
||||||
|
<div className="wc-block-components-totals-wrapper">
|
||||||
|
<TotalsFooterItem
|
||||||
|
currency={ totalsCurrency }
|
||||||
|
values={ cartTotals }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<OrderMetaSlotFill />
|
||||||
</div>
|
</div>
|
||||||
<OrderMetaSlotFill />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,11 +4,17 @@
|
||||||
import { TotalsFooterItem } from '@woocommerce/base-components/cart-checkout';
|
import { TotalsFooterItem } from '@woocommerce/base-components/cart-checkout';
|
||||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
|
||||||
|
import { useId, useState } from '@wordpress/element';
|
||||||
|
import clsx from 'clsx';
|
||||||
/**
|
/**
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { OrderMetaSlotFill } from './slotfills';
|
import { OrderMetaSlotFill, CheckoutOrderSummaryFill } from './slotfills';
|
||||||
|
import { useContainerWidthContext } from '../../../../base/context';
|
||||||
|
import { FormattedMonetaryAmount } from '../../../../../../packages/components';
|
||||||
|
import { FormStepHeading } from '../../form-step';
|
||||||
|
|
||||||
const FrontendBlock = ( {
|
const FrontendBlock = ( {
|
||||||
children,
|
children,
|
||||||
|
@ -18,19 +24,104 @@ const FrontendBlock = ( {
|
||||||
className?: string;
|
className?: string;
|
||||||
} ): JSX.Element | null => {
|
} ): JSX.Element | null => {
|
||||||
const { cartTotals } = useStoreCart();
|
const { cartTotals } = useStoreCart();
|
||||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
const { isLarge } = useContainerWidthContext();
|
||||||
|
const [ isOpen, setIsOpen ] = useState( false );
|
||||||
|
|
||||||
|
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||||
|
const totalPrice = parseInt( cartTotals.total_price, 10 );
|
||||||
|
const ariaControlsId = useId();
|
||||||
|
|
||||||
|
const orderSummaryProps = ! isLarge
|
||||||
|
? {
|
||||||
|
role: 'button',
|
||||||
|
onClick: () => setIsOpen( ! isOpen ),
|
||||||
|
'aria-expanded': isOpen,
|
||||||
|
'aria-controls': ariaControlsId,
|
||||||
|
tabIndex: 0,
|
||||||
|
onKeyDown: ( event: React.KeyboardEvent ) => {
|
||||||
|
if ( event.key === 'Enter' || event.key === ' ' ) {
|
||||||
|
setIsOpen( ! isOpen );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
// Render the summary once here in the block and once in the fill. The fill can be slotted once elsewhere. The fill is only
|
||||||
|
// rendered on small and mobile screens.
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<>
|
||||||
{ children }
|
<div className={ className }>
|
||||||
<div className="wc-block-components-totals-wrapper">
|
<div
|
||||||
<TotalsFooterItem
|
className={ clsx(
|
||||||
currency={ totalsCurrency }
|
'wc-block-components-checkout-order-summary__title',
|
||||||
values={ cartTotals }
|
{
|
||||||
/>
|
'is-open': isOpen,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
{ ...orderSummaryProps }
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="wc-block-components-checkout-order-summary__title-text"
|
||||||
|
role="heading"
|
||||||
|
>
|
||||||
|
{ __( 'Order summary', 'woocommerce' ) }
|
||||||
|
</p>
|
||||||
|
{ ! isLarge && (
|
||||||
|
<>
|
||||||
|
<FormattedMonetaryAmount
|
||||||
|
currency={ totalsCurrency }
|
||||||
|
value={ totalPrice }
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
className="wc-block-components-checkout-order-summary__title-icon"
|
||||||
|
icon={ isOpen ? chevronUp : chevronDown }
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={ clsx(
|
||||||
|
'wc-block-components-checkout-order-summary__content',
|
||||||
|
{
|
||||||
|
'is-open': isOpen,
|
||||||
|
}
|
||||||
|
) }
|
||||||
|
id={ ariaControlsId }
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
<div className="wc-block-components-totals-wrapper">
|
||||||
|
<TotalsFooterItem
|
||||||
|
currency={ totalsCurrency }
|
||||||
|
values={ cartTotals }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<OrderMetaSlotFill />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<OrderMetaSlotFill />
|
|
||||||
</div>
|
{ ! isLarge && (
|
||||||
|
<CheckoutOrderSummaryFill>
|
||||||
|
<div
|
||||||
|
className={ `${ className } checkout-order-summary-block-fill-wrapper` }
|
||||||
|
>
|
||||||
|
<FormStepHeading>
|
||||||
|
<>{ __( 'Order summary', 'woocommerce' ) }</>
|
||||||
|
</FormStepHeading>
|
||||||
|
<div className="checkout-order-summary-block-fill">
|
||||||
|
{ children }
|
||||||
|
<div className="wc-block-components-totals-wrapper">
|
||||||
|
<TotalsFooterItem
|
||||||
|
currency={ totalsCurrency }
|
||||||
|
values={ cartTotals }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<OrderMetaSlotFill />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CheckoutOrderSummaryFill>
|
||||||
|
) }
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { registerBlockType } from '@wordpress/blocks';
|
||||||
import { Edit, Save } from './edit';
|
import { Edit, Save } from './edit';
|
||||||
import attributes from './attributes';
|
import attributes from './attributes';
|
||||||
import deprecated from './deprecated';
|
import deprecated from './deprecated';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
registerBlockType( 'woocommerce/checkout-order-summary-block', {
|
registerBlockType( 'woocommerce/checkout-order-summary-block', {
|
||||||
icon: {
|
icon: {
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* External dependencies
|
* External dependencies
|
||||||
*/
|
*/
|
||||||
import { ExperimentalOrderMeta } from '@woocommerce/blocks-checkout';
|
import {
|
||||||
|
ExperimentalOrderMeta,
|
||||||
|
createSlotFill,
|
||||||
|
} from '@woocommerce/blocks-checkout';
|
||||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||||
|
|
||||||
// @todo Consider deprecating OrderMetaSlotFill and DiscountSlotFill in favour of inner block areas.
|
// @todo Consider deprecating OrderMetaSlotFill and DiscountSlotFill in favour of inner block areas.
|
||||||
|
@ -17,3 +20,10 @@ export const OrderMetaSlotFill = (): JSX.Element => {
|
||||||
|
|
||||||
return <ExperimentalOrderMeta.Slot { ...slotFillProps } />;
|
return <ExperimentalOrderMeta.Slot { ...slotFillProps } />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkoutOrderSummarySlotName = 'checkoutOrderSummaryActionArea';
|
||||||
|
|
||||||
|
export const {
|
||||||
|
Fill: CheckoutOrderSummaryFill,
|
||||||
|
Slot: CheckoutOrderSummarySlot,
|
||||||
|
} = createSlotFill( checkoutOrderSummarySlotName );
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
.wp-block-woocommerce-checkout-order-summary-block {
|
||||||
|
border: 1px solid $universal-border-light;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.wc-block-components-formatted-money-amount {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-totals-wrapper:first-of-type {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title {
|
||||||
|
margin-top: $gap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title-text {
|
||||||
|
margin: 0 0 $gap $gap;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title-open-close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-order-summary-block-fill {
|
||||||
|
border: 1px solid $universal-border-light;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.wc-block-components-totals-wrapper:first-of-type {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-totals-item {
|
||||||
|
padding-left: $gap;
|
||||||
|
padding-right: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-totals-coupon {
|
||||||
|
padding-left: $gap;
|
||||||
|
padding-right: $gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-dark-controls {
|
||||||
|
.wp-block-woocommerce-checkout-order-summary-block {
|
||||||
|
border: 1px solid $input-border-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-small,
|
||||||
|
.is-medium,
|
||||||
|
.is-mobile {
|
||||||
|
.wp-block-woocommerce-checkout-order-summary-block {
|
||||||
|
margin-top: 0;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&.checkout-order-summary-block-fill-wrapper {
|
||||||
|
padding-top: $gap-larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title {
|
||||||
|
padding: 20px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border-top: 1px solid $universal-border-light;
|
||||||
|
border-bottom: 1px solid $universal-border-light;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title-text {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__title-icon {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wc-block-components-checkout-order-summary__content {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
.wc-block-components-totals-wrapper:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { termsConsentDefaultText, termsCheckboxDefaultText } from './constants';
|
import { termsConsentDefaultText, termsCheckboxDefaultText } from './constants';
|
||||||
|
import { CheckoutOrderSummarySlot } from '../checkout-order-summary-block/slotfills';
|
||||||
|
|
||||||
const FrontendBlock = ( {
|
const FrontendBlock = ( {
|
||||||
text,
|
text,
|
||||||
|
@ -73,41 +74,47 @@ const FrontendBlock = ( {
|
||||||
] );
|
] );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={ clsx(
|
<CheckoutOrderSummarySlot />
|
||||||
'wc-block-checkout__terms',
|
<div
|
||||||
{
|
className={ clsx(
|
||||||
'wc-block-checkout__terms--disabled': isDisabled,
|
'wc-block-checkout__terms',
|
||||||
'wc-block-checkout__terms--with-separator':
|
{
|
||||||
showSeparator !== 'false' && showSeparator !== false,
|
'wc-block-checkout__terms--disabled': isDisabled,
|
||||||
},
|
'wc-block-checkout__terms--with-separator':
|
||||||
className
|
showSeparator !== 'false' &&
|
||||||
) }
|
showSeparator !== false,
|
||||||
>
|
},
|
||||||
{ checkbox ? (
|
className
|
||||||
<>
|
) }
|
||||||
<CheckboxControl
|
>
|
||||||
id="terms-and-conditions"
|
{ checkbox ? (
|
||||||
checked={ checked }
|
<>
|
||||||
onChange={ () => setChecked( ( value ) => ! value ) }
|
<CheckboxControl
|
||||||
hasError={ hasError }
|
id="terms-and-conditions"
|
||||||
disabled={ isDisabled }
|
checked={ checked }
|
||||||
>
|
onChange={ () =>
|
||||||
<span
|
setChecked( ( value ) => ! value )
|
||||||
dangerouslySetInnerHTML={ {
|
}
|
||||||
__html: text || termsCheckboxDefaultText,
|
hasError={ hasError }
|
||||||
} }
|
disabled={ isDisabled }
|
||||||
/>
|
>
|
||||||
</CheckboxControl>
|
<span
|
||||||
</>
|
dangerouslySetInnerHTML={ {
|
||||||
) : (
|
__html: text || termsCheckboxDefaultText,
|
||||||
<span
|
} }
|
||||||
dangerouslySetInnerHTML={ {
|
/>
|
||||||
__html: text || termsConsentDefaultText,
|
</CheckboxControl>
|
||||||
} }
|
</>
|
||||||
/>
|
) : (
|
||||||
) }
|
<span
|
||||||
</div>
|
dangerouslySetInnerHTML={ {
|
||||||
|
__html: text || termsConsentDefaultText,
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
padding-top: $gap-largest;
|
padding-top: $gap-largest;
|
||||||
border-top: 1px solid $universal-border-light;
|
border-top: 1px solid $universal-border-light;
|
||||||
|
|
||||||
|
|
||||||
.is-mobile &,
|
.is-mobile &,
|
||||||
|
.is-medium &,
|
||||||
.is-small & {
|
.is-small & {
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import clsx from 'clsx';
|
||||||
import { Sidebar } from '@woocommerce/base-components/sidebar-layout';
|
import { Sidebar } from '@woocommerce/base-components/sidebar-layout';
|
||||||
import { StoreNoticesContainer } from '@woocommerce/blocks-components';
|
import { StoreNoticesContainer } from '@woocommerce/blocks-components';
|
||||||
import { useObservedViewport } from '@woocommerce/base-hooks';
|
import { useObservedViewport } from '@woocommerce/base-hooks';
|
||||||
|
import { useContainerWidthContext } from '@woocommerce/base-context';
|
||||||
|
|
||||||
const FrontendBlock = ( {
|
const FrontendBlock = ( {
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
@ -15,11 +17,14 @@ const FrontendBlock = ( {
|
||||||
const [ observedRef, observedElement, viewWindow ] =
|
const [ observedRef, observedElement, viewWindow ] =
|
||||||
useObservedViewport< HTMLDivElement >();
|
useObservedViewport< HTMLDivElement >();
|
||||||
const isSticky = observedElement.height < viewWindow.height;
|
const isSticky = observedElement.height < viewWindow.height;
|
||||||
|
const { isLarge } = useContainerWidthContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
ref={ observedRef }
|
ref={ observedRef }
|
||||||
className={ clsx( 'wc-block-checkout__sidebar', className, {
|
className={ clsx( 'wc-block-checkout__sidebar', className, {
|
||||||
'is-sticky': isSticky,
|
'is-sticky': isSticky,
|
||||||
|
'is-large': isLarge,
|
||||||
} ) }
|
} ) }
|
||||||
>
|
>
|
||||||
<StoreNoticesContainer
|
<StoreNoticesContainer
|
||||||
|
|
|
@ -27,23 +27,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-dark-controls {
|
|
||||||
.wp-block-woocommerce-checkout-order-summary-block {
|
|
||||||
border-color: $input-border-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wp-block-woocommerce-checkout.is-loading {
|
.wp-block-woocommerce-checkout.is-loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -170,6 +170,7 @@
|
||||||
"@wordpress/data": "6.15.0",
|
"@wordpress/data": "6.15.0",
|
||||||
"@wordpress/data-controls": "2.2.7",
|
"@wordpress/data-controls": "2.2.7",
|
||||||
"@wordpress/date": "4.44.0",
|
"@wordpress/date": "4.44.0",
|
||||||
|
"@wordpress/editor": "wp-6.7",
|
||||||
"@wordpress/dependency-extraction-webpack-plugin": "4.28.0",
|
"@wordpress/dependency-extraction-webpack-plugin": "4.28.0",
|
||||||
"@wordpress/dom": "3.27.0",
|
"@wordpress/dom": "3.27.0",
|
||||||
"@wordpress/dom-ready": "3.27.0",
|
"@wordpress/dom-ready": "3.27.0",
|
||||||
|
|
|
@ -23,11 +23,26 @@ import BlockErrorBoundary from '../components/error-boundary';
|
||||||
* @param {Array} fills The list of fills to check for a valid one in.
|
* @param {Array} fills The list of fills to check for a valid one in.
|
||||||
* @return {boolean} True if this slot contains any valid fills.
|
* @return {boolean} True if this slot contains any valid fills.
|
||||||
*/
|
*/
|
||||||
export const hasValidFills = ( fills ) =>
|
export const hasValidFills = ( fills: [] ) =>
|
||||||
Array.isArray( fills ) && fills.filter( Boolean ).length > 0;
|
Array.isArray( fills ) && fills.filter( Boolean ).length > 0;
|
||||||
|
|
||||||
export { useSlot, useSlotFills };
|
export { useSlot, useSlotFills };
|
||||||
|
|
||||||
|
type SlotFill = {
|
||||||
|
Fill: (
|
||||||
|
props: Partial< {
|
||||||
|
bubblesVirtually: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
} >
|
||||||
|
) => JSX.Element;
|
||||||
|
Slot: (
|
||||||
|
props: Partial< {
|
||||||
|
bubblesVirtually: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
} >
|
||||||
|
) => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstracts @wordpress/components createSlotFill, wraps Fill in an error boundary and passes down fillProps.
|
* Abstracts @wordpress/components createSlotFill, wraps Fill in an error boundary and passes down fillProps.
|
||||||
*
|
*
|
||||||
|
@ -36,8 +51,10 @@ export { useSlot, useSlotFills };
|
||||||
*
|
*
|
||||||
* @return {Object} Returns a newly wrapped Fill and Slot.
|
* @return {Object} Returns a newly wrapped Fill and Slot.
|
||||||
*/
|
*/
|
||||||
export const createSlotFill = ( slotName, onError = null ) => {
|
export const createSlotFill = ( slotName: string, onError = null ) => {
|
||||||
const { Fill: BaseFill, Slot: BaseSlot } = baseCreateSlotFill( slotName );
|
const { Fill: BaseFill, Slot: BaseSlot } = baseCreateSlotFill(
|
||||||
|
slotName
|
||||||
|
) as SlotFill;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Fill that will get rendered inside associate slot.
|
* A Fill that will get rendered inside associate slot.
|
||||||
|
@ -47,9 +64,9 @@ export const createSlotFill = ( slotName, onError = null ) => {
|
||||||
* @param {Object} props Items props.
|
* @param {Object} props Items props.
|
||||||
* @param {Array} props.children Children to be rendered.
|
* @param {Array} props.children Children to be rendered.
|
||||||
*/
|
*/
|
||||||
const Fill = ( { children } ) => (
|
const Fill = ( { children }: { children: React.ReactNode } ) => (
|
||||||
<BaseFill>
|
<BaseFill>
|
||||||
{ ( fillProps ) =>
|
{ ( fillProps: unknown ) =>
|
||||||
Children.map( children, ( fill ) => (
|
Children.map( children, ( fill ) => (
|
||||||
<BlockErrorBoundary
|
<BlockErrorBoundary
|
||||||
/* Returning null would trigger the default error display.
|
/* Returning null would trigger the default error display.
|
||||||
|
@ -59,6 +76,7 @@ export const createSlotFill = ( slotName, onError = null ) => {
|
||||||
CURRENT_USER_IS_ADMIN ? onError : () => null
|
CURRENT_USER_IS_ADMIN ? onError : () => null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{ /* @ts-expect-error It's not clear how to accurately type `fill`. */ }
|
||||||
{ cloneElement( fill, fillProps ) }
|
{ cloneElement( fill, fillProps ) }
|
||||||
</BlockErrorBoundary>
|
</BlockErrorBoundary>
|
||||||
) )
|
) )
|
||||||
|
@ -76,7 +94,9 @@ export const createSlotFill = ( slotName, onError = null ) => {
|
||||||
* @param {Element|string} props.as Element used to render the slot, defaults to div.
|
* @param {Element|string} props.as Element used to render the slot, defaults to div.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const Slot = ( props ) => <BaseSlot { ...props } bubblesVirtually />;
|
const Slot = ( props: object ) => (
|
||||||
|
<BaseSlot { ...props } bubblesVirtually />
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Fill,
|
Fill,
|
|
@ -99,7 +99,7 @@ test.describe( 'Shopper → Translations', () => {
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole( 'button', {
|
page.getByRole( 'heading', {
|
||||||
name: 'Besteloverzicht',
|
name: 'Besteloverzicht',
|
||||||
} )
|
} )
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
const { webcrypto } = require( 'node:crypto' );
|
||||||
|
|
||||||
|
global.crypto = webcrypto;
|
||||||
|
|
||||||
|
global.TextEncoder = require( 'util' ).TextEncoder;
|
||||||
|
global.TextDecoder = require( 'util' ).TextDecoder;
|
||||||
|
|
||||||
// Set up `wp.*` aliases. Doing this because any tests importing wp stuff will likely run into this.
|
// Set up `wp.*` aliases. Doing this because any tests importing wp stuff will likely run into this.
|
||||||
global.wp = {};
|
global.wp = {};
|
||||||
require( '@wordpress/data' );
|
require( '@wordpress/data' );
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: enhancement
|
||||||
|
|
||||||
|
Checkout: Add a collapsible order summary for smaller screens.
|
|
@ -786,10 +786,8 @@
|
||||||
"node_modules/@woocommerce/e2e-core-tests/CHANGELOG.md",
|
"node_modules/@woocommerce/e2e-core-tests/CHANGELOG.md",
|
||||||
"node_modules/@woocommerce/api/dist/",
|
"node_modules/@woocommerce/api/dist/",
|
||||||
"node_modules/@woocommerce/admin-e2e-tests/build",
|
"node_modules/@woocommerce/admin-e2e-tests/build",
|
||||||
"node_modules/@woocommerce/classic-assets/build",
|
|
||||||
"node_modules/@woocommerce/block-library/build",
|
"node_modules/@woocommerce/block-library/build",
|
||||||
"node_modules/@woocommerce/block-library/blocks.ini",
|
"node_modules/@woocommerce/block-library/blocks.ini",
|
||||||
"node_modules/@woocommerce/admin-library/build",
|
|
||||||
"package.json",
|
"package.json",
|
||||||
"!node_modules/@woocommerce/admin-e2e-tests/*.ts.map",
|
"!node_modules/@woocommerce/admin-e2e-tests/*.ts.map",
|
||||||
"!node_modules/@woocommerce/admin-e2e-tests/*.tsbuildinfo",
|
"!node_modules/@woocommerce/admin-e2e-tests/*.tsbuildinfo",
|
||||||
|
|
2270
pnpm-lock.yaml
2270
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue