@akinon/pz-similar-products 1.92.0-rc.20 → 1.92.0-rc.22
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/CHANGELOG.md +12 -0
- package/README.md +311 -21
- package/package.json +1 -1
- package/src/hooks/use-similar-products.ts +27 -12
- package/src/types/index.ts +36 -0
- package/src/utils/image-conversion.ts +44 -0
- package/src/utils/index.ts +6 -1
- package/src/views/filters.tsx +722 -628
- package/src/views/header-image-search-feature.tsx +16 -4
- package/src/views/image-search.tsx +136 -84
- package/src/views/main.tsx +12 -3
- package/src/views/product-image-search-feature.tsx +12 -3
- package/src/views/results.tsx +19 -7
- package/src/views/search-modal.tsx +42 -7
|
@@ -8,18 +8,29 @@ import { SimilarProductsPlugin } from './main';
|
|
|
8
8
|
|
|
9
9
|
interface HeaderImageSearchFeatureProps {
|
|
10
10
|
className?: string;
|
|
11
|
+
isEnabled?: boolean;
|
|
12
|
+
settings?: any;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function HeaderImageSearchFeature({
|
|
14
|
-
className
|
|
16
|
+
className,
|
|
17
|
+
isEnabled: isEnabledProp,
|
|
18
|
+
settings
|
|
15
19
|
}: HeaderImageSearchFeatureProps) {
|
|
16
|
-
const { isEnabled, isLoading } = useImageSearchFeature();
|
|
20
|
+
const { isEnabled: hookIsEnabled, isLoading } = useImageSearchFeature();
|
|
21
|
+
|
|
22
|
+
const envEnabled = process.env.NEXT_PUBLIC_ENABLE_IMAGE_SEARCH === 'true';
|
|
23
|
+
const finalIsEnabled = envEnabled
|
|
24
|
+
? true
|
|
25
|
+
: isEnabledProp !== undefined
|
|
26
|
+
? isEnabledProp
|
|
27
|
+
: hookIsEnabled;
|
|
17
28
|
|
|
18
29
|
const [isImageSearchModalOpen, setIsImageSearchModalOpen] = useState(false);
|
|
19
30
|
const [isResultsModalOpen, setIsResultsModalOpen] = useState(false);
|
|
20
31
|
const [uploadedImageFile, setUploadedImageFile] = useState<File | null>(null);
|
|
21
32
|
|
|
22
|
-
if (isLoading || !
|
|
33
|
+
if (isLoading || !finalIsEnabled) {
|
|
23
34
|
return null;
|
|
24
35
|
}
|
|
25
36
|
|
|
@@ -44,7 +55,7 @@ export function HeaderImageSearchFeature({
|
|
|
44
55
|
|
|
45
56
|
return (
|
|
46
57
|
<>
|
|
47
|
-
{
|
|
58
|
+
{finalIsEnabled && (
|
|
48
59
|
<>
|
|
49
60
|
<ImageSearchButton
|
|
50
61
|
onClick={handleOpenImageSearch}
|
|
@@ -60,6 +71,7 @@ export function HeaderImageSearchFeature({
|
|
|
60
71
|
uploadedImageFile={uploadedImageFile}
|
|
61
72
|
onImageUpload={handleImageUpload}
|
|
62
73
|
showResetButton={false}
|
|
74
|
+
settings={settings}
|
|
63
75
|
/>
|
|
64
76
|
</>
|
|
65
77
|
)}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import React, { useEffect } from 'react';
|
|
4
4
|
import { Modal, Button, Input } from '@akinon/next/components';
|
|
5
5
|
import { useLocalization } from '@akinon/next/hooks';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
import { convertWebPToJPEG } from '../utils/image-conversion';
|
|
6
8
|
|
|
7
9
|
interface ImageSearchModalProps {
|
|
8
10
|
isOpen: boolean;
|
|
@@ -11,63 +13,33 @@ interface ImageSearchModalProps {
|
|
|
11
13
|
handleImageFileChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
12
14
|
className?: string;
|
|
13
15
|
clearError?: () => void;
|
|
16
|
+
settings?: any;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
const convertWebPToJPEG = (file: File): Promise<File> => {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const canvas = document.createElement('canvas');
|
|
19
|
-
const ctx = canvas.getContext('2d');
|
|
20
|
-
const img = new Image();
|
|
21
|
-
|
|
22
|
-
img.onload = () => {
|
|
23
|
-
canvas.width = img.naturalWidth;
|
|
24
|
-
canvas.height = img.naturalHeight;
|
|
25
|
-
|
|
26
|
-
if (!ctx) {
|
|
27
|
-
reject(new Error('Canvas context not available'));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
ctx.drawImage(img, 0, 0);
|
|
32
|
-
|
|
33
|
-
canvas.toBlob(
|
|
34
|
-
(blob) => {
|
|
35
|
-
if (!blob) {
|
|
36
|
-
reject(new Error('Failed to convert image'));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const convertedFile = new File(
|
|
41
|
-
[blob],
|
|
42
|
-
file.name.replace(/\.webp$/i, '.jpg'),
|
|
43
|
-
{
|
|
44
|
-
type: 'image/jpeg',
|
|
45
|
-
lastModified: Date.now()
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
resolve(convertedFile);
|
|
50
|
-
},
|
|
51
|
-
'image/jpeg',
|
|
52
|
-
0.9
|
|
53
|
-
);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
img.onerror = () => reject(new Error('Failed to load image'));
|
|
57
|
-
img.src = URL.createObjectURL(file);
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
19
|
export function ImageSearchModal({
|
|
62
20
|
isOpen,
|
|
63
21
|
setIsOpen,
|
|
64
22
|
fileInputRef,
|
|
65
23
|
handleImageFileChange,
|
|
66
24
|
className,
|
|
67
|
-
clearError
|
|
25
|
+
clearError,
|
|
26
|
+
settings
|
|
68
27
|
}: ImageSearchModalProps) {
|
|
69
28
|
const { t } = useLocalization();
|
|
70
29
|
|
|
30
|
+
// Check for custom modal renderer first
|
|
31
|
+
if (settings?.customRenderers?.render?.imageSearchModal?.renderModal) {
|
|
32
|
+
return settings.customRenderers.render.imageSearchModal.renderModal({
|
|
33
|
+
isOpen,
|
|
34
|
+
setIsOpen,
|
|
35
|
+
fileInputRef,
|
|
36
|
+
handleImageFileChange,
|
|
37
|
+
className,
|
|
38
|
+
clearError,
|
|
39
|
+
settings
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
71
43
|
useEffect(() => {
|
|
72
44
|
if (isOpen && clearError) {
|
|
73
45
|
clearError();
|
|
@@ -107,46 +79,126 @@ export function ImageSearchModal({
|
|
|
107
79
|
};
|
|
108
80
|
|
|
109
81
|
return (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
82
|
+
<>
|
|
83
|
+
{isOpen && (
|
|
84
|
+
<div
|
|
85
|
+
className={twMerge(
|
|
86
|
+
'fixed inset-0 bg-black/50 z-[60]',
|
|
87
|
+
settings?.customStyles?.imageSearchModalOverlay
|
|
88
|
+
)}
|
|
89
|
+
onClick={() => setIsOpen(false)}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<Modal
|
|
94
|
+
portalId="image-search-modal"
|
|
95
|
+
title={t('common.search.image_search.title')}
|
|
96
|
+
open={isOpen}
|
|
97
|
+
setOpen={setIsOpen}
|
|
98
|
+
className={`w-full max-w-2xl max-h-[90vh] overflow-auto ${
|
|
99
|
+
className || ''
|
|
100
|
+
}`}
|
|
101
|
+
>
|
|
102
|
+
<div
|
|
103
|
+
className={twMerge(
|
|
104
|
+
'grid grid-cols-1 md:grid-cols-2 gap-4',
|
|
105
|
+
settings?.customStyles?.imageSearchContent
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
className={twMerge(
|
|
110
|
+
'md:col-span-1 p-4',
|
|
111
|
+
settings?.customStyles?.imageSearchUploadSection
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
<p
|
|
115
|
+
className={twMerge(
|
|
116
|
+
'mb-4 text-lg font-medium',
|
|
117
|
+
settings?.customStyles?.imageSearchUploadTitle
|
|
118
|
+
)}
|
|
119
|
+
>
|
|
120
|
+
{t('common.search.image_search.upload_image')}
|
|
121
|
+
</p>
|
|
122
|
+
<div
|
|
123
|
+
className={twMerge(
|
|
124
|
+
'border-2 border-dashed border-gray-300 rounded-lg p-8 text-center',
|
|
125
|
+
settings?.customStyles?.imageSearchUploadArea
|
|
126
|
+
)}
|
|
133
127
|
>
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
<Input
|
|
129
|
+
type="file"
|
|
130
|
+
ref={fileInputRef}
|
|
131
|
+
onChange={handleFileSelect}
|
|
132
|
+
accept="image/*"
|
|
133
|
+
className="hidden"
|
|
134
|
+
/>
|
|
135
|
+
<Button
|
|
136
|
+
className={twMerge(
|
|
137
|
+
'w-full py-4 px-6 border-2 border-primary text-white hover:bg-primary hover:text-white transition-colors rounded-md',
|
|
138
|
+
settings?.customStyles?.imageSearchUploadButton
|
|
139
|
+
)}
|
|
140
|
+
onClick={() => fileInputRef.current?.click()}
|
|
141
|
+
>
|
|
142
|
+
{t('common.search.image_search.select_image')}
|
|
143
|
+
</Button>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div
|
|
147
|
+
className={twMerge(
|
|
148
|
+
'md:col-span-1 bg-gray-50 p-4 rounded-md h-fit my-4',
|
|
149
|
+
settings?.customStyles?.imageSearchTipsSection
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<p
|
|
153
|
+
className={twMerge(
|
|
154
|
+
'mb-2 font-medium',
|
|
155
|
+
settings?.customStyles?.imageSearchTipsTitle
|
|
156
|
+
)}
|
|
157
|
+
>
|
|
158
|
+
{t('common.search.image_search.best_results')}
|
|
159
|
+
</p>
|
|
160
|
+
<ul
|
|
161
|
+
className={twMerge(
|
|
162
|
+
'text-left list-disc pl-5 text-sm text-gray-600',
|
|
163
|
+
settings?.customStyles?.imageSearchTipsList
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
<li
|
|
167
|
+
className={twMerge(
|
|
168
|
+
'mb-2',
|
|
169
|
+
settings?.customStyles?.imageSearchTipsItem
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{t('common.search.image_search.tip_1')}
|
|
173
|
+
</li>
|
|
174
|
+
<li
|
|
175
|
+
className={twMerge(
|
|
176
|
+
'mb-2',
|
|
177
|
+
settings?.customStyles?.imageSearchTipsItem
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
{t('common.search.image_search.tip_2')}
|
|
181
|
+
</li>
|
|
182
|
+
<li
|
|
183
|
+
className={twMerge(
|
|
184
|
+
'mb-2',
|
|
185
|
+
settings?.customStyles?.imageSearchTipsItem
|
|
186
|
+
)}
|
|
187
|
+
>
|
|
188
|
+
{t('common.search.image_search.tip_3')}
|
|
189
|
+
</li>
|
|
190
|
+
<li
|
|
191
|
+
className={twMerge(
|
|
192
|
+
'mb-2',
|
|
193
|
+
settings?.customStyles?.imageSearchTipsItem
|
|
194
|
+
)}
|
|
195
|
+
>
|
|
196
|
+
{t('common.search.image_search.tip_4')}
|
|
197
|
+
</li>
|
|
198
|
+
</ul>
|
|
136
199
|
</div>
|
|
137
200
|
</div>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{t('common.search.image_search.best_results')}
|
|
141
|
-
</p>
|
|
142
|
-
<ul className="text-left list-disc pl-5 text-sm text-gray-600">
|
|
143
|
-
<li className="mb-2">{t('common.search.image_search.tip_1')}</li>
|
|
144
|
-
<li className="mb-2">{t('common.search.image_search.tip_2')}</li>
|
|
145
|
-
<li className="mb-2">{t('common.search.image_search.tip_3')}</li>
|
|
146
|
-
<li className="mb-2">{t('common.search.image_search.tip_4')}</li>
|
|
147
|
-
</ul>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
</Modal>
|
|
201
|
+
</Modal>
|
|
202
|
+
</>
|
|
151
203
|
);
|
|
152
204
|
}
|
package/src/views/main.tsx
CHANGED
|
@@ -56,7 +56,8 @@ export function SimilarProductsPlugin({
|
|
|
56
56
|
loadedPages,
|
|
57
57
|
fetchSimilarProductsByImageUrl,
|
|
58
58
|
fetchSimilarProductsByImageCrop,
|
|
59
|
-
clearError
|
|
59
|
+
clearError,
|
|
60
|
+
clearResults
|
|
60
61
|
} = useSimilarProducts(product);
|
|
61
62
|
|
|
62
63
|
const {
|
|
@@ -108,6 +109,7 @@ export function SimilarProductsPlugin({
|
|
|
108
109
|
if (!isOpen) {
|
|
109
110
|
setHasInitialSearchDone(false);
|
|
110
111
|
clearError();
|
|
112
|
+
clearResults();
|
|
111
113
|
}
|
|
112
114
|
}, [
|
|
113
115
|
isOpen,
|
|
@@ -115,7 +117,8 @@ export function SimilarProductsPlugin({
|
|
|
115
117
|
activeIndex,
|
|
116
118
|
hasUploadedImage,
|
|
117
119
|
hasInitialSearchDone,
|
|
118
|
-
clearError
|
|
120
|
+
clearError,
|
|
121
|
+
clearResults
|
|
119
122
|
]);
|
|
120
123
|
|
|
121
124
|
useEffect(() => {
|
|
@@ -142,6 +145,11 @@ export function SimilarProductsPlugin({
|
|
|
142
145
|
}
|
|
143
146
|
};
|
|
144
147
|
|
|
148
|
+
const handleModalClose = () => {
|
|
149
|
+
resetCrop();
|
|
150
|
+
onClose();
|
|
151
|
+
};
|
|
152
|
+
|
|
145
153
|
const ModalComponent =
|
|
146
154
|
settings.customRenderers?.Modal || SimilarProductsModal;
|
|
147
155
|
const ImageSearchModalComponent =
|
|
@@ -151,7 +159,7 @@ export function SimilarProductsPlugin({
|
|
|
151
159
|
<>
|
|
152
160
|
<ModalComponent
|
|
153
161
|
isOpen={isOpen}
|
|
154
|
-
onClose={
|
|
162
|
+
onClose={handleModalClose}
|
|
155
163
|
searchResults={searchResults}
|
|
156
164
|
resultsKey={resultsKey}
|
|
157
165
|
isLoading={isLoading}
|
|
@@ -194,6 +202,7 @@ export function SimilarProductsPlugin({
|
|
|
194
202
|
handleImageFileChange={handleImageSearchFileChange}
|
|
195
203
|
className={settings.customStyles?.imageSearchModal}
|
|
196
204
|
clearError={clearError}
|
|
205
|
+
settings={settings}
|
|
197
206
|
/>
|
|
198
207
|
</>
|
|
199
208
|
);
|
|
@@ -12,17 +12,26 @@ interface ProductImageSearchFeatureProps {
|
|
|
12
12
|
activeIndex?: number;
|
|
13
13
|
settings?: any;
|
|
14
14
|
className?: string;
|
|
15
|
+
isEnabled?: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function ProductImageSearchFeature({
|
|
18
19
|
product,
|
|
19
20
|
activeIndex = 0,
|
|
20
21
|
settings: userSettings,
|
|
21
|
-
className = 'absolute top-6 left-6 z-[20]'
|
|
22
|
+
className = 'absolute top-6 left-6 z-[20]',
|
|
23
|
+
isEnabled: isEnabledProp
|
|
22
24
|
}: ProductImageSearchFeatureProps) {
|
|
23
25
|
const settings = mergeSettings(userSettings);
|
|
24
26
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
25
|
-
const { isEnabled } = useImageSearchFeature();
|
|
27
|
+
const { isEnabled: hookIsEnabled } = useImageSearchFeature();
|
|
28
|
+
|
|
29
|
+
const envEnabled = process.env.NEXT_PUBLIC_ENABLE_IMAGE_SEARCH === 'true';
|
|
30
|
+
const finalIsEnabled = envEnabled
|
|
31
|
+
? true
|
|
32
|
+
: isEnabledProp !== undefined
|
|
33
|
+
? isEnabledProp
|
|
34
|
+
: hookIsEnabled;
|
|
26
35
|
|
|
27
36
|
const handleClick = () => {
|
|
28
37
|
setIsModalOpen(true);
|
|
@@ -30,7 +39,7 @@ export function ProductImageSearchFeature({
|
|
|
30
39
|
|
|
31
40
|
return (
|
|
32
41
|
<>
|
|
33
|
-
{
|
|
42
|
+
{finalIsEnabled && (
|
|
34
43
|
<>
|
|
35
44
|
<SimilarProductsButton onClick={handleClick} className={className} />
|
|
36
45
|
|
package/src/views/results.tsx
CHANGED
|
@@ -95,7 +95,12 @@ export function SimilarProductsResultsGrid({
|
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
97
|
<div className={emptyStateClassName}>
|
|
98
|
-
<div
|
|
98
|
+
<div
|
|
99
|
+
className={twMerge(
|
|
100
|
+
'text-center',
|
|
101
|
+
settings?.customStyles?.emptyStateInner
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
99
104
|
<svg
|
|
100
105
|
className={emptyStateIconClassName}
|
|
101
106
|
fill="none"
|
|
@@ -229,7 +234,12 @@ export function SimilarProductsResultsGrid({
|
|
|
229
234
|
)}
|
|
230
235
|
|
|
231
236
|
{renderProductPrice() || (
|
|
232
|
-
<div
|
|
237
|
+
<div
|
|
238
|
+
className={twMerge(
|
|
239
|
+
'price-container',
|
|
240
|
+
settings?.customStyles?.productPriceContainer
|
|
241
|
+
)}
|
|
242
|
+
>
|
|
233
243
|
<span className={productPriceClassName}>
|
|
234
244
|
{product.price} ₺
|
|
235
245
|
</span>
|
|
@@ -516,10 +526,14 @@ export function SimilarProductsResultsGrid({
|
|
|
516
526
|
);
|
|
517
527
|
};
|
|
518
528
|
|
|
519
|
-
if (isLoading && (!searchResults || !searchResults.products)) {
|
|
529
|
+
if (isLoading && (!searchResults || !searchResults.products?.length)) {
|
|
520
530
|
return renderLoadingState();
|
|
521
531
|
}
|
|
522
532
|
|
|
533
|
+
if (!isLoading && searchResults && searchResults.products?.length === 0) {
|
|
534
|
+
return renderEmptyState();
|
|
535
|
+
}
|
|
536
|
+
|
|
523
537
|
const renderLoadingOverlay = () => {
|
|
524
538
|
if (settings?.customRenderers?.render?.resultsGrid?.renderLoadingOverlay) {
|
|
525
539
|
return settings.customRenderers.render.resultsGrid.renderLoadingOverlay();
|
|
@@ -575,16 +589,14 @@ export function SimilarProductsResultsGrid({
|
|
|
575
589
|
<div className={gridClassName}>
|
|
576
590
|
{isLoading &&
|
|
577
591
|
searchResults &&
|
|
578
|
-
searchResults.products &&
|
|
592
|
+
searchResults.products?.length > 0 &&
|
|
579
593
|
renderLoadingOverlay()}
|
|
580
594
|
|
|
581
|
-
{searchResults && searchResults.products?.length > 0
|
|
595
|
+
{searchResults && searchResults.products?.length > 0 && (
|
|
582
596
|
<div className={resultsContainerClassName}>
|
|
583
597
|
{renderGridContainer()}
|
|
584
598
|
{renderPagination()}
|
|
585
599
|
</div>
|
|
586
|
-
) : (
|
|
587
|
-
renderEmptyState()
|
|
588
600
|
)}
|
|
589
601
|
</div>
|
|
590
602
|
);
|
|
@@ -196,7 +196,12 @@ export function SimilarProductsModal({
|
|
|
196
196
|
|
|
197
197
|
return (
|
|
198
198
|
<div className={containerClassName}>
|
|
199
|
-
<div
|
|
199
|
+
<div
|
|
200
|
+
className={twMerge(
|
|
201
|
+
'flex flex-wrap gap-2',
|
|
202
|
+
settings?.customStyles?.activeFiltersWrapper
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
200
205
|
{activeFilters.map((filter) => {
|
|
201
206
|
if (
|
|
202
207
|
settings?.customRenderers?.render?.modal?.renderActiveFilterTag
|
|
@@ -321,6 +326,10 @@ export function SimilarProductsModal({
|
|
|
321
326
|
};
|
|
322
327
|
|
|
323
328
|
const renderSortDropdown = () => {
|
|
329
|
+
if (!searchResults?.products?.length || !searchResults?.sorters?.length) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
324
333
|
if (settings?.customRenderers?.render?.modal?.renderSortDropdown) {
|
|
325
334
|
return settings.customRenderers.render.modal.renderSortDropdown({
|
|
326
335
|
sorters: searchResults?.sorters || [],
|
|
@@ -348,12 +357,29 @@ export function SimilarProductsModal({
|
|
|
348
357
|
|
|
349
358
|
return (
|
|
350
359
|
<div className={containerClassName}>
|
|
351
|
-
<div
|
|
352
|
-
|
|
360
|
+
<div
|
|
361
|
+
className={twMerge(
|
|
362
|
+
'flex items-center justify-between p-3 md:p-4',
|
|
363
|
+
settings?.customStyles?.controlsInner
|
|
364
|
+
)}
|
|
365
|
+
>
|
|
366
|
+
<div
|
|
367
|
+
className={twMerge(
|
|
368
|
+
'flex items-center gap-3',
|
|
369
|
+
settings?.customStyles?.controlsLeft
|
|
370
|
+
)}
|
|
371
|
+
>
|
|
353
372
|
{renderItemCount()}
|
|
354
373
|
{renderFilterToggle()}
|
|
355
374
|
</div>
|
|
356
|
-
<div
|
|
375
|
+
<div
|
|
376
|
+
className={twMerge(
|
|
377
|
+
'relative',
|
|
378
|
+
settings?.customStyles?.controlsRight
|
|
379
|
+
)}
|
|
380
|
+
>
|
|
381
|
+
{renderSortDropdown()}
|
|
382
|
+
</div>
|
|
357
383
|
</div>
|
|
358
384
|
</div>
|
|
359
385
|
);
|
|
@@ -371,8 +397,12 @@ export function SimilarProductsModal({
|
|
|
371
397
|
{renderActiveFilters()}
|
|
372
398
|
{renderControls()}
|
|
373
399
|
|
|
374
|
-
|
|
375
|
-
|
|
400
|
+
<div
|
|
401
|
+
className={twMerge(
|
|
402
|
+
'flex flex-col md:grid md:grid-cols-5 gap-0 flex-1 overflow-hidden',
|
|
403
|
+
settings?.customStyles?.modalContent
|
|
404
|
+
)}
|
|
405
|
+
>
|
|
376
406
|
<FilterSidebarComponent
|
|
377
407
|
isFilterMenuOpen={isFilterMenuOpen}
|
|
378
408
|
setIsFilterMenuOpen={setIsFilterMenuOpen}
|
|
@@ -403,7 +433,12 @@ export function SimilarProductsModal({
|
|
|
403
433
|
className={settings?.customStyles?.filterSidebar}
|
|
404
434
|
/>
|
|
405
435
|
|
|
406
|
-
<div
|
|
436
|
+
<div
|
|
437
|
+
className={twMerge(
|
|
438
|
+
'flex-1 md:col-span-4 p-3 md:p-4 overflow-y-auto',
|
|
439
|
+
settings?.customStyles?.resultsContainer
|
|
440
|
+
)}
|
|
441
|
+
>
|
|
407
442
|
<ResultsGridComponent
|
|
408
443
|
searchResults={searchResults}
|
|
409
444
|
resultsKey={resultsKey}
|