* Product selection when out of context for price and title blocks

* Move product element name/description/icons to constant files

* Add attributes and hocs to all elements

* Standalone block rendering

* Add a placeholder if title has no content

* Revert "Add a placeholder if title has no content"

This reverts commit 29115154b33eedc661ccd3cc758acdbc5041ffbc.

* parentClassName is not always present

* Loading state

* Wrap description in P

* Fixed loading styles when nested

* Maintain product shape in useProductData

* feature gate elements from showing in inserter

* fix feature flag

* include price PR

* edit withProductSelector to be a hoc

* fix lint issue

Co-authored-by: Seghir Nadir <nadir.seghir@gmail.com>
This commit is contained in:
Mike Jolley 2020-07-22 13:20:54 +01:00 committed by GitHub
parent ff2f135e7e
commit cd9f7e0ccb
160 changed files with 1673 additions and 859 deletions

View File

@ -236,7 +236,7 @@
}
.wc-block-grid__products .wc-block-grid__product-onsale,
.wc-block-layout .wc-block-components-product-sale-badge {
.wc-block-components-product-sale-badge {
background: $twentytwenty-highlights-color;
color: #fff;
font-family: $twentytwenty-headings;

View File

@ -13,7 +13,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-price',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/price" */ './product/price/block'
/* webpackChunkName: "atomic-block-components/price" */ './product-elements/price/block'
)
),
} );
@ -22,7 +22,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-image',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/image" */ './product/image/frontend'
/* webpackChunkName: "atomic-block-components/image" */ './product-elements/image/frontend'
)
),
} );
@ -31,7 +31,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-title',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/title" */ './product/title/frontend'
/* webpackChunkName: "atomic-block-components/title" */ './product-elements/title/frontend'
)
),
} );
@ -40,7 +40,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-rating',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/rating" */ './product/rating/block'
/* webpackChunkName: "atomic-block-components/rating" */ './product-elements/rating/block'
)
),
} );
@ -49,7 +49,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-button',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/button" */ './product/button/block'
/* webpackChunkName: "atomic-block-components/button" */ './product-elements/button/block'
)
),
} );
@ -58,7 +58,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-summary',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/summary" */ './product/summary/block'
/* webpackChunkName: "atomic-block-components/summary" */ './product-elements/summary/block'
)
),
} );
@ -67,7 +67,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-sale-badge',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/sale-badge" */ './product/sale-badge/block'
/* webpackChunkName: "atomic-block-components/sale-badge" */ './product-elements/sale-badge/block'
)
),
} );
@ -76,7 +76,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-sku',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/sku" */ './product/sku/block'
/* webpackChunkName: "atomic-block-components/sku" */ './product-elements/sku/block'
)
),
} );
@ -85,7 +85,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-category-list',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/category-list" */ './product/category-list/block'
/* webpackChunkName: "atomic-block-components/category-list" */ './product-elements/category-list/block'
)
),
} );
@ -94,7 +94,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-tag-list',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/tag-list" */ './product/tag-list/block'
/* webpackChunkName: "atomic-block-components/tag-list" */ './product-elements/tag-list/block'
)
),
} );
@ -103,7 +103,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-stock-indicator',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/stock-indicator" */ './product/stock-indicator/block'
/* webpackChunkName: "atomic-block-components/stock-indicator" */ './product-elements/stock-indicator/block'
)
),
} );
@ -112,7 +112,7 @@ registerBlockComponent( {
blockName: 'woocommerce/product-add-to-cart',
component: lazy( () =>
import(
/* webpackChunkName: "atomic-block-components/add-to-cart" */ './product/add-to-cart/frontend'
/* webpackChunkName: "atomic-block-components/add-to-cart" */ './product-elements/add-to-cart/frontend'
)
),
} );

View File

@ -1,15 +1,15 @@
/**
* Internal dependencies
*/
import './product/title';
import './product/price';
import './product/image';
import './product/rating';
import './product/button';
import './product/summary';
import './product/sale-badge';
import './product/sku';
import './product/category-list';
import './product/tag-list';
import './product/stock-indicator';
import './product/add-to-cart';
import './product-elements/title';
import './product-elements/price';
import './product-elements/image';
import './product-elements/rating';
import './product-elements/button';
import './product-elements/summary';
import './product-elements/sale-badge';
import './product-elements/sku';
import './product-elements/category-list';
import './product-elements/tag-list';
import './product-elements/stock-indicator';
import './product-elements/add-to-cart';

View File

