Add separator to "Place order" button and use the block style variations API (#52017)
This commit is contained in:
parent
601e14a253
commit
a24502fe23
|
@ -2,19 +2,31 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import clsx from 'clsx';
|
||||
import { useCheckoutSubmit } from '@woocommerce/base-context/hooks';
|
||||
import {
|
||||
useCheckoutSubmit,
|
||||
useStoreCart,
|
||||
} from '@woocommerce/base-context/hooks';
|
||||
import { Icon, check } from '@wordpress/icons';
|
||||
import Button from '@woocommerce/base-components/button';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import {
|
||||
FormattedMonetaryAmount,
|
||||
Spinner,
|
||||
} from '@woocommerce/blocks-components';
|
||||
|
||||
interface PlaceOrderButton {
|
||||
interface PlaceOrderButtonProps {
|
||||
label: string;
|
||||
fullWidth?: boolean | undefined;
|
||||
fullWidth?: boolean;
|
||||
showPrice?: boolean;
|
||||
priceSeparator?: string;
|
||||
}
|
||||
|
||||
const PlaceOrderButton = ( {
|
||||
label,
|
||||
fullWidth = false,
|
||||
}: PlaceOrderButton ): JSX.Element => {
|
||||
showPrice = false,
|
||||
priceSeparator = '·',
|
||||
}: PlaceOrderButtonProps ): JSX.Element => {
|
||||
const {
|
||||
onSubmit,
|
||||
isCalculating,
|
||||
|
@ -23,6 +35,46 @@ const PlaceOrderButton = ( {
|
|||
waitingForRedirect,
|
||||
} = useCheckoutSubmit();
|
||||
|
||||
const { cartTotals } = useStoreCart();
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
|
||||
const buttonLabel = (
|
||||
<div
|
||||
// Hide this from screen readers while the checkout is processing. The text will not be removed from the
|
||||
// DOM, it will just be hidden with CSS to maintain the button's size while the spinner appears.
|
||||
aria-hidden={ waitingForProcessing || waitingForRedirect }
|
||||
className={ clsx(
|
||||
'wc-block-components-checkout-place-order-button__text',
|
||||
{
|
||||
'wc-block-components-checkout-place-order-button__text--visually-hidden':
|
||||
waitingForProcessing || waitingForRedirect,
|
||||
}
|
||||
) }
|
||||
>
|
||||
{ label }
|
||||
{ showPrice && (
|
||||
<>
|
||||
<style>
|
||||
{ `.wp-block-woocommerce-checkout-actions-block {
|
||||
.wc-block-components-checkout-place-order-button__separator {
|
||||
&::after {
|
||||
content: "${ priceSeparator }";
|
||||
}
|
||||
}
|
||||
}` }
|
||||
</style>
|
||||
<div className="wc-block-components-checkout-place-order-button__separator" />
|
||||
<div className="wc-block-components-checkout-place-order-button__price">
|
||||
<FormattedMonetaryAmount
|
||||
value={ cartTotals.total_price }
|
||||
currency={ totalsCurrency }
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={ clsx(
|
||||
|
@ -30,7 +82,8 @@ const PlaceOrderButton = ( {
|
|||
{
|
||||
'wc-block-components-checkout-place-order-button--full-width':
|
||||
fullWidth,
|
||||
}
|
||||
},
|
||||
{ 'wc-blocks-components-button--loading': waitingForProcessing }
|
||||
) }
|
||||
onClick={ onSubmit }
|
||||
disabled={
|
||||
|
@ -41,7 +94,9 @@ const PlaceOrderButton = ( {
|
|||
}
|
||||
showSpinner={ waitingForProcessing }
|
||||
>
|
||||
{ waitingForRedirect ? <Icon icon={ check } /> : label }
|
||||
{ waitingForProcessing && <Spinner /> }
|
||||
{ waitingForRedirect && <Icon icon={ check } /> }
|
||||
{ buttonLabel }
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,25 +7,6 @@ import {
|
|||
} from './constants';
|
||||
|
||||
export default {
|
||||
cartPageId: {
|
||||
type: 'number',
|
||||
default: 0,
|
||||
},
|
||||
showReturnToCart: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
className: {
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
lock: {
|
||||
type: 'object',
|
||||
default: {
|
||||
move: true,
|
||||
remove: true,
|
||||
},
|
||||
},
|
||||
placeOrderButtonLabel: {
|
||||
type: 'string',
|
||||
default: defaultPlaceOrderButtonLabel,
|
||||
|
|
|
@ -19,11 +19,25 @@
|
|||
"remove": true,
|
||||
"move": true
|
||||
}
|
||||
},
|
||||
"cartPageId": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"showReturnToCart": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"className": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"priceSeparator": {
|
||||
"type": "string",
|
||||
"default": "·"
|
||||
}
|
||||
},
|
||||
"parent": [
|
||||
"woocommerce/checkout-fields-block"
|
||||
],
|
||||
"parent": [ "woocommerce/checkout-fields-block" ],
|
||||
"textdomain": "woocommerce",
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3
|
||||
|
|
|
@ -18,18 +18,27 @@ import { applyCheckoutFilter } from '@woocommerce/blocks-checkout';
|
|||
import { defaultPlaceOrderButtonLabel } from './constants';
|
||||
import './style.scss';
|
||||
|
||||
export type BlockAttributes = {
|
||||
cartPageId: number;
|
||||
showReturnToCart: boolean;
|
||||
className?: string;
|
||||
placeOrderButtonLabel: string;
|
||||
priceSeparator: string;
|
||||
returnToCartButtonLabel: string;
|
||||
};
|
||||
|
||||
const Block = ( {
|
||||
cartPageId,
|
||||
showReturnToCart,
|
||||
className,
|
||||
placeOrderButtonLabel,
|
||||
returnToCartButtonLabel,
|
||||
priceSeparator,
|
||||
}: {
|
||||
cartPageId: number;
|
||||
showReturnToCart: boolean;
|
||||
className?: string;
|
||||
placeOrderButtonLabel: string;
|
||||
returnToCartButtonLabel: string;
|
||||
} ): JSX.Element => {
|
||||
const { paymentMethodButtonLabel } = useCheckoutSubmit();
|
||||
|
||||
|
@ -41,6 +50,8 @@ const Block = ( {
|
|||
defaultPlaceOrderButtonLabel,
|
||||
} );
|
||||
|
||||
const showPrice = className?.includes( 'is-style-with-price' ) || false;
|
||||
|
||||
return (
|
||||
<div className={ clsx( 'wc-block-checkout__actions', className ) }>
|
||||
<StoreNoticesContainer
|
||||
|
@ -54,9 +65,22 @@ const Block = ( {
|
|||
{ returnToCartButtonLabel }
|
||||
</ReturnToCartButton>
|
||||
) }
|
||||
{ showPrice && (
|
||||
<style>
|
||||
{ `.wp-block-woocommerce-checkout-actions-block {
|
||||
.wc-block-components-checkout-place-order-button__separator {
|
||||
&::after {
|
||||
content: "${ priceSeparator }";
|
||||
}
|
||||
}
|
||||
}` }
|
||||
</style>
|
||||
) }
|
||||
<PlaceOrderButton
|
||||
label={ label }
|
||||
fullWidth={ ! showReturnToCart }
|
||||
showPrice={ showPrice }
|
||||
priceSeparator={ priceSeparator }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,14 +11,19 @@ import {
|
|||
useBlockProps,
|
||||
} from '@wordpress/block-editor';
|
||||
import PageSelector from '@woocommerce/editor-components/page-selector';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { PanelBody, ToggleControl, TextControl } from '@wordpress/components';
|
||||
import { CHECKOUT_PAGE_ID } from '@woocommerce/block-settings';
|
||||
import { ReturnToCartButton } from '@woocommerce/base-components/cart-checkout';
|
||||
import EditableButton from '@woocommerce/editor-components/editable-button';
|
||||
import { useStoreCart } from '@woocommerce/base-context';
|
||||
import { getCurrencyFromPriceResponse } from '@woocommerce/price-format';
|
||||
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { BlockAttributes } from './block';
|
||||
import './editor.scss';
|
||||
import {
|
||||
defaultPlaceOrderButtonLabel,
|
||||
defaultReturnToCartButtonLabel,
|
||||
|
@ -28,12 +33,7 @@ export const Edit = ( {
|
|||
attributes,
|
||||
setAttributes,
|
||||
}: {
|
||||
attributes: {
|
||||
showReturnToCart: boolean;
|
||||
cartPageId: number;
|
||||
placeOrderButtonLabel: string;
|
||||
returnToCartButtonLabel: string;
|
||||
};
|
||||
attributes: BlockAttributes;
|
||||
setAttributes: ( attributes: Record< string, unknown > ) => void;
|
||||
} ): JSX.Element => {
|
||||
const blockProps = useBlockProps();
|
||||
|
@ -43,6 +43,8 @@ export const Edit = ( {
|
|||
placeOrderButtonLabel,
|
||||
returnToCartButtonLabel,
|
||||
} = attributes;
|
||||
const { cartTotals } = useStoreCart();
|
||||
const totalsCurrency = getCurrencyFromPriceResponse( cartTotals );
|
||||
const { current: savedCartPageId } = useRef( cartPageId );
|
||||
const currentPostId = useSelect(
|
||||
( select ) => {
|
||||
|
@ -55,10 +57,12 @@ export const Edit = ( {
|
|||
[ savedCartPageId ]
|
||||
);
|
||||
|
||||
const showPrice = blockProps.className.includes( 'is-style-with-price' );
|
||||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<InspectorControls>
|
||||
<PanelBody title={ __( 'Navigation options', 'woocommerce' ) }>
|
||||
<PanelBody title={ __( 'Options', 'woocommerce' ) }>
|
||||
<ToggleControl
|
||||
label={ __(
|
||||
'Show a "Return to Cart" link',
|
||||
|
@ -75,7 +79,21 @@ export const Edit = ( {
|
|||
} )
|
||||
}
|
||||
/>
|
||||
|
||||
{ showPrice && (
|
||||
<TextControl
|
||||
label={ __( 'Price separator', 'woocommerce' ) }
|
||||
id="price-separator"
|
||||
value={ attributes.priceSeparator }
|
||||
onChange={ ( value ) => {
|
||||
setAttributes( {
|
||||
priceSeparator: value,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</PanelBody>
|
||||
|
||||
{ showReturnToCart &&
|
||||
! (
|
||||
currentPostId === CHECKOUT_PAGE_ID &&
|
||||
|
@ -132,7 +150,28 @@ export const Edit = ( {
|
|||
placeOrderButtonLabel: content,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
>
|
||||
{ showPrice && (
|
||||
<>
|
||||
<style>
|
||||
{ `.wp-block-woocommerce-checkout-actions-block {
|
||||
.wc-block-components-checkout-place-order-button__separator {
|
||||
&::after {
|
||||
content: "${ attributes.priceSeparator }";
|
||||
}
|
||||
}
|
||||
}` }
|
||||
</style>
|
||||
<div className="wc-block-components-checkout-place-order-button__separator"></div>
|
||||
<div className="wc-block-components-checkout-place-order-button__price">
|
||||
<FormattedMonetaryAmount
|
||||
value={ cartTotals.total_price }
|
||||
currency={ totalsCurrency }
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
</EditableButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// Shows the text, separator, and price alongside each other in the editor.
|
||||
.wp-block-woocommerce-checkout-actions-block {
|
||||
.wc-block-components-checkout-place-order-button {
|
||||
span.wc-block-components-button__text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,5 +8,9 @@ import { withFilteredAttributes } from '@woocommerce/shared-hocs';
|
|||
*/
|
||||
import Block from './block';
|
||||
import attributes from './attributes';
|
||||
import metadata from './block.json';
|
||||
|
||||
export default withFilteredAttributes( attributes )( Block );
|
||||
export default withFilteredAttributes( {
|
||||
...attributes,
|
||||
...metadata.attributes,
|
||||
} )( Block );
|
||||
|
|
|
@ -4,15 +4,24 @@
|
|||
import { Icon, button } from '@wordpress/icons';
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import type { BlockConfiguration } from '@wordpress/blocks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import attributes from './attributes';
|
||||
import { Edit, Save } from './edit';
|
||||
import metadata from './block.json';
|
||||
import './style.scss';
|
||||
|
||||
const blockConfig: BlockConfiguration = {
|
||||
example: {
|
||||
attributes: {
|
||||
showPrice: true,
|
||||
placeOrderButtonLabel: __( 'Place Order', 'woocommerce' ),
|
||||
showReturnToCart: false,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
|
@ -21,7 +30,7 @@ const blockConfig: BlockConfiguration = {
|
|||
/>
|
||||
),
|
||||
},
|
||||
attributes,
|
||||
attributes: { ...attributes, ...metadata.attributes },
|
||||
save: Save,
|
||||
edit: Edit,
|
||||
};
|
||||
|
|
|
@ -1,48 +1,70 @@
|
|||
.wc-block-checkout__actions {
|
||||
.wp-block-woocommerce-checkout-actions-block {
|
||||
.wc-block-checkout__actions {
|
||||
&_row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&_row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.wc-block-components-checkout-place-order-button {
|
||||
width: 50%;
|
||||
padding: 1em;
|
||||
height: auto;
|
||||
|
||||
.wc-block-components-checkout-place-order-button {
|
||||
width: 50%;
|
||||
padding: 1em;
|
||||
height: auto;
|
||||
&--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
// Keeps the text in the DOM but not visible, so it doesn't cause the button to resize while showing the spinner.
|
||||
.wc-block-components-checkout-place-order-button__text--visually-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.wc-block-components-button__text {
|
||||
> svg {
|
||||
fill: $white;
|
||||
vertical-align: top;
|
||||
.wc-block-components-checkout-place-order-button__text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// Prevent runts/widows. Overwrite `balance` with `pretty`, as `pretty` is not supported in all browsers.
|
||||
text-wrap: balance;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.wc-block-components-checkout-place-order-button__separator {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.wc-block-components-button__text {
|
||||
> svg {
|
||||
fill: $white;
|
||||
vertical-align: top;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-mobile {
|
||||
.wc-block-checkout__actions {
|
||||
.is-mobile {
|
||||
.wc-block-checkout__actions {
|
||||
.wc-block-checkout__actions_row {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.wc-block-checkout__actions_row {
|
||||
flex-direction: column-reverse;
|
||||
.wc-block-components-checkout-place-order-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wc-block-components-checkout-return-to-cart-button {
|
||||
margin: $gap auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-checkout-place-order-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wc-block-components-checkout-return-to-cart-button {
|
||||
margin: $gap auto;
|
||||
.is-large {
|
||||
.wc-block-checkout__actions {
|
||||
padding: 0 0 $gap-largest 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-large {
|
||||
.wc-block-checkout__actions {
|
||||
padding: 0 0 $gap-largest 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,18 @@ export interface EditableButtonProps
|
|||
* The current value of the editable button.
|
||||
*/
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* The children of the editable button.
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const EditableButton = ( {
|
||||
onChange,
|
||||
placeholder,
|
||||
value,
|
||||
children,
|
||||
...props
|
||||
}: EditableButtonProps ) => {
|
||||
return (
|
||||
|
@ -35,6 +41,7 @@ const EditableButton = ( {
|
|||
placeholder={ placeholder }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
{ children }
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add a separator to the Checkout "Place order" button, customizable via block style variations.
|
|
@ -11,4 +11,39 @@ class CheckoutActionsBlock extends AbstractInnerBlock {
|
|||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout-actions-block';
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
parent::initialize();
|
||||
|
||||
add_action( 'wp_loaded', array( $this, 'register_style_variations' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register style variations for the block.
|
||||
*/
|
||||
public function register_style_variations() {
|
||||
register_block_style(
|
||||
$this->get_full_block_name(),
|
||||
array(
|
||||
'name' => 'without-price',
|
||||
'label' => __( 'Hide Price', 'woocommerce' ),
|
||||
'is_default' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_block_style(
|
||||
$this->get_full_block_name(),
|
||||
array(
|
||||
'name' => 'with-price',
|
||||
'label' => __( 'Show Price', 'woocommerce' ),
|
||||
'is_default' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue