@faststore/core 4.7.1-dev.1 → 4.8.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/.turbo/turbo-generate.log +3 -3
  2. package/.turbo/turbo-test.log +104 -104
  3. package/CHANGELOG.md +21 -0
  4. package/cms/faststore/components/cms_component__cartsidebar.jsonc +74 -0
  5. package/cms/faststore/schema.json +78 -0
  6. package/cms/faststore/sections.json +78 -0
  7. package/package.json +6 -6
  8. package/src/components/cart/CartRecommendationShelf/CartRecommendationProductCard.tsx +88 -0
  9. package/src/components/cart/CartRecommendationShelf/CartRecommendationShelf.tsx +110 -0
  10. package/src/components/cart/CartRecommendationShelf/index.ts +4 -0
  11. package/src/components/cart/CartRecommendationShelf/section.module.scss +124 -0
  12. package/src/components/cart/CartSidebar/CartSidebar.tsx +47 -1
  13. package/src/components/product/ProductCard/ProductCard.tsx +5 -3
  14. package/src/components/sections/RecommendationShelf/README.md +74 -6
  15. package/src/components/sections/RecommendationShelf/RecommendationShelf.tsx +15 -3
  16. package/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts +8 -2
  17. package/src/components/sections/RecommendationShelf/index.ts +7 -1
  18. package/src/sdk/recommendations/useRecommendationShelf.ts +2 -2
  19. package/test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx +125 -0
  20. package/test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx +286 -0
  21. package/test/components/CartSidebar/CartSidebar.browser.test.tsx +209 -0
  22. package/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx +19 -0
  23. package/test/sdk/recommendations/useRecommendationShelf.browser.test.tsx +1 -1
@@ -2985,6 +2985,84 @@
2985
2985
  "default": "Tax included"
2986
2986
  }
2987
2987
  }
2988
+ },
2989
+ "recommendations": {
2990
+ "title": "Recommendations",
2991
+ "description": "Optional shelf of personalized product recommendations rendered inside the cart drawer, between the cart items and the order summary.",
2992
+ "type": "object",
2993
+ "properties": {
2994
+ "shouldDisplayRecommendationShelf": {
2995
+ "title": "Should display Recommendation Shelf?",
2996
+ "description": "When enabled, fetches recommendations when the shopper opens the cart. Off by default so stores that do not use Recommendations never hit the Recommendations API. The personalization session starts via experimental.enableRecommendations, or as a fallback when this shelf mounts.",
2997
+ "type": "boolean",
2998
+ "default": false
2999
+ },
3000
+ "title": {
3001
+ "title": "Title",
3002
+ "description": "Override the shelf title. If not set, the campaign title will be used.",
3003
+ "type": "string"
3004
+ },
3005
+ "campaignVrn": {
3006
+ "title": "Campaign VRN",
3007
+ "description": "Recommendation campaign VRN (e.g. vrn:recommendations:my-account:rec-cross-v2:abc123). The mini cart shelf only mounts when the cart has items, so it never fetches or renders on an empty cart — including context-agnostic campaigns (top sellers, personalized, last seen, search-based). Use a page Recommendation Shelf for empty-cart campaigns.",
3008
+ "type": "string",
3009
+ "pattern": "^vrn:recommendations:[^:]+:(rec-cross-v2|rec-similar-v2|rec-persona-v2|rec-last-v2|rec-top-items-v2|rec-search-v2|rec-next-v2|rec-visual-v2):[^:]+$"
3010
+ },
3011
+ "carouselConfiguration": {
3012
+ "title": "Carousel Configuration",
3013
+ "type": "object",
3014
+ "properties": {
3015
+ "itemsPerPage": {
3016
+ "title": "Items per page",
3017
+ "description": "Number of products visible at once. Fractional values such as 1.5 hint that the carousel scrolls.",
3018
+ "type": "number",
3019
+ "default": 1
3020
+ },
3021
+ "variant": {
3022
+ "title": "Carousel track variant",
3023
+ "description": "How the carousel navigates between items.",
3024
+ "type": "string",
3025
+ "default": "scroll",
3026
+ "enum": ["slide", "scroll"],
3027
+ "enumNames": ["Slide", "Scroll"]
3028
+ },
3029
+ "infiniteMode": {
3030
+ "title": "Infinite navigation?",
3031
+ "description": "Only applies to the 'slide' variant.",
3032
+ "type": "boolean",
3033
+ "default": false
3034
+ },
3035
+ "controls": {
3036
+ "title": "Navigation controls",
3037
+ "description": "Which navigation elements should be visible.",
3038
+ "type": "string",
3039
+ "default": "navigationArrows",
3040
+ "enum": ["complete", "navigationArrows", "paginationBullets"],
3041
+ "enumNames": [
3042
+ "Arrows and bullets",
3043
+ "Arrows only",
3044
+ "Bullets only"
3045
+ ]
3046
+ }
3047
+ }
3048
+ },
3049
+ "productCardConfiguration": {
3050
+ "title": "Product Card Configuration",
3051
+ "type": "object",
3052
+ "properties": {
3053
+ "showDiscountBadge": {
3054
+ "title": "Show discount badge?",
3055
+ "type": "boolean",
3056
+ "default": true
3057
+ },
3058
+ "bordered": {
3059
+ "title": "Cards should be bordered?",
3060
+ "type": "boolean",
3061
+ "default": false
3062
+ }
3063
+ }
3064
+ }
3065
+ }
2988
3066
  }
