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( - '
- <%2$s href="%3$s" class="%4$s" style="%5$s" %6$s>%7$s + '
+ <%3$s href="%4$s" class="%5$s" style="%6$s" %7$s>%8$s
', esc_attr( $text_align_styles_and_classes['class'] ?? '' ), + esc_attr( $classname . ' ' . $custom_width_classes ), $html_element, esc_url( $product->add_to_cart_url() ), isset( $args['class'] ) ? esc_attr( $args['class'] ) : '',