@ -3,6 +3,10 @@ export const blockAttributes = {
type: 'boolean',
default: false,
},
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -6,6 +6,7 @@ import classnames from 'classnames';
import { AddToCartFormContextProvider } from '@woocommerce/base-context';
import { useProductDataContext } from '@woocommerce/shared-context';
import { isEmpty } from 'lodash';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -25,13 +26,10 @@ import {
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {boolean} [props.showFormElements] Should form elements be shown?
* @param {Object} [props.product] Optional product object. Product from context will be
* used if this is not provided.
* @return {*} The component.
*/
const Block = ( { className, showFormElements, ...props } ) => {
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product || {};
const Block = ( { className, showFormElements } ) => {
const { product } = useProductDataContext();
const componentClass = classnames(
className,
'wc-block-components-product-add-to-cart',
@ -50,9 +48,7 @@ const Block = ( { className, showFormElements, ...props } ) => {
<div className={ componentClass }>
<>
{ showFormElements ? (
<AddToCartForm
productType={ product.type || 'simple' }
/>
<AddToCartForm productType={ product.type } />
) : (
<AddToCartButton />
) }
@ -80,7 +76,6 @@ const AddToCartForm = ( { productType } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,12 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { cart, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __( 'Add to Cart', 'woo-gutenberg-products-block' );
export const BLOCK_ICON = <Icon srcElement={ cart } />;
export const BLOCK_DESCRIPTION = __(
'Displays an add to cart button. Optionally displays other add to cart form elements.',
'woo-gutenberg-products-block'
);

View File

@ -13,10 +13,11 @@ import { InspectorControls } from '@wordpress/block-editor';
*/
import './style.scss';
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
export default ( { attributes, setAttributes } ) => {
const productDataContext = useProductDataContext();
const product = productDataContext.product || {};
const Edit = ( { attributes, setAttributes } ) => {
const { product } = useProductDataContext();
const { className, showFormElements } = attributes;
return (
@ -26,7 +27,7 @@ export default ( { attributes, setAttributes } ) => {
'wc-block-components-product-add-to-cart'
) }
>
<EditProductLink productId={ product.id || 0 } />
<EditProductLink productId={ product.id } />
{ product.type !== 'external' && (
<InspectorControls>
<PanelBody
@ -57,3 +58,12 @@ export default ( { attributes, setAttributes } ) => {
</div>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's add to cart form.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import edit from './edit';
import attributes from './attributes';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
edit,
attributes,
};
registerExperimentalBlockType( 'woocommerce/product-add-to-cart', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,43 @@
.wc-block-components-product-add-to-cart {
margin: 0;
display: flex;
flex-wrap: wrap;
.wc-block-components-product-add-to-cart-button {
margin: 0 0 em($gap-small) 0;
.wc-block-components-button__text {
display: block;
> svg {
fill: currentColor;
vertical-align: top;
width: 1.5em;
height: 1.5em;
margin: -0.25em 0 -0.25em 0.5em;
}
}
}
.wc-block-components-product-add-to-cart-quantity {
margin: 0 1em em($gap-small) 0;
width: 5em;
padding: 0.618em;
background: $white;
border: 1px solid #ccc;
border-radius: 2px;
color: #43454b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.125);
text-align: center;
}
}
.wc-block-components-product-add-to-cart--placeholder {
.wc-block-components-product-add-to-cart-quantity,
.wc-block-components-product-add-to-cart-button {
@include placeholder();
}
}
.wc-block-grid .wc-block-components-product-add-to-cart {
justify-content: center;
}

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -12,6 +12,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -23,14 +24,11 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
return (
<div
@ -38,10 +36,12 @@ const Block = ( { className, ...props } ) => {
className,
'wp-block-button',
'wc-block-components-product-button',
`${ parentClassName }__product-add-to-cart`
{
[ `${ parentClassName }__product-add-to-cart` ]: parentClassName,
}
) }
>
{ product ? (
{ product.id ? (
<AddToCartButton product={ product } />
) : (
<AddToCartButtonPlaceholder />
@ -142,7 +142,6 @@ const AddToCartButtonPlaceholder = () => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { cart, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Add to Cart Button',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ cart } />;
export const BLOCK_DESCRIPTION = __(
'Display a call to action button which either adds the product to the cart, or links to the product page.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,29 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Disabled } from '@wordpress/components';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return (
<Disabled>
<Block { ...attributes } />
</Disabled>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's add to cart button.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-button', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,4 +1,4 @@
.wc-block-layout .wc-block-components-product-button {
.wp-block-button.wc-block-components-product-button {
word-break: break-word;
white-space: normal;
margin-top: 0;
@ -19,7 +19,7 @@
}
}
.wc-block-layout--is-loading .wc-block-components-product-button > .wc-block-components-product-button__button {
.is-loading .wc-block-components-product-button > .wc-block-components-product-button__button {
@include placeholder();
min-width: 8em;
min-height: 3em;

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -9,6 +9,7 @@ import {
useProductDataContext,
} from '@woocommerce/shared-context';
import { isEmpty } from 'lodash';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -20,16 +21,13 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const { product } = productDataContext || props || {};
const { product } = useProductDataContext();
if ( isEmpty( product ) || isEmpty( product.categories ) ) {
if ( isEmpty( product.categories ) ) {
return null;
}
@ -38,7 +36,9 @@ const Block = ( { className, ...props } ) => {
className={ classnames(
className,
'wc-block-components-product-category-list',
`${ parentClassName }__product-category-list`
{
[ `${ parentClassName }__product-category-list` ]: parentClassName,
}
) }
>
{ __( 'Categories:', 'woo-gutenberg-products-block' ) }{ ' ' }
@ -59,7 +59,6 @@ const Block = ( { className, ...props } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { folder, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Category List',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ folder } />;
export const BLOCK_DESCRIPTION = __(
'Display a list of categories belonging to a product.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Disabled } from '@wordpress/components';
import EditProductLink from '@woocommerce/block-components/edit-product-link';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return (
<>
<EditProductLink />
<Disabled>
<Block { ...attributes } />
</Disabled>
</>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's categories.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerExperimentalBlockType( 'woocommerce/product-category-list', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,4 +1,4 @@
.wc-block-layout .wc-block-components-product-tag-list {
.wc-block-components-product-category-list {
margin-top: 0;
margin-bottom: em($gap-small);

View File

@ -15,6 +15,10 @@ export const blockAttributes = {
type: 'string',
default: 'full-size',
},
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -9,6 +9,8 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
import { isEmpty } from 'lodash';
/**
* Internal dependencies
@ -21,11 +23,10 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {string} [props.imageSizing] Size of image to use.
* @param {boolean} [props.productLink] Whether or not to display a link to the product page.
* @param {boolean} [props.showSaleBadge] Whether or not to display the on sale badge.
* @param {string} [props.saleBadgeAlign] How should the sale badge be aligned if displayed.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( {
@ -34,21 +35,21 @@ const Block = ( {
productLink = true,
showSaleBadge,
saleBadgeAlign = 'right',
...props
} ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
const [ imageLoaded, setImageLoaded ] = useState( false );
if ( ! product ) {
if ( ! product.id ) {
return (
<div
className={ classnames(
className,
'wc-block-components-product-image',
'wc-block-components-product-image--placeholder',
`${ parentClassName }__product-image`
{
[ `${ parentClassName }__product-image` ]: parentClassName,
}
) }
>
<ImagePlaceholder />
@ -56,15 +57,16 @@ const Block = ( {
);
}
const image =
product?.images && product.images.length ? product.images[ 0 ] : null;
const image = ! isEmpty( product.images ) ? product.images[ 0 ] : null;
return (
<div
className={ classnames(
className,
'wc-block-components-product-image',
`${ parentClassName }__product-image`
{
[ `${ parentClassName }__product-image` ]: parentClassName,
}
) }
>
{ productLink ? (
@ -103,7 +105,9 @@ const Block = ( {
};
const ImagePlaceholder = () => {
return <img src={ PLACEHOLDER_IMG_SRC } alt="" />;
return (
<img src={ PLACEHOLDER_IMG_SRC } alt="" width={ 500 } height={ 500 } />
);
};
const Image = ( { image, onLoad, loaded, showFullSize } ) => {
@ -135,10 +139,9 @@ const Image = ( { image, onLoad, loaded, showFullSize } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
productLink: PropTypes.bool,
showSaleBadge: PropTypes.bool,
saleBadgeAlign: PropTypes.string,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { image, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Image',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ image } />;
export const BLOCK_DESCRIPTION = __(
'Display the main product image',
'woo-gutenberg-products-block'
);

View File

@ -12,8 +12,10 @@ import { getAdminLink } from '@woocommerce/settings';
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
export default ( { attributes, setAttributes } ) => {
const Edit = ( { attributes, setAttributes } ) => {
const {
productLink,
imageSizing,
@ -146,3 +148,12 @@ export default ( { attributes, setAttributes } ) => {
</>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's image.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-image', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,5 +1,5 @@
.editor-styles-wrapper .wc-block-layout .wc-block-grid__products .wc-block-grid__product .wc-block-components-product-image,
.wc-block-layout .wc-block-components-product-image {
.editor-styles-wrapper .wc-block-grid__products .wc-block-grid__product .wc-block-components-product-image,
.wc-block-components-product-image {
margin-top: 0;
margin-bottom: $gap-small;
text-decoration: none;
@ -48,6 +48,6 @@
}
}
.wc-block-layout--is-loading .wc-block-components-product-image {
.is-loading .wc-block-components-product-image {
@include placeholder();
}

View File

@ -0,0 +1,45 @@
/**
* External dependencies
*/
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
let blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
if ( isFeaturePluginBuild() ) {
blockAttributes = {
...blockAttributes,
align: {
type: 'string',
},
fontSize: {
type: 'string',
},
customFontSize: {
type: 'number',
},
saleFontSize: {
type: 'string',
},
customSaleFontSize: {
type: 'number',
},
color: {
type: 'string',
},
saleColor: {
type: 'string',
},
customColor: {
type: 'string',
},
customSaleColor: {
type: 'string',
},
};
}
export default blockAttributes;

View File

@ -11,6 +11,8 @@ import {
} from '@woocommerce/shared-context';
import { getColorClassName, getFontSizeClass } from '@wordpress/block-editor';
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
*/
@ -45,11 +47,9 @@ const Block = ( {
customColor,
saleColor,
customSaleColor,
...props
} ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
const colorClass = getColorClassName( 'color', color );
const fontSizeClass = getFontSizeClass( fontSize );
@ -80,20 +80,22 @@ const Block = ( {
fontSize: customSaleFontSize,
};
if ( ! product ) {
if ( ! product.id ) {
return (
<div
className={ classnames(
className,
'price',
'wc-block-components-product-price',
`${ parentClassName }__product-price`
{
[ `${ parentClassName }__product-price` ]: parentClassName,
}
) }
/>
);
}
const prices = product.prices || {};
const prices = product.prices;
const currency = getCurrencyFromPriceResponse( prices );
return (
@ -102,8 +104,8 @@ const Block = ( {
className,
'price',
'wc-block-components-product-price',
`${ parentClassName }__product-price`,
{
[ `${ parentClassName }__product-price` ]: parentClassName,
[ `wc-block-components-product-price__align-${ align }` ]:
align && isFeaturePluginBuild(),
}
@ -155,8 +157,10 @@ const PriceRange = ( { currency, minAmount, maxAmount, classes, style } ) => {
<span
className={ classnames(
'wc-block-components-product-price__value',
`${ parentClassName }__product-price__value`,
{ [ classes ]: isFeaturePluginBuild() }
{
[ `${ parentClassName }__product-price__value` ]: parentClassName,
[ classes ]: isFeaturePluginBuild()
}
) }
style={ isFeaturePluginBuild() ? style : {} }
>
@ -188,8 +192,9 @@ const SalePrice = ( {
<del
className={ classnames(
'wc-block-components-product-price__regular',
`${ parentClassName }__product-price__regular`,
{ [ classes ]: isFeaturePluginBuild() }
{
[ `${ parentClassName }__product-price__regular` ]: parentClassName,
[ classes ]: isFeaturePluginBuild() }
) }
style={ isFeaturePluginBuild() ? style : {} }
>
@ -201,8 +206,9 @@ const SalePrice = ( {
<span
className={ classnames(
'wc-block-components-product-price__value',
`${ parentClassName }__product-price__value`,
{ [ saleClasses ]: isFeaturePluginBuild() }
{
[ `${ parentClassName }__product-price__value` ]: parentClassName,
[ saleClasses ]: isFeaturePluginBuild() }
) }
style={ isFeaturePluginBuild() ? saleStyle : {} }
>
@ -245,4 +251,4 @@ Block.propTypes = {
customSaleColor: PropTypes.string,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { bill, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Price',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ bill } />;
export const BLOCK_DESCRIPTION = __(
'Display the price of a product.',
'woo-gutenberg-products-block'
);

View File

@ -18,6 +18,8 @@ import { isFeaturePluginBuild } from '@woocommerce/block-settings';
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const TextControl = ( {
fontSize,
@ -117,6 +119,14 @@ const Price = isFeaturePluginBuild()
withColors( 'color', { textColor: 'color' } ),
withColors( 'saleColor', { textColor: 'saleColor' } ),
withColors( 'originalColor', { textColor: 'originalColor' } ),
withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's price.",
'woo-gutenberg-products-block'
),
} ),
] )( PriceEdit )
: PriceEdit;

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import edit from './edit';
import attributes from './attributes';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-price', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,28 @@
.wc-block-components-product-price {
margin-top: 0;
margin-bottom: $gap-small;
display: block;
&__regular {
margin-right: 0.5em;
}
}
.is-loading {
.wc-block-components-product-price::before {
@include placeholder();
content: ".";
display: inline-block;
width: 5em;
}
/*rtl:begin:ignore*/
.wc-block-components-product-price__align-left {
text-align: left;
}
.wc-block-components-product-price__align-center {
text-align: center;
}
.wc-block-components-product-price__align-right {
text-align: right;
}
/*rtl:end:ignore*/
}

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -8,6 +8,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -19,14 +20,11 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
const rating = getAverageRating( product );
if ( ! rating ) {
@ -48,7 +46,9 @@ const Block = ( { className, ...props } ) => {
className,
'star-rating',
'wc-block-components-product-rating',
`${ parentClassName }__product-rating`
{
[ `${ parentClassName }__product-rating` ]: parentClassName,
}
) }
>
<div
@ -67,14 +67,13 @@ const Block = ( { className, ...props } ) => {
const getAverageRating = ( product ) => {
// eslint-disable-next-line camelcase
const rating = parseFloat( product?.average_rating || 0 );
const rating = parseFloat( product.average_rating );
return Number.isFinite( rating ) && rating > 0 ? rating : 0;
};
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { star, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Rating',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ star } />;
export const BLOCK_DESCRIPTION = __(
'Display the average rating of a product.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return <Block { ...attributes } />;
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's rating.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-rating', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,52 @@
.wc-block-components-product-rating {
display: block;
margin-top: 0;
margin-bottom: $gap-small;
&__stars {
overflow: hidden;
position: relative;
width: 5.3em;
height: 1.618em;
line-height: 1.618;
font-size: 1em;
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
font-family: star;
font-weight: 400;
margin: 0 auto;
text-align: left;
&::before {
content: "\53\53\53\53\53";
top: 0;
left: 0;
right: 0;
position: absolute;
opacity: 0.5;
color: #aaa;
white-space: nowrap;
}
span {
overflow: hidden;
top: 0;
left: 0;
right: 0;
position: absolute;
padding-top: 1.5em;
}
span::before {
content: "\53\53\53\53\53";
top: 0;
left: 0;
right: 0;
position: absolute;
color: #000;
white-space: nowrap;
}
}
}
.wc-block-single-product {
.wc-block-components-product-rating__stars {
margin: 0;
}
}

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -9,6 +9,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -21,16 +22,13 @@ import './style.scss';
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {string} [props.align] Alignment of the badge.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, align, ...props } ) => {
const Block = ( { className, align } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
if ( ! product || ! product.on_sale ) {
if ( ! product.id || ! product.on_sale ) {
return null;
}
@ -45,7 +43,9 @@ const Block = ( { className, align, ...props } ) => {
'wc-block-components-product-sale-badge',
className,
alignClass,
`${ parentClassName }__product-onsale`
{
[ `${ parentClassName }__product-onsale` ]: parentClassName,
}
) }
>
<Label
@ -62,7 +62,6 @@ const Block = ( { className, align, ...props } ) => {
Block.propTypes = {
className: PropTypes.string,
align: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { tag, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'On-Sale Badge',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ tag } />;
export const BLOCK_DESCRIPTION = __(
'Displays an on-sale badge if the product is on-sale.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return <Block { ...attributes } />;
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's sale-badge.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,35 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
supports: {
html: false,
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-sale-badge', {
...sharedConfig,
...blockConfig,
} );

View File

@ -3,17 +3,17 @@
*/
import { __ } from '@wordpress/i18n';
import { Icon, grid } from '@woocommerce/icons';
import { isExperimentalBuild } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import save from './save';
import save from '../save';
/**
* Holds default config for this collection of blocks.
*/
export default {
category: 'woocommerce',
category: 'woocommerce-product-elements',
keywords: [ __( 'WooCommerce', 'woo-gutenberg-products-block' ) ],
icon: {
src: <Icon srcElement={ grid } />,
@ -22,7 +22,9 @@ export default {
supports: {
html: false,
},
parent: [ 'woocommerce/all-products', 'woocommerce/single-product' ],
parent: isExperimentalBuild()
? null
: [ '@woocommerce/all-products', '@woocommerce/single-product' ],
save,
deprecated: [
{

View File

@ -0,0 +1,11 @@
.wc-atomic-blocks-product__selection {
width: 100%;
}
.wc-atomic-blocks-product__edit-card {
padding: 16px;
border-top: 1px solid #e2e4e7;
.wc-atomic-blocks-product__edit-card-title {
margin: 0 0 $gap;
}
}

View File

@ -0,0 +1,90 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import ProductControl from '@woocommerce/block-components/product-control';
import { Placeholder, Button, Toolbar } from '@wordpress/components';
import { BlockControls } from '@wordpress/block-editor';
import TextToolbarButton from '@woocommerce/block-components/text-toolbar-button';
import { useProductDataContext } from '@woocommerce/shared-context';
/**
* Internal dependencies
*/
import './editor.scss';
/**
* This HOC shows a product selection interface if context is not present in the editor.
*
* @param {Object} selectorArgs Options for the selector.
*
*/
const withProductSelector = ( selectorArgs ) => ( OriginalComponent ) => {
return ( props ) => {
const productDataContext = useProductDataContext();
const { attributes, setAttributes } = props;
const { productId } = attributes;
const [ isEditing, setIsEditing ] = useState( ! productId );
if ( productDataContext.hasContext ) {
return <OriginalComponent { ...props } />;
}
return (
<>
{ isEditing ? (
<Placeholder
icon={ selectorArgs.icon || '' }
label={ selectorArgs.label || '' }
className="wc-atomic-blocks-product"
>
{ !! selectorArgs.description && (
<div>{ selectorArgs.description }</div>
) }
<div className="wc-atomic-blocks-product__selection">
<ProductControl
selected={ productId || 0 }
showVariations
onChange={ ( value = [] ) => {
setAttributes( {
productId: value[ 0 ]
? value[ 0 ].id
: 0,
} );
} }
/>
<Button
isDefault
disabled={ ! productId }
onClick={ () => {
setIsEditing( false );
} }
>
{ __( 'Done', 'woo-gutenberg-products-block' ) }
</Button>
</div>
</Placeholder>
) : (
<>
<BlockControls>
<Toolbar>
<TextToolbarButton
onClick={ () => setIsEditing( true ) }
>
{ __(
'Switch product…',
'woo-gutenberg-products-block'
) }
</TextToolbarButton>
</Toolbar>
</BlockControls>
<OriginalComponent { ...props } />
</>
) }
</>
);
};
};
export default withProductSelector;

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -8,6 +8,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -19,15 +20,12 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product || {};
const sku = product.sku || '';
const { product } = useProductDataContext();
const sku = product.sku;
if ( ! sku ) {
return null;
@ -38,7 +36,9 @@ const Block = ( { className, ...props } ) => {
className={ classnames(
className,
'wc-block-components-product-sku',
`${ parentClassName }__product-sku`
{
[ `${ parentClassName }__product-sku` ]: parentClassName,
}
) }
>
{ __( 'SKU:', 'woo-gutenberg-products-block' ) }{ ' ' }
@ -49,7 +49,6 @@ const Block = ( { className, ...props } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,12 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { barcode, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __( 'Product SKU', 'woo-gutenberg-products-block' );
export const BLOCK_ICON = <Icon srcElement={ barcode } />;
export const BLOCK_DESCRIPTION = __(
'Display the SKU of a product.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import EditProductLink from '@woocommerce/block-components/edit-product-link';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return (
<>
<EditProductLink />
<Block { ...attributes } />
</>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's SKU.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerExperimentalBlockType( 'woocommerce/product-sku', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,7 @@
.wc-block-components-product-sku {
margin-top: 0;
margin-bottom: $gap-small;
display: block;
text-transform: uppercase;
@include font-size(small);
}

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -8,7 +8,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { isEmpty } from 'lodash';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -20,16 +20,13 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product || {};
const { product } = useProductDataContext();
if ( isEmpty( product ) || ! product.is_purchasable ) {
if ( ! product.id || ! product.is_purchasable ) {
return null;
}
@ -42,8 +39,8 @@ const Block = ( { className, ...props } ) => {
className={ classnames(
className,
'wc-block-components-product-stock-indicator',
`${ parentClassName }__stock-indicator`,
{
[ `${ parentClassName }__stock-indicator` ]: parentClassName,
'wc-block-components-product-stock-indicator--in-stock': inStock,
'wc-block-components-product-stock-indicator--out-of-stock': ! inStock,
'wc-block-components-product-stock-indicator--low-stock': !! lowStock,
@ -78,7 +75,6 @@ const stockText = ( inStock, isBackordered ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { box, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Stock Indicator',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ box } />;
export const BLOCK_DESCRIPTION = __(
'Display product stock status.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import EditProductLink from '@woocommerce/block-components/edit-product-link';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return (
<>
<EditProductLink />
<Block { ...attributes } />
</>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's stock.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerExperimentalBlockType( 'woocommerce/product-stock-indicator', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,17 @@
.wc-block-components-product-stock-indicator {
margin-top: 0;
margin-bottom: em($gap-small);
display: block;
@include font-size(small);
&--in-stock {
color: $valid-green;
}
&--out-of-stock {
color: $error-red;
}
&--low-stock,
&--available-on-backorder {
color: $notice-yellow;
}
}

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -9,6 +9,7 @@ import {
useInnerBlockLayoutContext,
useProductDataContext,
} from '@woocommerce/shared-context';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -20,21 +21,21 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const { product } = productDataContext || props;
const { product } = useProductDataContext();
if ( ! product ) {
return (
<div
className={ classnames(
className,
`wc-block-components-product-summary`
`wc-block-components-product-summary`,
{
[ `${ parentClassName }__product-summary` ]: parentClassName,
}
) }
/>
);
@ -55,7 +56,9 @@ const Block = ( { className, ...props } ) => {
className={ classnames(
className,
`wc-block-components-product-summary`,
`${ parentClassName }__product-summary`
{
[ `${ parentClassName }__product-summary` ]: parentClassName,
}
) }
source={ source }
maxLength={ 150 }
@ -66,7 +69,6 @@ const Block = ( { className, ...props } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { notes, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Summary',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ notes } />;
export const BLOCK_DESCRIPTION = __(
'Display a short description about a product.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return <Block { ...attributes } />;
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's short description.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-summary', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,9 +1,8 @@
.wc-block-layout .wc-block-components-product-summary {
.wc-block-components-product-summary {
margin-top: 0;
margin-bottom: $gap-small;
}
.wc-block-layout--is-loading .wc-block-components-product-summary::before {
.is-loading .wc-block-components-product-summary::before {
@include placeholder();
content: ".";
display: block;

View File

@ -0,0 +1,8 @@
export const blockAttributes = {
productId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -9,6 +9,7 @@ import {
useProductDataContext,
} from '@woocommerce/shared-context';
import { isEmpty } from 'lodash';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
@ -20,16 +21,13 @@ import './style.scss';
*
* @param {Object} props Incoming props.
* @param {string} [props.className] CSS Class name for the component.
* @param {Object} [props.product] Optional product object. Product from context will be used if
* this is not provided.
* @return {*} The component.
*/
const Block = ( { className, ...props } ) => {
const Block = ( { className } ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const { product } = productDataContext || props || {};
const { product } = useProductDataContext();
if ( isEmpty( product ) || isEmpty( product.tags ) ) {
if ( isEmpty( product.tags ) ) {
return null;
}
@ -38,7 +36,9 @@ const Block = ( { className, ...props } ) => {
className={ classnames(
className,
'wc-block-components-product-tag-list',
`${ parentClassName }__product-tag-list`
{
[ `${ parentClassName }__product-tag-list` ]: parentClassName,
}
) }
>
{ __( 'Tags:', 'woo-gutenberg-products-block' ) }{ ' ' }
@ -59,7 +59,6 @@ const Block = ( { className, ...props } ) => {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { tag, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Tag List',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ tag } />;
export const BLOCK_DESCRIPTION = __(
'Display a list of tags belonging to a product.',
'woo-gutenberg-products-block'
);

View File

@ -0,0 +1,33 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Disabled } from '@wordpress/components';
import EditProductLink from '@woocommerce/block-components/edit-product-link';
/**
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const Edit = ( { attributes } ) => {
return (
<>
<EditProductLink />
<Disabled>
<Block { ...attributes } />
</Disabled>
</>
);
};
export default withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's tags.",
'woo-gutenberg-products-block'
),
} )( Edit );

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerExperimentalBlockType( 'woocommerce/product-tag-list', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,4 +1,4 @@
.wc-block-layout .wc-block-components-product-category-list {
.wc-block-components-product-tag-list {
margin-top: 0;
margin-bottom: em($gap-small);

View File

@ -12,6 +12,10 @@ let blockAttributes = {
type: 'boolean',
default: true,
},
productId: {
type: 'number',
default: 0,
},
};
if ( isFeaturePluginBuild() ) {

View File

@ -11,6 +11,8 @@ import {
import { getColorClassName, getFontSizeClass } from '@wordpress/block-editor';
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
import { gatedStyledText } from '@woocommerce/atomic-utils';
import { withProductDataContext } from '@woocommerce/shared-hocs';
/**
* Internal dependencies
*/
@ -41,11 +43,9 @@ export const Block = ( {
customColor,
fontSize,
customFontSize,
...props
} ) => {
const { parentClassName } = useInnerBlockLayoutContext();
const productDataContext = useProductDataContext();
const product = props.product || productDataContext.product;
const { product } = useProductDataContext();
const TagName = `h${ headingLevel }`;
const colorClass = getColorClassName( 'color', color );
@ -58,19 +58,20 @@ export const Block = ( {
[ fontSizeClass ]: fontSizeClass,
} );
if ( ! product ) {
if ( ! product.id ) {
return (
<TagName
// @ts-ignore
className={ classnames(
className,
'wc-block-components-product-title',
`${ parentClassName }__product-title`,
{
[ `${ parentClassName }__product-title` ]: parentClassName,
[ `wc-block-components-product-title--align-${ align }` ]:
align && isFeaturePluginBuild(),
[ titleClasses ]: isFeaturePluginBuild()
},
{ [ titleClasses ]: isFeaturePluginBuild() }
) }
style={ gatedStyledText( {
color: customColor,
@ -88,11 +89,11 @@ export const Block = ( {
className={ classnames(
className,
'wc-block-components-product-title',
`${ parentClassName }__product-title`,
{
[ `${ parentClassName }__product-title` ]: parentClassName,
[ `wc-block-components-product-title__align-${ align }` ]:
align && isFeaturePluginBuild(),
}
},
) }
>
{ productLink ? (
@ -118,7 +119,6 @@ export const Block = ( {
Block.propTypes = {
className: PropTypes.string,
product: PropTypes.object,
headingLevel: PropTypes.number,
productLink: PropTypes.bool,
align: PropTypes.string,
@ -128,4 +128,4 @@ Block.propTypes = {
customFontSize: PropTypes.number,
};
export default Block;
export default withProductDataContext( Block );

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { bookmark, Icon } from '@woocommerce/icons';
export const BLOCK_TITLE = __(
'Product Title',
'woo-gutenberg-products-block'
);
export const BLOCK_ICON = <Icon srcElement={ bookmark } />;
export const BLOCK_DESCRIPTION = __(
'Display the title of a product.',
'woo-gutenberg-products-block'
);

View File

@ -20,6 +20,8 @@ import HeadingToolbar from '@woocommerce/block-components/heading-toolbar';
* Internal dependencies
*/
import Block from './block';
import withProductSelector from '../shared/with-product-selector';
import { BLOCK_TITLE, BLOCK_ICON } from './constants';
const TitleEdit = ( {
color,
@ -115,6 +117,14 @@ const Title = isFeaturePluginBuild()
? compose( [
withFontSizes( 'fontSize' ),
withColors( 'color', { textColor: 'color' } ),
withProductSelector( {
icon: BLOCK_ICON,
label: BLOCK_TITLE,
description: __(
"Choose a product to display it's title.",
'woo-gutenberg-products-block'
),
} ),
] )( TitleEdit )
: TitleEdit;

View File

@ -0,0 +1,32 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import sharedConfig from '../shared/config';
import attributes from './attributes';
import edit from './edit';
import {
BLOCK_TITLE as title,
BLOCK_ICON as icon,
BLOCK_DESCRIPTION as description,
} from './constants';
const blockConfig = {
title,
description,
icon: {
src: icon,
foreground: '#874FB9',
},
attributes,
edit,
};
registerBlockType( 'woocommerce/product-title', {
...sharedConfig,
...blockConfig,
} );

View File

@ -0,0 +1,35 @@
.wc-block-components-product-title {
margin-top: 0;
margin-bottom: $gap-small;
}
.wc-block-grid .wc-block-components-product-title {
line-height: 1.5;
font-weight: 700;
padding: 0;
color: inherit;
font-size: inherit;
display: block;
}
.is-loading {
.wc-block-components-product-title::before {
@include placeholder();
content: ".";
display: inline-block;
width: 7em;
}
.wc-block-grid .wc-block-components-product-title::before {
width: 10em;
}
}
/*rtl:begin:ignore*/
.wc-block-components-product-title--align-left {
text-align: left;
}
.wc-block-components-product-title--align-center {
text-align: center;
}
.wc-block-components-product-title--align-right {
text-align: right;
}
/*rtl:end:ignore*/

View File

@ -1,32 +0,0 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { registerExperimentalBlockType } from '@woocommerce/block-settings';
import { Icon, cart } from '@woocommerce/icons';
/**
* Internal dependencies
*/
import sharedConfig from '../shared-config';
import edit from './edit';
import attributes from './attributes';
const blockConfig = {
title: __( 'Add to Cart', 'woo-gutenberg-products-block' ),
description: __(
'Displays an add to cart button. Optionally displays other add to cart form elements.',
'woo-gutenberg-products-block'
),
icon: {
src: <Icon srcElement={ cart } />,
foreground: '#96588a',
},
edit,
attributes,
};
registerExperimentalBlockType( 'woocommerce/product-add-to-cart', {
...sharedConfig,
...blockConfig,
} );

View File

@ -1,45 +0,0 @@
.wc-block-layout {
.wc-block-components-product-add-to-cart {
margin: 0;
display: flex;
flex-wrap: wrap;
.wc-block-components-product-add-to-cart-button {
margin: 0 0 em($gap-small) 0;
.wc-block-components-button__text {
display: block;
> svg {
fill: currentColor;
vertical-align: top;
width: 1.5em;
height: 1.5em;
margin: -0.25em 0 -0.25em 0.5em;
}
}
}
.wc-block-components-product-add-to-cart-quantity {
margin: 0 1em em($gap-small) 0;
width: 5em;
padding: 0.618em;
background: $white;
border: 1px solid #ccc;
border-radius: 2px;
color: #43454b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.125);
text-align: center;
}
}
.wc-block-components-product-add-to-cart--placeholder {
.wc-block-components-product-add-to-cart-quantity,
.wc-block-components-product-add-to-cart-button {
@include placeholder();
}
}
.wc-block-grid .wc-block-components-product-add-to-cart {
justify-content: center;
}
}

View File

@ -1,17 +0,0 @@
/**
* External dependencies
*/
import { Disabled } from '@wordpress/components';
/**
* Internal dependencies
*/
import Block from './block';
export default ( { attributes } ) => {
return (
<Disabled>
<Block { ...attributes } />
</Disabled>
);
};

Some files were not shown because too many files have changed in this diff Show More