Add Inner Block Rendering and Atomic Block Styles (https://github.com/woocommerce/woocommerce-blocks/pull/2607)
* Register Atomic Blocks and save some block content * renderInnerBlocks utility * Frontend Rendering * Clean up atomic block classnames * Move shared styles * Create a hoc for attribute mapping * Rename some unpluralised class names * Remove prefixes from atomic component class names * Updated styles * Update styles from master * Revert product list styles * 2020 fixes * Separate renderFrontend from renderInnerBlocks * Lazy loading of components * Tweak loading classes * FIx all products loading state * Revert lazy implementation - creates too many unneccessary files due to webpack config * Cleanup * Remove wcBlocksBuildUrl * Move call to register_atomic_blocks * Remove duplicate key * reuse render frontend * Corectly handle frontend attribute mapping to keep editor working * Style updates * Update side effects * Remove width style from rating to fix alignment * Move ssr grid styles to main stylesheet * Put back prefixed classnames * 2020 styling fixes * Create frontend files instead of doing it all in block map * Update assets/js/atomic/utils/get-block-map.js Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com> * Update assets/js/atomic/utils/render-parent-block.js Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com> * Fix last child alignment regardless of block type * More specificity fixes * 2020 button alignment * static fix to prevent offsets * fix placeholder image in firefox * Issues reported in feedback Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
parent
2e8a3c8d6e
commit
da58a8b44f
|
@ -1,3 +1,282 @@
|
|||
.wc-block-link-button {
|
||||
@include link-button();
|
||||
}
|
||||
|
||||
// These styles are for the server side rendered product grid blocks.
|
||||
.wc-block-grid__products .wc-block-grid__product-image {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.edit-post-visual-editor .editor-block-list__block .wc-block-grid__product-title,
|
||||
.editor-styles-wrapper .wc-block-grid__product-title,
|
||||
.wc-block-grid__product-title {
|
||||
font-family: inherit;
|
||||
line-height: 1.2em;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
display: block;
|
||||
}
|
||||
.wc-block-grid__product-price {
|
||||
display: block;
|
||||
|
||||
.wc-block-grid__product-price__regular {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
.wc-block-grid__product-add-to-cart {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
a,
|
||||
button {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
margin: 0 auto !important;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.added::after {
|
||||
font-family: WooCommerce; /* stylelint-disable-line */
|
||||
content: "\e017";
|
||||
}
|
||||
|
||||
&.loading::after {
|
||||
font-family: WooCommerce; /* stylelint-disable-line */
|
||||
content: "\e031";
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
.wc-block-grid__product-rating {
|
||||
display: block;
|
||||
|
||||
.wc-block-grid__product-rating__stars,
|
||||
.star-rating {
|
||||
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-grid__product-onsale {
|
||||
@include font-size(small);
|
||||
padding: em($gap-smallest) em($gap-small);
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
border: 1px solid #43454b;
|
||||
border-radius: 3px;
|
||||
color: #43454b;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Element spacing.
|
||||
.wc-block-grid__product {
|
||||
.wc-block-grid__product-image,
|
||||
.wc-block-grid__product-title {
|
||||
margin: 0 0 $gap-small;
|
||||
}
|
||||
// If centered when toggling alignment on, use auto margins to prevent flexbox stretching it.
|
||||
.wc-block-grid__product-price,
|
||||
.wc-block-grid__product-rating,
|
||||
.wc-block-grid__product-add-to-cart,
|
||||
.wc-block-grid__product-onsale {
|
||||
margin: 0 auto $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentysixteen {
|
||||
.wc-block-grid {
|
||||
// Prevent white theme styles.
|
||||
.price ins {
|
||||
color: #77a464;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentynineteen {
|
||||
.wc-block-grid__product {
|
||||
font-size: 0.88889em;
|
||||
}
|
||||
// Change the title font to match headings.
|
||||
.wc-block-grid__product-title,
|
||||
.wc-block-grid__product-onsale,
|
||||
.wc-block-components-product-title,
|
||||
.wc-block-components-product-sale-badge {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.wc-block-grid__product-title::before {
|
||||
display: none;
|
||||
}
|
||||
.wc-block-grid__product-onsale,
|
||||
.wc-block-components-product-sale-badge {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentytwenty {
|
||||
$twentytwenty-headings: -apple-system, blinkmacsystemfont, "Helvetica Neue", helvetica, sans-serif;
|
||||
$twentytwenty-highlights-color: #cd2653;
|
||||
|
||||
.wc-block-grid__product-link {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.wc-block-grid__product-title,
|
||||
.wc-block-components-product-title {
|
||||
font-family: $twentytwenty-headings;
|
||||
color: #000;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.wp-block-columns .wc-block-components-product-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.wc-block-grid__product-price,
|
||||
.wc-block-components-product-price {
|
||||
&__value,
|
||||
.woocommerce-Price-amount {
|
||||
font-family: $twentytwenty-headings;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
del {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-rating,
|
||||
.star-rating {
|
||||
font-size: 0.7em;
|
||||
|
||||
.wc-block-grid__product-rating__stars,
|
||||
.wc-block-components-product-rating__stars {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-add-to-cart > .wp-block-button__link,
|
||||
.wc-block-components-product-button > .wp-block-button__link {
|
||||
font-family: $twentytwenty-headings;
|
||||
}
|
||||
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale,
|
||||
.wc-block-layout .wc-block-components-product-sale-badge {
|
||||
background: $twentytwenty-highlights-color;
|
||||
color: #fff;
|
||||
font-family: $twentytwenty-headings;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// These styles are not applied to the All Products atomic block, so it can be positioned normally.
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Override style from WC Core that set its position to absolute.
|
||||
// These rulesets can be removed once https://github.com/woocommerce/woocommerce/pull/26516 is released.
|
||||
.wc-block-grid__products .wc-block-components-product-sale-badge {
|
||||
position: static;
|
||||
}
|
||||
.wc-block-grid__products .wc-block-grid__product-image .wc-block-components-product-sale-badge {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// These styles are not applied to the All Products atomic block, so it can be positioned normally.
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale:not(.wc-block-components-product-sale-badge) {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale {
|
||||
@include font-size(small);
|
||||
padding: em($gap-smaller);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1168px) {
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale {
|
||||
@include font-size(small);
|
||||
padding: em($gap-smaller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export { default as ProductButton } from './button/block';
|
||||
export { default as ProductImage } from './image/block';
|
||||
export { default as ProductPrice } from './price/block';
|
||||
export { default as ProductRating } from './rating/block';
|
||||
export { default as ProductSaleBadge } from './sale-badge/block';
|
||||
export { default as ProductSummary } from './summary/block';
|
||||
export { default as ProductTitle } from './title/block';
|
|
@ -13,6 +13,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Button Block Component.
|
||||
*
|
||||
|
@ -22,22 +27,18 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductButton = ( { className, ...props } ) => {
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-add-to-cart`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
componentClass,
|
||||
'wp-block-button',
|
||||
{
|
||||
'is-loading': ! product,
|
||||
}
|
||||
'wc-block-components-product-button',
|
||||
`${ parentClassName }__product-add-to-cart`
|
||||
) }
|
||||
>
|
||||
{ product ? (
|
||||
|
@ -61,12 +62,7 @@ const AddToCartButton = ( { product } ) => {
|
|||
is_in_stock: isInStock,
|
||||
} = product;
|
||||
|
||||
const {
|
||||
cartQuantity,
|
||||
addingToCart,
|
||||
cartIsLoading,
|
||||
addToCart,
|
||||
} = useStoreAddToCart( id );
|
||||
const { cartQuantity, addingToCart, addToCart } = useStoreAddToCart( id );
|
||||
|
||||
useEffect( () => {
|
||||
// Avoid running on first mount when cart quantity is first set.
|
||||
|
@ -77,10 +73,6 @@ const AddToCartButton = ( { product } ) => {
|
|||
triggerFragmentRefresh();
|
||||
}, [ cartQuantity ] );
|
||||
|
||||
if ( cartIsLoading ) {
|
||||
return <AddToCartButtonPlaceholder />;
|
||||
}
|
||||
|
||||
const addedToCart = Number.isFinite( cartQuantity ) && cartQuantity > 0;
|
||||
const allowAddToCart = ! hasOptions && isPurchasable && isInStock;
|
||||
const buttonAriaLabel = decodeEntities(
|
||||
|
@ -102,42 +94,33 @@ const AddToCartButton = ( { product } ) => {
|
|||
__( 'Add to cart', 'woo-gutenberg-products-block' )
|
||||
);
|
||||
|
||||
const ButtonTag = allowAddToCart ? 'button' : 'a';
|
||||
const buttonProps = {};
|
||||
|
||||
if ( ! allowAddToCart ) {
|
||||
return (
|
||||
<a
|
||||
href={ permalink }
|
||||
aria-label={ buttonAriaLabel }
|
||||
className={ classnames(
|
||||
'wp-block-button__link',
|
||||
'add_to_cart_button',
|
||||
{
|
||||
loading: addingToCart,
|
||||
added: addedToCart,
|
||||
}
|
||||
) }
|
||||
rel="nofollow"
|
||||
>
|
||||
{ buttonText }
|
||||
</a>
|
||||
);
|
||||
buttonProps.href = permalink;
|
||||
buttonProps.rel = 'nofollow';
|
||||
} else {
|
||||
buttonProps.onClick = addToCart;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={ addToCart }
|
||||
<ButtonTag
|
||||
aria-label={ buttonAriaLabel }
|
||||
className={ classnames(
|
||||
'wp-block-button__link',
|
||||
'add_to_cart_button',
|
||||
'wc-block-components-product-button__button',
|
||||
{
|
||||
loading: addingToCart,
|
||||
added: addedToCart,
|
||||
}
|
||||
) }
|
||||
disabled={ addingToCart }
|
||||
{ ...buttonProps }
|
||||
>
|
||||
{ buttonText }
|
||||
</button>
|
||||
</ButtonTag>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -146,16 +129,18 @@ const AddToCartButtonPlaceholder = () => {
|
|||
<button
|
||||
className={ classnames(
|
||||
'wp-block-button__link',
|
||||
'add_to_cart_button'
|
||||
'add_to_cart_button',
|
||||
'wc-block-components-product-button__button',
|
||||
'wc-block-components-product-button__button--placeholder'
|
||||
) }
|
||||
disabled={ true }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
ProductButton.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ProductButton;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
.wc-block-layout .wc-block-components-product-button {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
|
||||
.wc-block-components-product-button__button {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
margin: 0 auto;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wc-block-components-product-button__button--placeholder {
|
||||
@include placeholder();
|
||||
min-width: 8em;
|
||||
min-height: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-layout--is-loading .wc-block-components-product-button > .wc-block-components-product-button__button {
|
||||
@include placeholder();
|
||||
min-width: 8em;
|
||||
min-height: 3em;
|
||||
}
|
|
@ -13,7 +13,8 @@ import {
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductSaleBadge from '../sale-badge/block.js';
|
||||
import ProductSaleBadge from './../sale-badge/block';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Image Block Component.
|
||||
|
@ -27,19 +28,16 @@ import ProductSaleBadge from '../sale-badge/block.js';
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductImage = ( {
|
||||
const Block = ( {
|
||||
className,
|
||||
productLink = true,
|
||||
showSaleBadge = true,
|
||||
showSaleBadge,
|
||||
saleBadgeAlign = 'right',
|
||||
...props
|
||||
} ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-image`;
|
||||
|
||||
const [ imageLoaded, setImageLoaded ] = useState( false );
|
||||
|
||||
if ( ! product ) {
|
||||
|
@ -47,11 +45,12 @@ const ProductImage = ( {
|
|||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
componentClass,
|
||||
'is-loading'
|
||||
'wc-block-components-product-image',
|
||||
'wc-block-components-product-image--placeholder',
|
||||
`${ parentClassName }__product-image`
|
||||
) }
|
||||
>
|
||||
<ImagePlaceholder componentClass={ componentClass } />
|
||||
<ImagePlaceholder />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -60,14 +59,22 @@ const ProductImage = ( {
|
|||
product?.images && product.images.length ? product.images[ 0 ] : null;
|
||||
|
||||
return (
|
||||
<div className={ classnames( className, componentClass ) }>
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-components-product-image',
|
||||
`${ parentClassName }__product-image`
|
||||
) }
|
||||
>
|
||||
{ productLink ? (
|
||||
<a href={ product.permalink } rel="nofollow">
|
||||
{ showSaleBadge && (
|
||||
<ProductSaleBadge align={ saleBadgeAlign } />
|
||||
{ !! showSaleBadge && (
|
||||
<ProductSaleBadge
|
||||
align={ saleBadgeAlign }
|
||||
product={ product }
|
||||
/>
|
||||
) }
|
||||
<Image
|
||||
componentClass={ componentClass }
|
||||
image={ image }
|
||||
onLoad={ () => setImageLoaded( true ) }
|
||||
loaded={ imageLoaded }
|
||||
|
@ -75,11 +82,13 @@ const ProductImage = ( {
|
|||
</a>
|
||||
) : (
|
||||
<>
|
||||
{ showSaleBadge && (
|
||||
<ProductSaleBadge align={ saleBadgeAlign } />
|
||||
{ !! showSaleBadge && (
|
||||
<ProductSaleBadge
|
||||
align={ saleBadgeAlign }
|
||||
product={ product }
|
||||
/>
|
||||
) }
|
||||
<Image
|
||||
componentClass={ componentClass }
|
||||
image={ image }
|
||||
onLoad={ () => setImageLoaded( true ) }
|
||||
loaded={ imageLoaded }
|
||||
|
@ -90,26 +99,16 @@ const ProductImage = ( {
|
|||
);
|
||||
};
|
||||
|
||||
const ImagePlaceholder = ( { componentClass } ) => {
|
||||
return (
|
||||
<img
|
||||
className={ classnames(
|
||||
`${ componentClass }__image`,
|
||||
`${ componentClass }__image_placeholder`
|
||||
) }
|
||||
src={ PLACEHOLDER_IMG_SRC }
|
||||
alt=""
|
||||
/>
|
||||
);
|
||||
const ImagePlaceholder = () => {
|
||||
return <img src={ PLACEHOLDER_IMG_SRC } alt="" />;
|
||||
};
|
||||
|
||||
const Image = ( { componentClass, image, onLoad, loaded } ) => {
|
||||
const Image = ( { image, onLoad, loaded } ) => {
|
||||
const { thumbnail, srcset, sizes, alt } = image || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<img
|
||||
className={ classnames( `${ componentClass }__image` ) }
|
||||
src={ thumbnail }
|
||||
srcSet={ srcset }
|
||||
sizes={ sizes }
|
||||
|
@ -117,14 +116,12 @@ const Image = ( { componentClass, image, onLoad, loaded } ) => {
|
|||
onLoad={ onLoad }
|
||||
hidden={ ! loaded }
|
||||
/>
|
||||
{ ! loaded && (
|
||||
<ImagePlaceholder componentClass={ componentClass } />
|
||||
) }
|
||||
{ ! loaded && <ImagePlaceholder /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ProductImage.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
productLink: PropTypes.bool,
|
||||
|
@ -132,4 +129,4 @@ ProductImage.propTypes = {
|
|||
saleBadgeAlign: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ProductImage;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { compose } from '@wordpress/compose';
|
||||
import withFilteredAttributes from '@woocommerce/base-hocs/with-filtered-attributes';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import attributes from './attributes';
|
||||
|
||||
export default compose( withFilteredAttributes( attributes ) )( Block );
|
|
@ -0,0 +1,53 @@
|
|||
.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 {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-product-sale-badge {
|
||||
&--alignleft {
|
||||
position: absolute;
|
||||
left: $gap-smaller/2;
|
||||
top: $gap-smaller/2;
|
||||
right: auto;
|
||||
margin: 0;
|
||||
}
|
||||
&--aligncenter {
|
||||
position: absolute;
|
||||
top: $gap-smaller/2;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
transform: translateX(-50%);
|
||||
margin: 0;
|
||||
}
|
||||
&--alignright {
|
||||
position: absolute;
|
||||
right: $gap-smaller/2;
|
||||
top: $gap-smaller/2;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-layout--is-loading .wc-block-components-product-image {
|
||||
@include placeholder();
|
||||
}
|
|
@ -10,6 +10,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Price Block Component.
|
||||
*
|
||||
|
@ -19,21 +24,19 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductPrice = ( { className, ...props } ) => {
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-price`;
|
||||
|
||||
if ( ! product ) {
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
componentClass,
|
||||
'price',
|
||||
'is-loading'
|
||||
'wc-block-components-product-price',
|
||||
`${ parentClassName }__product-price`
|
||||
) }
|
||||
/>
|
||||
);
|
||||
|
@ -43,17 +46,22 @@ const ProductPrice = ( { className, ...props } ) => {
|
|||
const currency = getCurrencyFromPriceResponse( prices );
|
||||
|
||||
return (
|
||||
<div className={ classnames( className, componentClass, 'price' ) }>
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
'price',
|
||||
'wc-block-components-product-price',
|
||||
`${ parentClassName }__product-price`
|
||||
) }
|
||||
>
|
||||
{ hasPriceRange( prices ) ? (
|
||||
<PriceRange
|
||||
componentClass={ componentClass }
|
||||
currency={ currency }
|
||||
minAmount={ prices.price_range.min_amount }
|
||||
maxAmount={ prices.price_range.max_amount }
|
||||
/>
|
||||
) : (
|
||||
<Price
|
||||
componentClass={ componentClass }
|
||||
currency={ currency }
|
||||
price={ prices.price }
|
||||
regularPrice={ prices.regular_price }
|
||||
|
@ -71,9 +79,16 @@ const hasPriceRange = ( prices ) => {
|
|||
);
|
||||
};
|
||||
|
||||
const PriceRange = ( { componentClass, currency, minAmount, maxAmount } ) => {
|
||||
const PriceRange = ( { currency, minAmount, maxAmount } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
|
||||
return (
|
||||
<span className={ `${ componentClass }__value` }>
|
||||
<span
|
||||
className={ classnames(
|
||||
'wc-block-components-product-price__value',
|
||||
`${ parentClassName }__product-price__value`
|
||||
) }
|
||||
>
|
||||
<FormattedMonetaryAmount
|
||||
currency={ currency }
|
||||
value={ minAmount }
|
||||
|
@ -87,18 +102,30 @@ const PriceRange = ( { componentClass, currency, minAmount, maxAmount } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
const Price = ( { componentClass, currency, price, regularPrice } ) => {
|
||||
const Price = ( { currency, price, regularPrice } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
{ regularPrice !== price && (
|
||||
<del className={ `${ componentClass }__regular` }>
|
||||
<del
|
||||
className={ classnames(
|
||||
'wc-block-components-product-price__regular',
|
||||
`${ parentClassName }__product-price__regular`
|
||||
) }
|
||||
>
|
||||
<FormattedMonetaryAmount
|
||||
currency={ currency }
|
||||
value={ regularPrice }
|
||||
/>
|
||||
</del>
|
||||
) }
|
||||
<span className={ `${ componentClass }__value` }>
|
||||
<span
|
||||
className={ classnames(
|
||||
'wc-block-components-product-price__value',
|
||||
`${ parentClassName }__product-price__value`
|
||||
) }
|
||||
>
|
||||
<FormattedMonetaryAmount
|
||||
currency={ currency }
|
||||
value={ price }
|
||||
|
@ -108,9 +135,9 @@ const Price = ( { componentClass, currency, price, regularPrice } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
ProductPrice.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ProductPrice;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
.wc-block-layout {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Rating Block Component.
|
||||
*
|
||||
|
@ -18,13 +23,10 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductRating = ( { className, ...props } ) => {
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-rating`;
|
||||
|
||||
const rating = getAverageRating( product );
|
||||
|
||||
if ( ! rating ) {
|
||||
|
@ -42,10 +44,18 @@ const ProductRating = ( { className, ...props } ) => {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={ classnames( className, componentClass, 'star-rating' ) }
|
||||
className={ classnames(
|
||||
className,
|
||||
'star-rating',
|
||||
'wc-block-components-product-rating',
|
||||
`${ parentClassName }__product-rating`
|
||||
) }
|
||||
>
|
||||
<div
|
||||
className={ `${ componentClass }__stars` }
|
||||
className={ classnames(
|
||||
'wc-block-components-product-rating__stars',
|
||||
`${ parentClassName }__product-rating__stars`
|
||||
) }
|
||||
role="img"
|
||||
aria-label={ ratingText }
|
||||
>
|
||||
|
@ -62,9 +72,9 @@ const getAverageRating = ( product ) => {
|
|||
return Number.isFinite( rating ) && rating > 0 ? rating : 0;
|
||||
};
|
||||
|
||||
ProductRating.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ProductRating;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
.wc-block-layout {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Sale Badge Block Component.
|
||||
*
|
||||
|
@ -20,27 +25,27 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductSaleBadge = ( { className, align, ...props } ) => {
|
||||
const Block = ( { className, align, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-onsale`;
|
||||
|
||||
if ( ! product || ! product.on_sale ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const alignClass =
|
||||
typeof align === 'string' ? `${ componentClass }--align${ align }` : '';
|
||||
typeof align === 'string'
|
||||
? `wc-block-components-product-sale-badge--align${ align }`
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
'wc-block-component__sale-badge',
|
||||
'wc-block-components-product-sale-badge',
|
||||
className,
|
||||
alignClass,
|
||||
componentClass
|
||||
`${ parentClassName }__product-onsale`
|
||||
) }
|
||||
>
|
||||
<Label
|
||||
|
@ -54,10 +59,10 @@ const ProductSaleBadge = ( { className, align, ...props } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
ProductSaleBadge.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
align: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ProductSaleBadge;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
.wc-block-components-product-sale-badge {
|
||||
margin: 0 auto $gap-small;
|
||||
@include font-size(small);
|
||||
padding: em($gap-smallest) em($gap-small);
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
border: 1px solid #43454b;
|
||||
border-radius: 3px;
|
||||
color: #43454b;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
z-index: 9;
|
||||
position: static;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
const save = ( { attributes } ) => {
|
||||
return (
|
||||
<div className={ classnames( 'is-loading', attributes.className ) } />
|
||||
);
|
||||
};
|
||||
|
||||
export default save;
|
|
@ -4,6 +4,11 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, grid } from '@woocommerce/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import save from './save';
|
||||
|
||||
/**
|
||||
* Holds default config for this collection of blocks.
|
||||
*/
|
||||
|
@ -18,5 +23,10 @@ export default {
|
|||
html: false,
|
||||
},
|
||||
parent: [ 'woocommerce/all-products', 'woocommerce/single-product' ],
|
||||
save() {},
|
||||
save,
|
||||
deprecated: [
|
||||
{
|
||||
save() {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -10,6 +10,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Summary Block Component.
|
||||
*
|
||||
|
@ -19,20 +24,17 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductSummary = ( { className, ...props } ) => {
|
||||
const Block = ( { className, ...props } ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const { product } = productDataContext || props;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-summary`;
|
||||
|
||||
if ( ! product ) {
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
className,
|
||||
componentClass,
|
||||
'is-loading'
|
||||
`wc-block-components-product-summary`
|
||||
) }
|
||||
/>
|
||||
);
|
||||
|
@ -50,7 +52,11 @@ const ProductSummary = ( { className, ...props } ) => {
|
|||
|
||||
return (
|
||||
<Summary
|
||||
className={ classnames( className, componentClass ) }
|
||||
className={ classnames(
|
||||
className,
|
||||
`wc-block-components-product-summary`,
|
||||
`${ parentClassName }__product-summary`
|
||||
) }
|
||||
source={ source }
|
||||
maxLength={ 150 }
|
||||
countType={ countType }
|
||||
|
@ -58,9 +64,9 @@ const ProductSummary = ( { className, ...props } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
ProductSummary.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ProductSummary;
|
||||
export default Block;
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.wc-block-layout .wc-block-components-product-summary {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
|
||||
.wc-block-layout--is-loading .wc-block-components-product-summary::before {
|
||||
@include placeholder();
|
||||
content: ".";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 6em;
|
||||
}
|
|
@ -9,6 +9,11 @@ import {
|
|||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Product Title Block Component.
|
||||
*
|
||||
|
@ -20,18 +25,15 @@ import {
|
|||
* this is not provided.
|
||||
* @return {*} The component.
|
||||
*/
|
||||
const ProductTitle = ( {
|
||||
export const Block = ( {
|
||||
className,
|
||||
headingLevel = 2,
|
||||
productLink = true,
|
||||
...props
|
||||
} ) => {
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const productDataContext = useProductDataContext();
|
||||
const product = props.product || productDataContext.product;
|
||||
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const componentClass = `${ layoutStyleClassPrefix }__product-title`;
|
||||
|
||||
const TagName = `h${ headingLevel }`;
|
||||
|
||||
if ( ! product ) {
|
||||
|
@ -40,8 +42,8 @@ const ProductTitle = ( {
|
|||
// @ts-ignore
|
||||
className={ classnames(
|
||||
className,
|
||||
componentClass,
|
||||
'is-loading'
|
||||
'wc-block-components-product-title',
|
||||
`${ parentClassName }__product-title`
|
||||
) }
|
||||
/>
|
||||
);
|
||||
|
@ -51,7 +53,13 @@ const ProductTitle = ( {
|
|||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<TagName className={ classnames( className, componentClass ) }>
|
||||
<TagName
|
||||
className={ classnames(
|
||||
className,
|
||||
'wc-block-components-product-title',
|
||||
`${ parentClassName }__product-title`
|
||||
) }
|
||||
>
|
||||
{ productLink ? (
|
||||
<a href={ product.permalink } rel="nofollow">
|
||||
{ productName }
|
||||
|
@ -63,11 +71,11 @@ const ProductTitle = ( {
|
|||
);
|
||||
};
|
||||
|
||||
ProductTitle.propTypes = {
|
||||
Block.propTypes = {
|
||||
className: PropTypes.string,
|
||||
product: PropTypes.object,
|
||||
headingLevel: PropTypes.number,
|
||||
productLink: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ProductTitle;
|
||||
export default Block;
|
||||
|
|
|
@ -23,7 +23,7 @@ export default ( { attributes, setAttributes } ) => {
|
|||
<p>{ __( 'Level', 'woo-gutenberg-products-block' ) }</p>
|
||||
<HeadingToolbar
|
||||
isCollapsed={ false }
|
||||
minLevel={ 2 }
|
||||
minLevel={ 1 }
|
||||
maxLevel={ 7 }
|
||||
selectedLevel={ headingLevel }
|
||||
onChange={ ( newLevel ) =>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { compose } from '@wordpress/compose';
|
||||
import withFilteredAttributes from '@woocommerce/base-hocs/with-filtered-attributes';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import attributes from './attributes';
|
||||
|
||||
export default compose( withFilteredAttributes( attributes ) )( Block );
|
|
@ -0,0 +1,25 @@
|
|||
.wc-block-layout {
|
||||
.wc-block-components-product-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
.wc-block-grid .wc-block-components-product-title {
|
||||
line-height: 1.2em;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,18 +6,16 @@ import { getRegisteredInnerBlocks } from '@woocommerce/blocks-registry';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
ProductTitle,
|
||||
ProductPrice,
|
||||
ProductButton,
|
||||
ProductImage,
|
||||
ProductRating,
|
||||
ProductSummary,
|
||||
ProductSaleBadge,
|
||||
} from '../blocks/product/block-components';
|
||||
import ProductButton from '../blocks/product/button/block';
|
||||
import ProductImage from '../blocks/product/image/frontend';
|
||||
import ProductPrice from '../blocks/product/price/block';
|
||||
import ProductRating from '../blocks/product/rating/block';
|
||||
import ProductSaleBadge from '../blocks/product/sale-badge/block';
|
||||
import ProductSummary from '../blocks/product/summary/block';
|
||||
import ProductTitle from '../blocks/product/title/frontend';
|
||||
|
||||
/**
|
||||
* Map blocks names to components.
|
||||
* Map blocks to components suitable for use on the frontend.
|
||||
*
|
||||
* @param {string} blockName Name of the parent block. Used to get extension children.
|
||||
*/
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
export * from './get-block-map.js';
|
||||
export * from './create-blocks-from-template.js';
|
||||
export * from './render-parent-block.js';
|
||||
export * from './render-inner-blocks.js';
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { cloneElement, isValidElement } from '@wordpress/element';
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getBlockMap } from './get-block-map';
|
||||
|
||||
/**
|
||||
* Replaces saved block HTML markup with Inner Block Components.
|
||||
*
|
||||
* @param {Object} props Render props.
|
||||
* @param {Array} props.children Children/inner blocks to render.
|
||||
* @param {string} props.blockName Parent Block Name used to get the block map and for keys.
|
||||
* @param {number} [props.depth] Depth of inner blocks being rendered.
|
||||
*/
|
||||
export const renderInnerBlocks = ( {
|
||||
children,
|
||||
blockName: parentBlockName,
|
||||
depth = 1,
|
||||
} ) => {
|
||||
const blockMap = getBlockMap( parentBlockName );
|
||||
|
||||
return Array.from( children ).map( ( el, index ) => {
|
||||
const componentProps = {
|
||||
...el.dataset,
|
||||
key: `${ parentBlockName }_${ depth }_${ index }`,
|
||||
};
|
||||
|
||||
const componentChildren =
|
||||
el.children && el.children.length
|
||||
? renderInnerBlocks( {
|
||||
children: el.children,
|
||||
blockName: parentBlockName,
|
||||
depth: depth + 1,
|
||||
} )
|
||||
: null;
|
||||
|
||||
const LayoutComponent =
|
||||
componentProps.blockName && blockMap[ componentProps.blockName ]
|
||||
? blockMap[ componentProps.blockName ]
|
||||
: null;
|
||||
|
||||
if ( ! LayoutComponent ) {
|
||||
const element = parse( el.outerHTML );
|
||||
|
||||
if ( isValidElement( element ) ) {
|
||||
return componentChildren
|
||||
? cloneElement( element, componentProps, componentChildren )
|
||||
: cloneElement( element, componentProps );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<LayoutComponent { ...componentProps }>
|
||||
{ componentChildren }
|
||||
</LayoutComponent>
|
||||
);
|
||||
} );
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { renderFrontend } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { renderInnerBlocks } from './render-inner-blocks';
|
||||
|
||||
/**
|
||||
* Renders a block component in the place of a specified set of selectors.
|
||||
*
|
||||
* @param {Object} props Render props.
|
||||
* @param {Function} props.Block React component to use as a replacement.
|
||||
* @param {string} props.selector CSS selector to match the elements to replace.
|
||||
* @param {string} [props.blockName] Optional Block Name. Used for inner block component mapping.
|
||||
* @param {Function} [props.getProps] Function to generate the props object for the block.
|
||||
*/
|
||||
export const renderParentBlock = ( {
|
||||
Block,
|
||||
selector,
|
||||
blockName = '',
|
||||
getProps = () => {},
|
||||
} ) => {
|
||||
const getPropsWithChildren = ( el, i ) => {
|
||||
const children =
|
||||
el.children && el.children.length
|
||||
? renderInnerBlocks( {
|
||||
blockName,
|
||||
children: el.children,
|
||||
} )
|
||||
: null;
|
||||
return { ...getProps( el, i ), children };
|
||||
};
|
||||
renderFrontend( {
|
||||
Block,
|
||||
selector,
|
||||
getProps: getPropsWithChildren,
|
||||
} );
|
||||
};
|
|
@ -15,20 +15,20 @@ import './style.scss';
|
|||
const ExpressCheckoutContainer = ( { children } ) => {
|
||||
return (
|
||||
<>
|
||||
<div className="wc-block-component-express-checkout">
|
||||
<div className="wc-block-components-express-checkout">
|
||||
<Title
|
||||
className="wc-block-component-express-checkout__title"
|
||||
className="wc-block-components-express-checkout__title"
|
||||
headingLevel="2"
|
||||
>
|
||||
{ __( 'Express checkout', 'woo-gutenberg-products-block' ) }
|
||||
</Title>
|
||||
<div className="wc-block-component-express-checkout__content">
|
||||
<div className="wc-block-components-express-checkout__content">
|
||||
<StoreNoticesProvider context="wc/express-payment-area">
|
||||
{ children }
|
||||
</StoreNoticesProvider>
|
||||
</div>
|
||||
</div>
|
||||
<div className="wc-block-component-express-checkout-continue-rule">
|
||||
<div className="wc-block-components-express-checkout-continue-rule">
|
||||
{ __( 'Or continue below', 'woo-gutenberg-products-block' ) }
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -59,7 +59,7 @@ const ExpressPaymentMethods = () => {
|
|||
<li key="noneRegistered">No registered Payment Methods</li>
|
||||
);
|
||||
return (
|
||||
<ul className="wc-block-component-express-checkout-payment-event-buttons">
|
||||
<ul className="wc-block-components-express-checkout-payment-event-buttons">
|
||||
{ content }
|
||||
</ul>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.wc-block-component-express-checkout {
|
||||
.wc-block-components-express-checkout {
|
||||
margin: auto;
|
||||
border: 2px solid $black;
|
||||
border-radius: 5px;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
|
||||
.wc-block-component-express-checkout__title {
|
||||
.wc-block-components-express-checkout__title {
|
||||
background-color: $white;
|
||||
padding-left: $gap-small;
|
||||
padding-right: $gap-small;
|
||||
|
@ -17,11 +17,11 @@
|
|||
top: 0;
|
||||
}
|
||||
|
||||
.wc-block-component-express-checkout__content {
|
||||
.wc-block-components-express-checkout__content {
|
||||
padding: $gap $gap-large 0;
|
||||
}
|
||||
|
||||
.wc-block-component-express-checkout-payment-event-buttons {
|
||||
.wc-block-components-express-checkout-payment-event-buttons {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -190,7 +190,7 @@
|
|||
|
||||
// For Twenty Twenty we need to increase specificity of the title.
|
||||
.theme-twentytwenty {
|
||||
.wc-block-component-express-checkout .wc-block-component-express-checkout__title {
|
||||
.wc-block-components-express-checkout .wc-block-components-express-checkout__title {
|
||||
padding-left: $gap-small;
|
||||
padding-right: $gap-small;
|
||||
margin-left: $gap-small;
|
||||
|
|
|
@ -13,10 +13,11 @@ import { renderProductLayout } from './utils';
|
|||
|
||||
const ProductListItem = ( { product, attributes, instanceId } ) => {
|
||||
const { layoutConfig } = attributes;
|
||||
const { layoutStyleClassPrefix, parentName } = useInnerBlockLayoutContext();
|
||||
const { parentClassName, parentName } = useInnerBlockLayoutContext();
|
||||
const isLoading = Object.keys( product ).length === 0;
|
||||
const classes = classnames( `${ layoutStyleClassPrefix }__product`, {
|
||||
const classes = classnames( `${ parentClassName }__product`, {
|
||||
'is-loading': isLoading,
|
||||
'wc-block-layout--is-loading': isLoading, // This can be removed when switching to inner block rendering.
|
||||
} );
|
||||
|
||||
return (
|
||||
|
|
|
@ -114,7 +114,7 @@ const ProductList = ( {
|
|||
const { products, totalProducts, productsLoading } = useStoreProducts(
|
||||
queryState
|
||||
);
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const totalQuery = extractPaginationAndSortAttributes( queryState );
|
||||
|
||||
// These are possible filters.
|
||||
|
@ -162,7 +162,7 @@ const ProductList = ( {
|
|||
const alignClass = typeof align !== 'undefined' ? 'align' + align : '';
|
||||
|
||||
return classnames(
|
||||
layoutStyleClassPrefix,
|
||||
parentClassName,
|
||||
alignClass,
|
||||
'has-' + columns + '-columns',
|
||||
{
|
||||
|
@ -206,7 +206,7 @@ const ProductList = ( {
|
|||
) }
|
||||
{ ! hasProducts && ! hasFilters && <NoProducts /> }
|
||||
{ hasProducts && (
|
||||
<ul className={ `${ layoutStyleClassPrefix }__products` }>
|
||||
<ul className={ `${ parentClassName }__products` }>
|
||||
{ listProducts.map( ( product = {}, i ) => (
|
||||
<ProductListItem
|
||||
key={ product.id || i }
|
||||
|
|
|
@ -6,23 +6,19 @@ import { useInnerBlockLayoutContext } from '@woocommerce/shared-context';
|
|||
import { Icon, search } from '@woocommerce/icons';
|
||||
|
||||
const NoMatchingProducts = ( { resetCallback = () => {} } ) => {
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
return (
|
||||
<div className={ `${ layoutStyleClassPrefix }__no-products` }>
|
||||
<div className={ `${ parentClassName }__no-products` }>
|
||||
<Icon
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-image` }
|
||||
className={ `${ parentClassName }__no-products-image` }
|
||||
alt=""
|
||||
srcElement={ search }
|
||||
size={ 100 }
|
||||
/>
|
||||
<strong
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-title` }
|
||||
>
|
||||
<strong className={ `${ parentClassName }__no-products-title` }>
|
||||
{ __( 'No products found', 'woo-gutenberg-products-block' ) }
|
||||
</strong>
|
||||
<p
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-description` }
|
||||
>
|
||||
<p className={ `${ parentClassName }__no-products-description` }>
|
||||
{ __(
|
||||
'We were unable to find any results based on your search.',
|
||||
'woo-gutenberg-products-block'
|
||||
|
|
|
@ -6,23 +6,19 @@ import { useInnerBlockLayoutContext } from '@woocommerce/shared-context';
|
|||
import { Icon, notice } from '@woocommerce/icons';
|
||||
|
||||
const NoProducts = () => {
|
||||
const { layoutStyleClassPrefix } = useInnerBlockLayoutContext();
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
return (
|
||||
<div className={ `${ layoutStyleClassPrefix }__no-products` }>
|
||||
<div className={ `${ parentClassName }__no-products` }>
|
||||
<Icon
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-image` }
|
||||
className={ `${ parentClassName }__no-products-image` }
|
||||
alt=""
|
||||
srcElement={ notice }
|
||||
size={ 100 }
|
||||
/>
|
||||
<strong
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-title` }
|
||||
>
|
||||
<strong className={ `${ parentClassName }__no-products-title` }>
|
||||
{ __( 'No products', 'woo-gutenberg-products-block' ) }
|
||||
</strong>
|
||||
<p
|
||||
className={ `${ layoutStyleClassPrefix }__no-products-description` }
|
||||
>
|
||||
<p className={ `${ parentClassName }__no-products-description` }>
|
||||
{ __(
|
||||
'There are currently no products available to display.',
|
||||
'woo-gutenberg-products-block'
|
||||
|
|
|
@ -55,217 +55,14 @@
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
// Extra specificity to avoid editor styles on linked images.
|
||||
.wc-block-grid__products .wc-block-grid__product-image {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
|
||||
&[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.is-loading & {
|
||||
@include placeholder();
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-post-visual-editor .editor-block-list__block .wc-block-grid__product-title,
|
||||
.editor-styles-wrapper .wc-block-grid__product-title,
|
||||
.wc-block-grid__product-title {
|
||||
font-family: inherit;
|
||||
line-height: 1.2em;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
display: block;
|
||||
|
||||
.is-loading &::before {
|
||||
@include placeholder();
|
||||
content: ".";
|
||||
display: inline-block;
|
||||
width: 6em;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-price {
|
||||
display: block;
|
||||
|
||||
.wc-block-grid__product-price__value {
|
||||
margin-left: 0.5em;
|
||||
|
||||
.is-loading &::before {
|
||||
@include placeholder();
|
||||
content: ".";
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-add-to-cart {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
a,
|
||||
button {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
margin: 0 auto !important;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 0.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&.added::after {
|
||||
font-family: WooCommerce; /* stylelint-disable-line */
|
||||
content: "\e017";
|
||||
}
|
||||
|
||||
&.loading::after {
|
||||
font-family: WooCommerce; /* stylelint-disable-line */
|
||||
content: "\e031";
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.is-loading & {
|
||||
@include placeholder();
|
||||
min-width: 7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-rating {
|
||||
display: block;
|
||||
|
||||
.wc-block-grid__product-rating__stars,
|
||||
.star-rating {
|
||||
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-grid__product-onsale {
|
||||
border: 1px solid #43454b;
|
||||
color: #43454b;
|
||||
background: #fff;
|
||||
padding: 0.202em 0.6180469716em;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
border-radius: 3px;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
margin: $gap-smaller auto;
|
||||
}
|
||||
|
||||
.editor-styles-wrapper .wc-block-grid__products .wc-block-grid__product .wc-block-grid__product-image,
|
||||
.wc-block-grid__product-image {
|
||||
.wc-block-grid__product-onsale {
|
||||
&.wc-block-grid__product-onsale--alignleft {
|
||||
position: absolute;
|
||||
left: $gap-smaller/2;
|
||||
top: $gap-smaller/2;
|
||||
right: auto;
|
||||
margin: 0;
|
||||
}
|
||||
&.wc-block-grid__product-onsale--aligncenter {
|
||||
position: absolute;
|
||||
top: $gap-smaller/2;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin: 0;
|
||||
}
|
||||
&.wc-block-grid__product-onsale--alignright {
|
||||
position: absolute;
|
||||
right: $gap-smaller/2;
|
||||
top: $gap-smaller/2;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Element spacing.
|
||||
.wc-block-grid__product {
|
||||
.wc-block-grid__product-image,
|
||||
.wc-block-grid__product-title,
|
||||
.wc-block-grid__product-price,
|
||||
.wc-block-grid__product-rating,
|
||||
.wc-block-grid__product-add-to-cart {
|
||||
margin-top: 0;
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentytwenty .wc-block-grid,
|
||||
.wc-block-grid {
|
||||
&.has-aligned-buttons {
|
||||
.wc-block-grid__product {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.wc-block-grid__product > .wc-block-grid__product-title:last-child,
|
||||
.wc-block-grid__product > div:last-child {
|
||||
.wc-block-grid__product > :last-child {
|
||||
margin-top: auto;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: $gap-small;
|
||||
|
@ -288,7 +85,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Responsive media styles.
|
||||
@include breakpoint( "<480px" ) {
|
||||
.wc-block-grid {
|
||||
@for $i from 2 to 9 {
|
||||
|
@ -305,11 +101,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.wc-block-grid__product-image img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint( "480px-600px" ) {
|
||||
.wc-block-grid {
|
||||
@for $i from 2 to 9 {
|
||||
|
@ -331,120 +125,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.wc-block-grid__product-image img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentysixteen {
|
||||
.wc-block-grid {
|
||||
// Prevent white theme styles.
|
||||
.price ins {
|
||||
color: #77a464;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentynineteen {
|
||||
.wc-block-grid__product {
|
||||
font-size: 0.88889em;
|
||||
}
|
||||
// Change the title font to match headings.
|
||||
.wc-block-grid__product-title,
|
||||
.wc-block-grid__product-onsale {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.wc-block-grid__product-title::before {
|
||||
display: none;
|
||||
}
|
||||
.wc-block-grid__product-onsale {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentytwenty {
|
||||
$twentytwenty-headings: -apple-system, blinkmacsystemfont, "Helvetica Neue", helvetica, sans-serif;
|
||||
$twentytwenty-highlights-color: #cd2653;
|
||||
|
||||
.wc-block-grid__product-link {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.wc-block-grid__product-title {
|
||||
font-family: $twentytwenty-headings;
|
||||
color: #000;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.wc-block-grid__product-price {
|
||||
.wc-block-grid__product-price__value,
|
||||
.woocommerce-Price-amount {
|
||||
font-family: $twentytwenty-headings;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
del {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-rating {
|
||||
.wc-block-grid__product-rating__stars,
|
||||
.star-rating {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-grid__product-add-to-cart > .wp-block-button__link {
|
||||
font-family: $twentytwenty-headings;
|
||||
}
|
||||
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale {
|
||||
background: $twentytwenty-highlights-color;
|
||||
color: #fff;
|
||||
font-family: $twentytwenty-headings;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
// Override style from WC Core that set its position to absolute.
|
||||
// These rulesets can be removed once https://github.com/woocommerce/woocommerce/pull/26516 is released.
|
||||
.wc-block-grid__products .wc-block-component__sale-badge {
|
||||
position: static;
|
||||
}
|
||||
.wc-block-grid__products .wc-block-grid__product-image .wc-block-component__sale-badge {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// These styles are not applied to the All Products atomic block, so it can be positioned normally.
|
||||
.wc-block-grid__products .wc-block-grid__product-onsale:not(.wc-block-component__sale-badge) {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.wc-block-grid__product-onsale {
|
||||
font-size: 0.75em;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1168px) {
|
||||
.wc-block-grid__product-onsale {
|
||||
font-size: 0.85em;
|
||||
padding: 0.75em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { getValidBlockAttributes } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
* HOC that filters given attributes by valid block attribute values, or uses defaults if undefined.
|
||||
*
|
||||
* @param {Object} blockAttributes Component being wrapped.
|
||||
*/
|
||||
const withFilteredAttributes = ( blockAttributes ) => ( OriginalComponent ) => {
|
||||
return ( ownProps ) => {
|
||||
const validBlockAttributes = getValidBlockAttributes(
|
||||
blockAttributes,
|
||||
ownProps
|
||||
);
|
||||
|
||||
return (
|
||||
<OriginalComponent { ...ownProps } { ...validBlockAttributes } />
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withFilteredAttributes;
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Given some block attributes, gets attributes from the dataset or uses defaults.
|
||||
*
|
||||
* @param {Object} blockAttributes Object containing block attributes.
|
||||
* @param {Array} rawAttributes Dataset from DOM.
|
||||
* @return {Array} Array of parsed attributes.
|
||||
*/
|
||||
export const getValidBlockAttributes = ( blockAttributes, rawAttributes ) => {
|
||||
const attributes = [];
|
||||
|
||||
Object.keys( blockAttributes ).forEach( ( key ) => {
|
||||
if ( typeof rawAttributes[ key ] !== 'undefined' ) {
|
||||
switch ( blockAttributes[ key ].type ) {
|
||||
case 'boolean':
|
||||
attributes[ key ] = rawAttributes[ key ] !== 'false';
|
||||
break;
|
||||
case 'number':
|
||||
attributes[ key ] = parseInt( rawAttributes[ key ], 10 );
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
attributes[ key ] = JSON.parse( rawAttributes[ key ] );
|
||||
break;
|
||||
default:
|
||||
attributes[ key ] = rawAttributes[ key ];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
attributes[ key ] = blockAttributes[ key ].default;
|
||||
}
|
||||
} );
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
export default getValidBlockAttributes;
|
|
@ -4,3 +4,4 @@ export * from './address';
|
|||
export * from './shipping-rates';
|
||||
export * from './legacy-events';
|
||||
export * from './render-frontend';
|
||||
export * from './get-valid-block-attributes';
|
||||
|
|
|
@ -4,49 +4,18 @@
|
|||
import { render } from 'react-dom';
|
||||
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
|
||||
|
||||
/**
|
||||
* Given some block attributes, gets attributes from the dataset or uses defaults.
|
||||
*
|
||||
* @param {Object} blockAttributes Object containing block attributes.
|
||||
* @param {Array} dataset Dataset from DOM.
|
||||
* @return {Array} Array of parsed attributes.
|
||||
*/
|
||||
export const getAttributesFromDataset = ( blockAttributes, dataset ) => {
|
||||
const attributes = [];
|
||||
|
||||
Object.keys( blockAttributes ).forEach( ( key ) => {
|
||||
if ( typeof dataset[ key ] !== 'undefined' ) {
|
||||
switch ( blockAttributes[ key ].type ) {
|
||||
case 'boolean':
|
||||
attributes[ key ] = dataset[ key ] !== 'false';
|
||||
break;
|
||||
case 'number':
|
||||
attributes[ key ] = parseInt( dataset[ key ], 10 );
|
||||
break;
|
||||
default:
|
||||
attributes[ key ] = dataset[ key ];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
attributes[ key ] = blockAttributes[ key ].default;
|
||||
}
|
||||
} );
|
||||
|
||||
return attributes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a block component in the place of a specified set of selectors.
|
||||
*
|
||||
* @param {Object} props Render props.
|
||||
* @param {string} props.selector CSS selector to match the elements to replace.
|
||||
* @param {Function} props.Block React component to use as a replacement.
|
||||
* @param {string} props.selector CSS selector to match the elements to replace.
|
||||
* @param {Function} [props.getProps ] Function to generate the props object for the block.
|
||||
* @param {Function} [props.getErrorBoundaryProps] Function to generate the props object for the error boundary.
|
||||
*/
|
||||
export const renderFrontend = ( {
|
||||
selector,
|
||||
Block,
|
||||
selector,
|
||||
getProps = () => {},
|
||||
getErrorBoundaryProps = () => {},
|
||||
} ) => {
|
||||
|
@ -61,7 +30,6 @@ export const renderFrontend = ( {
|
|||
...el.dataset,
|
||||
...props.attributes,
|
||||
};
|
||||
|
||||
el.classList.remove( 'is-loading' );
|
||||
|
||||
render(
|
||||
|
|
|
@ -11,7 +11,7 @@ import { CURRENT_USER_IS_ADMIN } from '@woocommerce/block-settings';
|
|||
import { __experimentalCreateInterpolateElement } from 'wordpress-element';
|
||||
import {
|
||||
renderFrontend,
|
||||
getAttributesFromDataset,
|
||||
getValidBlockAttributes,
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ const CartFrontend = ( props ) => {
|
|||
const getProps = ( el ) => {
|
||||
return {
|
||||
emptyCart: el.innerHTML,
|
||||
attributes: getAttributesFromDataset( blockAttributes, el.dataset ),
|
||||
attributes: getValidBlockAttributes( blockAttributes, el.dataset ),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { CURRENT_USER_IS_ADMIN } from '@woocommerce/block-settings';
|
|||
import { __experimentalCreateInterpolateElement } from 'wordpress-element';
|
||||
import {
|
||||
renderFrontend,
|
||||
getAttributesFromDataset,
|
||||
getValidBlockAttributes,
|
||||
} from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
|
@ -75,7 +75,7 @@ const CheckoutFrontend = ( props ) => {
|
|||
|
||||
const getProps = ( el ) => {
|
||||
return {
|
||||
attributes: getAttributesFromDataset( blockAttributes, el.dataset ),
|
||||
attributes: getValidBlockAttributes( blockAttributes, el.dataset ),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.wc-block-component-express-checkout-continue-rule {
|
||||
.wc-block-components-express-checkout-continue-rule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
@ -160,15 +160,15 @@
|
|||
|
||||
// Loading placeholder state.
|
||||
.wc-block-checkout--is-loading {
|
||||
.wc-block-component-express-checkout,
|
||||
.wc-block-components-express-checkout,
|
||||
.wc-block-checkout__actions button {
|
||||
@include placeholder();
|
||||
@include force-content();
|
||||
}
|
||||
.wc-block-component-express-checkout {
|
||||
.wc-block-components-express-checkout {
|
||||
min-height: 150px;
|
||||
}
|
||||
.wc-block-component-express-checkout-continue-rule span {
|
||||
.wc-block-components-express-checkout-continue-rule span {
|
||||
@include placeholder();
|
||||
@include force-content();
|
||||
width: 150px;
|
||||
|
|
|
@ -34,7 +34,7 @@ class Block extends Component {
|
|||
return (
|
||||
<InnerBlockLayoutContextProvider
|
||||
parentName="woocommerce/all-products"
|
||||
layoutStyleClassPrefix="wc-block-grid"
|
||||
parentClassName="wc-block-grid"
|
||||
>
|
||||
<ProductListContainer
|
||||
attributes={ attributes }
|
||||
|
|
|
@ -204,17 +204,22 @@ class Editor extends Component {
|
|||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
</Tip>
|
||||
<div className="wc-block-grid has-1-columns">
|
||||
<ul className="wc-block-grid__products">
|
||||
<li className="wc-block-grid__product">
|
||||
<ProductDataContextProvider
|
||||
product={ previewProducts[ 0 ] }
|
||||
>
|
||||
<InnerBlocks { ...InnerBlockProps } />
|
||||
</ProductDataContextProvider>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<InnerBlockLayoutContextProvider
|
||||
parentName="woocommerce/all-products"
|
||||
parentClassName="wc-block-grid"
|
||||
>
|
||||
<div className="wc-block-grid has-1-columns">
|
||||
<ul className="wc-block-grid__products">
|
||||
<li className="wc-block-grid__product">
|
||||
<ProductDataContextProvider
|
||||
product={ previewProducts[ 0 ] }
|
||||
>
|
||||
<InnerBlocks { ...InnerBlockProps } />
|
||||
</ProductDataContextProvider>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</InnerBlockLayoutContextProvider>
|
||||
<div className="wc-block-all-products__actions">
|
||||
<Button
|
||||
className="wc-block-all-products__done-button"
|
||||
|
@ -280,23 +285,16 @@ class Editor extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<InnerBlockLayoutContextProvider
|
||||
parentName="woocommerce/all-products"
|
||||
layoutStyleClassPrefix="wc-block-grid"
|
||||
<div
|
||||
className={ getBlockClassName(
|
||||
'wc-block-all-products',
|
||||
attributes
|
||||
) }
|
||||
>
|
||||
<div
|
||||
className={ getBlockClassName(
|
||||
'wc-block-all-products',
|
||||
attributes
|
||||
) }
|
||||
>
|
||||
{ this.getBlockControls() }
|
||||
{ this.getInspectorControls() }
|
||||
{ isEditing
|
||||
? this.renderEditMode()
|
||||
: this.renderViewMode() }
|
||||
</div>
|
||||
</InnerBlockLayoutContextProvider>
|
||||
{ this.getBlockControls() }
|
||||
{ this.getInspectorControls() }
|
||||
{ isEditing ? this.renderEditMode() : this.renderViewMode() }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,12 +2,33 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { withProduct } from '@woocommerce/block-hocs';
|
||||
import {
|
||||
InnerBlockLayoutContextProvider,
|
||||
ProductDataContextProvider,
|
||||
} from '@woocommerce/shared-context';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { BLOCK_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* The Single Product Block.
|
||||
*/
|
||||
const Block = () => {
|
||||
return null;
|
||||
const Block = ( { isLoading, product, children } ) => {
|
||||
const className = 'wc-block-single-product';
|
||||
|
||||
return (
|
||||
<InnerBlockLayoutContextProvider
|
||||
parentName={ BLOCK_NAME }
|
||||
parentClassName={ className }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<ProductDataContextProvider product={ product }>
|
||||
<div className={ className }>{ children }</div>
|
||||
</ProductDataContextProvider>
|
||||
</InnerBlockLayoutContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default withProduct( Block );
|
||||
|
|
|
@ -31,7 +31,7 @@ export const DEFAULT_INNER_BLOCKS = [
|
|||
{},
|
||||
[
|
||||
[ 'woocommerce/product-sale-badge' ],
|
||||
[ 'woocommerce/product-title' ],
|
||||
[ 'woocommerce/product-title', { headingLevel: 1 } ],
|
||||
[ 'woocommerce/product-rating' ],
|
||||
[ 'woocommerce/product-price' ],
|
||||
[ 'woocommerce/product-summary' ],
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
ProductDataContextProvider,
|
||||
} from '@woocommerce/shared-context';
|
||||
import { createBlocksFromTemplate } from '@woocommerce/atomic-utils';
|
||||
import classnames from 'classnames';
|
||||
import { PanelBody, Button } from '@wordpress/components';
|
||||
import { Icon, restore } from '@woocommerce/icons';
|
||||
|
||||
|
@ -26,7 +25,7 @@ import {
|
|||
/**
|
||||
* Component to handle edit mode of the "Single Product Block".
|
||||
*/
|
||||
const LayoutEditor = ( { product, clientId, isLoading } ) => {
|
||||
const LayoutEditor = ( { isLoading, product, clientId } ) => {
|
||||
const baseClassName = 'wc-block-single-product';
|
||||
const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );
|
||||
|
||||
|
@ -41,7 +40,8 @@ const LayoutEditor = ( { product, clientId, isLoading } ) => {
|
|||
return (
|
||||
<InnerBlockLayoutContextProvider
|
||||
parentName={ BLOCK_NAME }
|
||||
layoutStyleClassPrefix={ baseClassName }
|
||||
parentClassName={ baseClassName }
|
||||
isLoading={ isLoading }
|
||||
>
|
||||
<ProductDataContextProvider product={ product }>
|
||||
<InspectorControls>
|
||||
|
@ -66,11 +66,7 @@ const LayoutEditor = ( { product, clientId, isLoading } ) => {
|
|||
</Button>
|
||||
</PanelBody>
|
||||
</InspectorControls>
|
||||
<div
|
||||
className={ classnames( baseClassName, {
|
||||
'is-loading': isLoading,
|
||||
} ) }
|
||||
>
|
||||
<div className={ baseClassName }>
|
||||
<InnerBlocks
|
||||
template={ DEFAULT_INNER_BLOCKS }
|
||||
allowedBlocks={ ALLOWED_INNER_BLOCKS }
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { StoreNoticesProvider } from '@woocommerce/base-context';
|
||||
import {
|
||||
renderFrontend,
|
||||
getAttributesFromDataset,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { getValidBlockAttributes } from '@woocommerce/base-utils';
|
||||
import { renderParentBlock } from '@woocommerce/atomic-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import blockAttributes from './attributes';
|
||||
import { BLOCK_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* Wrapper component to supply the notice provider.
|
||||
|
@ -28,12 +27,13 @@ const FrontendBlock = ( props ) => {
|
|||
|
||||
const getProps = ( el ) => {
|
||||
return {
|
||||
attributes: getAttributesFromDataset( blockAttributes, el.dataset ),
|
||||
attributes: getValidBlockAttributes( blockAttributes, el.dataset ),
|
||||
};
|
||||
};
|
||||
|
||||
renderFrontend( {
|
||||
selector: '.wp-block-woocommerce-single-product',
|
||||
renderParentBlock( {
|
||||
Block: FrontendBlock,
|
||||
blockName: BLOCK_NAME,
|
||||
selector: '.wp-block-woocommerce-single-product',
|
||||
getProps,
|
||||
} );
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Ensure textarea bg color is transparent for block titles.
|
||||
// Some themes (e.g. Twenty Twenty) set a non-white background for the editor, and Gutenberg sets white background for text inputs, creating this issue.
|
||||
// https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/1204
|
||||
.wc-block-component-title {
|
||||
.wc-block-components-title {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ const BlockTitle = ( { className, headingLevel, onChange, heading } ) => {
|
|||
<TagName>
|
||||
<PlainText
|
||||
className={ classnames(
|
||||
'wc-block-component-title',
|
||||
'wc-block-components-title',
|
||||
className
|
||||
) }
|
||||
value={ heading }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { createContext, useContext } from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* This context is a configuration object used for connecting
|
||||
|
@ -13,7 +14,8 @@ import { createContext, useContext } from '@wordpress/element';
|
|||
*/
|
||||
const InnerBlockLayoutContext = createContext( {
|
||||
parentName: '',
|
||||
layoutStyleClassPrefix: '',
|
||||
parentClassName: '',
|
||||
isLoading: false,
|
||||
} );
|
||||
|
||||
export const useInnerBlockLayoutContext = () =>
|
||||
|
@ -21,17 +23,25 @@ export const useInnerBlockLayoutContext = () =>
|
|||
|
||||
export const InnerBlockLayoutContextProvider = ( {
|
||||
parentName = '',
|
||||
layoutStyleClassPrefix = '',
|
||||
parentClassName = '',
|
||||
isLoading = false,
|
||||
children,
|
||||
} ) => {
|
||||
const contextValue = {
|
||||
parentName,
|
||||
layoutStyleClassPrefix,
|
||||
parentClassName,
|
||||
isLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<InnerBlockLayoutContext.Provider value={ contextValue }>
|
||||
{ children }
|
||||
<div
|
||||
className={ classnames( 'wc-block-layout', {
|
||||
'wc-block-layout--is-loading': isLoading,
|
||||
} ) }
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
</InnerBlockLayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -39,5 +49,5 @@ export const InnerBlockLayoutContextProvider = ( {
|
|||
InnerBlockLayoutContextProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
parentName: PropTypes.string,
|
||||
layoutStyleClassPrefix: PropTypes.string,
|
||||
parentClassName: PropTypes.string,
|
||||
};
|
||||
|
|
|
@ -7581,6 +7581,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/domhandler": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/domhandler/-/domhandler-2.4.1.tgz",
|
||||
"integrity": "sha512-cfBw6q6tT5sa1gSPFSRKzF/xxYrrmeiut7E0TxNBObiLSBTuFEHibcfEe3waQPEDbqBsq+ql/TOniw65EyDFMA=="
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||
|
@ -15823,7 +15828,6 @@
|
|||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"entities": "^2.0.0"
|
||||
|
@ -15832,14 +15836,12 @@
|
|||
"domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz",
|
||||
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -15858,8 +15860,7 @@
|
|||
"domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"domexception": {
|
||||
"version": "2.0.1",
|
||||
|
@ -15880,7 +15881,6 @@
|
|||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
|
@ -15889,7 +15889,6 @@
|
|||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
|
@ -16119,8 +16118,7 @@
|
|||
"entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
},
|
||||
"enzyme": {
|
||||
"version": "3.11.0",
|
||||
|
@ -19127,6 +19125,16 @@
|
|||
"integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"html-dom-parser": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-0.2.3.tgz",
|
||||
"integrity": "sha512-GdzE63/U0IQEvcpAz0cUdYx2zQx0Ai+HWvE9TXEgwP27+SymUzKa7iB4DhjYpf2IdNLfTTOBuMS5nxeWOosSMQ==",
|
||||
"requires": {
|
||||
"@types/domhandler": "2.4.1",
|
||||
"domhandler": "2.4.2",
|
||||
"htmlparser2": "3.10.1"
|
||||
}
|
||||
},
|
||||
"html-element-map": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz",
|
||||
|
@ -19171,6 +19179,17 @@
|
|||
"terser": "^4.6.3"
|
||||
}
|
||||
},
|
||||
"html-react-parser": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-0.10.5.tgz",
|
||||
"integrity": "sha512-rtMWZ7KZjd9sO8jyIX7am0vGCIL45tCmctTnassT/BrTGeTaAZ4nQyqoGcx2v+vB8CAY+Q5PZiiV6eOiowq8dQ==",
|
||||
"requires": {
|
||||
"@types/domhandler": "2.4.1",
|
||||
"html-dom-parser": "0.2.3",
|
||||
"react-property": "1.0.1",
|
||||
"style-to-object": "0.3.0"
|
||||
}
|
||||
},
|
||||
"html-tags": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
|
||||
|
@ -19236,7 +19255,6 @@
|
|||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^1.3.1",
|
||||
"domhandler": "^2.3.0",
|
||||
|
@ -19250,7 +19268,6 @@
|
|||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
@ -19660,8 +19677,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -19672,8 +19688,7 @@
|
|||
"inline-style-parser": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
|
||||
"integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "7.1.0",
|
||||
|
@ -31356,6 +31371,11 @@
|
|||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-property/-/react-property-1.0.1.tgz",
|
||||
"integrity": "sha512-1tKOwxFn3dXVomH6pM9IkLkq2Y8oh+fh/lYW3MJ/B03URswUTqttgckOlbxY2XHF3vPG6uanSc4dVsLW/wk3wQ=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
|
||||
|
@ -34555,7 +34575,6 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
},
|
||||
|
@ -34563,8 +34582,7 @@
|
|||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -34672,7 +34690,6 @@
|
|||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
|
||||
"integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inline-style-parser": "0.1.1"
|
||||
}
|
||||
|
@ -36559,8 +36576,7 @@
|
|||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"util.promisify": {
|
||||
"version": "1.0.1",
|
||||
|
@ -37877,7 +37893,7 @@
|
|||
}
|
||||
},
|
||||
"woocommerce": {
|
||||
"version": "git+https://github.com/woocommerce/woocommerce.git#5213b05d57b0e0e703eaed3519694fa98a51516f",
|
||||
"version": "git+https://github.com/woocommerce/woocommerce.git#5a746a0775d8407127e314ffde73d5ebb15284bf",
|
||||
"from": "git+https://github.com/woocommerce/woocommerce.git#release/4.1",
|
||||
"dev": true
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"sideEffects": [
|
||||
"*.css",
|
||||
"*.scss",
|
||||
"./assets/js/atomic/blocks/product/**",
|
||||
"./assets/js/atomic/blocks/**",
|
||||
"./assets/js/filters/**",
|
||||
"./assets/js/settings/blocks/**",
|
||||
"./assets/js/middleware/**"
|
||||
|
@ -165,6 +165,7 @@
|
|||
"config": "3.3.1",
|
||||
"dinero.js": "1.8.1",
|
||||
"downshift": "4.1.0",
|
||||
"html-react-parser": "^0.10.5",
|
||||
"jest-environment-jsdom-sixteen": "1.0.3",
|
||||
"react-number-format": "4.4.1",
|
||||
"reakit": "1.0.2",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/**
|
||||
* Atomic blocks.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* AtomicBlock class.
|
||||
*/
|
||||
class AtomicBlock extends AbstractBlock {
|
||||
/**
|
||||
* Inject attributes and block name.
|
||||
*
|
||||
* @param array|\WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function render( $attributes = [], $content = '' ) {
|
||||
$block_attributes = is_a( $attributes, '\WP_Block' ) ? $attributes->attributes : $attributes;
|
||||
return $this->inject_html_data_attributes( $content, $block_attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block type with WordPress.
|
||||
*/
|
||||
public function register_block_type() {
|
||||
register_block_type(
|
||||
$this->namespace . '/' . $this->block_name,
|
||||
array(
|
||||
'render_callback' => array( $this, 'render' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts block attributes to HTML data attributes.
|
||||
*
|
||||
* @param array $attributes Key value pairs of attributes.
|
||||
* @return string Rendered HTML attributes.
|
||||
*/
|
||||
protected function get_html_data_attributes( array $attributes ) {
|
||||
$data = parent::get_html_data_attributes( $attributes );
|
||||
return trim( $data . ' data-block-name="' . esc_attr( $this->namespace . '/' . $this->block_name ) . '"' );
|
||||
}
|
||||
}
|
|
@ -172,8 +172,8 @@ class Checkout extends AbstractBlock {
|
|||
return '
|
||||
<div class="wc-block-skeleton wc-block-sidebar-layout wc-block-checkout wc-block-checkout--is-loading wc-block-checkout--skeleton hidden" aria-hidden="true">
|
||||
<div class="wc-block-main wc-block-checkout__main">
|
||||
<div class="wc-block-component-express-checkout"></div>
|
||||
<div class="wc-block-component-express-checkout-continue-rule"><span></span></div>
|
||||
<div class="wc-block-components-express-checkout"></div>
|
||||
<div class="wc-block-components-express-checkout-continue-rule"><span></span></div>
|
||||
<form class="wc-block-checkout-form">
|
||||
<fieldset class="wc-block-checkout__contact-fields wc-block-checkout-step">
|
||||
<span></span>
|
||||
|
|
|
@ -83,6 +83,26 @@ class Library {
|
|||
$instance = new $class();
|
||||
$instance->register_block_type();
|
||||
}
|
||||
self::register_atomic_blocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register atomic blocks on the PHP side.
|
||||
*/
|
||||
protected static function register_atomic_blocks() {
|
||||
$atomic_blocks = [
|
||||
'product-title',
|
||||
'product-button',
|
||||
'product-image',
|
||||
'product-price',
|
||||
'product-rating',
|
||||
'product-sale-badge',
|
||||
'product-summary',
|
||||
];
|
||||
foreach ( $atomic_blocks as $atomic_block ) {
|
||||
$instance = new \Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock( $atomic_block );
|
||||
$instance->register_block_type();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue