rebase code on oct-10-2023
This commit is contained in:
@@ -54,24 +54,6 @@ registerBlockComponent( {
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-rating-counter',
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "product-rating-counter" */ './product-elements/rating-counter/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-average-rating',
|
||||
component: lazy( () =>
|
||||
import(
|
||||
/* webpackChunkName: "product-average-rating" */ './product-elements/average-rating/block'
|
||||
)
|
||||
),
|
||||
} );
|
||||
|
||||
registerBlockComponent( {
|
||||
blockName: 'woocommerce/product-button',
|
||||
component: lazy( () =>
|
||||
|
||||
@@ -6,8 +6,6 @@ import './product-elements/price';
|
||||
import './product-elements/image';
|
||||
import './product-elements/rating';
|
||||
import './product-elements/rating-stars';
|
||||
import './product-elements/rating-counter';
|
||||
import './product-elements/average-rating';
|
||||
import './product-elements/button';
|
||||
import './product-elements/summary';
|
||||
import './product-elements/sale-badge';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "woocommerce/product-average-rating",
|
||||
"version": "1.0.0",
|
||||
"title": "Product Average Rating (Beta)",
|
||||
"description": "Display the average rating of a product",
|
||||
"attributes": {
|
||||
"textAlign": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce" ],
|
||||
"ancestor": [ "woocommerce/single-product" ],
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useProductDataContext } from '@woocommerce/shared-context';
|
||||
import { useStyleProps } from '@woocommerce/base-hooks';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { withProductDataContext } from '@woocommerce/shared-hocs';
|
||||
|
||||
type ProductAverageRatingProps = {
|
||||
className?: string;
|
||||
textAlign?: string;
|
||||
};
|
||||
|
||||
export const Block = ( props: ProductAverageRatingProps ): JSX.Element => {
|
||||
const { textAlign } = props;
|
||||
const styleProps = useStyleProps( props );
|
||||
const { product } = useProductDataContext();
|
||||
|
||||
const className = classnames(
|
||||
styleProps.className,
|
||||
'wc-block-components-product-average-rating',
|
||||
{
|
||||
[ `has-text-align-${ textAlign }` ]: textAlign,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ className } style={ styleProps.style }>
|
||||
{ Number( product.average_rating ) > 0
|
||||
? product.average_rating
|
||||
: __( 'No ratings', 'woo-gutenberg-products-block' ) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withProductDataContext( Block );
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
AlignmentToolbar,
|
||||
BlockControls,
|
||||
useBlockProps,
|
||||
} from '@wordpress/block-editor';
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
|
||||
export interface BlockAttributes {
|
||||
textAlign: string;
|
||||
}
|
||||
|
||||
const Edit = ( props: BlockEditProps< BlockAttributes > ): JSX.Element => {
|
||||
const { attributes, setAttributes } = props;
|
||||
const blockProps = useBlockProps( {
|
||||
className: 'wp-block-woocommerce-product-average-rating',
|
||||
} );
|
||||
|
||||
return (
|
||||
<>
|
||||
<BlockControls>
|
||||
<AlignmentToolbar
|
||||
value={ attributes.textAlign }
|
||||
onChange={ ( newAlign ) => {
|
||||
setAttributes( { textAlign: newAlign || '' } );
|
||||
} }
|
||||
/>
|
||||
</BlockControls>
|
||||
<div { ...blockProps }>
|
||||
<Block { ...attributes } />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, starHalf } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import { supports } from './support';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ starHalf }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
supports,
|
||||
edit,
|
||||
} );
|
||||
@@ -1,26 +0,0 @@
|
||||
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
|
||||
export const supports = {
|
||||
...( isFeaturePluginBuild() && {
|
||||
color: {
|
||||
text: true,
|
||||
background: true,
|
||||
__experimentalSkipSerialization: true,
|
||||
},
|
||||
spacing: {
|
||||
margin: true,
|
||||
padding: true,
|
||||
__experimentalSkipSerialization: true,
|
||||
},
|
||||
typography: {
|
||||
fontSize: true,
|
||||
__experimentalFontWeight: true,
|
||||
__experimentalSkipSerialization: true,
|
||||
},
|
||||
__experimentalSelector: '.wc-block-components-product-average-rating',
|
||||
} ),
|
||||
};
|
||||
@@ -34,7 +34,6 @@
|
||||
"background": false,
|
||||
"link": true
|
||||
},
|
||||
"interactivity": true,
|
||||
"html": false,
|
||||
"typography": {
|
||||
"fontSize": true,
|
||||
@@ -58,9 +57,6 @@
|
||||
"label": "Outline"
|
||||
}
|
||||
],
|
||||
"viewScript": [
|
||||
"wc-product-button-interactivity-frontend"
|
||||
],
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
}
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
|
||||
import { store as interactivityStore } from '@woocommerce/interactivity';
|
||||
import { dispatch, select, subscribe } from '@wordpress/data';
|
||||
import { Cart } from '@woocommerce/type-defs/cart';
|
||||
import { createRoot } from '@wordpress/element';
|
||||
import NoticeBanner from '@woocommerce/base-components/notice-banner';
|
||||
|
||||
type Context = {
|
||||
woocommerce: {
|
||||
isLoading: boolean;
|
||||
addToCartText: string;
|
||||
productId: number;
|
||||
displayViewCart: boolean;
|
||||
quantityToAdd: number;
|
||||
temporaryNumberOfItems: number;
|
||||
animationStatus: AnimationStatus;
|
||||
};
|
||||
};
|
||||
|
||||
enum AnimationStatus {
|
||||
IDLE = 'IDLE',
|
||||
SLIDE_OUT = 'SLIDE-OUT',
|
||||
SLIDE_IN = 'SLIDE-IN',
|
||||
}
|
||||
|
||||
type State = {
|
||||
woocommerce: {
|
||||
cart: Cart | undefined;
|
||||
inTheCartText: string;
|
||||
};
|
||||
};
|
||||
|
||||
type Store = {
|
||||
state: State;
|
||||
context: Context;
|
||||
selectors: any;
|
||||
ref: HTMLElement;
|
||||
};
|
||||
|
||||
const storeNoticeClass = '.wc-block-store-notices';
|
||||
|
||||
const createNoticeContainer = () => {
|
||||
const noticeContainer = document.createElement( 'div' );
|
||||
noticeContainer.classList.add( storeNoticeClass.replace( '.', '' ) );
|
||||
return noticeContainer;
|
||||
};
|
||||
|
||||
const injectNotice = ( domNode: Element, errorMessage: string ) => {
|
||||
const root = createRoot( domNode );
|
||||
|
||||
root.render(
|
||||
<NoticeBanner status="error" onRemove={ () => root.unmount() }>
|
||||
{ errorMessage }
|
||||
</NoticeBanner>
|
||||
);
|
||||
|
||||
domNode?.scrollIntoView( {
|
||||
behavior: 'smooth',
|
||||
inline: 'nearest',
|
||||
} );
|
||||
};
|
||||
|
||||
const getProductById = ( cartState: Cart | undefined, productId: number ) => {
|
||||
return cartState?.items.find( ( item ) => item.id === productId );
|
||||
};
|
||||
|
||||
const getTextButton = ( {
|
||||
addToCartText,
|
||||
inTheCartText,
|
||||
numberOfItems,
|
||||
}: {
|
||||
addToCartText: string;
|
||||
inTheCartText: string;
|
||||
numberOfItems: number;
|
||||
} ) => {
|
||||
if ( numberOfItems === 0 ) {
|
||||
return addToCartText;
|
||||
}
|
||||
return inTheCartText.replace( '###', numberOfItems.toString() );
|
||||
};
|
||||
|
||||
const productButtonSelectors = {
|
||||
woocommerce: {
|
||||
addToCartText: ( store: Store ) => {
|
||||
const { context, state, selectors } = store;
|
||||
|
||||
// We use the temporary number of items when there's no animation, or the
|
||||
// second part of the animation hasn't started.
|
||||
if (
|
||||
context.woocommerce.animationStatus === AnimationStatus.IDLE ||
|
||||
context.woocommerce.animationStatus ===
|
||||
AnimationStatus.SLIDE_OUT
|
||||
) {
|
||||
return getTextButton( {
|
||||
addToCartText: context.woocommerce.addToCartText,
|
||||
inTheCartText: state.woocommerce.inTheCartText,
|
||||
numberOfItems: context.woocommerce.temporaryNumberOfItems,
|
||||
} );
|
||||
}
|
||||
|
||||
return getTextButton( {
|
||||
addToCartText: context.woocommerce.addToCartText,
|
||||
inTheCartText: state.woocommerce.inTheCartText,
|
||||
numberOfItems:
|
||||
selectors.woocommerce.numberOfItemsInTheCart( store ),
|
||||
} );
|
||||
},
|
||||
displayViewCart: ( store: Store ) => {
|
||||
const { context, selectors } = store;
|
||||
if ( ! context.woocommerce.displayViewCart ) return false;
|
||||
if ( ! selectors.woocommerce.hasCartLoaded( store ) ) {
|
||||
return context.woocommerce.temporaryNumberOfItems > 0;
|
||||
}
|
||||
return selectors.woocommerce.numberOfItemsInTheCart( store ) > 0;
|
||||
},
|
||||
hasCartLoaded: ( { state }: { state: State } ) => {
|
||||
return state.woocommerce.cart !== undefined;
|
||||
},
|
||||
numberOfItemsInTheCart: ( { state, context }: Store ) => {
|
||||
const product = getProductById(
|
||||
state.woocommerce.cart,
|
||||
context.woocommerce.productId
|
||||
);
|
||||
return product?.quantity || 0;
|
||||
},
|
||||
slideOutAnimation: ( { context }: Store ) =>
|
||||
context.woocommerce.animationStatus === AnimationStatus.SLIDE_OUT,
|
||||
slideInAnimation: ( { context }: Store ) =>
|
||||
context.woocommerce.animationStatus === AnimationStatus.SLIDE_IN,
|
||||
},
|
||||
};
|
||||
|
||||
interactivityStore(
|
||||
// @ts-expect-error: Store function isn't typed.
|
||||
{
|
||||
selectors: productButtonSelectors,
|
||||
actions: {
|
||||
woocommerce: {
|
||||
addToCart: async ( store: Store ) => {
|
||||
const { context, selectors, ref } = store;
|
||||
|
||||
if ( ! ref.classList.contains( 'ajax_add_to_cart' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.woocommerce.isLoading = true;
|
||||
|
||||
// Allow 3rd parties to validate and quit early.
|
||||
// https://github.com/woocommerce/woocommerce/blob/154dd236499d8a440edf3cde712511b56baa8e45/plugins/woocommerce/client/legacy/js/frontend/add-to-cart.js/#L74-L77
|
||||
const event = new CustomEvent(
|
||||
'should_send_ajax_request.adding_to_cart',
|
||||
{ detail: [ ref ], cancelable: true }
|
||||
);
|
||||
const shouldSendRequest =
|
||||
document.body.dispatchEvent( event );
|
||||
|
||||
if ( shouldSendRequest === false ) {
|
||||
const ajaxNotSentEvent = new CustomEvent(
|
||||
'ajax_request_not_sent.adding_to_cart',
|
||||
{ detail: [ false, false, ref ] }
|
||||
);
|
||||
document.body.dispatchEvent( ajaxNotSentEvent );
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
await dispatch( storeKey ).addItemToCart(
|
||||
context.woocommerce.productId,
|
||||
context.woocommerce.quantityToAdd
|
||||
);
|
||||
|
||||
// After the cart has been updated, sync the temporary number of
|
||||
// items again.
|
||||
context.woocommerce.temporaryNumberOfItems =
|
||||
selectors.woocommerce.numberOfItemsInTheCart(
|
||||
store
|
||||
);
|
||||
} catch ( error ) {
|
||||
const storeNoticeBlock =
|
||||
document.querySelector( storeNoticeClass );
|
||||
|
||||
if ( ! storeNoticeBlock ) {
|
||||
document
|
||||
.querySelector( '.entry-content' )
|
||||
?.prepend( createNoticeContainer() );
|
||||
}
|
||||
|
||||
const domNode =
|
||||
storeNoticeBlock ??
|
||||
document.querySelector( storeNoticeClass );
|
||||
|
||||
if ( domNode ) {
|
||||
injectNotice( domNode, error.message );
|
||||
}
|
||||
|
||||
// We don't care about errors blocking execution, but will
|
||||
// console.error for troubleshooting.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( error );
|
||||
} finally {
|
||||
context.woocommerce.displayViewCart = true;
|
||||
context.woocommerce.isLoading = false;
|
||||
}
|
||||
},
|
||||
handleAnimationEnd: (
|
||||
store: Store & { event: AnimationEvent }
|
||||
) => {
|
||||
const { event, context, selectors } = store;
|
||||
if ( event.animationName === 'slideOut' ) {
|
||||
// When the first part of the animation (slide-out) ends, we move
|
||||
// to the second part (slide-in).
|
||||
context.woocommerce.animationStatus =
|
||||
AnimationStatus.SLIDE_IN;
|
||||
} else if ( event.animationName === 'slideIn' ) {
|
||||
// When the second part of the animation ends, we update the
|
||||
// temporary number of items to sync it with the cart and reset the
|
||||
// animation status so it can be triggered again.
|
||||
context.woocommerce.temporaryNumberOfItems =
|
||||
selectors.woocommerce.numberOfItemsInTheCart(
|
||||
store
|
||||
);
|
||||
context.woocommerce.animationStatus =
|
||||
AnimationStatus.IDLE;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
effects: {
|
||||
woocommerce: {
|
||||
startAnimation: ( store: Store ) => {
|
||||
const { context, selectors } = store;
|
||||
// We start the animation if the cart has loaded, the temporary number
|
||||
// of items is out of sync with the number of items in the cart, the
|
||||
// button is not loading (because that means the user started the
|
||||
// interaction) and the animation hasn't started yet.
|
||||
if (
|
||||
selectors.woocommerce.hasCartLoaded( store ) &&
|
||||
context.woocommerce.temporaryNumberOfItems !==
|
||||
selectors.woocommerce.numberOfItemsInTheCart(
|
||||
store
|
||||
) &&
|
||||
! context.woocommerce.isLoading &&
|
||||
context.woocommerce.animationStatus ===
|
||||
AnimationStatus.IDLE
|
||||
) {
|
||||
context.woocommerce.animationStatus =
|
||||
AnimationStatus.SLIDE_OUT;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
afterLoad: ( store: Store ) => {
|
||||
const { state, selectors } = store;
|
||||
// Subscribe to changes in Cart data.
|
||||
subscribe( () => {
|
||||
const cartData = select( storeKey ).getCartData();
|
||||
const isResolutionFinished =
|
||||
select( storeKey ).hasFinishedResolution( 'getCartData' );
|
||||
if ( isResolutionFinished ) {
|
||||
state.woocommerce.cart = cartData;
|
||||
}
|
||||
}, storeKey );
|
||||
|
||||
// This selector triggers a fetch of the Cart data. It is done in a
|
||||
// `requestIdleCallback` to avoid potential performance issues.
|
||||
requestIdleCallback( () => {
|
||||
if ( ! selectors.woocommerce.hasCartLoaded( store ) ) {
|
||||
select( storeKey ).getCartData();
|
||||
}
|
||||
} );
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -1,79 +1,15 @@
|
||||
.wp-block-button.wc-block-components-product-button {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: $gap-small;
|
||||
|
||||
.wp-block-button__link {
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
// Set button font size and padding so it inherits from parent.
|
||||
padding: 0.5em 1em;
|
||||
font-size: 1em;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&.loading::after {
|
||||
font-family: WooCommerce; /* stylelint-disable-line */
|
||||
content: "\e031";
|
||||
animation: spin 2s linear infinite;
|
||||
margin-left: 0.5em;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
a[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(90%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translate(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-product-button__button {
|
||||
border-style: none;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
|
||||
&.wc-block-slide-out {
|
||||
animation: slideOut 0.1s linear 1 normal forwards;
|
||||
}
|
||||
&.wc-block-slide-in {
|
||||
animation: slideIn 0.1s linear 1 normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-product-button__button--placeholder {
|
||||
|
||||
@@ -54,7 +54,7 @@ const Edit = ( {
|
||||
const blockProps = useBlockProps( { style: { width, height } } );
|
||||
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
|
||||
const isBlockThemeEnabled = getSettingWithCoercion(
|
||||
'isBlockThemeEnabled',
|
||||
'is_block_theme_enabled',
|
||||
false,
|
||||
isBoolean
|
||||
);
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
"version": "1.0.0",
|
||||
"icon": "info",
|
||||
"title": "Product Details",
|
||||
"description": "Display a product's description, attributes, and reviews.",
|
||||
"description": "Display a product’s description, attributes, and reviews.",
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce" ],
|
||||
"supports": {
|
||||
"align": true,
|
||||
"spacing": {
|
||||
"margin": true
|
||||
}
|
||||
"align": true
|
||||
},
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"name": "woocommerce/product-rating-counter",
|
||||
"version": "1.0.0",
|
||||
"title": "Product Rating Counter",
|
||||
"description": "Display the review count of a product",
|
||||
"attributes": {
|
||||
"productId": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"isDescendentOfQueryLoop": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"textAlign": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"isDescendentOfSingleProductBlock": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"isDescendentOfSingleProductTemplate": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"usesContext": [ "query", "queryId", "postId" ],
|
||||
"category": "woocommerce",
|
||||
"keywords": [ "WooCommerce" ],
|
||||
"supports": {
|
||||
"align": true
|
||||
},
|
||||
"ancestor": [ "woocommerce/single-product" ],
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, _n, sprintf } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
useInnerBlockLayoutContext,
|
||||
useProductDataContext,
|
||||
} from '@woocommerce/shared-context';
|
||||
import { useStyleProps } from '@woocommerce/base-hooks';
|
||||
import { withProductDataContext } from '@woocommerce/shared-hocs';
|
||||
import { isNumber, ProductResponseItem } from '@woocommerce/types';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
|
||||
const getRatingCount = ( product: ProductResponseItem ) => {
|
||||
const count = isNumber( product.review_count )
|
||||
? product.review_count
|
||||
: parseInt( product.review_count, 10 );
|
||||
|
||||
return Number.isFinite( count ) && count > 0 ? count : 0;
|
||||
};
|
||||
|
||||
const ReviewsCount = ( props: { reviews: number } ): JSX.Element => {
|
||||
const { reviews } = props;
|
||||
|
||||
const reviewsCount = reviews
|
||||
? sprintf(
|
||||
/* translators: %s is referring to the total of reviews for a product */
|
||||
_n(
|
||||
'(%s customer review)',
|
||||
'(%s customer reviews)',
|
||||
reviews,
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
reviews
|
||||
)
|
||||
: __( '(X customer reviews)', 'woo-gutenberg-products-block' );
|
||||
|
||||
return (
|
||||
<span className="wc-block-components-product-rating-counter__reviews_count">
|
||||
<Disabled>
|
||||
<a href="/">{ reviewsCount }</a>
|
||||
</Disabled>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
type ProductRatingCounterProps = {
|
||||
className?: string;
|
||||
textAlign?: string;
|
||||
isDescendentOfSingleProductBlock: boolean;
|
||||
isDescendentOfQueryLoop: boolean;
|
||||
postId: number;
|
||||
productId: number;
|
||||
shouldDisplayMockedReviewsWhenProductHasNoReviews: boolean;
|
||||
};
|
||||
|
||||
export const Block = (
|
||||
props: ProductRatingCounterProps
|
||||
): JSX.Element | undefined => {
|
||||
const { textAlign, shouldDisplayMockedReviewsWhenProductHasNoReviews } =
|
||||
props;
|
||||
const styleProps = useStyleProps( props );
|
||||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const { product } = useProductDataContext();
|
||||
const reviews = getRatingCount( product );
|
||||
|
||||
const className = classnames(
|
||||
styleProps.className,
|
||||
'wc-block-components-product-rating-counter',
|
||||
{
|
||||
[ `${ parentClassName }__product-rating` ]: parentClassName,
|
||||
[ `has-text-align-${ textAlign }` ]: textAlign,
|
||||
}
|
||||
);
|
||||
|
||||
if ( reviews || shouldDisplayMockedReviewsWhenProductHasNoReviews ) {
|
||||
return (
|
||||
<div className={ className } style={ styleProps.style }>
|
||||
<div className="wc-block-components-product-rating-counter__container">
|
||||
<ReviewsCount reviews={ reviews } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default withProductDataContext( Block );
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
AlignmentToolbar,
|
||||
BlockControls,
|
||||
useBlockProps,
|
||||
} from '@wordpress/block-editor';
|
||||
import type { BlockEditProps } from '@wordpress/blocks';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import { BlockAttributes } from './types';
|
||||
import { useIsDescendentOfSingleProductBlock } from '../shared/use-is-descendent-of-single-product-block';
|
||||
import { useIsDescendentOfSingleProductTemplate } from '../shared/use-is-descendent-of-single-product-template';
|
||||
|
||||
const Edit = (
|
||||
props: BlockEditProps< BlockAttributes > & { context: Context }
|
||||
): JSX.Element => {
|
||||
const { attributes, setAttributes, context } = props;
|
||||
const blockProps = useBlockProps( {
|
||||
className: 'wp-block-woocommerce-product-rating-counter',
|
||||
} );
|
||||
const blockAttrs = {
|
||||
...attributes,
|
||||
...context,
|
||||
shouldDisplayMockedReviewsWhenProductHasNoReviews: true,
|
||||
};
|
||||
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
|
||||
const { isDescendentOfSingleProductBlock } =
|
||||
useIsDescendentOfSingleProductBlock( {
|
||||
blockClientId: blockProps?.id,
|
||||
} );
|
||||
let { isDescendentOfSingleProductTemplate } =
|
||||
useIsDescendentOfSingleProductTemplate();
|
||||
|
||||
if ( isDescendentOfQueryLoop || isDescendentOfSingleProductBlock ) {
|
||||
isDescendentOfSingleProductTemplate = false;
|
||||
}
|
||||
|
||||
useEffect( () => {
|
||||
setAttributes( {
|
||||
isDescendentOfQueryLoop,
|
||||
isDescendentOfSingleProductBlock,
|
||||
isDescendentOfSingleProductTemplate,
|
||||
} );
|
||||
}, [
|
||||
setAttributes,
|
||||
isDescendentOfQueryLoop,
|
||||
isDescendentOfSingleProductBlock,
|
||||
isDescendentOfSingleProductTemplate,
|
||||
] );
|
||||
|
||||
return (
|
||||
<>
|
||||
<BlockControls>
|
||||
<AlignmentToolbar
|
||||
value={ attributes.textAlign }
|
||||
onChange={ ( newAlign ) => {
|
||||
setAttributes( { textAlign: newAlign || '' } );
|
||||
} }
|
||||
/>
|
||||
</BlockControls>
|
||||
<div { ...blockProps }>
|
||||
<Block { ...blockAttrs } />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Edit;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, starFilled } from '@wordpress/icons';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import { supports } from './support';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ starFilled }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
supports,
|
||||
edit,
|
||||
} );
|
||||
@@ -1,24 +0,0 @@
|
||||
/* eslint-disable @wordpress/no-unsafe-wp-apis */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
|
||||
export const supports = {
|
||||
...( isFeaturePluginBuild() && {
|
||||
color: {
|
||||
text: false,
|
||||
background: false,
|
||||
link: true,
|
||||
},
|
||||
spacing: {
|
||||
margin: true,
|
||||
padding: true,
|
||||
},
|
||||
typography: {
|
||||
fontSize: true,
|
||||
__experimentalSkipSerialization: true,
|
||||
},
|
||||
__experimentalSelector: '.wc-block-components-product-rating-counter',
|
||||
} ),
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface BlockAttributes {
|
||||
productId: number;
|
||||
isDescendentOfQueryLoop: boolean;
|
||||
isDescendentOfSingleProductBlock: boolean;
|
||||
isDescendentOfSingleProductTemplate: boolean;
|
||||
textAlign: string;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "woocommerce/product-rating-stars",
|
||||
"version": "1.0.0",
|
||||
"icon": "info",
|
||||
"title": "Product Rating Stars",
|
||||
"description": "Display the average rating of a product with stars",
|
||||
"attributes": {
|
||||
@@ -31,7 +32,6 @@
|
||||
"supports": {
|
||||
"align": true
|
||||
},
|
||||
"ancestor": [ "woocommerce/single-product" ],
|
||||
"textdomain": "woocommerce",
|
||||
"apiVersion": 2,
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json"
|
||||
|
||||
@@ -50,14 +50,12 @@ const NoRating = ( { parentClassName }: { parentClassName: string } ) => {
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
'wc-block-components-product-rating-stars__norating-container',
|
||||
`${ parentClassName }-product-rating-stars__norating-container`
|
||||
'wc-block-components-product-rating__norating-container',
|
||||
`${ parentClassName }-product-rating__norating-container`
|
||||
) }
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'wc-block-components-product-rating-stars__norating'
|
||||
}
|
||||
className={ 'wc-block-components-product-rating__norating' }
|
||||
role="img"
|
||||
>
|
||||
<span style={ starStyle } />
|
||||
@@ -94,8 +92,8 @@ const Rating = ( props: RatingProps ): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className={ classnames(
|
||||
'wc-block-components-product-rating-stars__stars',
|
||||
`${ parentClassName }__product-rating-stars__stars`
|
||||
'wc-block-components-product-rating__stars',
|
||||
`${ parentClassName }__product-rating__stars`
|
||||
) }
|
||||
role="img"
|
||||
aria-label={ ratingText }
|
||||
@@ -126,7 +124,7 @@ export const Block = ( props: ProductRatingStarsProps ): JSX.Element | null => {
|
||||
|
||||
const className = classnames(
|
||||
styleProps.className,
|
||||
'wc-block-components-product-rating-stars',
|
||||
'wc-block-components-product-rating',
|
||||
{
|
||||
[ `${ parentClassName }__product-rating` ]: parentClassName,
|
||||
[ `has-text-align-${ textAlign }` ]: textAlign,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Icon, starFilled } from '@wordpress/icons';
|
||||
|
||||
export const BLOCK_TITLE: string = __(
|
||||
'Product Rating Stars',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
export const BLOCK_ICON: JSX.Element = (
|
||||
<Icon
|
||||
icon={ starFilled }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
);
|
||||
export const BLOCK_DESCRIPTION: string = __(
|
||||
'Display the average rating of a product with stars',
|
||||
'woo-gutenberg-products-block'
|
||||
);
|
||||
@@ -1,25 +1,36 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { registerBlockType } from '@wordpress/blocks';
|
||||
import { Icon, starFilled } from '@wordpress/icons';
|
||||
import { BlockConfiguration } from '@wordpress/blocks';
|
||||
import { registerBlockSingleProductTemplate } from '@woocommerce/atomic-utils';
|
||||
import { isExperimentalBuild } from '@woocommerce/block-settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import metadata from './block.json';
|
||||
import edit from './edit';
|
||||
import sharedConfig from '../shared/config';
|
||||
import { supports } from './support';
|
||||
import { BLOCK_ICON } from './constants';
|
||||
|
||||
registerBlockType( metadata, {
|
||||
icon: {
|
||||
src: (
|
||||
<Icon
|
||||
icon={ starFilled }
|
||||
className="wc-block-editor-components-block-icon"
|
||||
/>
|
||||
),
|
||||
},
|
||||
const blockConfig: BlockConfiguration = {
|
||||
...sharedConfig,
|
||||
ancestor: [
|
||||
'woocommerce/all-products',
|
||||
'woocommerce/single-product',
|
||||
'core/post-template',
|
||||
'woocommerce/product-template',
|
||||
],
|
||||
icon: { src: BLOCK_ICON },
|
||||
supports,
|
||||
edit,
|
||||
} );
|
||||
};
|
||||
|
||||
if ( isExperimentalBuild() ) {
|
||||
registerBlockSingleProductTemplate( {
|
||||
blockName: 'woocommerce/product-rating-stars',
|
||||
blockMetadata: metadata,
|
||||
blockSettings: blockConfig,
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.wc-block-components-product-rating-stars {
|
||||
.wc-block-components-product-rating {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||
font-family: star;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
|
||||
&::before {
|
||||
content: "\53\53\53\53\53";
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { isFeaturePluginBuild } from '@woocommerce/block-settings';
|
||||
import { __experimentalGetSpacingClassesAndStyles } from '@wordpress/block-editor';
|
||||
|
||||
export const supports = {
|
||||
...( isFeaturePluginBuild() && {
|
||||
@@ -22,4 +23,10 @@ export const supports = {
|
||||
},
|
||||
__experimentalSelector: '.wc-block-components-product-rating',
|
||||
} ),
|
||||
...( ! isFeaturePluginBuild() &&
|
||||
typeof __experimentalGetSpacingClassesAndStyles === 'function' && {
|
||||
spacing: {
|
||||
margin: true,
|
||||
},
|
||||
} ),
|
||||
};
|
||||
|
||||
@@ -11,11 +11,6 @@ import { useStyleProps } from '@woocommerce/base-hooks';
|
||||
import { withProductDataContext } from '@woocommerce/shared-hocs';
|
||||
import { isNumber, ProductResponseItem } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
type RatingProps = {
|
||||
reviews: number;
|
||||
rating: number;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
.wc-block-components-product-rating {
|
||||
.wc-block-components-product-rating__container {
|
||||
> * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-product-rating__stars {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user