@wix/headless-stores 0.0.8 → 0.0.10
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.
- package/cjs/dist/react/BuyNow.d.ts +1 -0
- package/cjs/dist/react/BuyNow.js +1 -0
- package/cjs/dist/react/Category.d.ts +17 -0
- package/cjs/dist/react/Category.js +37 -0
- package/cjs/dist/react/Collection.d.ts +141 -0
- package/cjs/dist/react/Collection.js +198 -0
- package/cjs/dist/react/FilteredCollection.d.ts +65 -0
- package/cjs/dist/react/FilteredCollection.js +117 -0
- package/cjs/dist/react/PayNow.d.ts +1 -0
- package/cjs/dist/react/PayNow.js +1 -0
- package/cjs/dist/react/Product.d.ts +70 -0
- package/cjs/dist/react/Product.js +56 -0
- package/cjs/dist/react/ProductMediaGallery.d.ts +128 -0
- package/cjs/dist/react/ProductMediaGallery.js +100 -0
- package/cjs/dist/react/ProductModifiers.d.ts +156 -0
- package/cjs/dist/react/ProductModifiers.js +159 -0
- package/cjs/dist/react/ProductVariantSelector.d.ts +169 -0
- package/cjs/dist/react/ProductVariantSelector.js +166 -0
- package/cjs/dist/react/RelatedProducts.d.ts +60 -0
- package/cjs/dist/react/RelatedProducts.js +68 -0
- package/cjs/dist/react/SocialSharing.d.ts +119 -0
- package/cjs/dist/react/SocialSharing.js +80 -0
- package/cjs/dist/react/Sort.d.ts +17 -0
- package/cjs/dist/react/Sort.js +41 -0
- package/cjs/dist/react/index.d.ts +10 -0
- package/cjs/dist/react/index.js +33 -0
- package/cjs/dist/services/catalog-options-service.d.ts +30 -0
- package/cjs/dist/services/catalog-options-service.js +162 -0
- package/cjs/dist/services/catalog-price-range-service.d.ts +23 -0
- package/cjs/dist/services/catalog-price-range-service.js +95 -0
- package/cjs/dist/services/category-service.d.ts +25 -0
- package/cjs/dist/services/category-service.js +67 -0
- package/cjs/dist/services/collection-service.d.ts +37 -0
- package/cjs/dist/services/collection-service.js +454 -0
- package/cjs/dist/services/filter-service.d.ts +56 -0
- package/cjs/dist/services/filter-service.js +155 -0
- package/cjs/dist/services/product-media-gallery-service.d.ts +25 -0
- package/cjs/dist/services/product-media-gallery-service.js +105 -0
- package/cjs/dist/services/product-modifiers-service.d.ts +36 -0
- package/cjs/dist/services/product-modifiers-service.js +104 -0
- package/cjs/dist/services/product-service.d.ts +27 -0
- package/cjs/dist/services/product-service.js +51 -0
- package/cjs/dist/services/related-products-service.d.ts +25 -0
- package/cjs/dist/services/related-products-service.js +54 -0
- package/cjs/dist/services/selected-variant-service.d.ts +51 -0
- package/cjs/dist/services/selected-variant-service.js +396 -0
- package/cjs/dist/services/social-sharing-service.d.ts +41 -0
- package/cjs/dist/services/social-sharing-service.js +157 -0
- package/cjs/dist/services/sort-service.d.ts +19 -0
- package/cjs/dist/services/sort-service.js +37 -0
- package/cjs/dist/utils/url-params.d.ts +5 -0
- package/cjs/dist/utils/url-params.js +50 -0
- package/dist/react/BuyNow.d.ts +1 -0
- package/dist/react/BuyNow.js +1 -0
- package/dist/react/Category.d.ts +17 -0
- package/dist/react/Category.js +31 -0
- package/dist/react/Collection.d.ts +141 -0
- package/dist/react/Collection.js +190 -0
- package/dist/react/FilteredCollection.d.ts +65 -0
- package/dist/react/FilteredCollection.js +107 -0
- package/dist/react/PayNow.d.ts +1 -0
- package/dist/react/PayNow.js +1 -0
- package/dist/react/Product.d.ts +70 -0
- package/dist/react/Product.js +50 -0
- package/dist/react/ProductMediaGallery.d.ts +128 -0
- package/dist/react/ProductMediaGallery.js +92 -0
- package/dist/react/ProductModifiers.d.ts +156 -0
- package/dist/react/ProductModifiers.js +151 -0
- package/dist/react/ProductVariantSelector.d.ts +169 -0
- package/dist/react/ProductVariantSelector.js +157 -0
- package/dist/react/RelatedProducts.d.ts +60 -0
- package/dist/react/RelatedProducts.js +60 -0
- package/dist/react/SocialSharing.d.ts +119 -0
- package/dist/react/SocialSharing.js +71 -0
- package/dist/react/Sort.d.ts +17 -0
- package/dist/react/Sort.js +36 -0
- package/dist/react/index.d.ts +10 -0
- package/dist/react/index.js +10 -0
- package/dist/services/catalog-options-service.d.ts +30 -0
- package/dist/services/catalog-options-service.js +158 -0
- package/dist/services/catalog-price-range-service.d.ts +23 -0
- package/dist/services/catalog-price-range-service.js +91 -0
- package/dist/services/category-service.d.ts +25 -0
- package/dist/services/category-service.js +63 -0
- package/dist/services/collection-service.d.ts +37 -0
- package/dist/services/collection-service.js +417 -0
- package/dist/services/filter-service.d.ts +56 -0
- package/dist/services/filter-service.js +152 -0
- package/dist/services/product-media-gallery-service.d.ts +25 -0
- package/dist/services/product-media-gallery-service.js +101 -0
- package/dist/services/product-modifiers-service.d.ts +36 -0
- package/dist/services/product-modifiers-service.js +100 -0
- package/dist/services/product-service.d.ts +27 -0
- package/dist/services/product-service.js +47 -0
- package/dist/services/related-products-service.d.ts +25 -0
- package/dist/services/related-products-service.js +50 -0
- package/dist/services/selected-variant-service.d.ts +51 -0
- package/dist/services/selected-variant-service.js +392 -0
- package/dist/services/social-sharing-service.d.ts +41 -0
- package/dist/services/social-sharing-service.js +153 -0
- package/dist/services/sort-service.d.ts +19 -0
- package/dist/services/sort-service.js +34 -0
- package/dist/utils/url-params.d.ts +5 -0
- package/dist/utils/url-params.js +46 -0
- package/package.json +2 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for ProductName headless component
|
|
3
|
+
*/
|
|
4
|
+
export interface ProductNameProps {
|
|
5
|
+
/** Render prop function that receives product name data */
|
|
6
|
+
children: (props: ProductNameRenderProps) => React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render props for ProductName component
|
|
10
|
+
*/
|
|
11
|
+
export interface ProductNameRenderProps {
|
|
12
|
+
/** Product name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Whether product has a name */
|
|
15
|
+
hasName: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Headless component for product name display
|
|
19
|
+
*/
|
|
20
|
+
export declare const Name: (props: ProductNameProps) => import("react").ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Props for ProductDescription headless component
|
|
23
|
+
*/
|
|
24
|
+
export interface ProductDescriptionProps {
|
|
25
|
+
/** Render prop function that receives product description data */
|
|
26
|
+
children: (props: ProductDescriptionRenderProps) => React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Render props for ProductDescription component
|
|
30
|
+
*/
|
|
31
|
+
export interface ProductDescriptionRenderProps {
|
|
32
|
+
/** Product description (may contain HTML) */
|
|
33
|
+
description: string;
|
|
34
|
+
/** Whether product has a description */
|
|
35
|
+
hasDescription: boolean;
|
|
36
|
+
/** Whether description contains HTML */
|
|
37
|
+
isHtml: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Headless component for product description display
|
|
41
|
+
*/
|
|
42
|
+
export declare const Description: (props: ProductDescriptionProps) => import("react").ReactNode;
|
|
43
|
+
/**
|
|
44
|
+
* Props for ProductDetails headless component
|
|
45
|
+
*/
|
|
46
|
+
export interface ProductDetailsProps {
|
|
47
|
+
/** Render prop function that receives product details data */
|
|
48
|
+
children: (props: ProductDetailsRenderProps) => React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render props for ProductDetails component
|
|
52
|
+
*/
|
|
53
|
+
export interface ProductDetailsRenderProps {
|
|
54
|
+
/** Product SKU */
|
|
55
|
+
sku: string | null;
|
|
56
|
+
/** Product weight */
|
|
57
|
+
weight: string | null;
|
|
58
|
+
/** Product dimensions (if available) */
|
|
59
|
+
dimensions: string | null;
|
|
60
|
+
/** Whether product has SKU */
|
|
61
|
+
hasSku: boolean;
|
|
62
|
+
/** Whether product has weight */
|
|
63
|
+
hasWeight: boolean;
|
|
64
|
+
/** Whether product has dimensions */
|
|
65
|
+
hasDimensions: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Headless component for product details display
|
|
69
|
+
*/
|
|
70
|
+
export declare const Details: (props: ProductDetailsProps) => import("react").ReactNode;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useService } from "@wix/services-manager-react";
|
|
2
|
+
import { ProductServiceDefinition } from "../services/product-service";
|
|
3
|
+
import { SelectedVariantServiceDefinition } from "../services/selected-variant-service";
|
|
4
|
+
/**
|
|
5
|
+
* Headless component for product name display
|
|
6
|
+
*/
|
|
7
|
+
export const Name = (props) => {
|
|
8
|
+
const service = useService(ProductServiceDefinition);
|
|
9
|
+
const product = service.product.get();
|
|
10
|
+
const name = product?.name || "";
|
|
11
|
+
return props.children({
|
|
12
|
+
name,
|
|
13
|
+
hasName: !!name,
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Headless component for product description display
|
|
18
|
+
*/
|
|
19
|
+
export const Description = (props) => {
|
|
20
|
+
const service = useService(ProductServiceDefinition);
|
|
21
|
+
const product = service.product.get();
|
|
22
|
+
// Handle v3 description which can be string or RichContent
|
|
23
|
+
const rawDescription = product?.description;
|
|
24
|
+
const description = typeof rawDescription === "string" ? rawDescription : "";
|
|
25
|
+
const hasDescription = !!description;
|
|
26
|
+
const isHtml = description.includes("<") && description.includes(">");
|
|
27
|
+
return props.children({
|
|
28
|
+
description,
|
|
29
|
+
hasDescription,
|
|
30
|
+
isHtml,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Headless component for product details display
|
|
35
|
+
*/
|
|
36
|
+
export const Details = (props) => {
|
|
37
|
+
const selectedVariantService = useService(SelectedVariantServiceDefinition);
|
|
38
|
+
const selectedVariant = selectedVariantService.currentVariant?.get();
|
|
39
|
+
let sku = selectedVariant?.sku || null;
|
|
40
|
+
let weight = selectedVariant?.physicalProperties?.weight?.toString() || null;
|
|
41
|
+
let dimensions = null;
|
|
42
|
+
return props.children({
|
|
43
|
+
sku,
|
|
44
|
+
weight,
|
|
45
|
+
dimensions,
|
|
46
|
+
hasSku: !!sku,
|
|
47
|
+
hasWeight: !!weight,
|
|
48
|
+
hasDimensions: false,
|
|
49
|
+
});
|
|
50
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for Viewport headless component
|
|
3
|
+
*/
|
|
4
|
+
export interface ViewportProps {
|
|
5
|
+
/** Render prop function that receives viewport data */
|
|
6
|
+
children: (props: ViewportRenderProps) => React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Render props for Viewport component
|
|
10
|
+
*/
|
|
11
|
+
export interface ViewportRenderProps {
|
|
12
|
+
/** Current selected image */
|
|
13
|
+
image: any | null;
|
|
14
|
+
/** Image URL */
|
|
15
|
+
src: string | null;
|
|
16
|
+
/** Alt text for image */
|
|
17
|
+
alt: string;
|
|
18
|
+
/** Whether image is loading */
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
/** Current image index */
|
|
21
|
+
currentIndex: number;
|
|
22
|
+
/** Total number of images */
|
|
23
|
+
totalImages: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Headless component for displaying the main viewport image
|
|
27
|
+
*/
|
|
28
|
+
export declare const Viewport: (props: ViewportProps) => import("react").ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Props for Thumbnail headless component
|
|
31
|
+
*/
|
|
32
|
+
export interface ThumbnailProps {
|
|
33
|
+
/** Index of the media item */
|
|
34
|
+
index: number;
|
|
35
|
+
/** Render prop function that receives thumbnail data */
|
|
36
|
+
children: (props: ThumbnailRenderProps) => React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Render props for Thumbnail component
|
|
40
|
+
*/
|
|
41
|
+
export interface ThumbnailRenderProps {
|
|
42
|
+
/** Media item data */
|
|
43
|
+
item: any | null;
|
|
44
|
+
/** Thumbnail image URL */
|
|
45
|
+
src: string | null;
|
|
46
|
+
/** Whether this thumbnail is currently active */
|
|
47
|
+
isActive: boolean;
|
|
48
|
+
/** Function to select this image */
|
|
49
|
+
onSelect: () => void;
|
|
50
|
+
/** Index of this thumbnail */
|
|
51
|
+
index: number;
|
|
52
|
+
/** Alt text for thumbnail */
|
|
53
|
+
alt: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Headless component for individual media thumbnail
|
|
57
|
+
*/
|
|
58
|
+
export declare const Thumbnail: (props: ThumbnailProps) => import("react").ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Props for Next headless component
|
|
61
|
+
*/
|
|
62
|
+
export interface NextProps {
|
|
63
|
+
/** Render prop function that receives next navigation data */
|
|
64
|
+
children: (props: NextRenderProps) => React.ReactNode;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Render props for Next component
|
|
68
|
+
*/
|
|
69
|
+
export interface NextRenderProps {
|
|
70
|
+
/** Function to go to next image */
|
|
71
|
+
onNext: () => void;
|
|
72
|
+
/** Whether there is a next image available */
|
|
73
|
+
canGoNext: boolean;
|
|
74
|
+
/** Current image index */
|
|
75
|
+
currentIndex: number;
|
|
76
|
+
/** Total number of images */
|
|
77
|
+
totalImages: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Headless component for next image navigation
|
|
81
|
+
*/
|
|
82
|
+
export declare const Next: (props: NextProps) => import("react").ReactNode;
|
|
83
|
+
/**
|
|
84
|
+
* Props for Previous headless component
|
|
85
|
+
*/
|
|
86
|
+
export interface PreviousProps {
|
|
87
|
+
/** Render prop function that receives previous navigation data */
|
|
88
|
+
children: (props: PreviousRenderProps) => React.ReactNode;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Render props for Previous component
|
|
92
|
+
*/
|
|
93
|
+
export interface PreviousRenderProps {
|
|
94
|
+
/** Function to go to previous image */
|
|
95
|
+
onPrevious: () => void;
|
|
96
|
+
/** Whether there is a previous image available */
|
|
97
|
+
canGoPrevious: boolean;
|
|
98
|
+
/** Current image index */
|
|
99
|
+
currentIndex: number;
|
|
100
|
+
/** Total number of images */
|
|
101
|
+
totalImages: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Headless component for previous image navigation
|
|
105
|
+
*/
|
|
106
|
+
export declare const Previous: (props: PreviousProps) => import("react").ReactNode;
|
|
107
|
+
/**
|
|
108
|
+
* Props for Indicator headless component
|
|
109
|
+
*/
|
|
110
|
+
export interface IndicatorProps {
|
|
111
|
+
/** Render prop function that receives indicator data */
|
|
112
|
+
children: (props: IndicatorRenderProps) => React.ReactNode;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Render props for Indicator component
|
|
116
|
+
*/
|
|
117
|
+
export interface IndicatorRenderProps {
|
|
118
|
+
/** Current image index (1-based for display) */
|
|
119
|
+
current: number;
|
|
120
|
+
/** Total number of images */
|
|
121
|
+
total: number;
|
|
122
|
+
/** Whether gallery has images */
|
|
123
|
+
hasImages: boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Headless component for media gallery indicator/counter
|
|
127
|
+
*/
|
|
128
|
+
export declare const Indicator: (props: IndicatorProps) => import("react").ReactNode;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useService } from "@wix/services-manager-react";
|
|
2
|
+
import { ProductMediaGalleryServiceDefinition } from "../services/product-media-gallery-service";
|
|
3
|
+
/**
|
|
4
|
+
* Headless component for displaying the main viewport image
|
|
5
|
+
*/
|
|
6
|
+
export const Viewport = (props) => {
|
|
7
|
+
const mediaService = useService(ProductMediaGalleryServiceDefinition);
|
|
8
|
+
const currentIndex = mediaService.selectedImageIndex.get();
|
|
9
|
+
const isLoading = mediaService.isLoading.get();
|
|
10
|
+
const totalImages = mediaService.totalImages.get();
|
|
11
|
+
const productName = mediaService.productName.get();
|
|
12
|
+
const relevantImages = mediaService.relevantImages.get();
|
|
13
|
+
// Get the current image from the relevant images array
|
|
14
|
+
const src = relevantImages[currentIndex] || null;
|
|
15
|
+
const alt = productName || "Product image";
|
|
16
|
+
return props.children({
|
|
17
|
+
image: src,
|
|
18
|
+
src,
|
|
19
|
+
alt,
|
|
20
|
+
isLoading,
|
|
21
|
+
currentIndex,
|
|
22
|
+
totalImages,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Headless component for individual media thumbnail
|
|
27
|
+
*/
|
|
28
|
+
export const Thumbnail = (props) => {
|
|
29
|
+
const mediaService = useService(ProductMediaGalleryServiceDefinition);
|
|
30
|
+
const product = mediaService.product.get();
|
|
31
|
+
const currentIndex = mediaService.selectedImageIndex.get();
|
|
32
|
+
const productName = mediaService.productName.get();
|
|
33
|
+
const relevantImages = mediaService.relevantImages.get();
|
|
34
|
+
// Get the image source from the centralized relevant images
|
|
35
|
+
const src = relevantImages[props.index] || null;
|
|
36
|
+
const isActive = currentIndex === props.index;
|
|
37
|
+
const alt = `${productName || "Product"} image ${props.index + 1}`;
|
|
38
|
+
const onSelect = () => {
|
|
39
|
+
mediaService.setSelectedImageIndex(props.index);
|
|
40
|
+
};
|
|
41
|
+
return props.children({
|
|
42
|
+
item: product?.media?.main || null,
|
|
43
|
+
src,
|
|
44
|
+
isActive,
|
|
45
|
+
onSelect,
|
|
46
|
+
index: props.index,
|
|
47
|
+
alt,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Headless component for next image navigation
|
|
52
|
+
*/
|
|
53
|
+
export const Next = (props) => {
|
|
54
|
+
const mediaService = useService(ProductMediaGalleryServiceDefinition);
|
|
55
|
+
const currentIndex = mediaService.selectedImageIndex.get();
|
|
56
|
+
const totalImages = mediaService.totalImages.get();
|
|
57
|
+
const canGoNext = currentIndex < totalImages - 1;
|
|
58
|
+
return props.children({
|
|
59
|
+
onNext: mediaService.nextImage,
|
|
60
|
+
canGoNext,
|
|
61
|
+
currentIndex,
|
|
62
|
+
totalImages,
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Headless component for previous image navigation
|
|
67
|
+
*/
|
|
68
|
+
export const Previous = (props) => {
|
|
69
|
+
const mediaService = useService(ProductMediaGalleryServiceDefinition);
|
|
70
|
+
const currentIndex = mediaService.selectedImageIndex.get();
|
|
71
|
+
const totalImages = mediaService.totalImages.get();
|
|
72
|
+
const canGoPrevious = currentIndex > 0;
|
|
73
|
+
return props.children({
|
|
74
|
+
onPrevious: mediaService.previousImage,
|
|
75
|
+
canGoPrevious,
|
|
76
|
+
currentIndex,
|
|
77
|
+
totalImages,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Headless component for media gallery indicator/counter
|
|
82
|
+
*/
|
|
83
|
+
export const Indicator = (props) => {
|
|
84
|
+
const mediaService = useService(ProductMediaGalleryServiceDefinition);
|
|
85
|
+
const currentIndex = mediaService.selectedImageIndex.get();
|
|
86
|
+
const totalImages = mediaService.totalImages.get();
|
|
87
|
+
return props.children({
|
|
88
|
+
current: currentIndex + 1,
|
|
89
|
+
total: totalImages,
|
|
90
|
+
hasImages: totalImages > 0,
|
|
91
|
+
});
|
|
92
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { productsV3 } from "@wix/stores";
|
|
2
|
+
/**
|
|
3
|
+
* Props for Modifiers headless component
|
|
4
|
+
*/
|
|
5
|
+
export interface ModifiersProps {
|
|
6
|
+
/** Render prop function that receives modifiers data */
|
|
7
|
+
children: (props: ModifiersRenderProps) => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Render props for Modifiers component
|
|
11
|
+
*/
|
|
12
|
+
export interface ModifiersRenderProps {
|
|
13
|
+
/** Array of product modifiers */
|
|
14
|
+
modifiers: productsV3.ConnectedModifier[];
|
|
15
|
+
/** Whether product has modifiers */
|
|
16
|
+
hasModifiers: boolean;
|
|
17
|
+
/** Currently selected modifier values */
|
|
18
|
+
selectedModifiers: Record<string, any>;
|
|
19
|
+
/** Whether all required modifiers are filled */
|
|
20
|
+
areAllRequiredModifiersFilled: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Headless component for all product modifiers
|
|
24
|
+
*/
|
|
25
|
+
export declare const Modifiers: (props: ModifiersProps) => import("react").ReactNode;
|
|
26
|
+
/**
|
|
27
|
+
* Props for Modifier headless component
|
|
28
|
+
*/
|
|
29
|
+
export interface ModifierProps {
|
|
30
|
+
/** Product modifier data */
|
|
31
|
+
modifier: productsV3.ConnectedModifier;
|
|
32
|
+
/** Render prop function that receives modifier data */
|
|
33
|
+
children: (props: ModifierRenderProps) => React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render props for Modifier component
|
|
37
|
+
*/
|
|
38
|
+
export interface ModifierRenderProps {
|
|
39
|
+
/** Modifier name */
|
|
40
|
+
name: string;
|
|
41
|
+
/** Modifier type */
|
|
42
|
+
type: any;
|
|
43
|
+
/** Whether this modifier is mandatory */
|
|
44
|
+
mandatory: boolean;
|
|
45
|
+
/** Array of choices for this modifier (for choice-based modifiers) */
|
|
46
|
+
choices: productsV3.ConnectedModifierChoice[];
|
|
47
|
+
/** Currently selected value for this modifier */
|
|
48
|
+
selectedValue: any;
|
|
49
|
+
/** Whether this modifier has choices */
|
|
50
|
+
hasChoices: boolean;
|
|
51
|
+
/** Whether this modifier is a free text type */
|
|
52
|
+
isFreeText: boolean;
|
|
53
|
+
/** Maximum characters for free text */
|
|
54
|
+
maxChars?: number;
|
|
55
|
+
/** Placeholder text for free text */
|
|
56
|
+
placeholder?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Headless component for a specific product modifier
|
|
60
|
+
*/
|
|
61
|
+
export declare const Modifier: (props: ModifierProps) => import("react").ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* Props for ModifierChoice headless component
|
|
64
|
+
*/
|
|
65
|
+
export interface ChoiceProps {
|
|
66
|
+
/** Product modifier data */
|
|
67
|
+
modifier: productsV3.ConnectedModifier;
|
|
68
|
+
/** Choice data */
|
|
69
|
+
choice: productsV3.ConnectedModifierChoice;
|
|
70
|
+
/** Render prop function that receives choice data */
|
|
71
|
+
children: (props: ChoiceRenderProps) => React.ReactNode;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render props for ModifierChoice component
|
|
75
|
+
*/
|
|
76
|
+
export interface ChoiceRenderProps {
|
|
77
|
+
/** Choice value to display */
|
|
78
|
+
value: string;
|
|
79
|
+
/** Choice description (for color options) */
|
|
80
|
+
description: string | undefined;
|
|
81
|
+
/** Whether this choice is currently selected */
|
|
82
|
+
isSelected: boolean;
|
|
83
|
+
/** Function to select this choice */
|
|
84
|
+
onSelect: () => void;
|
|
85
|
+
/** Modifier name */
|
|
86
|
+
modifierName: string;
|
|
87
|
+
/** Choice value */
|
|
88
|
+
choiceValue: string;
|
|
89
|
+
/** Color code for swatch choices */
|
|
90
|
+
colorCode?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Headless component for individual modifier choice selection
|
|
94
|
+
*/
|
|
95
|
+
export declare const Choice: (props: ChoiceProps) => import("react").ReactNode;
|
|
96
|
+
/**
|
|
97
|
+
* Props for ModifierFreeText headless component
|
|
98
|
+
*/
|
|
99
|
+
export interface FreeTextProps {
|
|
100
|
+
/** Product modifier data */
|
|
101
|
+
modifier: productsV3.ConnectedModifier;
|
|
102
|
+
/** Render prop function that receives free text data */
|
|
103
|
+
children: (props: FreeTextRenderProps) => React.ReactNode;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Render props for ModifierFreeText component
|
|
107
|
+
*/
|
|
108
|
+
export interface FreeTextRenderProps {
|
|
109
|
+
/** Current text value */
|
|
110
|
+
value: string;
|
|
111
|
+
/** Function to update text value */
|
|
112
|
+
onChange: (value: string) => void;
|
|
113
|
+
/** Whether this modifier is mandatory */
|
|
114
|
+
mandatory: boolean;
|
|
115
|
+
/** Maximum characters allowed */
|
|
116
|
+
maxChars?: number;
|
|
117
|
+
/** Placeholder text */
|
|
118
|
+
placeholder?: string;
|
|
119
|
+
/** Character count */
|
|
120
|
+
charCount: number;
|
|
121
|
+
/** Whether character limit is exceeded */
|
|
122
|
+
isOverLimit: boolean;
|
|
123
|
+
/** Modifier name */
|
|
124
|
+
modifierName: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Headless component for free text modifier input
|
|
128
|
+
*/
|
|
129
|
+
export declare const FreeText: (props: FreeTextProps) => import("react").ReactNode;
|
|
130
|
+
/**
|
|
131
|
+
* Props for ModifierToggleFreeText headless component
|
|
132
|
+
*/
|
|
133
|
+
export interface ToggleFreeTextProps {
|
|
134
|
+
/** Product modifier data */
|
|
135
|
+
modifier: productsV3.ConnectedModifier;
|
|
136
|
+
/** Render prop function that receives toggle data */
|
|
137
|
+
children: (props: ToggleFreeTextRenderProps) => React.ReactNode;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Render props for ModifierToggleFreeText component
|
|
141
|
+
*/
|
|
142
|
+
export interface ToggleFreeTextRenderProps {
|
|
143
|
+
/** Whether the text input is shown */
|
|
144
|
+
isTextInputShown: boolean;
|
|
145
|
+
/** Function to toggle text input visibility */
|
|
146
|
+
onToggle: () => void;
|
|
147
|
+
/** Whether this modifier is mandatory */
|
|
148
|
+
mandatory: boolean;
|
|
149
|
+
/** Modifier name */
|
|
150
|
+
modifierName: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Headless component for toggling free text modifier input
|
|
154
|
+
* Used for optional free text modifiers where a checkbox shows/hides the input
|
|
155
|
+
*/
|
|
156
|
+
export declare const ToggleFreeText: (props: ToggleFreeTextProps) => import("react").ReactNode;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useService } from "@wix/services-manager-react";
|
|
3
|
+
import { ProductModifiersServiceDefinition } from "../services/product-modifiers-service";
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook to safely get the modifiers service
|
|
6
|
+
*/
|
|
7
|
+
function useModifiersService() {
|
|
8
|
+
try {
|
|
9
|
+
return useService(ProductModifiersServiceDefinition);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Headless component for all product modifiers
|
|
17
|
+
*/
|
|
18
|
+
export const Modifiers = (props) => {
|
|
19
|
+
const modifiersService = useModifiersService();
|
|
20
|
+
if (!modifiersService) {
|
|
21
|
+
return props.children({
|
|
22
|
+
modifiers: [],
|
|
23
|
+
hasModifiers: false,
|
|
24
|
+
selectedModifiers: {},
|
|
25
|
+
areAllRequiredModifiersFilled: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const modifiers = modifiersService.modifiers.get();
|
|
29
|
+
const hasModifiers = modifiersService.hasModifiers.get();
|
|
30
|
+
const selectedModifiers = modifiersService.selectedModifiers.get();
|
|
31
|
+
const areAllRequiredModifiersFilled = modifiersService.areAllRequiredModifiersFilled();
|
|
32
|
+
return props.children({
|
|
33
|
+
modifiers,
|
|
34
|
+
hasModifiers,
|
|
35
|
+
selectedModifiers,
|
|
36
|
+
areAllRequiredModifiersFilled,
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Headless component for a specific product modifier
|
|
41
|
+
*/
|
|
42
|
+
export const Modifier = (props) => {
|
|
43
|
+
const modifiersService = useModifiersService();
|
|
44
|
+
const { modifier } = props;
|
|
45
|
+
const name = modifier.name || "";
|
|
46
|
+
const type = modifier.modifierRenderType;
|
|
47
|
+
const mandatory = modifier.mandatory || false;
|
|
48
|
+
const choices = modifier.choicesSettings?.choices || [];
|
|
49
|
+
const hasChoices = choices.length > 0;
|
|
50
|
+
const isFreeText = type === "FREE_TEXT";
|
|
51
|
+
const freeTextSettings = modifier.freeTextSettings;
|
|
52
|
+
const maxChars = freeTextSettings?.maxLength;
|
|
53
|
+
const placeholder = freeTextSettings?.placeholder;
|
|
54
|
+
const selectedValue = modifiersService?.selectedModifiers.get()[name] || null;
|
|
55
|
+
return props.children({
|
|
56
|
+
name,
|
|
57
|
+
type,
|
|
58
|
+
mandatory,
|
|
59
|
+
choices,
|
|
60
|
+
selectedValue,
|
|
61
|
+
hasChoices,
|
|
62
|
+
isFreeText,
|
|
63
|
+
maxChars,
|
|
64
|
+
placeholder,
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Headless component for individual modifier choice selection
|
|
69
|
+
*/
|
|
70
|
+
export const Choice = (props) => {
|
|
71
|
+
const modifiersService = useModifiersService();
|
|
72
|
+
const { modifier, choice } = props;
|
|
73
|
+
const modifierName = modifier.name || "";
|
|
74
|
+
const renderType = modifier.modifierRenderType;
|
|
75
|
+
// For TEXT_CHOICES, use choice.key; for SWATCH_CHOICES, use choice.name
|
|
76
|
+
const choiceValue = renderType === "TEXT_CHOICES"
|
|
77
|
+
? (choice.key || choice.name || "")
|
|
78
|
+
: (choice.name || "");
|
|
79
|
+
const value = choice.name || ""; // Display name is always choice.name
|
|
80
|
+
const description = choice.description;
|
|
81
|
+
const colorCode = choice.colorCode;
|
|
82
|
+
const selectedValue = modifiersService?.getModifierValue(modifierName);
|
|
83
|
+
const isSelected = selectedValue?.choiceValue === choiceValue;
|
|
84
|
+
const onSelect = () => {
|
|
85
|
+
modifiersService?.setModifierChoice(modifierName, choiceValue);
|
|
86
|
+
};
|
|
87
|
+
return props.children({
|
|
88
|
+
value,
|
|
89
|
+
description,
|
|
90
|
+
isSelected,
|
|
91
|
+
onSelect,
|
|
92
|
+
modifierName,
|
|
93
|
+
choiceValue,
|
|
94
|
+
colorCode,
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Headless component for free text modifier input
|
|
99
|
+
*/
|
|
100
|
+
export const FreeText = (props) => {
|
|
101
|
+
const modifiersService = useModifiersService();
|
|
102
|
+
const { modifier } = props;
|
|
103
|
+
const modifierName = modifier.name || "";
|
|
104
|
+
const mandatory = modifier.mandatory || false;
|
|
105
|
+
const freeTextSettings = modifier.freeTextSettings;
|
|
106
|
+
const maxChars = freeTextSettings?.maxLength;
|
|
107
|
+
const placeholder = freeTextSettings?.placeholder;
|
|
108
|
+
const selectedValue = modifiersService?.getModifierValue(modifierName);
|
|
109
|
+
const value = selectedValue?.freeTextValue || "";
|
|
110
|
+
const charCount = value.length;
|
|
111
|
+
const isOverLimit = maxChars ? charCount > maxChars : false;
|
|
112
|
+
const onChange = (newValue) => {
|
|
113
|
+
if (maxChars && newValue.length > maxChars)
|
|
114
|
+
return;
|
|
115
|
+
modifiersService?.setModifierFreeText(modifierName, newValue);
|
|
116
|
+
};
|
|
117
|
+
return props.children({
|
|
118
|
+
value,
|
|
119
|
+
onChange,
|
|
120
|
+
mandatory,
|
|
121
|
+
maxChars,
|
|
122
|
+
placeholder,
|
|
123
|
+
charCount,
|
|
124
|
+
isOverLimit,
|
|
125
|
+
modifierName,
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Headless component for toggling free text modifier input
|
|
130
|
+
* Used for optional free text modifiers where a checkbox shows/hides the input
|
|
131
|
+
*/
|
|
132
|
+
export const ToggleFreeText = (props) => {
|
|
133
|
+
const modifiersService = useModifiersService();
|
|
134
|
+
const { modifier } = props;
|
|
135
|
+
const modifierName = modifier.name || "";
|
|
136
|
+
const mandatory = modifier.mandatory || false;
|
|
137
|
+
const [isTextInputShown, setIsTextInputShown] = useState(mandatory);
|
|
138
|
+
const onToggle = () => {
|
|
139
|
+
const newState = !isTextInputShown;
|
|
140
|
+
setIsTextInputShown(newState);
|
|
141
|
+
if (!newState) {
|
|
142
|
+
modifiersService?.clearModifier(modifierName);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return props.children({
|
|
146
|
+
isTextInputShown,
|
|
147
|
+
onToggle,
|
|
148
|
+
mandatory,
|
|
149
|
+
modifierName,
|
|
150
|
+
});
|
|
151
|
+
};
|