@wix/headless-stores 0.0.57 → 0.0.58
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/ProductList.js +1 -0
- package/cjs/dist/react/ProductListSort.d.ts +14 -0
- package/cjs/dist/react/ProductListSort.js +14 -0
- package/cjs/dist/react/core/ProductList.d.ts +3 -0
- package/cjs/dist/react/core/ProductList.js +2 -0
- package/cjs/dist/react/core/ProductListFilters.d.ts +8 -180
- package/cjs/dist/react/core/ProductListFilters.js +137 -171
- package/cjs/dist/react/core/ProductListPagination.d.ts +0 -192
- package/cjs/dist/react/core/ProductListPagination.js +2 -160
- package/cjs/dist/react/core/ProductListSort.d.ts +9 -57
- package/cjs/dist/react/core/ProductListSort.js +32 -52
- package/cjs/dist/services/index.d.ts +2 -2
- package/cjs/dist/services/products-list-search-service.d.ts +3 -162
- package/cjs/dist/services/products-list-search-service.js +31 -424
- package/cjs/dist/services/products-list-service.d.ts +86 -4
- package/cjs/dist/services/products-list-service.js +125 -4
- package/dist/react/ProductList.js +1 -0
- package/dist/react/ProductListSort.d.ts +14 -0
- package/dist/react/ProductListSort.js +14 -0
- package/dist/react/core/ProductList.d.ts +3 -0
- package/dist/react/core/ProductList.js +2 -0
- package/dist/react/core/ProductListFilters.d.ts +8 -180
- package/dist/react/core/ProductListFilters.js +137 -171
- package/dist/react/core/ProductListPagination.d.ts +0 -192
- package/dist/react/core/ProductListPagination.js +2 -160
- package/dist/react/core/ProductListSort.d.ts +9 -57
- package/dist/react/core/ProductListSort.js +32 -52
- package/dist/services/index.d.ts +2 -2
- package/dist/services/products-list-search-service.d.ts +3 -162
- package/dist/services/products-list-search-service.js +31 -424
- package/dist/services/products-list-service.d.ts +86 -4
- package/dist/services/products-list-service.js +125 -4
- package/package.json +4 -4
|
@@ -48,6 +48,7 @@ export const Root = React.forwardRef((props, ref) => {
|
|
|
48
48
|
count: products?.length || 0,
|
|
49
49
|
},
|
|
50
50
|
aggregations: {}, // Empty aggregation data
|
|
51
|
+
customizations: [],
|
|
51
52
|
};
|
|
52
53
|
return (_jsx(CoreProductList.Root, { productsListConfig: serviceConfig, productsListSearchConfig: productsListSearchConfig, children: _jsx(RootContent, { children: children, className: className, ref: ref }) }));
|
|
53
54
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Sort as SortPrimitive } from '@wix/headless-components/react';
|
|
2
|
+
import { productsV3 } from '@wix/stores';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
export interface ProductListSortProps {
|
|
5
|
+
children?: (props: {
|
|
6
|
+
currentSort: productsV3.V3ProductSearch['sort'];
|
|
7
|
+
sortOptions: SortPrimitive.SortOption[];
|
|
8
|
+
setSort: (sort: productsV3.V3ProductSearch['sort']) => void;
|
|
9
|
+
}) => React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
as?: 'select' | 'list';
|
|
12
|
+
asChild?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const ProductListSort: React.ForwardRefExoticComponent<ProductListSortProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Sort as SortPrimitive } from '@wix/headless-components/react';
|
|
3
|
+
import { ProductListSort as ProductListSortPrimitive } from './core/ProductListSort.js';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export const ProductListSort = React.forwardRef(({ children, className, as, asChild }, ref) => {
|
|
6
|
+
return (_jsx(ProductListSortPrimitive, { children: ({ currentSort, sortOptions, setSort }) => {
|
|
7
|
+
if (asChild && children) {
|
|
8
|
+
return children({ currentSort, sortOptions, setSort });
|
|
9
|
+
}
|
|
10
|
+
return (_jsx(SortPrimitive.Root, { ref: ref, value: currentSort, onChange: (value) => {
|
|
11
|
+
setSort(value);
|
|
12
|
+
}, sortOptions: sortOptions, as: as, className: className }));
|
|
13
|
+
} }));
|
|
14
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ProductsListServiceConfig } from '../../services/products-list-service.js';
|
|
2
2
|
import { productsV3 } from '@wix/stores';
|
|
3
3
|
import { ProductsListSearchServiceConfig } from '../../services/products-list-search-service.js';
|
|
4
|
+
import { CategoriesListServiceConfig } from '../../services/categories-list-service.js';
|
|
4
5
|
/**
|
|
5
6
|
* Props for Root headless component
|
|
6
7
|
*/
|
|
@@ -11,6 +12,8 @@ export interface RootProps {
|
|
|
11
12
|
productsListConfig: ProductsListServiceConfig;
|
|
12
13
|
/** Configuration for the ProductListSearch service */
|
|
13
14
|
productsListSearchConfig?: ProductsListSearchServiceConfig;
|
|
15
|
+
/** Configuration for the CategoriesList service */
|
|
16
|
+
categoriesListConfig?: CategoriesListServiceConfig;
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
16
19
|
* Root component that provides both ProductList and ProductListSearch service contexts to its children.
|
|
@@ -4,6 +4,7 @@ import { createServicesMap } from '@wix/services-manager';
|
|
|
4
4
|
import { ProductListService, ProductsListServiceDefinition, } from '../../services/products-list-service.js';
|
|
5
5
|
import { ProductService, ProductServiceDefinition, } from '../../services/product-service.js';
|
|
6
6
|
import { ProductsListSearchService, ProductsListSearchServiceDefinition, } from '../../services/products-list-search-service.js';
|
|
7
|
+
import { CategoriesListService, CategoriesListServiceDefinition, } from '../../services/categories-list-service.js';
|
|
7
8
|
/**
|
|
8
9
|
* Root component that provides both ProductList and ProductListSearch service contexts to its children.
|
|
9
10
|
* This component sets up the necessary services for managing products list state, including search,
|
|
@@ -64,6 +65,7 @@ import { ProductsListSearchService, ProductsListSearchServiceDefinition, } from
|
|
|
64
65
|
*/
|
|
65
66
|
export function Root(props) {
|
|
66
67
|
return (_jsx(WixServices, { servicesMap: createServicesMap()
|
|
68
|
+
.addService(CategoriesListServiceDefinition, CategoriesListService, props.categoriesListConfig)
|
|
67
69
|
.addService(ProductsListServiceDefinition, ProductListService, props.productsListConfig)
|
|
68
70
|
.addService(ProductsListSearchServiceDefinition, ProductsListSearchService, props.productsListSearchConfig), children: props.children }));
|
|
69
71
|
}
|
|
@@ -1,61 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { type ProductOption, InventoryStatusType } from '../../services/products-list-search-service.js';
|
|
3
2
|
import { Category } from '@wix/auto_sdk_categories_categories';
|
|
4
|
-
|
|
5
|
-
* Props for InventoryStatus headless component
|
|
6
|
-
*/
|
|
7
|
-
export interface InventoryStatusProps {
|
|
8
|
-
/** Content to display (can be a render function receiving inventory status controls or ReactNode) */
|
|
9
|
-
children: ((props: InventoryStatusRenderProps) => ReactNode) | ReactNode;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Render props for InventoryStatus component
|
|
13
|
-
*/
|
|
14
|
-
export interface InventoryStatusRenderProps {
|
|
15
|
-
/** Available inventory status options */
|
|
16
|
-
availableInventoryStatuses: InventoryStatusType[];
|
|
17
|
-
/** Currently selected inventory statuses */
|
|
18
|
-
selectedInventoryStatuses: InventoryStatusType[];
|
|
19
|
-
/** Function to toggle an inventory status filter */
|
|
20
|
-
toggleInventoryStatus: (status: InventoryStatusType) => void;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Headless component for managing inventory status filters
|
|
24
|
-
*
|
|
25
|
-
* @component
|
|
26
|
-
* @example
|
|
27
|
-
* ```tsx
|
|
28
|
-
* import { ProductList, ProductListFilters } from '@wix/stores/components';
|
|
29
|
-
*
|
|
30
|
-
* function InventoryStatusFilter() {
|
|
31
|
-
* return (
|
|
32
|
-
* <ProductList.Root
|
|
33
|
-
* productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
|
|
34
|
-
* productsListSearchConfig={{ customizations: [] }}
|
|
35
|
-
* >
|
|
36
|
-
* <ProductListFilters.InventoryStatus>
|
|
37
|
-
* {({ availableInventoryStatuses, selectedInventoryStatuses, toggleInventoryStatus }) => (
|
|
38
|
-
* <div>
|
|
39
|
-
* <h4>Inventory Status:</h4>
|
|
40
|
-
* {availableInventoryStatuses.map(status => (
|
|
41
|
-
* <label key={status}>
|
|
42
|
-
* <input
|
|
43
|
-
* type="checkbox"
|
|
44
|
-
* checked={selectedInventoryStatuses.includes(status)}
|
|
45
|
-
* onChange={() => toggleInventoryStatus(status)}
|
|
46
|
-
* />
|
|
47
|
-
* {status}
|
|
48
|
-
* </label>
|
|
49
|
-
* ))}
|
|
50
|
-
* </div>
|
|
51
|
-
* )}
|
|
52
|
-
* </ProductListFilters.InventoryStatus>
|
|
53
|
-
* </ProductList.Root>
|
|
54
|
-
* );
|
|
55
|
-
* }
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export declare function InventoryStatus(props: InventoryStatusProps): ReactNode;
|
|
3
|
+
import React from 'react';
|
|
59
4
|
/**
|
|
60
5
|
* Props for ResetTrigger headless component
|
|
61
6
|
*/
|
|
@@ -103,136 +48,19 @@ export interface ResetTriggerRenderProps {
|
|
|
103
48
|
* ```
|
|
104
49
|
*/
|
|
105
50
|
export declare function ResetTrigger(props: ResetTriggerProps): ReactNode;
|
|
106
|
-
/**
|
|
107
|
-
* Props for PriceRange headless component
|
|
108
|
-
*/
|
|
109
|
-
export interface PriceRangeProps {
|
|
110
|
-
/** Content to display (can be a render function receiving price range controls or ReactNode) */
|
|
111
|
-
children: ((props: PriceRangeRenderProps) => ReactNode) | ReactNode;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Render props for PriceRange component
|
|
115
|
-
*/
|
|
116
|
-
export interface PriceRangeRenderProps {
|
|
117
|
-
/** Current minimum price filter value */
|
|
118
|
-
selectedMinPrice: number;
|
|
119
|
-
/** Current maximum price filter value */
|
|
120
|
-
selectedMaxPrice: number;
|
|
121
|
-
/** Catalog minimum price */
|
|
122
|
-
availableMinPrice: number;
|
|
123
|
-
/** Catalog maximum price */
|
|
124
|
-
availableMaxPrice: number;
|
|
125
|
-
/** Function to update the minimum price filter */
|
|
126
|
-
setSelectedMinPrice: (minPrice: number) => void;
|
|
127
|
-
/** Function to update the maximum price filter */
|
|
128
|
-
setSelectedMaxPrice: (maxPrice: number) => void;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Headless component for managing price range filters (combined min/max)
|
|
132
|
-
*
|
|
133
|
-
* @component
|
|
134
|
-
* @example
|
|
135
|
-
* ```tsx
|
|
136
|
-
* import { ProductList, ProductListFilters } from '@wix/stores/components';
|
|
137
|
-
*
|
|
138
|
-
* function PriceRangeFilter() {
|
|
139
|
-
* return (
|
|
140
|
-
* <ProductList.Root
|
|
141
|
-
* productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
|
|
142
|
-
* productsListSearchConfig={{ customizations: [] }}
|
|
143
|
-
* >
|
|
144
|
-
* <ProductListFilters.PriceRange>
|
|
145
|
-
* {({ minPrice, maxPrice, setSelectedMinPrice, setSelectedMaxPrice }) => (
|
|
146
|
-
* <div className="price-range">
|
|
147
|
-
* <h4>Price Range:</h4>
|
|
148
|
-
* <div className="price-inputs">
|
|
149
|
-
* <input
|
|
150
|
-
* type="number"
|
|
151
|
-
* value={minPrice}
|
|
152
|
-
* onChange={(e) => setSelectedMinPrice(Number(e.target.value))}
|
|
153
|
-
* placeholder="Min"
|
|
154
|
-
* />
|
|
155
|
-
* <span>to</span>
|
|
156
|
-
* <input
|
|
157
|
-
* type="number"
|
|
158
|
-
* value={maxPrice}
|
|
159
|
-
* onChange={(e) => setSelectedMaxPrice(Number(e.target.value))}
|
|
160
|
-
* placeholder="Max"
|
|
161
|
-
* />
|
|
162
|
-
* </div>
|
|
163
|
-
* </div>
|
|
164
|
-
* )}
|
|
165
|
-
* </ProductListFilters.PriceRange>
|
|
166
|
-
* </ProductList.Root>
|
|
167
|
-
* );
|
|
168
|
-
* }
|
|
169
|
-
* ```
|
|
170
|
-
*/
|
|
171
|
-
export declare function PriceRange(props: PriceRangeProps): ReactNode;
|
|
172
51
|
export interface CategoryFilterRenderProps {
|
|
173
52
|
selectedCategory: Category | null;
|
|
174
|
-
setSelectedCategory: (category: Category
|
|
53
|
+
setSelectedCategory: (category: Category) => void;
|
|
175
54
|
}
|
|
176
55
|
export interface CategoryFilterProps {
|
|
177
56
|
/** Content to display (can be a render function receiving category data or ReactNode) */
|
|
178
57
|
children: ((props: CategoryFilterRenderProps) => ReactNode) | ReactNode;
|
|
179
58
|
}
|
|
180
59
|
export declare function CategoryFilter(props: CategoryFilterProps): ReactNode;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/** Content to display (can be a render function receiving product option data or ReactNode) */
|
|
186
|
-
children: ((props: ProductOptionRenderProps) => ReactNode) | ReactNode;
|
|
60
|
+
interface FilterProps {
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
asChild?: boolean;
|
|
63
|
+
className?: string;
|
|
187
64
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*/
|
|
191
|
-
export interface ProductOptionRenderProps {
|
|
192
|
-
/** Product option data */
|
|
193
|
-
option: ProductOption;
|
|
194
|
-
/** Currently selected choice IDs for this option */
|
|
195
|
-
selectedChoices: string[];
|
|
196
|
-
/** Function to toggle a choice selection */
|
|
197
|
-
toggleChoice: (choiceId: string) => void;
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Headless component that renders content for each product option in the list.
|
|
201
|
-
* Maps over all available product options and provides each option through a render prop.
|
|
202
|
-
* Only renders when options are available (not loading, no error, and has options).
|
|
203
|
-
* This follows the same collection pattern as ProductList.ItemContent and CategoryList.ItemContent.
|
|
204
|
-
*
|
|
205
|
-
* @component
|
|
206
|
-
* @example
|
|
207
|
-
* ```tsx
|
|
208
|
-
* import { ProductList, ProductListFilters } from '@wix/stores/components';
|
|
209
|
-
*
|
|
210
|
-
* function ProductOptionsFilter() {
|
|
211
|
-
* return (
|
|
212
|
-
* <ProductList.Root
|
|
213
|
-
* productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
|
|
214
|
-
* productsListSearchConfig={{ customizations: [] }}
|
|
215
|
-
* >
|
|
216
|
-
* <ProductListFilters.ProductOptions>
|
|
217
|
-
* {({ option, selectedChoices, toggleChoice }) => (
|
|
218
|
-
* <div key={option.id}>
|
|
219
|
-
* <h4>{option.name}</h4>
|
|
220
|
-
* {option.choices.map(choice => (
|
|
221
|
-
* <label key={choice.id}>
|
|
222
|
-
* <input
|
|
223
|
-
* type="checkbox"
|
|
224
|
-
* checked={selectedChoices.includes(choice.id)}
|
|
225
|
-
* onChange={() => toggleChoice(choice.id)}
|
|
226
|
-
* />
|
|
227
|
-
* {choice.name}
|
|
228
|
-
* </label>
|
|
229
|
-
* ))}
|
|
230
|
-
* </div>
|
|
231
|
-
* )}
|
|
232
|
-
* </ProductListFilters.ProductOptions>
|
|
233
|
-
* </ProductList.Root>
|
|
234
|
-
* );
|
|
235
|
-
* }
|
|
236
|
-
* ```
|
|
237
|
-
*/
|
|
238
|
-
export declare function ProductOptions(props: ProductOptionsProps): import("react/jsx-runtime").JSX.Element | null;
|
|
65
|
+
export declare const Filter: React.ForwardRefExoticComponent<FilterProps & React.RefAttributes<HTMLDivElement>>;
|
|
66
|
+
export {};
|
|
@@ -1,54 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useService } from '@wix/services-manager-react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
3
|
+
import { ProductsListServiceDefinition, InventoryStatusType, CategoriesListServiceDefinition, } from '../../services/index.js';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { Filter as FilterPrimitive, } from '@wix/headless-components/react';
|
|
6
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
// Conversion utilities for platform compatibility
|
|
9
|
+
function getInventoryStatusLabel(status) {
|
|
10
|
+
switch (status) {
|
|
11
|
+
case InventoryStatusType.IN_STOCK:
|
|
12
|
+
return 'In Stock';
|
|
13
|
+
case InventoryStatusType.OUT_OF_STOCK:
|
|
14
|
+
return 'Out of Stock';
|
|
15
|
+
case InventoryStatusType.PARTIALLY_OUT_OF_STOCK:
|
|
16
|
+
return 'Limited Stock';
|
|
17
|
+
default:
|
|
18
|
+
return String(status);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function buildSearchFilterData(availableOptions, availableInventoryStatuses, availableMinPrice, availableMaxPrice) {
|
|
22
|
+
// Build consolidated filter options using search field names
|
|
23
|
+
const filterOptions = [
|
|
24
|
+
// Price range - use a logical key that maps to both min/max fields
|
|
25
|
+
{
|
|
26
|
+
key: 'priceRange',
|
|
27
|
+
label: 'Price Range',
|
|
28
|
+
type: 'range',
|
|
29
|
+
displayType: 'range',
|
|
30
|
+
validValues: [availableMinPrice, availableMaxPrice],
|
|
31
|
+
valueFormatter: (value) => `$${value}`,
|
|
32
|
+
fieldName: [
|
|
33
|
+
'actualPriceRange.minValue.amount',
|
|
34
|
+
'actualPriceRange.maxValue.amount',
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
// Product options (colors, sizes, etc.) - individual filters for each option type
|
|
38
|
+
...availableOptions.map((option) => ({
|
|
39
|
+
key: option.id,
|
|
40
|
+
label: String(option.name),
|
|
41
|
+
type: 'multi',
|
|
42
|
+
displayType: option.optionRenderType === 'SWATCH_CHOICES'
|
|
43
|
+
? 'color'
|
|
44
|
+
: 'text',
|
|
45
|
+
fieldName: 'options.choicesSettings.choices.choiceId',
|
|
46
|
+
fieldType: 'array',
|
|
47
|
+
validValues: option.choices.map((choice) => choice.id),
|
|
48
|
+
valueFormatter: (value) => {
|
|
49
|
+
const choice = option.choices.find((c) => c.id === value);
|
|
50
|
+
const name = choice?.name || String(value);
|
|
51
|
+
return option.optionRenderType === 'SWATCH_CHOICES'
|
|
52
|
+
? name.toLowerCase()
|
|
53
|
+
: name;
|
|
54
|
+
},
|
|
55
|
+
})),
|
|
56
|
+
// Inventory status - use actual search field name
|
|
57
|
+
{
|
|
58
|
+
key: 'inventory.availabilityStatus',
|
|
59
|
+
label: 'Availability',
|
|
60
|
+
type: 'multi',
|
|
61
|
+
displayType: 'text',
|
|
62
|
+
fieldName: 'inventory.availabilityStatus',
|
|
63
|
+
fieldType: 'singular',
|
|
64
|
+
validValues: availableInventoryStatuses,
|
|
65
|
+
valueFormatter: (value) => getInventoryStatusLabel(value),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
return { filterOptions };
|
|
52
69
|
}
|
|
53
70
|
/**
|
|
54
71
|
* Headless component for resetting all filters
|
|
@@ -81,136 +98,85 @@ export function InventoryStatus(props) {
|
|
|
81
98
|
* ```
|
|
82
99
|
*/
|
|
83
100
|
export function ResetTrigger(props) {
|
|
84
|
-
const service = useService(
|
|
85
|
-
const resetFilters = service.
|
|
86
|
-
const isFiltered = service.isFiltered.get();
|
|
101
|
+
const service = useService(ProductsListServiceDefinition);
|
|
102
|
+
const resetFilters = service.resetFilter;
|
|
103
|
+
const isFiltered = service.isFiltered().get();
|
|
87
104
|
return typeof props.children === 'function'
|
|
88
105
|
? props.children({ resetFilters, isFiltered })
|
|
89
106
|
: props.children;
|
|
90
107
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Headless component for managing price range filters (combined min/max)
|
|
93
|
-
*
|
|
94
|
-
* @component
|
|
95
|
-
* @example
|
|
96
|
-
* ```tsx
|
|
97
|
-
* import { ProductList, ProductListFilters } from '@wix/stores/components';
|
|
98
|
-
*
|
|
99
|
-
* function PriceRangeFilter() {
|
|
100
|
-
* return (
|
|
101
|
-
* <ProductList.Root
|
|
102
|
-
* productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
|
|
103
|
-
* productsListSearchConfig={{ customizations: [] }}
|
|
104
|
-
* >
|
|
105
|
-
* <ProductListFilters.PriceRange>
|
|
106
|
-
* {({ minPrice, maxPrice, setSelectedMinPrice, setSelectedMaxPrice }) => (
|
|
107
|
-
* <div className="price-range">
|
|
108
|
-
* <h4>Price Range:</h4>
|
|
109
|
-
* <div className="price-inputs">
|
|
110
|
-
* <input
|
|
111
|
-
* type="number"
|
|
112
|
-
* value={minPrice}
|
|
113
|
-
* onChange={(e) => setSelectedMinPrice(Number(e.target.value))}
|
|
114
|
-
* placeholder="Min"
|
|
115
|
-
* />
|
|
116
|
-
* <span>to</span>
|
|
117
|
-
* <input
|
|
118
|
-
* type="number"
|
|
119
|
-
* value={maxPrice}
|
|
120
|
-
* onChange={(e) => setSelectedMaxPrice(Number(e.target.value))}
|
|
121
|
-
* placeholder="Max"
|
|
122
|
-
* />
|
|
123
|
-
* </div>
|
|
124
|
-
* </div>
|
|
125
|
-
* )}
|
|
126
|
-
* </ProductListFilters.PriceRange>
|
|
127
|
-
* </ProductList.Root>
|
|
128
|
-
* );
|
|
129
|
-
* }
|
|
130
|
-
* ```
|
|
131
|
-
*/
|
|
132
|
-
export function PriceRange(props) {
|
|
133
|
-
const service = useService(ProductsListSearchServiceDefinition);
|
|
134
|
-
const selectedMinPrice = service.selectedMinPrice.get();
|
|
135
|
-
const selectedMaxPrice = service.selectedMaxPrice.get();
|
|
136
|
-
const availableMinPrice = service.availableMinPrice.get();
|
|
137
|
-
const availableMaxPrice = service.availableMaxPrice.get();
|
|
138
|
-
const setSelectedMinPrice = service.setSelectedMinPrice;
|
|
139
|
-
const setSelectedMaxPrice = service.setSelectedMaxPrice;
|
|
140
|
-
return typeof props.children === 'function'
|
|
141
|
-
? props.children({
|
|
142
|
-
availableMinPrice,
|
|
143
|
-
selectedMinPrice,
|
|
144
|
-
selectedMaxPrice,
|
|
145
|
-
availableMaxPrice,
|
|
146
|
-
setSelectedMinPrice,
|
|
147
|
-
setSelectedMaxPrice,
|
|
148
|
-
})
|
|
149
|
-
: props.children;
|
|
150
|
-
}
|
|
151
108
|
export function CategoryFilter(props) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
109
|
+
const categoriesService = useService(CategoriesListServiceDefinition);
|
|
110
|
+
const productListService = useService(ProductsListServiceDefinition);
|
|
111
|
+
const categories = categoriesService.categories.get();
|
|
112
|
+
const setSelectedCategory = (category) => {
|
|
113
|
+
const currentFilter = productListService.searchOptions.get().filter || {};
|
|
114
|
+
if (!category) {
|
|
115
|
+
delete currentFilter['allCategoriesInfo.categories'];
|
|
116
|
+
productListService.setFilter(currentFilter);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
productListService.setFilter({
|
|
120
|
+
...currentFilter,
|
|
121
|
+
'allCategoriesInfo.categories': {
|
|
122
|
+
$matchItems: [{ id: { $in: [category._id] } }],
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const selectedCategoryId = productListService.searchOptions.get().filter['allCategoriesInfo.categories']?.$matchItems?.[0]?.id
|
|
127
|
+
?.$in?.[0];
|
|
128
|
+
const selectedCategory = categories?.find((c) => c._id === selectedCategoryId) || null;
|
|
155
129
|
return typeof props.children === 'function'
|
|
156
130
|
? props.children({ selectedCategory, setSelectedCategory })
|
|
157
131
|
: props.children;
|
|
158
132
|
}
|
|
159
133
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* Only renders when options are available (not loading, no error, and has options).
|
|
163
|
-
* This follows the same collection pattern as ProductList.ItemContent and CategoryList.ItemContent.
|
|
164
|
-
*
|
|
165
|
-
* @component
|
|
166
|
-
* @example
|
|
167
|
-
* ```tsx
|
|
168
|
-
* import { ProductList, ProductListFilters } from '@wix/stores/components';
|
|
169
|
-
*
|
|
170
|
-
* function ProductOptionsFilter() {
|
|
171
|
-
* return (
|
|
172
|
-
* <ProductList.Root
|
|
173
|
-
* productsListConfig={{ products: [], searchOptions: {}, pagingMetadata: {}, aggregations: {} }}
|
|
174
|
-
* productsListSearchConfig={{ customizations: [] }}
|
|
175
|
-
* >
|
|
176
|
-
* <ProductListFilters.ProductOptions>
|
|
177
|
-
* {({ option, selectedChoices, toggleChoice }) => (
|
|
178
|
-
* <div key={option.id}>
|
|
179
|
-
* <h4>{option.name}</h4>
|
|
180
|
-
* {option.choices.map(choice => (
|
|
181
|
-
* <label key={choice.id}>
|
|
182
|
-
* <input
|
|
183
|
-
* type="checkbox"
|
|
184
|
-
* checked={selectedChoices.includes(choice.id)}
|
|
185
|
-
* onChange={() => toggleChoice(choice.id)}
|
|
186
|
-
* />
|
|
187
|
-
* {choice.name}
|
|
188
|
-
* </label>
|
|
189
|
-
* ))}
|
|
190
|
-
* </div>
|
|
191
|
-
* )}
|
|
192
|
-
* </ProductListFilters.ProductOptions>
|
|
193
|
-
* </ProductList.Root>
|
|
194
|
-
* );
|
|
195
|
-
* }
|
|
196
|
-
* ```
|
|
134
|
+
* Internal component that provides filter data for the Filter component.
|
|
135
|
+
* Consolidates data from both search and list services.
|
|
197
136
|
*/
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
137
|
+
function AllFilters(props) {
|
|
138
|
+
const listService = useService(ProductsListServiceDefinition);
|
|
139
|
+
// Get current filter state
|
|
140
|
+
const currentSearchOptions = listService.searchOptions.get();
|
|
141
|
+
const currentFilter = currentSearchOptions.filter;
|
|
142
|
+
// Get available filter data
|
|
143
|
+
const availableOptions = listService.availableProductOptions.get();
|
|
144
|
+
const availableInventoryStatuses = listService.availableInventoryStatuses.get();
|
|
145
|
+
const availableMinPrice = listService.minPrice.get();
|
|
146
|
+
const availableMaxPrice = listService.maxPrice.get();
|
|
147
|
+
// Get filter state
|
|
148
|
+
const resetFilters = listService.resetFilter;
|
|
149
|
+
const isFiltered = listService.isFiltered().get();
|
|
150
|
+
// Build filter options and handlers
|
|
151
|
+
const searchFilterData = useMemo(() => {
|
|
152
|
+
const { filterOptions } = buildSearchFilterData(availableOptions, availableInventoryStatuses, availableMinPrice, availableMaxPrice);
|
|
153
|
+
const updateFilter = (newFilter) => {
|
|
154
|
+
listService.setFilter(newFilter);
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
filterValue: currentFilter,
|
|
158
|
+
filterOptions,
|
|
159
|
+
updateFilter,
|
|
160
|
+
clearFilters: resetFilters,
|
|
161
|
+
hasFilters: isFiltered,
|
|
162
|
+
};
|
|
163
|
+
}, [
|
|
164
|
+
availableOptions,
|
|
165
|
+
availableInventoryStatuses,
|
|
166
|
+
availableMinPrice,
|
|
167
|
+
availableMaxPrice,
|
|
168
|
+
currentFilter,
|
|
169
|
+
resetFilters,
|
|
170
|
+
isFiltered,
|
|
171
|
+
listService,
|
|
172
|
+
]);
|
|
173
|
+
return typeof props.children === 'function'
|
|
174
|
+
? props.children({ searchFilter: searchFilterData })
|
|
175
|
+
: props.children;
|
|
216
176
|
}
|
|
177
|
+
export const Filter = React.forwardRef(({ children, className, asChild }, ref) => {
|
|
178
|
+
const Comp = asChild ? Slot : 'div';
|
|
179
|
+
return (_jsx(AllFilters, { children: ({ searchFilter }) => {
|
|
180
|
+
return (_jsx(FilterPrimitive.Root, { value: searchFilter.filterValue, onChange: searchFilter.updateFilter, filterOptions: searchFilter.filterOptions, children: _jsx(Comp, { className: className, ref: ref, children: children }) }));
|
|
181
|
+
} }));
|
|
182
|
+
});
|