From ce618d625068b3305af53438b60a248da18496cd Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Fri, 13 Sep 2024 09:35:56 -0300 Subject: [PATCH] [Enhancement] Abstract rating block (#50810) * Create rating component * Refactor rating block * Fix ProductRating component * Refactor rating blocks * Add changelog * Review count maybe visible for rating stars * Rename props * Remove 0 after No reviews message * Remove review count from rating-stars --- .../product-elements/rating-stars/block.tsx | 143 ++------------ .../blocks/product-elements/rating/block.tsx | 160 ++------------- .../product-rating/index.tsx | 184 ++++++++++++++++++ .../dev-42582_use_one_rating_component | 4 + 4 files changed, 228 insertions(+), 263 deletions(-) create mode 100644 plugins/woocommerce-blocks/assets/js/editor-components/product-rating/index.tsx create mode 100644 plugins/woocommerce/changelog/dev-42582_use_one_rating_component 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 ( -
-
- { content } -
-
+ ); }; 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