Skip to content

Commit

Permalink
feat: implements new organism component "RatingSummary"
Browse files Browse the repository at this point in the history
  • Loading branch information
Guilera committed Feb 20, 2025
1 parent 68b05c9 commit a846caf
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 8 deletions.
5 changes: 5 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,3 +382,8 @@ export type {
SKUMatrixTriggerProps,
SKUMatrixSidebarProps,
} from './organisms/SKUMatrix'

export {
default as RatingSummary,
RatingSummaryProps,
} from './organisms/RatingSummary'
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { forwardRef, type HTMLAttributes } from 'react'
import RatingDistributionItem from './RatingDistributionItem'

export interface RatingDistributionProps
extends HTMLAttributes<HTMLOListElement> {
/**
* The rating distribution
*/
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
/**
* Optional test ID for testing.
*/
testId?: string
}

export const RatingDistribution = forwardRef<
HTMLOListElement,
RatingDistributionProps
>(function ProgressStatus(
{
distribution: { starsFive, starsFour, starsThree, starsTwo, starsOne },
testId = 'fs-rating-distribution',
...props
},
ref
) {
return (
<ol ref={ref} data-fs-rating-distribution data-testid={testId} {...props}>
<RatingDistributionItem ratingIndex={5} value={starsFive} />
<RatingDistributionItem ratingIndex={4} value={starsFour} />
<RatingDistributionItem ratingIndex={3} value={starsThree} />
<RatingDistributionItem ratingIndex={2} value={starsTwo} />
<RatingDistributionItem ratingIndex={1} value={starsOne} />
</ol>
)
})

export default RatingDistribution
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { forwardRef, type HTMLAttributes } from 'react'
import Icon from '../../atoms/Icon'

export interface RatingDistributionItemProps
extends HTMLAttributes<HTMLLIElement> {
ratingIndex: number
/* Current value of the progress */
value: number
/* Optional test ID for testing*/
testId?: string
}

const ItemStar = ({ ratingIndex }: { ratingIndex: number }) => (
<span data-fs-rating-distribution-item-star>
<p>{ratingIndex}</p>
<Icon data-fs-rating-distribution-item-star-icon name="Star" />
</span>
)

const ItemProgressBar = ({ value }: { value: number }) => (
<div data-fs-rating-distribution-item-progress-bar>
<div data-fs-rating-distribution-item-progress-bar-track>
<div
data-fs-rating-distribution-item-progress-bar-fill
style={{ width: `${value}%` }}
/>
</div>
</div>
)

const ItemPercentage = ({ percentage }: { percentage: number }) => (
<p data-fs-rating-distribution-item-percentage>{percentage}%</p>
)

const RatingDistributionItem = forwardRef<
HTMLLIElement,
RatingDistributionItemProps
>(function RatingDistributionItem(
{ ratingIndex, value, testId = 'fs-rating-distribution-item' },
ref
) {
return (
<li ref={ref} data-fs-distribution-item data-testid={testId}>
<ItemStar ratingIndex={ratingIndex} />
<ItemProgressBar value={value} />
<ItemPercentage percentage={value} />
</li>
)
})
export default RatingDistributionItem
124 changes: 124 additions & 0 deletions packages/components/src/organisms/RatingSummary/RatingSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { forwardRef, type HTMLAttributes } from 'react'
import Rating from '../../molecules/Rating'
import Button from '../../atoms/Button'
import RatingDistribution from './RatingDistribution'

export interface RatingSummaryProps extends HTMLAttributes<HTMLDivElement> {
/**
* The average rating of the product
*/
average: number
/**
* The total number of reviews of the product
*/
totalCount: number
/**
* The distribution of the ratings
*/
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
textLabels?: {
ratingCounter?: {
noReviewsText?: string
singleReviewText?: string
multipleReviewsText?: string
}
createReviewButton?: {
noReviewsText?: string
defaultText?: string
}
}
/**
* Optional test ID for testing.
*/
testId?: string
}

const RatingSummaryHeader = ({
average,
totalCount,
noReviewsText,
singleReviewText,
multipleReviewsText,
}: {
average: number
totalCount: number
noReviewsText: string
singleReviewText: string
multipleReviewsText: string
}) => {
const formattedAverage = average > 0 ? average.toPrecision(2) : ''
const totalCountText =
totalCount > 0
? `${totalCount} ${totalCount === 1 ? singleReviewText : multipleReviewsText}`
: noReviewsText

return (
<div data-fs-rating-summary-header>
<h2 data-fs-rating-summary-header-average>{formattedAverage}</h2>
<Rating value={average} />
<p data-fs-rating-summary-header-total-count>{totalCountText}</p>
</div>
)
}

export const RatingSummary = forwardRef<HTMLDivElement, RatingSummaryProps>(
function RatingSummary(
{
average,
totalCount,
distribution,
textLabels: {
ratingCounter: {
noReviewsText: ratingCounterNoReviewsText = 'No reviews yet',
singleReviewText: ratingCounterSingleReviewText = 'Review',
multipleReviewsText: ratingCounterMultipleReviewsText = 'Reviews',
} = {},
createReviewButton: {
noReviewsText:
createReviewButtonNoReviewsText = 'Write the first review',
defaultText: createReviewButtonDefaultText = 'Write a review',
} = {},
} = {},
testId = 'fs-rating-summary',
...props
},
ref
) {
const buttonText =
totalCount > 0
? createReviewButtonDefaultText
: createReviewButtonNoReviewsText

return (
<div ref={ref} data-fs-rating-summary data-testid={testId} {...props}>
<RatingSummaryHeader
average={average}
totalCount={totalCount}
noReviewsText={ratingCounterNoReviewsText}
singleReviewText={ratingCounterSingleReviewText}
multipleReviewsText={ratingCounterMultipleReviewsText}
/>
<Button
variant="secondary"
onClick={() => alert('Write a review button clicked!')}
>
{buttonText}
</Button>
{totalCount > 0 && (
<RatingDistribution
data-fs-rating-summary-distribution
distribution={distribution}
/>
)}
</div>
)
}
)

