Add separator to "Place order" button and use the block style variations API (#52017)

This commit is contained in:
Sam Seay 2024-11-01 05:49:23 +13:00 committed by GitHub
parent 601e14a253
commit a24502fe23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 277 additions and 74 deletions

View File

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

View File

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

View File

@ -19,12 +19,26 @@
"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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add a separator to the Checkout "Place order" button, customizable via block style variations.

View File

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