@wix/headless-stores 0.0.54 → 0.0.56

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.
@@ -188,6 +188,64 @@ export interface CompareAtPriceProps extends AsChildProps<{
188
188
  * ```
189
189
  */
190
190
  export declare const CompareAtPrice: React.ForwardRefExoticComponent<CompareAtPriceProps & React.RefAttributes<HTMLElement>>;
191
+ /**
192
+ * Props for Slug component
193
+ * @interface SlugProps
194
+ */
195
+ export interface SlugProps extends AsChildProps<{
196
+ slug: string;
197
+ }> {
198
+ }
199
+ /**
200
+ * Product Slug component that displays the product's slug
201
+ *
202
+ * @component
203
+ * @order 6
204
+ * @example
205
+ * ```tsx
206
+ * import { Product } from '@wix/stores/components';
207
+ *
208
+ * function ProductSlugLink() {
209
+ * return (
210
+ * <Product.Slug>
211
+ * {({ slug }) => (
212
+ * <a href={`/product/${slug}`}>
213
+ * View Product Details
214
+ * </a>
215
+ * )}
216
+ * </Product.Slug>
217
+ * );
218
+ * }
219
+ * ```
220
+ */
221
+ export declare const Slug: React.ForwardRefExoticComponent<SlugProps & React.RefAttributes<HTMLElement>>;
222
+ /**
223
+ * Props for Product Raw component
224
+ */
225
+ export interface RawProps extends AsChildProps<{
226
+ product: V3Product;
227
+ }> {
228
+ }
229
+ /**
230
+ * Provides access to the raw product data for advanced use cases.
231
+ * Similar to Category.Raw, this should only be used when you need custom access to product data.
232
+ *
233
+ * @component
234
+ * @example
235
+ * ```tsx
236
+ * // Custom rendering with forwardRef
237
+ * <Product.Raw asChild>
238
+ * {React.forwardRef(({product, ...props}, ref) => (
239
+ * <div ref={ref} {...props} className="product-debug">
240
+ * <span>Product ID: {product._id}</span>
241
+ * <span>SKU: {product.sku}</span>
242
+ * <span>Inventory: {product.inventory?.quantity}</span>
243
+ * </div>
244
+ * ))}
245
+ * </Product.Raw>
246
+ * ```
247
+ */
248
+ export declare const Raw: React.ForwardRefExoticComponent<RawProps & React.RefAttributes<HTMLElement>>;
191
249
  /**
192
250
  * Props for Product Variants container
193
251
  */
@@ -388,4 +446,47 @@ export interface ModifierOptionRepeaterProps {
388
446
  * @component
389
447
  */
390
448
  export declare const ModifierOptionRepeater: React.ForwardRefExoticComponent<ModifierOptionRepeaterProps & React.RefAttributes<HTMLElement>>;
391
- export {};
449
+ /**
450
+ * Props for Product MediaGallery component
451
+ */
452
+ export interface ProductMediaGalleryProps {
453
+ children: React.ReactNode;
454
+ infinite?: boolean;
455
+ autoPlay?: {
456
+ direction?: 'forward' | 'backward';
457
+ intervalMs?: number;
458
+ };
459
+ }
460
+ /**
461
+ * Container for product media gallery.
462
+ * Renders a MediaGallery.Root with the product media items.
463
+ *
464
+ * @component
465
+ * @example
466
+ * ```tsx
467
+ * // Default usage
468
+ * <Product.MediaGallery
469
+ * infinite={true}
470
+ * autoPlay={{ direction: "forward", intervalMs: 5000 }}
471
+ * >
472
+ * <MediaGallery.Viewport />
473
+ * <MediaGallery.Previous />
474
+ * <MediaGallery.Next />
475
+ * <MediaGallery.Thumbnails>
476
+ * <MediaGallery.ThumbnailRepeater>
477
+ * <MediaGallery.ThumbnailItem />
478
+ * </MediaGallery.ThumbnailRepeater>
479
+ * </MediaGallery.Thumbnails>
480
+ * </Product.MediaGallery>
481
+ *
482
+ * // Simple usage
483
+ * <Product.MediaGallery>
484
+ * <MediaGallery.Viewport className="rounded-lg" />
485
+ * </Product.MediaGallery>
486
+ * ```
487
+ */
488
+ export declare const ProductMediaGallery: React.ForwardRefExoticComponent<ProductMediaGalleryProps & React.RefAttributes<HTMLElement>>;
489
+ /**
490
+ * Alias for ProductMediaGallery to match the documented API
491
+ */
492
+ export { ProductMediaGallery as MediaGallery };
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { renderAsChild } from '../utils/index.js';
4
+ import { MediaGallery } from '@wix/headless-media/react';
4
5
  import * as CoreProduct from './core/Product.js';
5
6
  import * as ProductVariantSelector from './core/ProductVariantSelector.js';
6
7
  import * as ProductModifiers from './core/ProductModifiers.js';
@@ -36,12 +37,15 @@ var TestIds;
36
37
  TestIds["productDescription"] = "product-description";
37
38
  TestIds["productPrice"] = "product-price";
38
39
  TestIds["productCompareAtPrice"] = "product-compare-at-price";
40
+ TestIds["productSlug"] = "product-slug";
41
+ TestIds["productRaw"] = "product-raw";
39
42
  TestIds["productVariants"] = "product-variants";
40
43
  TestIds["productVariantOptions"] = "product-variant-options";
41
44
  TestIds["productVariantOption"] = "product-variant-option";
42
45
  TestIds["productModifiers"] = "product-modifiers";
43
46
  TestIds["productModifierOptions"] = "product-modifier-options";
44
47
  TestIds["productModifierOption"] = "product-modifier-option";
48
+ TestIds["productMediaGallery"] = "product-media-gallery";
45
49
  })(TestIds || (TestIds = {}));
46
50
  /**
47
51
  * Root component that provides all necessary service contexts for a complete product experience.
@@ -64,7 +68,9 @@ var TestIds;
64
68
  * ```
65
69
  */
66
70
  export function Root(props) {
67
- return (_jsx(CoreProduct.Root, { productServiceConfig: { product: props.product }, "data-testid": TestIds.productRoot, children: _jsx(ProductVariantSelector.Root, { children: _jsx(ProductModifiers.Root, { children: _jsx(SelectedVariant.Root, { children: props.children }) }) }) }));
71
+ return (_jsx(CoreProduct.Root, { productServiceConfig: { product: props.product }, "data-testid": TestIds.productRoot, children: _jsx(MediaGallery.Root, { mediaGalleryServiceConfig: {
72
+ media: props.product.media?.itemsInfo?.items ?? [],
73
+ }, children: _jsx(ProductVariantSelector.Root, { children: _jsx(ProductModifiers.Root, { children: _jsx(SelectedVariant.Root, { children: props.children }) }) }) }) }));
68
74
  }
69
75
  /**
70
76
  * Displays the product name with customizable rendering following the documented API.
@@ -281,6 +287,90 @@ export const CompareAtPrice = React.forwardRef((props, ref) => {
281
287
  return (_jsx("span", { className: className, ...attributes, ref: ref, children: compareAtPrice }));
282
288
  } }));
283
289
  });
290
+ /**
291
+ * Product Slug component that displays the product's slug
292
+ *
293
+ * @component
294
+ * @order 6
295
+ * @example
296
+ * ```tsx
297
+ * import { Product } from '@wix/stores/components';
298
+ *
299
+ * function ProductSlugLink() {
300
+ * return (
301
+ * <Product.Slug>
302
+ * {({ slug }) => (
303
+ * <a href={`/product/${slug}`}>
304
+ * View Product Details
305
+ * </a>
306
+ * )}
307
+ * </Product.Slug>
308
+ * );
309
+ * }
310
+ * ```
311
+ */
312
+ export const Slug = React.forwardRef((props, ref) => {
313
+ const { asChild, children } = props;
314
+ const testId = TestIds.productSlug;
315
+ return (_jsx(CoreProduct.Slug, { children: ({ slug }) => {
316
+ const attributes = {
317
+ 'data-testid': testId,
318
+ };
319
+ const slugData = { slug };
320
+ if (asChild) {
321
+ const rendered = renderAsChild({
322
+ children,
323
+ props: slugData,
324
+ ref,
325
+ content: slug,
326
+ attributes,
327
+ });
328
+ if (rendered)
329
+ return rendered;
330
+ }
331
+ return (_jsx("span", { ...attributes, ref: ref, children: slug }));
332
+ } }));
333
+ });
334
+ /**
335
+ * Provides access to the raw product data for advanced use cases.
336
+ * Similar to Category.Raw, this should only be used when you need custom access to product data.
337
+ *
338
+ * @component
339
+ * @example
340
+ * ```tsx
341
+ * // Custom rendering with forwardRef
342
+ * <Product.Raw asChild>
343
+ * {React.forwardRef(({product, ...props}, ref) => (
344
+ * <div ref={ref} {...props} className="product-debug">
345
+ * <span>Product ID: {product._id}</span>
346
+ * <span>SKU: {product.sku}</span>
347
+ * <span>Inventory: {product.inventory?.quantity}</span>
348
+ * </div>
349
+ * ))}
350
+ * </Product.Raw>
351
+ * ```
352
+ */
353
+ export const Raw = React.forwardRef((props, ref) => {
354
+ const { asChild, children } = props;
355
+ return (_jsx(CoreProduct.Content, { children: ({ product }) => {
356
+ const attributes = {
357
+ 'data-testid': TestIds.productRaw,
358
+ };
359
+ if (asChild) {
360
+ const rendered = renderAsChild({
361
+ children,
362
+ props: { product },
363
+ ref,
364
+ content: null,
365
+ attributes,
366
+ });
367
+ if (rendered)
368
+ return rendered;
369
+ }
370
+ // Raw component should not render anything by default when not using asChild
371
+ return null;
372
+ } }));
373
+ });
284
374
  /**
285
375
  * Container for product variant selection system.
286
376
  * Does not render when there are no variants.
@@ -537,3 +627,50 @@ export const ModifierOptionRepeater = React.forwardRef((props, _ref) => {
537
627
  }, allowedTypes: allowedTypes, children: children })) }, modifier._id));
538
628
  }) }));
539
629
  });
630
+ /**
631
+ * Container for product media gallery.
632
+ * Renders a MediaGallery.Root with the product media items.
633
+ *
634
+ * @component
635
+ * @example
636
+ * ```tsx
637
+ * // Default usage
638
+ * <Product.MediaGallery
639
+ * infinite={true}
640
+ * autoPlay={{ direction: "forward", intervalMs: 5000 }}
641
+ * >
642
+ * <MediaGallery.Viewport />
643
+ * <MediaGallery.Previous />
644
+ * <MediaGallery.Next />
645
+ * <MediaGallery.Thumbnails>
646
+ * <MediaGallery.ThumbnailRepeater>
647
+ * <MediaGallery.ThumbnailItem />
648
+ * </MediaGallery.ThumbnailRepeater>
649
+ * </MediaGallery.Thumbnails>
650
+ * </Product.MediaGallery>
651
+ *
652
+ * // Simple usage
653
+ * <Product.MediaGallery>
654
+ * <MediaGallery.Viewport className="rounded-lg" />
655
+ * </Product.MediaGallery>
656
+ * ```
657
+ */
658
+ export const ProductMediaGallery = React.forwardRef((props, ref) => {
659
+ const { children, infinite, autoPlay, ...otherProps } = props;
660
+ return (_jsx(CoreProduct.Media, { children: ({ mediaItems, mainMedia }) => {
661
+ const media = mediaItems.length > 0 ? mediaItems : mainMedia ? [mainMedia] : [];
662
+ const mediaGalleryServiceConfig = {
663
+ media,
664
+ infinite,
665
+ autoPlay,
666
+ };
667
+ const attributes = {
668
+ 'data-testid': TestIds.productMediaGallery,
669
+ };
670
+ return (_jsx("div", { ...attributes, ref: ref, ...otherProps, children: _jsx(MediaGallery.Root, { mediaGalleryServiceConfig: mediaGalleryServiceConfig, children: children }) }));
671
+ } }));
672
+ });
673
+ /**
674
+ * Alias for ProductMediaGallery to match the documented API
675
+ */
676
+ export { ProductMediaGallery as MediaGallery };
@@ -0,0 +1,163 @@
1
+ import type { V3Product } from '@wix/auto_sdk_stores_products-v-3';
2
+ import React from 'react';
3
+ import type { ProductsListSearchServiceConfig } from '../services/products-list-search-service.js';
4
+ import type { ProductsListServiceConfig } from '../services/products-list-service.js';
5
+ import { type AsChildProps } from '../utils/renderAsChild.js';
6
+ /**
7
+ * Props for the ProductList root component following the documented API
8
+ */
9
+ export interface ProductListRootProps {
10
+ children: React.ReactNode;
11
+ products?: V3Product[];
12
+ productsListConfig?: ProductsListServiceConfig;
13
+ productsListSearchConfig?: ProductsListSearchServiceConfig;
14
+ className?: string;
15
+ }
16
+ /**
17
+ * Root component that provides the ProductList service context for rendering product lists.
18
+ *
19
+ * @order 1
20
+ * @component
21
+ * @example
22
+ * ```tsx
23
+ * import { ProductList } from '@wix/stores/components';
24
+ *
25
+ * function ProductListPage({ products }) {
26
+ * return (
27
+ * <ProductList.Root products={products}>
28
+ * <ProductList.Products>
29
+ * <ProductList.ProductRepeater>
30
+ * <Product.Name />
31
+ * <Product.Price />
32
+ * </ProductList.ProductRepeater>
33
+ * </ProductList.Products>
34
+ * </ProductList.Root>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ export declare const Root: React.ForwardRefExoticComponent<ProductListRootProps & React.RefAttributes<HTMLElement>>;
40
+ /**
41
+ * Props for ProductList Raw component
42
+ */
43
+ export interface RawProps {
44
+ children: ((props: {
45
+ totalProducts: number;
46
+ displayedProducts: number;
47
+ isFiltered: boolean;
48
+ }) => React.ReactNode) | React.ReactNode;
49
+ }
50
+ /**
51
+ * Raw component that provides direct access to product list data.
52
+ * Similar to Product.Raw, this should only be used when you need custom access to list data.
53
+ *
54
+ * @component
55
+ * @example
56
+ * ```tsx
57
+ * <ProductList.Raw>
58
+ * {({ totalProducts, displayedProducts, isFiltered }) => (
59
+ * <div className="text-content-muted">
60
+ * Showing {displayedProducts} of {totalProducts} products
61
+ * {isFiltered && <span className="ml-2 text-brand-primary">(Filtered)</span>}
62
+ * </div>
63
+ * )}
64
+ * </ProductList.Raw>
65
+ * ```
66
+ */
67
+ export declare const Raw: React.ForwardRefExoticComponent<RawProps & React.RefAttributes<HTMLElement>>;
68
+ /**
69
+ * Props for ProductList Products component
70
+ */
71
+ export interface ProductsProps {
72
+ children: React.ReactNode;
73
+ emptyState?: React.ReactNode;
74
+ infiniteScroll?: boolean;
75
+ pageSize?: number;
76
+ className?: string;
77
+ }
78
+ /**
79
+ * Container for the product list with empty state support.
80
+ * Follows List Container Level pattern.
81
+ *
82
+ * @component
83
+ * @example
84
+ * ```tsx
85
+ * <ProductList.Products emptyState={<div>No products found</div>}>
86
+ * <ProductList.ProductRepeater>
87
+ * <Product.Name />
88
+ * <Product.Price />
89
+ * </ProductList.ProductRepeater>
90
+ * </ProductList.Products>
91
+ * ```
92
+ */
93
+ export declare const Products: React.ForwardRefExoticComponent<ProductsProps & React.RefAttributes<HTMLElement>>;
94
+ /**
95
+ * Props for ProductList ProductRepeater component
96
+ */
97
+ export interface ProductRepeaterProps {
98
+ children: React.ReactNode;
99
+ }
100
+ /**
101
+ * Repeater component that renders Product.Root for each product.
102
+ * Follows Repeater Level pattern.
103
+ * Note: Repeater components do NOT support asChild as per architecture rules.
104
+ *
105
+ * @component
106
+ * @example
107
+ * ```tsx
108
+ * <ProductList.ProductRepeater>
109
+ * <Product.Name />
110
+ * <Product.Price />
111
+ * <Product.MediaGallery>
112
+ * <MediaGallery.Viewport />
113
+ * </Product.MediaGallery>
114
+ * </ProductList.ProductRepeater>
115
+ * ```
116
+ */
117
+ export declare const ProductRepeater: React.ForwardRefExoticComponent<ProductRepeaterProps & React.RefAttributes<HTMLElement>>;
118
+ /**
119
+ * Props for ProductList LoadMoreTrigger component
120
+ */
121
+ export interface LoadMoreTriggerProps {
122
+ children?: React.ReactNode;
123
+ asChild?: boolean;
124
+ className?: string;
125
+ }
126
+ /**
127
+ * Displays a button to load more products. Not rendered if infiniteScroll is false or no products are left to load.
128
+ * Follows the architecture rules - does not support asChild as it's a simple trigger component.
129
+ *
130
+ * @component
131
+ * @example
132
+ * ```tsx
133
+ * <ProductList.LoadMoreTrigger asChild>
134
+ * <button>Load More</button>
135
+ * </ProductList.LoadMoreTrigger>
136
+ * ```
137
+ */
138
+ export declare const LoadMoreTrigger: React.ForwardRefExoticComponent<LoadMoreTriggerProps & React.RefAttributes<HTMLElement>>;
139
+ /**
140
+ * Props for ProductList Totals Displayed component
141
+ */
142
+ export interface TotalsDisplayedProps extends AsChildProps<{
143
+ displayedProducts: number;
144
+ }> {
145
+ }
146
+ /**
147
+ * Displays the number of products currently displayed.
148
+ *
149
+ * @component
150
+ * @example
151
+ * ```tsx
152
+ * <ProductList.TotalsDisplayed />
153
+ * // or with asChild
154
+ * <ProductList.TotalsDisplayed asChild>
155
+ * <strong />
156
+ * </ProductList.TotalsDisplayed>
157
+ * // or with render function
158
+ * <ProductList.TotalsDisplayed asChild>
159
+ * {({ displayedProducts }, ref) => <strong ref={ref}>{displayedProducts}</strong>}
160
+ * </ProductList.TotalsDisplayed>
161
+ * ```
162
+ */
163
+ export declare const TotalsDisplayed: React.ForwardRefExoticComponent<TotalsDisplayedProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,235 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useService } from '@wix/services-manager-react';
3
+ import React from 'react';
4
+ import { ProductsListServiceDefinition } from '../services/products-list-service.js';
5
+ import * as CoreProductList from './core/ProductList.js';
6
+ import * as CoreProductListPagination from './core/ProductListPagination.js';
7
+ import * as Product from './Product.js';
8
+ import { renderAsChild } from '../utils/renderAsChild.js';
9
+ var TestIds;
10
+ (function (TestIds) {
11
+ TestIds["productListRoot"] = "product-list-root";
12
+ TestIds["productListProducts"] = "product-list-products";
13
+ TestIds["productListItem"] = "product-list-item";
14
+ TestIds["productListLoadMore"] = "product-list-load-more";
15
+ TestIds["productListTotalsDisplayed"] = "product-list-totals-displayed";
16
+ })(TestIds || (TestIds = {}));
17
+ /**
18
+ * Root component that provides the ProductList service context for rendering product lists.
19
+ *
20
+ * @order 1
21
+ * @component
22
+ * @example
23
+ * ```tsx
24
+ * import { ProductList } from '@wix/stores/components';
25
+ *
26
+ * function ProductListPage({ products }) {
27
+ * return (
28
+ * <ProductList.Root products={products}>
29
+ * <ProductList.Products>
30
+ * <ProductList.ProductRepeater>
31
+ * <Product.Name />
32
+ * <Product.Price />
33
+ * </ProductList.ProductRepeater>
34
+ * </ProductList.Products>
35
+ * </ProductList.Root>
36
+ * );
37
+ * }
38
+ * ```
39
+ */
40
+ export const Root = React.forwardRef((props, ref) => {
41
+ const { children, products, productsListConfig, productsListSearchConfig, className, } = props;
42
+ const serviceConfig = productsListConfig || {
43
+ products: products || [],
44
+ searchOptions: {
45
+ cursorPaging: { limit: 10 },
46
+ },
47
+ pagingMetadata: {
48
+ count: products?.length || 0,
49
+ },
50
+ aggregations: {}, // Empty aggregation data
51
+ };
52
+ return (_jsx(CoreProductList.Root, { productsListConfig: serviceConfig, productsListSearchConfig: productsListSearchConfig, children: _jsx(RootContent, { children: children, className: className, ref: ref }) }));
53
+ });
54
+ /**
55
+ * Internal component to handle the Root content with service access
56
+ */
57
+ const RootContent = React.forwardRef((props, ref) => {
58
+ const { children, className } = props;
59
+ const productsListService = useService(ProductsListServiceDefinition);
60
+ const contextProducts = productsListService.products.get();
61
+ const pagingMetadata = productsListService.pagingMetadata.get();
62
+ const displayedProducts = contextProducts.length;
63
+ const totalProducts = pagingMetadata.count || contextProducts.length;
64
+ const isFiltered = false; // TODO: Implement filtering detection
65
+ const attributes = {
66
+ 'data-testid': TestIds.productListRoot,
67
+ 'data-total-products': totalProducts,
68
+ 'data-displayed-products': displayedProducts,
69
+ 'data-filtered': isFiltered,
70
+ className,
71
+ };
72
+ return (_jsx("div", { ...attributes, ref: ref, children: children }));
73
+ });
74
+ /**
75
+ * Raw component that provides direct access to product list data.
76
+ * Similar to Product.Raw, this should only be used when you need custom access to list data.
77
+ *
78
+ * @component
79
+ * @example
80
+ * ```tsx
81
+ * <ProductList.Raw>
82
+ * {({ totalProducts, displayedProducts, isFiltered }) => (
83
+ * <div className="text-content-muted">
84
+ * Showing {displayedProducts} of {totalProducts} products
85
+ * {isFiltered && <span className="ml-2 text-brand-primary">(Filtered)</span>}
86
+ * </div>
87
+ * )}
88
+ * </ProductList.Raw>
89
+ * ```
90
+ */
91
+ export const Raw = React.forwardRef((props, _ref) => {
92
+ const { children } = props;
93
+ const productsListService = useService(ProductsListServiceDefinition);
94
+ const products = productsListService.products.get();
95
+ const pagingMetadata = productsListService.pagingMetadata.get();
96
+ const displayedProducts = products.length;
97
+ const totalProducts = pagingMetadata.count || products.length;
98
+ const isFiltered = false; // TODO: Implement filtering detection
99
+ return typeof children === 'function'
100
+ ? children({ totalProducts, displayedProducts, isFiltered })
101
+ : children;
102
+ });
103
+ /**
104
+ * Container for the product list with empty state support.
105
+ * Follows List Container Level pattern.
106
+ *
107
+ * @component
108
+ * @example
109
+ * ```tsx
110
+ * <ProductList.Products emptyState={<div>No products found</div>}>
111
+ * <ProductList.ProductRepeater>
112
+ * <Product.Name />
113
+ * <Product.Price />
114
+ * </ProductList.ProductRepeater>
115
+ * </ProductList.Products>
116
+ * ```
117
+ */
118
+ export const Products = React.forwardRef((props, ref) => {
119
+ const { children, emptyState, infiniteScroll = true, pageSize = 0, className, } = props;
120
+ const productsListService = useService(ProductsListServiceDefinition);
121
+ const products = productsListService.products.get();
122
+ const hasProducts = products.length > 0;
123
+ if (!hasProducts) {
124
+ return emptyState || null;
125
+ }
126
+ const attributes = {
127
+ 'data-testid': TestIds.productListProducts,
128
+ 'data-empty': !hasProducts,
129
+ 'data-infinite-scroll': infiniteScroll,
130
+ 'data-page-size': pageSize,
131
+ className,
132
+ };
133
+ return (_jsx("div", { ...attributes, ref: ref, children: children }));
134
+ });
135
+ /**
136
+ * Repeater component that renders Product.Root for each product.
137
+ * Follows Repeater Level pattern.
138
+ * Note: Repeater components do NOT support asChild as per architecture rules.
139
+ *
140
+ * @component
141
+ * @example
142
+ * ```tsx
143
+ * <ProductList.ProductRepeater>
144
+ * <Product.Name />
145
+ * <Product.Price />
146
+ * <Product.MediaGallery>
147
+ * <MediaGallery.Viewport />
148
+ * </Product.MediaGallery>
149
+ * </ProductList.ProductRepeater>
150
+ * ```
151
+ */
152
+ export const ProductRepeater = React.forwardRef((props, _ref) => {
153
+ const { children } = props;
154
+ const productsListService = useService(ProductsListServiceDefinition);
155
+ const products = productsListService.products.get();
156
+ const hasProducts = products.length > 0;
157
+ if (!hasProducts)
158
+ return null;
159
+ return (_jsx(_Fragment, { children: products.map((product) => (_jsx(Product.Root, { product: product, "data-testid": TestIds.productListItem, "data-product-id": product._id, "data-product-available": true, children: children }, product._id))) }));
160
+ });
161
+ /**
162
+ * Displays a button to load more products. Not rendered if infiniteScroll is false or no products are left to load.
163
+ * Follows the architecture rules - does not support asChild as it's a simple trigger component.
164
+ *
165
+ * @component
166
+ * @example
167
+ * ```tsx
168
+ * <ProductList.LoadMoreTrigger asChild>
169
+ * <button>Load More</button>
170
+ * </ProductList.LoadMoreTrigger>
171
+ * ```
172
+ */
173
+ export const LoadMoreTrigger = React.forwardRef((props, ref) => {
174
+ const { asChild, children, className } = props;
175
+ return (_jsx(CoreProductListPagination.LoadMoreTrigger, { children: ({ loadMore, hasMoreProducts, isLoading }) => {
176
+ // Don't render if no more products to load
177
+ if (!hasMoreProducts)
178
+ return null;
179
+ const handleClick = () => loadMore(10);
180
+ const attributes = {
181
+ 'data-testid': TestIds.productListLoadMore,
182
+ className,
183
+ onClick: handleClick,
184
+ disabled: isLoading,
185
+ };
186
+ if (asChild && React.isValidElement(children)) {
187
+ return React.cloneElement(children, {
188
+ ...attributes,
189
+ onClick: handleClick,
190
+ ref,
191
+ });
192
+ }
193
+ return (_jsx("button", { ...attributes, ref: ref, children: children }));
194
+ } }));
195
+ });
196
+ /**
197
+ * Displays the number of products currently displayed.
198
+ *
199
+ * @component
200
+ * @example
201
+ * ```tsx
202
+ * <ProductList.TotalsDisplayed />
203
+ * // or with asChild
204
+ * <ProductList.TotalsDisplayed asChild>
205
+ * <strong />
206
+ * </ProductList.TotalsDisplayed>
207
+ * // or with render function
208
+ * <ProductList.TotalsDisplayed asChild>
209
+ * {({ displayedProducts }, ref) => <strong ref={ref}>{displayedProducts}</strong>}
210
+ * </ProductList.TotalsDisplayed>
211
+ * ```
212
+ */
213
+ export const TotalsDisplayed = React.forwardRef((props, ref) => {
214
+ const { asChild, children, className } = props;
215
+ const productsListService = useService(ProductsListServiceDefinition);
216
+ const products = productsListService.products.get();
217
+ const displayedProducts = products.length;
218
+ const attributes = {
219
+ 'data-testid': TestIds.productListTotalsDisplayed,
220
+ 'data-displayed': displayedProducts,
221
+ className,
222
+ };
223
+ if (asChild) {
224
+ const rendered = renderAsChild({
225
+ children,
226
+ props: { displayedProducts },
227
+ ref,
228
+ content: displayedProducts.toString(),
229
+ attributes,
230
+ });
231
+ if (rendered)
232
+ return rendered;
233
+ }
234
+ return (_jsx("span", { ...attributes, ref: ref, children: displayedProducts }));
235
+ });