export default RatingSummary
1 change: 1 addition & 0 deletions packages/components/src/organisms/RatingSummary/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, type RatingSummaryProps } from './RatingSummary'
4 changes: 2 additions & 2 deletions packages/core/@generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const documents = {
types.ProductSummary_ProductFragmentDoc,
'\n fragment Filter_facets on StoreFacet {\n ... on StoreFacetRange {\n key\n label\n\n min {\n selected\n absolute\n }\n\n max {\n selected\n absolute\n }\n\n __typename\n }\n ... on StoreFacetBoolean {\n key\n label\n values {\n label\n value\n selected\n quantity\n }\n\n __typename\n }\n }\n':
types.Filter_FacetsFragmentDoc,
'\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n':
'\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n distribution {\n starsOne\n starsTwo\n starsThree\n starsFour\n starsFive\n }\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n':
types.ProductDetailsFragment_ProductFragmentDoc,
'\n fragment ProductSKUMatrixSidebarFragment_product on StoreProduct {\n id: productID\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n allVariantProducts {\n\t\t\t\t\tsku\n name\n image {\n url\n alternateName\n }\n offers {\n highPrice\n lowPrice\n lowPriceWithTaxes\n offerCount\n priceCurrency\n offers {\n listPrice\n listPriceWithTaxes\n sellingPrice\n priceCurrency\n price\n priceWithTaxes\n priceValidUntil\n itemCondition\n availability\n quantity\n }\n }\n additionalProperty {\n propertyID\n value\n name\n valueReference\n }\n }\n }\n }\n }\n':
types.ProductSkuMatrixSidebarFragment_ProductFragmentDoc,
Expand Down Expand Up @@ -78,7 +78,7 @@ export function gql(
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n'
source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n\t\t\tskuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n rating {\n average\n totalCount\n distribution {\n starsOne\n starsTwo\n starsThree\n starsFour\n starsFive\n }\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n'
): typeof import('./graphql').ProductDetailsFragment_ProductFragmentDoc
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
Expand Down
47 changes: 42 additions & 5 deletions packages/core/@generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,17 @@ export type ProductDetailsFragment_ProductFragment = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}

export type ProductSkuMatrixSidebarFragment_ProductFragment = {
Expand Down Expand Up @@ -1462,7 +1472,17 @@ export type ServerProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}
}

Expand Down Expand Up @@ -1763,7 +1783,17 @@ export type ClientProductQueryQuery = {
value: any
valueReference: any
}>
rating: { average: number; totalCount: number }
rating: {
average: number
totalCount: number
distribution: {
starsOne: number
starsTwo: number
starsThree: number
starsFour: number
starsFive: number
}
}
}
}

Expand Down Expand Up @@ -2065,6 +2095,13 @@ export const ProductDetailsFragment_ProductFragmentDoc =
rating {
average
totalCount
distribution {
starsOne
starsTwo
starsThree
starsFour
starsFive
}
}
...CartProductItem
}
Expand Down Expand Up @@ -2342,7 +2379,7 @@ export const ServerCollectionPageQueryDocument = {
export const ServerProductQueryDocument = {
__meta__: {
operationName: 'ServerProductQuery',
operationHash: '0a3f449b2a88dc1f692fe1ae981370be53a02cce',
operationHash: '312acab1a14a3b35d6c70887b5cf289b5cf6cf76',
},
} as unknown as TypedDocumentString<
ServerProductQueryQuery,
Expand Down Expand Up @@ -2396,7 +2433,7 @@ export const ClientProductGalleryQueryDocument = {
export const ClientProductQueryDocument = {
__meta__: {
operationName: 'ClientProductQuery',
operationHash: 'e1599e2efe3664aad09c026919c1c104b4085f00',
operationHash: 'e678f7fc4d59a3e4cbf61295fc1e669f44724464',
},
} as unknown as TypedDocumentString<
ClientProductQueryQuery,
Expand Down
43 changes: 43 additions & 0 deletions packages/core/cms/faststore/sections.json
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,49 @@
"title": "Title",
"type": "string",
"default": "Reviews"
},
"ratingSummary": {
"title": "Rating Summary",
"type": "object",
"properties": {
"ratingCounter": {
"title": "Rating Counter",
"type": "object",
"properties": {
"noReviewsText": {
"title": "No reviews text",
"type": "string",
"default": "No reviews yet"
},
"multipleReviewsText": {
"title": "Multiple reviews text",
"type": "string",
"default": "Reviews"
},
"singleReviewText": {
"title": "Single review text",
"type": "string",
"default": "Review"
}
}
}
}
},
"createReviewButton": {
"title": "Create Review Button",
"type": "object",
"properties": {
"noReviewsText": {
"title": "No reviews Text",
"type": "string",
"default": "Write the first review"
},
"defaultText": {
"title": "Default Text",
"type": "string",
"default": "Write a review"
}
}
}
}
}
Expand Down
Loading

0 comments on commit a846caf

Please sign in to comment.