2989
3067
  }
2990
3068
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.7.1-dev.1",
3
+ "version": "4.8.0-dev.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.7.1-dev.1",
75
- "@faststore/diagnostics": "4.7.1-dev.1",
76
- "@faststore/ui": "4.7.1-dev.1",
77
- "@faststore/sdk": "4.7.1-dev.1",
78
- "@faststore/lighthouse": "4.7.1-dev.1"
74
+ "@faststore/api": "4.8.0-dev.0",
75
+ "@faststore/lighthouse": "4.8.0-dev.0",
76
+ "@faststore/sdk": "4.8.0-dev.0",
77
+ "@faststore/ui": "4.8.0-dev.0",
78
+ "@faststore/diagnostics": "4.8.0-dev.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -0,0 +1,88 @@
1
+ import type { MouseEventHandler } from 'react'
2
+
3
+ import ProductCard, {
4
+ type ProductCardProps,
5
+ } from 'src/components/product/ProductCard'
6
+ import { useBuyButton } from 'src/sdk/cart/useBuyButton'
7
+
8
+ export interface CartRecommendationProductCardProps extends ProductCardProps {}
9
+
10
+ /**
11
+ * Product card used by the cart drawer recommendation shelf. Same surface as
12
+ * the page shelf card, plus an add-to-cart action that appends the recommended
13
+ * SKU without reopening the drawer (`shouldOpenCart: false`).
14
+ *
15
+ * Adds the first offer returned by the recommendations API (the card already
16
+ * represents a specific `StoreProduct` / SKU). Multi-SKU matrix selection is
17
+ * out of scope here — shoppers who need another variant can open the PDP.
18
+ */
19
+ function CartRecommendationProductCard({
20
+ product,
21
+ buttonLabel = 'Add to cart',
22
+ onButtonClick,
23
+ ...otherProps
24
+ }: Readonly<CartRecommendationProductCardProps>) {
25
+ const {
26
+ id,
27
+ sku,
28
+ gtin,
29
+ brand,
30
+ isVariantOf,
31
+ isVariantOf: { name },
32
+ unitMultiplier,
33
+ image: [img],
34
+ offers: {
35
+ offers: [
36
+ {
37
+ listPrice,
38
+ price,
39
+ listPriceWithTaxes,
40
+ seller,
41
+ priceWithTaxes,
42
+ priceToken,
43
+ },
44
+ ],
45
+ },
46
+ additionalProperty,
47
+ } = product
48
+
49
+ const buyProps = useBuyButton(
50
+ {
51
+ id,
52
+ price,
53
+ priceWithTaxes,
54
+ listPrice,
55
+ listPriceWithTaxes,
56
+ seller,
57
+ quantity: 1,
58
+ priceToken,
59
+ itemOffered: {
60
+ sku,
61
+ name,
62
+ gtin,
63
+ image: [img],
64
+ brand,
65
+ isVariantOf,
66
+ additionalProperty,
67
+ unitMultiplier,
68
+ },
69
+ },
70
+ false
71
+ )
72
+
73
+ const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => {
74
+ buyProps.onClick(event)
75
+ onButtonClick?.(event)
76
+ }
77
+
78
+ return (
79
+ <ProductCard
80
+ {...otherProps}
81
+ product={product}
82
+ buttonLabel={buttonLabel}
83
+ onButtonClick={handleClick}
84
+ />
85
+ )
86
+ }
87
+
88
+ export default CartRecommendationProductCard
@@ -0,0 +1,110 @@
1
+ import type { CarouselProps } from '@faststore/ui'
2
+ import type { ProductSummary_ProductFragment } from '@generated/graphql'
3
+
4
+ import type { ProductCardProps } from 'src/components/product/ProductCard'
5
+ import {
6
+ RecommendationShelf,
7
+ type RecommendationProductCardMapper,
8
+ type RecommendationShelfProps,
9
+ } from 'src/components/sections/RecommendationShelf'
10
+
11
+ import CartRecommendationProductCard from './CartRecommendationProductCard'
12
+ import styles from './section.module.scss'
13
+
14
+ export type CartRecommendationShelfProps<
15
+ TCardProps extends object = ProductCardProps,
16
+ > = Omit<
17
+ RecommendationShelfProps<TCardProps>,
18
+ 'itemsContext' | 'className' | 'carouselConfiguration'
19
+ > & {
20
+ /**
21
+ * Drawer-safe carousel configuration. Mapped onto
22
+ * `RecommendationShelf`'s desktop/mobile paging props (the drawer is the
23
+ * same width on every breakpoint, so both viewports share this count).
24
+ */
25
+ readonly carouselConfiguration?: {
26
+ /**
27
+ * Products visible at once. Fractional values (e.g. `1.5`) hint that the
28
+ * carousel scrolls.
29
+ * @default 1
30
+ */
31
+ readonly itemsPerPage?: number
32
+ /** @default 'scroll' */
33
+ readonly variant?: CarouselProps['variant']
34
+ /** @default false */
35
+ readonly infiniteMode?: CarouselProps['infiniteMode']
36
+ /** @default 'navigationArrows' */
37
+ readonly controls?: CarouselProps['controls']
38
+ }
39
+ /**
40
+ * Forwarded from the cart drawer so recommended products display prices under
41
+ * the same tax rules as the cart items next to them. Applied through the
42
+ * default `mapProductToProductCard`; a custom mapper is responsible for
43
+ * forwarding it itself.
44
+ */
45
+ readonly taxesConfiguration?: ProductCardProps['taxesConfiguration']
46
+ }
47
+
48
+ /**
49
+ * Compact recommendation shelf for the cart drawer.
50
+ *
51
+ * Presentation is owned by `RecommendationShelf` so code-level overrides
52
+ * (`ProductCard`, `mapProductToProductCard`, …) work the same way as on the
53
+ * page shelf. This wrapper only locks cart-specific behaviour: cart context,
54
+ * drawer-safe carousel defaults, tax/image sizing for the narrow panel, and the
55
+ * default `CartRecommendationProductCard` (add-to-cart without reopening the
56
+ * drawer).
57
+ */
58
+ function CartRecommendationShelf<TCardProps extends object = ProductCardProps>({
59
+ title,
60
+ campaignVrn,
61
+ carouselConfiguration,
62
+ productCardConfiguration,
63
+ taxesConfiguration,
64
+ ProductCard,
65
+ mapProductToProductCard,
66
+ }: CartRecommendationShelfProps<TCardProps>) {
67
+ const {
68
+ itemsPerPage = 1,
69
+ variant = 'scroll',
70
+ infiniteMode = false,
71
+ controls = 'navigationArrows',
72
+ } = carouselConfiguration ?? {}
73
+
74
+ const mapToCardProps: RecommendationProductCardMapper<TCardProps> =
75
+ mapProductToProductCard ??
76
+ ((product: ProductSummary_ProductFragment, index: number) =>
77
+ ({
78
+ product,
79
+ index,
80
+ bordered: productCardConfiguration?.bordered ?? false,
81
+ showDiscountBadge: productCardConfiguration?.showDiscountBadge ?? true,
82
+ taxesConfiguration,
83
+ imgProps: { sizes: '(max-width: 768px) 45vw, 15vw' },
84
+ }) as TCardProps)
85
+
86
+ return (
87
+ <RecommendationShelf<TCardProps>
88
+ title={title}
89
+ campaignVrn={campaignVrn}
90
+ itemsContext="CART"
91
+ className={styles.section}
92
+ ProductCard={
93
+ (ProductCard ??
94
+ CartRecommendationProductCard) as RecommendationShelfProps<TCardProps>['ProductCard']
95
+ }
96
+ mapProductToProductCard={mapToCardProps}
97
+ productCardConfiguration={productCardConfiguration}
98
+ carouselConfiguration={{
99
+ itemsPerPageDesktop: itemsPerPage,
100
+ itemsPerPageMobile: itemsPerPage,
101
+ variant,
102
+ infiniteMode,
103
+ controls,
104
+ }}
105
+ data-fs-cart-recommendation-shelf
106
+ />
107
+ )
108
+ }
109
+
110
+ export default CartRecommendationShelf
@@ -0,0 +1,4 @@
1
+ export { default } from './CartRecommendationShelf'
2
+ export type { CartRecommendationShelfProps } from './CartRecommendationShelf'
3
+ export { default as CartRecommendationProductCard } from './CartRecommendationProductCard'
4
+ export type { CartRecommendationProductCardProps } from './CartRecommendationProductCard'
@@ -0,0 +1,124 @@
1
+ @use "sass:meta";
2
+
3
+ @layer components {
4
+ // Rendered as a direct child of `[data-fs-cart-sidebar]`, which is a flex
5
+ // column whose footer is sticky. The shelf therefore takes its natural height
6
+ // and never pushes the checkout button out of reach.
7
+ // Passed to `RecommendationShelf` as `className`, so page-section utilities
8
+ // (`section-product-shelf`, `layout__section`, …) are not applied.
9
+ .section {
10
+ --fs-product-shelf-items-padding-top: 0;
11
+ --fs-product-shelf-items-padding-bottom: var(--fs-spacing-1);
12
+ --fs-carousel-item-margin-right: var(--fs-spacing-2);
13
+
14
+ padding: var(--fs-spacing-2) 0;
15
+ border-top: var(--fs-border-width) solid var(--fs-border-color-light);
16
+
17
+ [data-fs-recommendation-shelf-title] {
18
+ padding: 0 var(--fs-spacing-3) var(--fs-spacing-2);
19
+ margin: 0;
20
+ font-size: var(--fs-text-size-2);
21
+ font-weight: var(--fs-text-weight-semibold);
22
+ color: var(--fs-color-text);
23
+ }
24
+
25
+ [data-fs-carousel],
26
+ [data-fs-product-shelf-skeleton] {
27
+ padding: 0 var(--fs-spacing-3);
28
+ }
29
+
30
+ [data-fs-recommendation-shelf-item] {
31
+ display: flex;
32
+ width: 100%;
33
+ height: 100%;
34
+
35
+ > * {
36
+ width: 100%;
37
+ }
38
+ }
39
+
40
+ // The drawer is ~340px wide on every breakpoint, so two cards land under
41
+ // the card's default 10rem min-width and would overflow the track. Let the
42
+ // card follow the carousel item width and scale its type down with it.
43
+ [data-fs-product-card] {
44
+ --fs-product-card-min-width: 0;
45
+ --fs-product-card-gap: var(--fs-spacing-1);
46
+ --fs-product-card-padding: var(--fs-spacing-1);
47
+ --fs-product-card-title-size: var(--fs-text-size-1);
48
+ --fs-product-card-price-size: var(--fs-text-size-1);
49
+ --fs-product-card-title-max-lines: 2;
50
+ }
51
+
52
+ // Full-width add-to-cart so the action stays tappable on ~half-drawer cards.
53
+ [data-fs-product-card-actions] {
54
+ width: 100%;
55
+
56
+ [data-fs-button] {
57
+ --fs-button-height: var(--fs-spacing-6);
58
+ --fs-button-text-size: var(--fs-text-size-1);
59
+ --fs-button-padding: var(--fs-spacing-0) var(--fs-spacing-1);
60
+ --fs-button-gap: var(--fs-spacing-1);
61
+
62
+ width: 100%;
63
+ }
64
+ }
65
+
66
+ // Button, Icon, Badge, Link, Price and ProductPrice already load in
67
+ // CartSidebar; this chunk only adds styles the drawer does not have.
68
+ @include meta.load-css("~@faststore/ui/src/components/atoms/SROnly/styles");
69
+ @include meta.load-css("~@faststore/ui/src/components/atoms/Skeleton/styles");
70
+ @include meta.load-css("~@faststore/ui/src/components/molecules/Carousel/styles");
71
+ @include meta.load-css("~@faststore/ui/src/components/molecules/DiscountBadge/styles");
72
+ @include meta.load-css("~@faststore/ui/src/components/molecules/Rating/styles");
73
+ @include meta.load-css("~@faststore/ui/src/components/molecules/ProductCard/styles");
74
+ @include meta.load-css("~@faststore/ui/src/components/molecules/ProductCardSkeleton/styles");
75
+ @include meta.load-css("~@faststore/ui/src/components/organisms/ProductShelf/styles");
76
+
77
+ // Keep the product fully visible: `cover` plus a height cap was clipping
78
+ // photos on both the wide drawer and the narrow one. A square box with
79
+ // `contain` stays compact without cropping.
80
+ [data-fs-product-card] [data-fs-product-card-image],
81
+ [data-fs-product-card-skeleton-image] {
82
+ width: min(100%, 7.5rem);
83
+ aspect-ratio: 1;
84
+ margin-inline: auto;
85
+ background-color: var(--fs-color-neutral-1);
86
+
87
+ img {
88
+ object-fit: contain;
89
+ }
90
+ }
91
+
92
+ [data-fs-product-card-skeleton] {
93
+ --fs-product-card-skeleton-sectioned-min-width: 0;
94
+ --fs-product-card-skeleton-padding: var(--fs-spacing-1);
95
+ }
96
+
97
+ // The drawer (and `[data-fs-product-shelf-items]`) clips overflow, so
98
+ // arrows must stay inside the padded carousel — not offset outside it.
99
+ [data-fs-product-shelf-items] {
100
+ overflow: visible;
101
+ }
102
+
103
+ [data-fs-carousel] {
104
+ overflow: visible;
105
+
106
+ [data-fs-carousel-track] {
107
+ padding: 0;
108
+ }
109
+
110
+ [data-fs-carousel-controls] {
111
+ width: 100%;
112
+ pointer-events: auto;
113
+ opacity: 1;
114
+
115
+ [data-fs-button] {
116
+ --fs-button-height: var(--fs-spacing-6);
117
+
118
+ width: var(--fs-button-height);
119
+ min-width: var(--fs-button-height);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
@@ -8,6 +8,7 @@ import type {
8
8
  import type { CurrencyCode, ViewCartEvent } from '@faststore/sdk'
9
9
  import { Icon, useFadeEffect, useUI } from '@faststore/ui'
10
10
  import { type ReactNode, useCallback, useEffect, useMemo } from 'react'
11
+ import type { CartRecommendationShelfProps } from '../CartRecommendationShelf'
11
12
  import { useCart } from 'src/sdk/cart'
12
13
  import { useCheckoutButton } from 'src/sdk/cart/useCheckoutButton'
13
14
  import { useSession } from 'src/sdk/session'
@@ -58,6 +59,15 @@ const OrderSummary = dynamic(
58
59
  () => import(/* webpackChunkName: "OrderSummary" */ '../OrderSummary'),
59
60
  { ssr: false }
60
61
  )
62
+ // Kept in its own chunk so stores without a mini cart shelf never download the
63
+ // shelf or the product card/carousel styles it pulls in.
64
+ const CartRecommendationShelf = dynamic(
65
+ () =>
66
+ import(
67
+ /* webpackChunkName: "CartRecommendationShelf" */ '../CartRecommendationShelf'
68
+ ),
69
+ { ssr: false }
70
+ )
61
71
 
62
72
  import styles from './section.module.scss'
63
73
 
@@ -127,6 +137,20 @@ export interface CartSidebarProps {
127
137
  usePriceWithTaxes?: boolean
128
138
  taxesLabel?: string
129
139
  }
140
+ /**
141
+ * Optional recommendation shelf rendered inside the drawer. Off by default
142
+ * (`shouldDisplayRecommendationShelf: false`) so the drawer stays as it was:
143
+ * no personalization session, no request, nothing rendered.
144
+ *
145
+ * Taxes are not configured here — the shelf reuses the drawer's own
146
+ * `taxesConfiguration` so recommended products and cart items price alike.
147
+ * `ProductCard` / `mapProductToProductCard` are code-level overrides (same
148
+ * contract as the page `RecommendationShelf`) and are omitted from the CMS
149
+ * schema; store customizations can still pass them when wrapping this section.
150
+ */
151
+ recommendations?: Omit<CartRecommendationShelfProps, 'taxesConfiguration'> & {
152
+ shouldDisplayRecommendationShelf?: boolean
153
+ }
130
154
  }
131
155
 
132
156
  function CartSidebar({
@@ -143,7 +167,8 @@ function CartSidebar({
143
167
  },
144
168
  quantitySelector,
145
169
  taxesConfiguration,
146
- }: CartSidebarProps) {
170
+ recommendations,
171
+ }: Readonly<CartSidebarProps>) {
147
172
  const btnProps = useCheckoutButton()
148
173
  const {
149
174
  items,
@@ -161,6 +186,26 @@ function CartSidebar({
161
186
 
162
187
  const isEmpty = useMemo(() => items.length === 0, [items])
163
188
 
189
+ // The mini cart shelf does not mount until the cart has at least one item,
190
+ // so empty carts never fetch — including context-agnostic campaigns. The
191
+ // element is only inserted in the non-empty footer below.
192
+ let recommendationShelf: ReactNode = null
193
+
194
+ if (
195
+ recommendations?.shouldDisplayRecommendationShelf &&
196
+ recommendations.campaignVrn
197
+ ) {
198
+ const { shouldDisplayRecommendationShelf: _, ...recommendationShelfProps } =
199
+ recommendations
200
+
201
+ recommendationShelf = (
202
+ <CartRecommendationShelf
203
+ {...recommendationShelfProps}
204
+ taxesConfiguration={taxesConfiguration}
205
+ />
206
+ )
207
+ }
208
+
164
209
  useEffect(() => {
165
210
  if (!displayCart) {
166
211
  return
@@ -214,6 +259,7 @@ function CartSidebar({
214
259
  </UICartSidebarList>
215
260
 
216
261
  <UICartSidebarFooter>
262
+ {recommendationShelf}
217
263
  <OrderSummary
218
264
  subTotal={
219
265
  taxesConfiguration?.usePriceWithTaxes
@@ -4,7 +4,7 @@ import {
4
4
  ProductCardImage as UIProductCardImage,
5
5
  ProductComparisonTrigger as UIProductComparisonTrigger,
6
6
  } from '@faststore/ui'
7
- import { memo, useMemo } from 'react'
7
+ import { memo, useMemo, type MouseEventHandler } from 'react'
8
8
 
9
9
  import { gql } from '@generated'
10
10
  import type { ProductSummary_ProductFragment } from '@generated/graphql'
@@ -42,9 +42,10 @@ export interface ProductCardProps {
42
42
  */
43
43
  ratingValue?: number
44
44
  /**
45
- * Callback function when button is clicked.
45
+ * Callback function when the card action button is clicked. When set, the
46
+ * button is rendered (unless the product is out of stock).
46
47
  */
47
- onButtonClick?: () => void
48
+ onButtonClick?: MouseEventHandler<HTMLButtonElement>
48
49
  /**
49
50
  * Specifies the button's label.
50
51
  */
@@ -178,6 +179,7 @@ function ProductCard({
178
179
  }}
179
180
  ratingValue={ratingValue}
180
181
  outOfStock={outOfStock}
182
+ buttonLabel={buttonLabel}
181
183
  onButtonClick={onButtonClick}
182
184
  linkProps={linkProps}
183
185
  showDiscountBadge={hasDiscount && showDiscountBadge}