Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Block Refinement: Price Block [draft] #9398

Draft
wants to merge 34 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b0b059c
Rename block.
nerrad May 1, 2023
75b60fd
Update block description.
nerrad May 1, 2023
ba2c832
Update Query Types
nerrad May 1, 2023
40ac73d
Account for undefined in type definitions
nerrad May 1, 2023
7681268
Implement new price block
nerrad May 23, 2023
a43d5aa
Change default template to use the core/group block with a row layout.
nerrad May 23, 2023
0e7ab4e
Revert using core/group block as inner block for prices
nerrad May 29, 2023
051e083
move Block defs to block.json.
nerrad May 29, 2023
b1ddfe6
remove unused file
nerrad May 29, 2023
726567f
add support for superscript style on price values
nerrad May 29, 2023
ac25bb4
Add todo about the use of __experimentalDefaultControls
nerrad May 29, 2023
6fcb2a3
A bunch of styles support
nerrad Jun 5, 2023
13cdf51
add support for blockGap
nerrad Jun 9, 2023
6411934
add discount block
nerrad Jun 12, 2023
ad28e2c
remove superfluous styles
nerrad Jun 12, 2023
fa42b3b
consider price ranges on current price
nerrad Jun 12, 2023
8928045
Fix context usage
nerrad Jun 12, 2023
3371590
fix missing usesContext values
nerrad Jun 19, 2023
6411938
Implement discount as inner blocks
nerrad Jun 19, 2023
7888559
switch to the stabilized “layout” property.
nerrad Jun 28, 2023
ba21592
Create server-side classes for current-price, discount-amount and ori…
tjcafferkey Jul 11, 2023
d2b1a3a
Merge branch 'trunk' into update/price-block-improvments
tjcafferkey Jul 21, 2023
27f00c2
Add postId to context
albarin Jul 25, 2023
15b594e
Fix discount class block name and add postId context
albarin Jul 25, 2023
8baaec8
Fix woocommerce/isDescendentOfSingleProductTemplate context, show pre…
albarin Jul 25, 2023
629a99f
Render inner block and fix class name
albarin Jul 26, 2023
fcf3267
Fix import an ancestor type
albarin Jul 26, 2023
829eb35
Add color support for inner price blocks
albarin Jul 26, 2023
20d7b5f
Refactor context and add color support for inner blocks
albarin Jul 27, 2023
2e67964
Add support for typography and dimension styles
albarin Jul 27, 2023
e6454bb
Render price inner blocks inline in the editor
albarin Jul 27, 2023
beb62a2
Add original price styles
albarin Jul 28, 2023
d38704c
Fix css test
albarin Jul 28, 2023
499f9d9
Merge branch 'trunk' into update/price-block-improvments
tjcafferkey Aug 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/js/atomic/blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import './product-elements/title';
import './product-elements/price';
import './product-elements/price-v2';
import './product-elements/image';
import './product-elements/rating';
import './product-elements/rating-stars';
Expand Down
48 changes: 48 additions & 0 deletions assets/js/atomic/blocks/product-elements/price-v2/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "woocommerce/product-price-v2",
"title": "Price",
"keywords": [ "WooCommerce" ],
"category": "woocommerce-product-elements",
"textdomain": "woo-gutenberg-products-block",
"description": "Display the price of a product, including any discounts.",
"supports": {
"layout": {
"allowSwitching": false,
"allowInheriting": false,
"default": { "type": "flex" }
},
Comment on lines +11 to +15
Copy link
Contributor Author

@nerrad nerrad Jul 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, the layout supports property only became recently available and will surface in WordPress 6.3. Prior to that it was __experimentalLayout which is still supported. It may be better to ship with __experimentalLayout if we want to support WP versions < 6.3 but even then testing should cover which versions of WP this will explicitly be supported by as that may influence what versions of WP this block is surfaced for.

"html": false
},
"usesContext": [ "queryId", "postId" ],
"providesContext": {
"woocommerce/isDescendentOfSingleProductTemplate":
"isDescendentOfSingleProductTemplate",
"woocommerce/isDescendentOfSingleProductBlock":
"isDescendentOfSingleProductBlock",
"woocommerce/withSuperScriptStyle": "withSuperScriptStyle"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fully implemented yet, I added this as a context value so that it could be used to provide the ability for child blocks to keep their superscript style separate from the parent block. It's possible that isn't great UX in which case this property could potentially just be removed from block context.

},
"attributes": {
"isDescendentOfSingleProductTemplate": {
"type": "boolean",
"default": false
},
"isDescendentOfSingleProductBlock": {
"type": "boolean",
"default": false
},
"productId": {
"type": "number",
"default": 0
},
"withSuperScriptStyle": {
"type": "boolean",
"default": false
}
},
"styles": [
{ "name": "default", "label": "Default", "isDefault": true },
{ "name": "price-super", "label": "Superscript", "isDefault": false }
Comment on lines +45 to +46
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block Styles are usually utilized explicitly for css styles, however given withSuperScript also impacts HTML, the selection also triggers attribute/context update.

]
}
11 changes: 11 additions & 0 deletions assets/js/atomic/blocks/product-elements/price-v2/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* External dependencies
*/
import { currencyDollar, Icon } from '@wordpress/icons';

