diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating-stars/block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating-stars/block.tsx
index d7ccf760a4b..f6d7f094da6 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating-stars/block.tsx
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating-stars/block.tsx
@@ -1,114 +1,26 @@
/**
* External dependencies
*/
-import { __, _n, sprintf } from '@wordpress/i18n';
-import clsx from 'clsx';
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 {
+ ProductRating,
+ getAverageRating,
+ getRatingCount,
+} from '@woocommerce/editor-components/product-rating';
/**
* Internal dependencies
*/
import './style.scss';
-type RatingProps = {
- reviews: number;
- rating: number;
- parentClassName?: string;
-};
-
-const getAverageRating = (
- product: Omit< ProductResponseItem, 'average_rating' > & {
- average_rating: string;
- }
-) => {
- const rating = parseFloat( product.average_rating );
-
- return Number.isFinite( rating ) && rating > 0 ? rating : 0;
-};
-
-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 getStarStyle = ( rating: number ) => ( {
- width: ( rating / 5 ) * 100 + '%',
-} );
-
-const NoRating = ( { parentClassName }: { parentClassName: string } ) => {
- const starStyle = getStarStyle( 0 );
-
- return (
-
-
-
-
-
{ __( 'No Reviews', 'woocommerce' ) }
-
- );
-};
-
-const Rating = ( props: RatingProps ): JSX.Element => {
- const { rating, reviews, parentClassName } = props;
-
- const starStyle = getStarStyle( rating );
-
- const ratingText = sprintf(
- /* translators: %f is referring to the average rating value */
- __( 'Rated %f out of 5', 'woocommerce' ),
- rating
- );
-
- const ratingHTML = {
- __html: sprintf(
- /* translators: %1$s is referring to the average rating value, %2$s is referring to the number of ratings */
- _n(
- 'Rated %1$s out of 5 based on %2$s customer rating',
- 'Rated %1$s out of 5 based on %2$s customer ratings',
- reviews,
- 'woocommerce'
- ),
- sprintf( '%f', rating ),
- sprintf( '%d', reviews )
- ),
- };
- return (
-
-
-
- );
-};
-
interface ProductRatingStarsProps {
className?: string;
textAlign?: string;
- isDescendentOfSingleProductBlock: boolean;
isDescendentOfQueryLoop: boolean;
postId: number;
productId: number;
@@ -116,42 +28,29 @@ interface ProductRatingStarsProps {
}
export const Block = ( props: ProductRatingStarsProps ): JSX.Element | null => {
- const { textAlign, shouldDisplayMockedReviewsWhenProductHasNoReviews } =
- props;
+ const {
+ textAlign = '',
+ shouldDisplayMockedReviewsWhenProductHasNoReviews,
+ } = props;
const styleProps = useStyleProps( props );
const { parentClassName } = useInnerBlockLayoutContext();
const { product } = useProductDataContext();
const rating = getAverageRating( product );
const reviews = getRatingCount( product );
-
- const className = clsx(
- styleProps.className,
- 'wc-block-components-product-rating-stars',
- {
- [ `${ parentClassName }__product-rating` ]: parentClassName,
- [ `has-text-align-${ textAlign }` ]: textAlign,
- }
- );
- const mockedRatings = shouldDisplayMockedReviewsWhenProductHasNoReviews ? (
-
- ) : null;
-
- const content = reviews ? (
-
- ) : (
- mockedRatings
- );
+ const className = 'wc-block-components-product-rating-stars';
return (
-
+
);
};
diff --git a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating/block.tsx b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating/block.tsx
index ed3368a0d01..22005f3b6d9 100644
--- a/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating/block.tsx
+++ b/plugins/woocommerce-blocks/assets/js/atomic/blocks/product-elements/rating/block.tsx
@@ -1,129 +1,23 @@
/**
* External dependencies
*/
-import { __, _n, sprintf } from '@wordpress/i18n';
-import clsx from 'clsx';
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 {
+ ProductRating,
+ getAverageRating,
+ getRatingCount,
+} from '@woocommerce/editor-components/product-rating';
/**
* Internal dependencies
*/
import './style.scss';
-type RatingProps = {
- reviews: number;
- rating: number;
- parentClassName?: string;
-};
-
-const getAverageRating = (
- product: Omit< ProductResponseItem, 'average_rating' > & {
- average_rating: string;
- }
-) => {
- const rating = parseFloat( product.average_rating );
-
- return Number.isFinite( rating ) && rating > 0 ? rating : 0;
-};
-
-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 getStarStyle = ( rating: number ) => ( {
- width: ( rating / 5 ) * 100 + '%',
-} );
-
-const NoRating = ( { parentClassName }: { parentClassName: string } ) => {
- const starStyle = getStarStyle( 0 );
-
- return (
-
-
-
-
-
{ __( 'No Reviews', 'woocommerce' ) }
-
- );
-};
-
-const Rating = ( props: RatingProps ): JSX.Element => {
- const { rating, reviews, parentClassName } = props;
-
- const starStyle = getStarStyle( rating );
-
- const ratingText = sprintf(
- /* translators: %f is referring to the average rating value */
- __( 'Rated %f out of 5', 'woocommerce' ),
- rating
- );
-
- const ratingHTML = {
- __html: sprintf(
- /* translators: %1$s is referring to the average rating value, %2$s is referring to the number of ratings */
- _n(
- 'Rated %1$s out of 5 based on %2$s customer rating',
- 'Rated %1$s out of 5 based on %2$s customer ratings',
- reviews,
- 'woocommerce'
- ),
- sprintf( '%f', rating ),
- sprintf( '%d', reviews )
- ),
- };
- return (
-
-
-
- );
-};
-
-const ReviewsCount = ( props: { reviews: number } ): JSX.Element => {
- const { reviews } = props;
-
- const reviewsCount = sprintf(
- /* translators: %s is referring to the total of reviews for a product */
- _n(
- '(%s customer review)',
- '(%s customer reviews)',
- reviews,
- 'woocommerce'
- ),
- reviews
- );
-
- return (
-
- { reviewsCount }
-
- );
-};
-
type ProductRatingProps = {
className?: string;
textAlign?: string;
@@ -136,7 +30,7 @@ type ProductRatingProps = {
export const Block = ( props: ProductRatingProps ): JSX.Element | undefined => {
const {
- textAlign,
+ textAlign = '',
isDescendentOfSingleProductBlock,
shouldDisplayMockedReviewsWhenProductHasNoReviews,
} = props;
@@ -146,38 +40,22 @@ export const Block = ( props: ProductRatingProps ): JSX.Element | undefined => {
const rating = getAverageRating( product );
const reviews = getRatingCount( product );
- const className = clsx(
- styleProps.className,
- 'wc-block-components-product-rating',
- {
- [ `${ parentClassName }__product-rating` ]: parentClassName,
- [ `has-text-align-${ textAlign }` ]: textAlign,
- }
- );
- const mockedRatings = shouldDisplayMockedReviewsWhenProductHasNoReviews ? (
-
- ) : null;
-
- const content = reviews ? (
-
- ) : (
- mockedRatings
- );
+ const className = 'wc-block-components-product-rating';
if ( reviews || shouldDisplayMockedReviewsWhenProductHasNoReviews ) {
return (
-
-
- { content }
- { reviews && isDescendentOfSingleProductBlock ? (
-
- ) : null }
-
-
+
);
}
};
diff --git a/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx b/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx
new file mode 100644
index 00000000000..f98bd4be1f5
--- /dev/null
+++ b/plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx
@@ -0,0 +1,184 @@
+/**
+ * External dependencies
+ */
+import { __, _n, sprintf } from '@wordpress/i18n';
+import clsx from 'clsx';
+import type { CSSProperties } from 'react';
+import { isNumber, ProductResponseItem } from '@woocommerce/types';
+
+type RatingProps = {
+ className: string;
+ reviews: number;
+ rating: number;
+ parentClassName?: string;
+};
+
+export const getAverageRating = (
+ product: Omit< ProductResponseItem, 'average_rating' > & {
+ average_rating: string;
+ }
+) => {
+ const rating = parseFloat( product.average_rating );
+
+ return Number.isFinite( rating ) && rating > 0 ? rating : 0;
+};
+
+export 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 getStarStyle = ( rating: number ) => ( {
+ width: ( rating / 5 ) * 100 + '%',
+} );
+
+const NoRating = ( {
+ className,
+ parentClassName,
+}: {
+ className: string;
+ parentClassName: string;
+} ) => {
+ const starStyle = getStarStyle( 0 );
+
+ return (
+
+
+
+
+
{ __( 'No Reviews', 'woocommerce' ) }
+
+ );
+};
+
+const Rating = ( props: RatingProps ): JSX.Element => {
+ const { className, rating, reviews, parentClassName } = props;
+
+ const starStyle = getStarStyle( rating );
+
+ const ratingText = sprintf(
+ /* translators: %f is referring to the average rating value */
+ __( 'Rated %f out of 5', 'woocommerce' ),
+ rating
+ );
+
+ const ratingHTML = {
+ __html: sprintf(
+ /* translators: %1$s is referring to the average rating value, %2$s is referring to the number of ratings */
+ _n(
+ 'Rated %1$s out of 5 based on %2$s customer rating',
+ 'Rated %1$s out of 5 based on %2$s customer ratings',
+ reviews,
+ 'woocommerce'
+ ),
+ sprintf( '%f', rating ),
+ sprintf( '%d', reviews )
+ ),
+ };
+ return (
+
+
+
+ );
+};
+
+const ReviewsCount = ( props: {
+ className: string;
+ reviews: number;
+} ): JSX.Element => {
+ const { className, reviews } = props;
+
+ const reviewsCount = sprintf(
+ /* translators: %s is referring to the total of reviews for a product */
+ _n(
+ '(%s customer review)',
+ '(%s customer reviews)',
+ reviews,
+ 'woocommerce'
+ ),
+ reviews
+ );
+
+ return (
+
+ { reviewsCount }
+
+ );
+};
+
+type ProductRatingProps = {
+ className: string;
+ showReviewCount?: boolean;
+ showMockedReviews?: boolean;
+ parentClassName?: string;
+ rating: number;
+ reviews: number;
+ styleProps: {
+ className: string;
+ style: CSSProperties;
+ };
+ textAlign?: string;
+};
+
+export const ProductRating = (
+ props: ProductRatingProps
+): JSX.Element | null => {
+ const {
+ className = 'wc-block-components-product-rating',
+ showReviewCount,
+ showMockedReviews,
+ parentClassName = '',
+ rating,
+ reviews,
+ styleProps,
+ textAlign,
+ } = props;
+
+ const wrapperClassName = clsx( styleProps.className, className, {
+ [ `${ parentClassName }__product-rating` ]: parentClassName,
+ [ `has-text-align-${ textAlign }` ]: textAlign,
+ } );
+
+ const mockedRatings = showMockedReviews && (
+
+ );
+
+ const content = reviews ? (
+
+ ) : (
+ mockedRatings
+ );
+
+ const isReviewCountVisible = reviews && showReviewCount;
+
+ return (
+
+
+ { content }
+ { isReviewCountVisible ? (
+
+ ) : null }
+
+
+ );
+};
diff --git a/plugins/woocommerce/changelog/dev-42582_use_one_rating_component b/plugins/woocommerce/changelog/dev-42582_use_one_rating_component
new file mode 100644
index 00000000000..e50c3ca32e2
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-42582_use_one_rating_component
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+[Enhancement] Abstract rating block #50810