diff --git a/assets/js/atomic/blocks/product-elements/button/attributes.ts b/assets/js/atomic/blocks/product-elements/button/attributes.ts
index 5d621390109..de91d6ff3a8 100644
--- a/assets/js/atomic/blocks/product-elements/button/attributes.ts
+++ b/assets/js/atomic/blocks/product-elements/button/attributes.ts
@@ -16,6 +16,9 @@ export const blockAttributes: BlockAttributes = {
type: 'string',
default: '',
},
+ width: {
+ type: 'number',
+ },
};
export default blockAttributes;
diff --git a/assets/js/atomic/blocks/product-elements/button/constants.tsx b/assets/js/atomic/blocks/product-elements/button/constants.tsx
index 2c69f873594..aa6304bbbf2 100644
--- a/assets/js/atomic/blocks/product-elements/button/constants.tsx
+++ b/assets/js/atomic/blocks/product-elements/button/constants.tsx
@@ -15,3 +15,5 @@ export const BLOCK_DESCRIPTION: string = __(
'Display a call to action button which either adds the product to the cart, or links to the product page.',
'woo-gutenberg-products-block'
);
+
+export const BLOCK_NAME = 'woocommerce/product-button';
diff --git a/assets/js/atomic/blocks/product-elements/button/edit.tsx b/assets/js/atomic/blocks/product-elements/button/edit.tsx
index 25a358b620a..4eac892cf8f 100644
--- a/assets/js/atomic/blocks/product-elements/button/edit.tsx
+++ b/assets/js/atomic/blocks/product-elements/button/edit.tsx
@@ -1,11 +1,19 @@
/**
* External dependencies
*/
-import { Disabled } from '@wordpress/components';
+import classnames from 'classnames';
+import {
+ Disabled,
+ Button,
+ ButtonGroup,
+ PanelBody,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
import {
AlignmentToolbar,
BlockControls,
useBlockProps,
+ InspectorControls,
} from '@wordpress/block-editor';
import type { BlockEditProps } from '@wordpress/blocks';
import { useEffect } from '@wordpress/element';
@@ -17,6 +25,52 @@ import { ProductQueryContext as Context } from '@woocommerce/blocks/product-quer
import Block from './block';
import { BlockAttributes } from './types';
+function WidthPanel( {
+ selectedWidth,
+ setAttributes,
+}: {
+ selectedWidth: number | undefined;
+ setAttributes: ( attributes: BlockAttributes ) => void;
+} ) {
+ function handleChange( newWidth: number ) {
+ // Check if we are toggling the width off
+ const width = selectedWidth === newWidth ? undefined : newWidth;
+
+ // Update attributes.
+ setAttributes( { width } );
+ }
+
+ return (
+
+
+ { [ 25, 50, 75, 100 ].map( ( widthValue ) => {
+ return (
+
+ );
+ } ) }
+
+
+ );
+}
+
const Edit = ( {
attributes,
setAttributes,
@@ -26,6 +80,7 @@ const Edit = ( {
} ): JSX.Element => {
const blockProps = useBlockProps();
const isDescendentOfQueryLoop = Number.isFinite( context?.queryId );
+ const { width } = attributes;
useEffect(
() => setAttributes( { isDescendentOfQueryLoop } ),
@@ -43,9 +98,21 @@ const Edit = ( {
/>
) }
+
+
+
-
+
>
diff --git a/assets/js/atomic/blocks/product-elements/button/index.ts b/assets/js/atomic/blocks/product-elements/button/index.ts
index 833722d1a65..1fcfd8f4221 100644
--- a/assets/js/atomic/blocks/product-elements/button/index.ts
+++ b/assets/js/atomic/blocks/product-elements/button/index.ts
@@ -3,6 +3,7 @@
*/
import { registerBlockType } from '@wordpress/blocks';
import type { BlockConfiguration } from '@wordpress/blocks';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -11,10 +12,12 @@ import { supports } from './supports';
import attributes from './attributes';
import sharedConfig from '../shared/config';
import edit from './edit';
+import save from './save';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
+ BLOCK_NAME,
} from './constants';
const blockConfig: BlockConfiguration = {
@@ -32,6 +35,18 @@ const blockConfig: BlockConfiguration = {
attributes,
supports,
edit,
+ save,
+ styles: [
+ {
+ name: 'fill',
+ label: __( 'Fill', 'woo-gutenberg-products-block' ),
+ isDefault: true,
+ },
+ {
+ name: 'outline',
+ label: __( 'Outline', 'woo-gutenberg-products-block' ),
+ },
+ ],
};
-registerBlockType( 'woocommerce/product-button', { ...blockConfig } );
+registerBlockType( BLOCK_NAME, { ...blockConfig } );
diff --git a/assets/js/atomic/blocks/product-elements/button/save.tsx b/assets/js/atomic/blocks/product-elements/button/save.tsx
new file mode 100644
index 00000000000..f6312ac04b2
--- /dev/null
+++ b/assets/js/atomic/blocks/product-elements/button/save.tsx
@@ -0,0 +1,33 @@
+/**
+ * External dependencies
+ */
+import { useBlockProps } from '@wordpress/block-editor';
+import classnames from 'classnames';
+
+/**
+ * Internal dependencies
+ */
+import { BlockAttributes } from './types';
+
+type Props = {
+ attributes: BlockAttributes;
+};
+
+const Save = ( { attributes }: Props ): JSX.Element | null => {
+ if ( attributes.isDescendentOfQueryLoop ) {
+ return null;
+ }
+
+ return (
+
+ );
+};
+
+export default Save;
diff --git a/assets/js/atomic/blocks/product-elements/button/style.scss b/assets/js/atomic/blocks/product-elements/button/style.scss
index 5dd78b9fe98..f2e2552316a 100644
--- a/assets/js/atomic/blocks/product-elements/button/style.scss
+++ b/assets/js/atomic/blocks/product-elements/button/style.scss
@@ -40,3 +40,35 @@
}
}
+// Style: Fill & Outline
+.wp-block-button.is-style-outline {
+ .wp-block-button__link {
+ border: 2px solid currentColor;
+
+ &:not(.has-text-color) {
+ color: currentColor;
+ }
+
+ &:not(.has-background) {
+ background-color: transparent;
+ background-image: none;
+ }
+ }
+}
+
+// Width setting
+.wp-block-button {
+ &.has-custom-width {
+ .wp-block-button__link {
+ box-sizing: border-box;
+ }
+ }
+
+ @for $i from 1 through 4 {
+ &.wp-block-button__width-#{$i * 25} {
+ .wp-block-button__link {
+ width: $i * 25%; // 25%, 50%, 75%, 100%
+ }
+ }
+ }
+}
diff --git a/assets/js/atomic/blocks/product-elements/button/supports.ts b/assets/js/atomic/blocks/product-elements/button/supports.ts
index 048625ee61a..be26be4188b 100644
--- a/assets/js/atomic/blocks/product-elements/button/supports.ts
+++ b/assets/js/atomic/blocks/product-elements/button/supports.ts
@@ -26,8 +26,16 @@ export const supports = {
} ),
typography: {
fontSize: true,
+ lineHeight: true,
__experimentalFontWeight: true,
- __experimentalSkipSerialization: true,
+ __experimentalFontFamily: true,
+ __experimentalFontStyle: true,
+ __experimentalTextTransform: true,
+ __experimentalTextDecoration: true,
+ __experimentalLetterSpacing: true,
+ __experimentalDefaultControls: {
+ fontSize: true,
+ },
},
__experimentalSelector:
'.wp-block-button.wc-block-components-product-button .wc-block-components-product-button__button',
diff --git a/assets/js/atomic/blocks/product-elements/button/types.ts b/assets/js/atomic/blocks/product-elements/button/types.ts
index a47b8ac4441..96b78d60573 100644
--- a/assets/js/atomic/blocks/product-elements/button/types.ts
+++ b/assets/js/atomic/blocks/product-elements/button/types.ts
@@ -10,6 +10,7 @@ export interface BlockAttributes {
className?: string | undefined;
textAlign?: string | undefined;
isDescendentOfQueryLoop?: boolean | undefined;
+ width?: number | undefined;
}
export interface AddToCartButtonPlaceholderAttributes {
diff --git a/assets/js/blocks/products/base-utils.js b/assets/js/blocks/products/base-utils.js
index 223012cf50b..fb334f9338e 100644
--- a/assets/js/blocks/products/base-utils.js
+++ b/assets/js/blocks/products/base-utils.js
@@ -1,3 +1,13 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * Internal dependencies
+ */
+import { BLOCK_NAME as PRODUCT_BUTTON_BLOCK_NAME } from '../../atomic/blocks/product-elements/button/constants';
+
/**
* The default layout built from the default template.
*/
@@ -29,6 +39,17 @@ export const getProductLayoutConfig = ( innerBlocks ) => {
block.innerBlocks.length > 0
? getProductLayoutConfig( block.innerBlocks )
: [],
+ /**
+ * Add custom width class to Add to cart button,
+ * This is needed to support "Width Setting" controls available in
+ * "woocommerce/product-button" block.
+ */
+ ...( block.name === PRODUCT_BUTTON_BLOCK_NAME && {
+ className: classnames( block.attributes.className, {
+ [ `has-custom-width wp-block-button__width-${ block.attributes?.width }` ]:
+ block.attributes?.width,
+ } ),
+ } ),
},
];
} );
diff --git a/src/BlockTypes/ProductButton.php b/src/BlockTypes/ProductButton.php
index 0f281951fe9..9d667de04a6 100644
--- a/src/BlockTypes/ProductButton.php
+++ b/src/BlockTypes/ProductButton.php
@@ -89,13 +89,16 @@ protected function render( $attributes, $content, $block ) {
$ajax_add_to_cart_enabled = get_option( 'woocommerce_enable_ajax_add_to_cart' ) === 'yes';
$is_ajax_button = $ajax_add_to_cart_enabled && ! $cart_redirect_after_add && $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && $product->is_in_stock();
$html_element = $is_ajax_button ? 'button' : 'a';
- $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'border_radius', 'font_size', 'font_weight', 'margin', 'padding', 'text_color' ) );
+ $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
+ $classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
+ $custom_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
$html_classes = implode(
' ',
array_filter(
array(
'wp-block-button__link',
+ 'wp-element-button',
'wc-block-components-product-button__button',
$product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
$is_ajax_button ? 'ajax_add_to_cart' : '',
@@ -137,10 +140,11 @@ protected function render( $attributes, $content, $block ) {
return apply_filters(
'woocommerce_loop_add_to_cart_link',
sprintf(
- '