export const BLOCK_ICON: JSX.Element = (
<Icon
icon={ currencyDollar }
className="wc-block-editor-components-block-icon"
/>
);
181 changes: 181 additions & 0 deletions assets/js/atomic/blocks/product-elements/price-v2/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/**
* External dependencies
*/
import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { useEffect, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import {
InnerBlockLayoutContextProvider,
useInnerBlockLayoutContext,
ProductDataContextProvider,
} from '@woocommerce/shared-context';
import { useStoreProducts } from '@woocommerce/base-context/hooks';

/**
* Internal dependencies
*/
import { BLOCK_NAME as priceDiscountName } from './inner-blocks/discount';
import { BLOCK_NAME as originalPriceName } from './inner-blocks/original-price';
import { BLOCK_NAME as currentPriceName } from './inner-blocks/current-price';
import { TEMPLATE } from './template';
import './editor.scss';

interface Attributes {
isDescendentOfSingleProductBlock: boolean;
isDescendentOfSingleProductTemplate: boolean;
withSuperScriptStyle: boolean;
productId?: number;
}

interface Context {
postId?: number;
queryId?: number;
}

interface Props {
context: Context;
attributes: Attributes;
setAttributes: ( attributes: Partial< Attributes > ) => void;
}

interface ContextProviderProps extends Props {
children: JSX.Element | JSX.Element[] | undefined;
}

type ProductIdProps = Partial< ContextProviderProps > & { productId: number };

const deriveSuperScriptFromClass = ( className: string ): boolean => {
if ( className ) {
const classList = className.split( ' ' );
return classList.includes( 'is-style-price-super' );
}
return false;
};

const ProviderFromAPI = ( {
productId,
children,
}: ProductIdProps ): JSX.Element => {
// TODO: this would be good to derive from the WP entity store at some point.
const { products, productsLoading } = useStoreProducts( {
include: productId,
} );
let product = null;
if ( products.length > 0 ) {
product =
products.find(
( productIteration ) => productIteration.id === productId
) || null;
}

return (
<ProductDataContextProvider
product={ product }
isLoading={ productsLoading }
>
{ children }
</ProductDataContextProvider>
);
};

const DerivedProductDataContextProvider = ( {
context,
attributes,
setAttributes,
children,
}: ContextProviderProps & { withSuperScript: boolean } ): JSX.Element => {
const { queryId, postId } = context;
const { productId } = attributes;
const isDescendentOfQueryLoop = Number.isFinite( queryId );
const id = isDescendentOfQueryLoop ? postId : productId;
const isDescendentOfSingleProductTemplate = useSelect(
( select ) => {
const editSiteStore = select( 'core/edit-site' );
const editorPostId = editSiteStore?.getEditedPostId<
string | undefined
>();

return Boolean(
editorPostId?.includes( '//single-product' ) &&
! isDescendentOfQueryLoop
);
},
[ isDescendentOfQueryLoop ]
);

useEffect(
() =>
setAttributes( {
isDescendentOfSingleProductTemplate,
} ),
[ isDescendentOfSingleProductTemplate, setAttributes ]
);
if ( id && id > 0 ) {
return <ProviderFromAPI productId={ id }>{ children }</ProviderFromAPI>;
}
return (
<ProductDataContextProvider isLoading={ false }>
{ children }
</ProductDataContextProvider>
);
};

const EditBlock = ( {
context,
attributes,
setAttributes,
withSuperScript,
}: Props & { withSuperScript: boolean } ): JSX.Element => {
const { parentClassName } = useInnerBlockLayoutContext();
return (
<DerivedProductDataContextProvider
context={ context }
attributes={ attributes }
setAttributes={ setAttributes }
withSuperScript={ withSuperScript }
>
<div className={ parentClassName }>
<InnerBlocks
allowedBlocks={ [
originalPriceName,
currentPriceName,
priceDiscountName,
] }
// todo add template for initial price layout
template={ TEMPLATE }
/>
</div>
</DerivedProductDataContextProvider>
);
};

const Edit = ( {
setAttributes,
attributes,
...props
}: Props ): JSX.Element => {
const blockProps = useBlockProps();
const withSuperScript = useMemo(
() => deriveSuperScriptFromClass( blockProps.className ),
[ blockProps.className ]
);
useEffect( () => {
setAttributes( { withSuperScriptStyle: withSuperScript } );
}, [ withSuperScript, setAttributes ] );
return (
<div { ...blockProps }>
<InnerBlockLayoutContextProvider
parentName={ 'woocommerce/price-block' }
parentClassName={ 'wc-block-price-element' }
>
<EditBlock
{ ...props }
attributes={ attributes }
setAttributes={ setAttributes }
/>
</InnerBlockLayoutContextProvider>
</div>
);
};

export default Edit;
12 changes: 12 additions & 0 deletions assets/js/atomic/blocks/product-elements/price-v2/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.wc-block-price-element {
.block-editor-inner-blocks {
.block-editor-block-list__layout {
display: flex;

.wp-block-woocommerce-original-price,
.wp-block-woocommerce-current-price {
margin-right: $gap-smaller;
}
}
}
}
24 changes: 24 additions & 0 deletions assets/js/atomic/blocks/product-elements/price-v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import edit from './edit';
import save from './save';
import { supports } from './supports';
import metadata from './block.json';
import { BLOCK_ICON as icon } from './constants';
import './inner-blocks';

const blockConfig = {
...metadata,
icon: { src: icon },
supports: { ...supports, ...metadata.supports },
edit,
save,
};

registerBlockType( metadata.name, blockConfig );
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "woocommerce/current-price",
"version": "1.0.0",
"icon": "info",
"title": "Current Price",
"description": "Display the current price for the product",
"supports": {
"color": {
"background": true,
"text": true
}
},
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"usesContext": ["woocommerce/withSuperScriptStyle", "woocommerce/isDescendentOfSingleProductTemplate", "postId"],
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* External dependencies
*/
import type { HTMLAttributes } from 'react';
import classnames from 'classnames';
import { useStyleProps } from '@woocommerce/base-hooks';
import { useInnerBlockLayoutContext } from '@woocommerce/shared-context';
import ProductPrice from '@woocommerce/base-components/product-price';

/**
* Internal dependencies
*/
import type { PriceProps } from '../../types';

type Props = PriceProps & HTMLAttributes< HTMLDivElement >;

const Block = ( {
attributes,
context,
rawPrice,
minPrice,
maxPrice,
currency,
isDescendentOfSingleProductTemplate,
}: Props ): JSX.Element | null => {
const { className } = attributes;
const { className: stylesClassName, style } = useStyleProps( attributes );
const { parentClassName } = useInnerBlockLayoutContext();
const wrapperClassName = classnames(
className,
{
[ `${ parentClassName }__product-price` ]: parentClassName,
},
stylesClassName
);
if ( ! rawPrice && ! isDescendentOfSingleProductTemplate ) {
return <ProductPrice className={ wrapperClassName } />;
}

const pricePreview = '3000';
const priceClassName = classnames( {
[ `${ parentClassName }__product-current-price__value` ]:
parentClassName,
} );

return (
<ProductPrice
className={ wrapperClassName }
style={ style }
priceClassName={ priceClassName }
currency={ currency }
withSuperScript={ context?.[ 'woocommerce/withSuperScriptStyle' ] }
price={
isDescendentOfSingleProductTemplate ? pricePreview : rawPrice
}
minPrice={ minPrice }
maxPrice={ maxPrice }
/>
);
};

export default Block;
Loading