@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @akinon/pz-similar-products
|
|
2
2
|
|
|
3
|
+
## 1.92.0-rc.22
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 143be2b: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
|
|
8
|
+
|
|
9
|
+
## 1.92.0-rc.21
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- d99a6a7: ZERO-3457: Fixed the settings prop and made sure everything is customizable.
|
|
14
|
+
|
|
3
15
|
## 1.92.0-rc.20
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -44,11 +44,12 @@
|
|
|
44
44
|
- [Modern Pagination with Progress](#markdown-header-modern-pagination-with-progress)
|
|
45
45
|
- [Advanced Modal with Custom Header & Controls](#markdown-header-advanced-modal-with-custom-header-controls)
|
|
46
46
|
- [Complete Brand Theme Integration](#markdown-header-complete-brand-theme-integration)
|
|
47
|
+
- [Advanced Component Customization](#markdown-header-advanced-component-customization)
|
|
47
48
|
|
|
48
49
|
### 📖 Styling Reference
|
|
49
50
|
|
|
50
|
-
- [Available Style Targets (
|
|
51
|
-
- [Available Render Functions (
|
|
51
|
+
- [Available Style Targets (50+ options)](#markdown-header-available-style-targets-50-options)
|
|
52
|
+
- [Available Render Functions (30+ options)](#markdown-header-available-render-functions-30-options)
|
|
52
53
|
|
|
53
54
|
### ✅ Best Practices
|
|
54
55
|
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
- **✂️ Image Cropping**: Built-in cropping functionality with manual confirmation for precise searches
|
|
66
67
|
- **🎛️ Advanced Filtering**: Dynamic facet-based filtering system
|
|
67
68
|
- **📄 Multiple Pagination Modes**: Traditional pagination, load more button, and infinite scroll
|
|
68
|
-
- **🎨 Granular Customization**:
|
|
69
|
+
- **🎨 Granular Customization**: 50+ style targets and 30+ render points
|
|
69
70
|
- **📱 Mobile Responsive**: Optimized for all device sizes
|
|
70
71
|
- **⚡ Performance Optimized**: Lazy loading and efficient rendering
|
|
71
72
|
- **🔧 TypeScript**: Full TypeScript support with detailed type definitions
|
|
@@ -174,49 +175,159 @@ interface SimilarProductsSettings {
|
|
|
174
175
|
loadMoreThreshold?: number; // Pixels from bottom for auto load (default: 100)
|
|
175
176
|
maxPagesLoadMore?: number; // Maximum pages to load in load-more mode
|
|
176
177
|
|
|
177
|
-
//
|
|
178
|
+
// 50+ granular style targets
|
|
178
179
|
customStyles?: {
|
|
179
180
|
// Main components
|
|
180
181
|
modal?: string;
|
|
182
|
+
modalContent?: string; // NEW: Grid container
|
|
181
183
|
filterSidebar?: string;
|
|
182
184
|
resultsGrid?: string;
|
|
185
|
+
resultsContainer?: string; // NEW: Results wrapper
|
|
186
|
+
imageSearchModal?: string;
|
|
183
187
|
|
|
184
|
-
//
|
|
188
|
+
// Active filters
|
|
189
|
+
activeFiltersContainer?: string;
|
|
190
|
+
activeFiltersWrapper?: string; // NEW: Filters wrapper
|
|
191
|
+
activeFilterTag?: string;
|
|
192
|
+
activeFilterTagButton?: string;
|
|
193
|
+
|
|
194
|
+
// Controls section
|
|
195
|
+
controlsContainer?: string;
|
|
196
|
+
controlsInner?: string; // NEW: Controls inner wrapper
|
|
197
|
+
controlsLeft?: string; // NEW: Left controls (count, filter)
|
|
198
|
+
controlsRight?: string; // NEW: Right controls (sort)
|
|
199
|
+
itemCount?: string;
|
|
200
|
+
sortDropdown?: string;
|
|
201
|
+
filterToggleButton?: string;
|
|
202
|
+
|
|
203
|
+
// Filter sidebar mobile
|
|
204
|
+
filterSidebarMobileHeader?: string;
|
|
205
|
+
filterSidebarMobileTitle?: string; // NEW: Mobile title
|
|
206
|
+
filterSidebarMobileCloseButton?: string; // NEW: Mobile close button
|
|
207
|
+
filterSidebarMobileCounter?: string; // NEW: Mobile counter wrapper
|
|
208
|
+
filterSidebarMobileCounterText?: string; // NEW: Mobile counter text
|
|
209
|
+
filterGroup?: string;
|
|
210
|
+
filterGroupTitle?: string;
|
|
211
|
+
filterGroupContent?: string;
|
|
212
|
+
filterItem?: string;
|
|
213
|
+
filterItemInput?: string;
|
|
214
|
+
filterItemLabel?: string;
|
|
215
|
+
filterItemCount?: string;
|
|
216
|
+
|
|
217
|
+
// Image section
|
|
218
|
+
imageSection?: string;
|
|
219
|
+
imageContainer?: string;
|
|
220
|
+
imageWrapper?: string;
|
|
221
|
+
cropButton?: string;
|
|
222
|
+
tickButton?: string;
|
|
223
|
+
uploadButton?: string;
|
|
224
|
+
resetButton?: string;
|
|
225
|
+
errorMessage?: string;
|
|
226
|
+
cropControls?: string;
|
|
227
|
+
|
|
228
|
+
// Products grid
|
|
229
|
+
gridContainer?: string;
|
|
185
230
|
productItem?: string;
|
|
231
|
+
productImageWrapper?: string;
|
|
186
232
|
productImage?: string;
|
|
233
|
+
productInfo?: string;
|
|
187
234
|
productTitle?: string;
|
|
188
235
|
productPrice?: string;
|
|
236
|
+
productPriceContainer?: string; // NEW: Price wrapper
|
|
237
|
+
productOldPrice?: string;
|
|
238
|
+
|
|
239
|
+
// Pagination & load more
|
|
240
|
+
pagination?: string;
|
|
241
|
+
paginationContainer?: string;
|
|
242
|
+
paginationInfo?: string;
|
|
189
243
|
paginationButton?: string;
|
|
244
|
+
paginationButtonActive?: string;
|
|
245
|
+
paginationButtonDisabled?: string;
|
|
246
|
+
paginationPrevious?: string;
|
|
247
|
+
paginationNext?: string;
|
|
190
248
|
loadMoreButton?: string;
|
|
191
249
|
loadMoreContainer?: string;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
imageSection?: string;
|
|
250
|
+
|
|
251
|
+
// Loading & empty states
|
|
195
252
|
loadingSpinner?: string;
|
|
196
|
-
|
|
253
|
+
loadingOverlay?: string;
|
|
254
|
+
emptyState?: string;
|
|
255
|
+
emptyStateInner?: string; // NEW: Empty state inner wrapper
|
|
256
|
+
emptyStateIcon?: string;
|
|
257
|
+
emptyStateText?: string;
|
|
258
|
+
|
|
259
|
+
// Image search modal
|
|
260
|
+
imageSearchContent?: string; // NEW: Search modal grid
|
|
261
|
+
imageSearchUploadSection?: string; // NEW: Upload section
|
|
262
|
+
imageSearchUploadTitle?: string; // NEW: Upload title
|
|
263
|
+
imageSearchUploadArea?: string; // NEW: Drag & drop area
|
|
264
|
+
imageSearchUploadButton?: string; // NEW: Upload button
|
|
265
|
+
imageSearchTipsSection?: string; // NEW: Tips section
|
|
266
|
+
imageSearchTipsTitle?: string; // NEW: Tips title
|
|
267
|
+
imageSearchTipsList?: string; // NEW: Tips list
|
|
268
|
+
imageSearchTipsItem?: string; // NEW: Individual tip
|
|
269
|
+
imageSearchModalOverlay?: string; // NEW: Image search modal overlay
|
|
270
|
+
|
|
271
|
+
// Mobile active filters
|
|
272
|
+
mobileActiveFilters?: string;
|
|
273
|
+
mobileActiveFilterTag?: string;
|
|
274
|
+
mobileClearAllButton?: string;
|
|
275
|
+
|
|
276
|
+
// New crop-related custom styles
|
|
277
|
+
cropComponent?: string;
|
|
278
|
+
cropImage?: string;
|
|
279
|
+
cropImageActive?: string;
|
|
280
|
+
cropImageNonCropping?: string;
|
|
281
|
+
cropImageContainer?: string;
|
|
282
|
+
cropOverlay?: string;
|
|
283
|
+
cropSelection?: string;
|
|
284
|
+
cropSelectionBorder?: string;
|
|
285
|
+
cropOverlayBackground?: string;
|
|
286
|
+
cropSelectionHighlight?: string;
|
|
197
287
|
};
|
|
198
288
|
|
|
199
289
|
// 25+ render functions for granular control
|
|
200
290
|
customRenderers?: {
|
|
291
|
+
// Component-level renderers (full control)
|
|
292
|
+
Modal?: React.ComponentType<SimilarProductsModalProps>;
|
|
293
|
+
FilterSidebar?: React.ComponentType<FilterSidebarProps>;
|
|
294
|
+
ResultsGrid?: React.ComponentType<ResultsGridProps>;
|
|
295
|
+
ImageSearchModal?: React.ComponentType<any>;
|
|
296
|
+
|
|
297
|
+
// Granular render functions (30+ options)
|
|
201
298
|
render?: {
|
|
202
299
|
modal?: {
|
|
300
|
+
renderModal?: (props) => React.ReactNode; // Full modal override
|
|
203
301
|
renderHeader?: (props) => React.ReactNode;
|
|
204
302
|
renderActiveFilters?: (props) => React.ReactNode;
|
|
205
303
|
renderControls?: (props) => React.ReactNode;
|
|
206
304
|
renderItemCount?: (props) => React.ReactNode;
|
|
305
|
+
renderSortDropdown?: (props) => React.ReactNode;
|
|
306
|
+
renderFilterToggleButton?: (props) => React.ReactNode;
|
|
307
|
+
// ... see examples for complete list
|
|
308
|
+
};
|
|
309
|
+
filterSidebar?: {
|
|
310
|
+
renderSidebar?: (props) => React.ReactNode; // Full sidebar override
|
|
311
|
+
renderImageSection?: (props) => React.ReactNode;
|
|
312
|
+
renderFilterGroup?: (props) => React.ReactNode;
|
|
313
|
+
renderCropButton?: (props) => React.ReactNode;
|
|
314
|
+
renderTickButton?: (props) => React.ReactNode;
|
|
315
|
+
renderUploadButton?: (props) => React.ReactNode;
|
|
316
|
+
renderResetButton?: (props) => React.ReactNode;
|
|
207
317
|
// ... see examples for complete list
|
|
208
318
|
};
|
|
209
319
|
resultsGrid?: {
|
|
320
|
+
renderGrid?: (props) => React.ReactNode; // Full grid override
|
|
210
321
|
renderProductItem?: (props) => React.ReactNode;
|
|
211
322
|
renderPagination?: (props) => React.ReactNode;
|
|
212
323
|
renderGridContainer?: (props) => React.ReactNode;
|
|
324
|
+
renderLoadMore?: (props) => React.ReactNode;
|
|
325
|
+
renderEmptyState?: (props) => React.ReactNode;
|
|
213
326
|
// ... see examples for complete list
|
|
214
327
|
};
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
renderCropButton?: (props) => React.ReactNode;
|
|
219
|
-
// ... see examples for complete list
|
|
328
|
+
imageSearchModal?: {
|
|
329
|
+
renderModal?: (props) => React.ReactNode; // Full image search modal override
|
|
330
|
+
renderUploadArea?: (props) => React.ReactNode;
|
|
220
331
|
};
|
|
221
332
|
};
|
|
222
333
|
};
|
|
@@ -458,7 +569,18 @@ const settings = {
|
|
|
458
569
|
productItem: 'hover:scale-105 transition-all duration-300 shadow-lg',
|
|
459
570
|
filterSidebar: 'bg-gradient-to-b from-gray-50 to-white',
|
|
460
571
|
paginationButton: 'rounded-full px-4 py-2 bg-blue-600 text-white',
|
|
461
|
-
activeFilterTag: 'bg-blue-100 text-blue-800 px-3 py-1 rounded-full'
|
|
572
|
+
activeFilterTag: 'bg-blue-100 text-blue-800 px-3 py-1 rounded-full',
|
|
573
|
+
|
|
574
|
+
// Advanced crop styling
|
|
575
|
+
cropComponent: 'border-2 border-dashed border-blue-300 rounded-lg',
|
|
576
|
+
cropImage: 'rounded-lg shadow-md',
|
|
577
|
+
cropImageActive: 'brightness-110 contrast-110',
|
|
578
|
+
cropImageNonCropping: 'hover:scale-105 transition-transform',
|
|
579
|
+
cropImageContainer: 'bg-gray-50 rounded-lg p-2',
|
|
580
|
+
cropOverlay: 'backdrop-blur-sm',
|
|
581
|
+
cropOverlayBackground: 'bg-black/60',
|
|
582
|
+
cropSelection: 'border-4 border-blue-500',
|
|
583
|
+
cropSelectionHighlight: 'shadow-2xl shadow-blue-500/50'
|
|
462
584
|
}
|
|
463
585
|
};
|
|
464
586
|
```
|
|
@@ -817,7 +939,8 @@ export function SiteHeader() {
|
|
|
817
939
|
settings: {
|
|
818
940
|
maxFileSize: 5,
|
|
819
941
|
customStyles: {
|
|
820
|
-
imageSearchModal: 'max-w-lg rounded-xl'
|
|
942
|
+
imageSearchModal: 'max-w-lg rounded-xl',
|
|
943
|
+
imageSearchModalOverlay: 'bg-black/70 backdrop-blur-sm' // Custom overlay
|
|
821
944
|
}
|
|
822
945
|
}
|
|
823
946
|
}}
|
|
@@ -1226,9 +1349,160 @@ const hybridSettings = {
|
|
|
1226
1349
|
};
|
|
1227
1350
|
```
|
|
1228
1351
|
|
|
1352
|
+
### Advanced Component Customization
|
|
1353
|
+
|
|
1354
|
+
The plugin now supports complete component overrides and granular element styling for maximum flexibility:
|
|
1355
|
+
|
|
1356
|
+
#### Full Component Override Example
|
|
1357
|
+
|
|
1358
|
+
```tsx
|
|
1359
|
+
// Custom Image Search Modal Component
|
|
1360
|
+
const CustomImageSearchModal = ({
|
|
1361
|
+
isOpen,
|
|
1362
|
+
setIsOpen,
|
|
1363
|
+
fileInputRef,
|
|
1364
|
+
handleImageFileChange,
|
|
1365
|
+
settings
|
|
1366
|
+
}) => {
|
|
1367
|
+
return (
|
|
1368
|
+
<Modal open={isOpen} setOpen={setIsOpen} className="custom-search-modal">
|
|
1369
|
+
<div className="custom-grid p-8">
|
|
1370
|
+
<div className="upload-section">
|
|
1371
|
+
<h3 className="text-2xl font-bold mb-4">Upload Your Image</h3>
|
|
1372
|
+
<div className="drag-drop-area border-2 border-dashed border-purple-300 rounded-xl p-12">
|
|
1373
|
+
<input
|
|
1374
|
+
type="file"
|
|
1375
|
+
ref={fileInputRef}
|
|
1376
|
+
onChange={handleImageFileChange}
|
|
1377
|
+
accept="image/*"
|
|
1378
|
+
className="hidden"
|
|
1379
|
+
/>
|
|
1380
|
+
<button
|
|
1381
|
+
onClick={() => fileInputRef.current?.click()}
|
|
1382
|
+
className="w-full py-6 px-8 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-xl"
|
|
1383
|
+
>
|
|
1384
|
+
Choose Image
|
|
1385
|
+
</button>
|
|
1386
|
+
</div>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
</Modal>
|
|
1390
|
+
);
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
// Using component override
|
|
1394
|
+
const advancedComponentSettings = {
|
|
1395
|
+
customRenderers: {
|
|
1396
|
+
// Complete component replacement
|
|
1397
|
+
ImageSearchModal: CustomImageSearchModal,
|
|
1398
|
+
|
|
1399
|
+
// Or use render functions for granular control
|
|
1400
|
+
render: {
|
|
1401
|
+
modal: {
|
|
1402
|
+
renderModal: (props) => (
|
|
1403
|
+
<CustomModal {...props} className="advanced-modal" />
|
|
1404
|
+
)
|
|
1405
|
+
},
|
|
1406
|
+
filterSidebar: {
|
|
1407
|
+
renderSidebar: (props) => (
|
|
1408
|
+
<CustomFilterSidebar {...props} className="advanced-sidebar" />
|
|
1409
|
+
)
|
|
1410
|
+
},
|
|
1411
|
+
resultsGrid: {
|
|
1412
|
+
renderGrid: (props) => (
|
|
1413
|
+
<CustomResultsGrid {...props} className="advanced-grid" />
|
|
1414
|
+
)
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
#### New Granular Style Targets
|
|
1422
|
+
|
|
1423
|
+
```tsx
|
|
1424
|
+
const advancedStylingSettings = {
|
|
1425
|
+
customStyles: {
|
|
1426
|
+
// NEW: Modal structure
|
|
1427
|
+
modalContent: 'md:grid-cols-3 gap-8',
|
|
1428
|
+
resultsContainer: 'p-8 md:p-12',
|
|
1429
|
+
|
|
1430
|
+
// NEW: Control sections
|
|
1431
|
+
controlsInner: 'border-none bg-gray-50 rounded-lg p-6',
|
|
1432
|
+
controlsLeft: 'gap-6',
|
|
1433
|
+
controlsRight: 'relative z-20',
|
|
1434
|
+
activeFiltersWrapper: 'mb-6',
|
|
1435
|
+
|
|
1436
|
+
// NEW: Mobile filter sidebar
|
|
1437
|
+
filterSidebarMobileTitle: 'text-3xl font-bold text-purple-600',
|
|
1438
|
+
filterSidebarMobileCloseButton: 'w-10 h-10 bg-red-100 hover:bg-red-200',
|
|
1439
|
+
filterSidebarMobileCounter: 'bg-blue-50 rounded-lg p-4',
|
|
1440
|
+
filterSidebarMobileCounterText: 'text-blue-800 font-semibold',
|
|
1441
|
+
|
|
1442
|
+
// NEW: Product grid enhancements
|
|
1443
|
+
productPriceContainer: 'flex flex-col space-y-2',
|
|
1444
|
+
emptyStateInner: 'py-24',
|
|
1445
|
+
|
|
1446
|
+
// NEW: Image search modal sections
|
|
1447
|
+
imageSearchContent: 'gap-12 md:grid-cols-1',
|
|
1448
|
+
imageSearchUploadSection:
|
|
1449
|
+
'bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl p-8',
|
|
1450
|
+
imageSearchUploadTitle: 'text-2xl font-bold text-gray-800',
|
|
1451
|
+
imageSearchUploadArea:
|
|
1452
|
+
'border-4 border-dashed border-purple-300 hover:border-purple-500 transition-colors',
|
|
1453
|
+
imageSearchUploadButton:
|
|
1454
|
+
'bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700',
|
|
1455
|
+
imageSearchTipsSection: 'bg-yellow-50 border-l-4 border-yellow-400',
|
|
1456
|
+
imageSearchTipsTitle: 'text-yellow-800 font-bold',
|
|
1457
|
+
imageSearchTipsList: 'text-yellow-700',
|
|
1458
|
+
imageSearchTipsItem: 'hover:text-yellow-900 transition-colors',
|
|
1459
|
+
imageSearchModalOverlay: 'bg-black/60 backdrop-blur-md' // NEW: Custom overlay
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
#### Combined Approach Example
|
|
1465
|
+
|
|
1466
|
+
```tsx
|
|
1467
|
+
// Mix component overrides with granular styling
|
|
1468
|
+
const hybridAdvancedSettings = {
|
|
1469
|
+
customRenderers: {
|
|
1470
|
+
// Override only specific components
|
|
1471
|
+
ImageSearchModal: CustomImageSearchModal,
|
|
1472
|
+
|
|
1473
|
+
render: {
|
|
1474
|
+
modal: {
|
|
1475
|
+
renderHeader: ({ title, onClose }) => (
|
|
1476
|
+
<div className="flex items-center justify-between bg-gradient-to-r from-blue-600 to-purple-600 text-white p-6 rounded-t-xl">
|
|
1477
|
+
<h2 className="text-2xl font-bold">{title}</h2>
|
|
1478
|
+
<button
|
|
1479
|
+
onClick={onClose}
|
|
1480
|
+
className="w-10 h-10 bg-white/20 hover:bg-white/30 rounded-full flex items-center justify-center transition-colors"
|
|
1481
|
+
>
|
|
1482
|
+
✕
|
|
1483
|
+
</button>
|
|
1484
|
+
</div>
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
|
|
1490
|
+
customStyles: {
|
|
1491
|
+
// Fine-tune specific elements
|
|
1492
|
+
modalContent: 'md:grid-cols-4 gap-6',
|
|
1493
|
+
resultsContainer: 'bg-gray-50 rounded-xl p-8',
|
|
1494
|
+
productItem:
|
|
1495
|
+
'bg-white rounded-lg shadow-sm hover:shadow-xl transition-all duration-300',
|
|
1496
|
+
productPriceContainer:
|
|
1497
|
+
'bg-gradient-to-r from-green-50 to-blue-50 rounded-lg p-3',
|
|
1498
|
+
controlsInner: 'bg-white rounded-lg shadow-sm border p-6'
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1229
1503
|
## Complete Styling Reference
|
|
1230
1504
|
|
|
1231
|
-
### Available Style Targets (
|
|
1505
|
+
### Available Style Targets (50+ options)
|
|
1232
1506
|
|
|
1233
1507
|
**Modal & Layout:**
|
|
1234
1508
|
|
|
@@ -1239,6 +1513,7 @@ const hybridSettings = {
|
|
|
1239
1513
|
- `filterSidebar`, `filterSidebarMobileHeader`, `filterGroup`, `filterGroupTitle`, `filterGroupContent`
|
|
1240
1514
|
- `filterItem`, `filterItemInput`, `filterItemLabel`, `filterItemCount`
|
|
1241
1515
|
- `imageSection`, `imageContainer`, `imageWrapper`, `cropButton`, `tickButton`, `uploadButton`, `resetButton`, `errorMessage`, `cropControls`
|
|
1516
|
+
- `cropComponent`, `cropImage`, `cropImageActive`, `cropImageNonCropping`, `cropImageContainer`, `cropOverlay`, `cropSelection`, `cropSelectionBorder`, `cropOverlayBackground`, `cropSelectionHighlight`
|
|
1242
1517
|
- `mobileActiveFilters`, `mobileActiveFilterTag`, `mobileClearAllButton`
|
|
1243
1518
|
|
|
1244
1519
|
**Products & Results:**
|
|
@@ -1258,26 +1533,41 @@ const hybridSettings = {
|
|
|
1258
1533
|
|
|
1259
1534
|
- `loadingSpinner`, `loadingOverlay`, `emptyState`, `emptyStateIcon`, `emptyStateText`
|
|
1260
1535
|
|
|
1261
|
-
### Available Render Functions (
|
|
1536
|
+
### Available Render Functions (30+ options)
|
|
1537
|
+
|
|
1538
|
+
**Component-Level Renderers (Full Control):**
|
|
1539
|
+
|
|
1540
|
+
- `Modal` - Complete modal component override
|
|
1541
|
+
- `FilterSidebar` - Complete sidebar component override
|
|
1542
|
+
- `ResultsGrid` - Complete grid component override
|
|
1543
|
+
- `ImageSearchModal` - Complete image search modal override
|
|
1262
1544
|
|
|
1263
1545
|
**Modal Renderers:**
|
|
1264
1546
|
|
|
1265
|
-
- `renderModal
|
|
1547
|
+
- `renderModal` - Full modal override
|
|
1548
|
+
- `renderHeader`, `renderCloseButton`, `renderActiveFilters`, `renderActiveFilterTag`
|
|
1266
1549
|
- `renderControls`, `renderSortDropdown`, `renderItemCount`, `renderFilterToggleButton`, `renderEmptyState`
|
|
1267
1550
|
|
|
1268
1551
|
**Filter Sidebar Renderers:**
|
|
1269
1552
|
|
|
1270
|
-
- `renderSidebar
|
|
1553
|
+
- `renderSidebar` - Full sidebar override
|
|
1554
|
+
- `renderMobileHeader`, `renderImageSection`, `renderImageContainer`
|
|
1271
1555
|
- `renderCropButton`, `renderTickButton`, `renderUploadButton`, `renderResetButton`, `renderErrorMessage`
|
|
1272
1556
|
- `renderFilterGroup`, `renderFilterGroupTitle`, `renderFilterItem`, `renderFilterItemInput`, `renderFilterItemLabel`, `renderFilterItemCount`
|
|
1273
1557
|
- `renderMobileActiveFilters`
|
|
1274
1558
|
|
|
1275
1559
|
**Results Grid Renderers:**
|
|
1276
1560
|
|
|
1277
|
-
- `renderGrid
|
|
1561
|
+
- `renderGrid` - Full grid override
|
|
1562
|
+
- `renderGridContainer`, `renderProductItem`, `renderProductImage`, `renderProductInfo`, `renderProductTitle`, `renderProductPrice`
|
|
1278
1563
|
- `renderPagination`, `renderPaginationButton`, `renderPaginationInfo`, `renderPaginationPrevious`, `renderPaginationNext`, `renderLoadMore`, `renderLoadMoreButton`
|
|
1279
1564
|
- `renderLoadingState`, `renderLoadingOverlay`, `renderEmptyState`
|
|
1280
1565
|
|
|
1566
|
+
**Image Search Modal Renderers:**
|
|
1567
|
+
|
|
1568
|
+
- `renderModal` - Full image search modal override
|
|
1569
|
+
- `renderUploadArea` - Upload section customization
|
|
1570
|
+
|
|
1281
1571
|
## Best Practices
|
|
1282
1572
|
|
|
1283
1573
|
### Performance Optimization
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
58
58
|
pagination: {
|
|
59
59
|
current_page: 1,
|
|
60
60
|
num_pages: 1,
|
|
61
|
-
page_size:
|
|
61
|
+
page_size: 20,
|
|
62
62
|
total_count: 0
|
|
63
63
|
}
|
|
64
64
|
}),
|
|
@@ -91,14 +91,6 @@ export function useSimilarProducts(product: Product) {
|
|
|
91
91
|
searchParams.page = String(page);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// Test için farklı parametre isimlerini deniyoruz
|
|
95
|
-
searchParams.page_size = '1';
|
|
96
|
-
searchParams.pageSize = '1';
|
|
97
|
-
searchParams.limit = '1';
|
|
98
|
-
searchParams.per_page = '1';
|
|
99
|
-
searchParams.size = '1';
|
|
100
|
-
console.log('🔍 API çağrısı searchParams:', searchParams);
|
|
101
|
-
|
|
102
94
|
if (facets) {
|
|
103
95
|
facets.forEach((facet) => {
|
|
104
96
|
if (String(facet.key) === 'category_ids') return;
|
|
@@ -633,7 +625,21 @@ export function useSimilarProducts(product: Product) {
|
|
|
633
625
|
if (!file) return;
|
|
634
626
|
|
|
635
627
|
try {
|
|
636
|
-
|
|
628
|
+
let processedFile = file;
|
|
629
|
+
|
|
630
|
+
if (file.type === 'image/webp') {
|
|
631
|
+
try {
|
|
632
|
+
const { convertWebPToJPEG } = await import(
|
|
633
|
+
'../utils/image-conversion'
|
|
634
|
+
);
|
|
635
|
+
processedFile = await convertWebPToJPEG(file);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
console.error('WebP conversion failed:', error);
|
|
638
|
+
processedFile = file;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const validation = await validateImageFileWithTranslation(processedFile);
|
|
637
643
|
if (!validation.isValid) {
|
|
638
644
|
setFileError(validation.error!);
|
|
639
645
|
return;
|
|
@@ -657,7 +663,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
657
663
|
|
|
658
664
|
reader.onerror = () =>
|
|
659
665
|
setFileError(t('common.similar_products.errors.file_read_error'));
|
|
660
|
-
reader.readAsDataURL(
|
|
666
|
+
reader.readAsDataURL(processedFile);
|
|
661
667
|
} catch (error) {
|
|
662
668
|
setFileError(t('common.similar_products.errors.processing_error'));
|
|
663
669
|
}
|
|
@@ -815,7 +821,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
815
821
|
result.facets,
|
|
816
822
|
resultsWithResetSorter.facets
|
|
817
823
|
),
|
|
818
|
-
sorters: resultsWithResetSorter.sorters
|
|
824
|
+
sorters: resultsWithResetSorter.sorters
|
|
819
825
|
};
|
|
820
826
|
updateResultsAndKey(finalResult);
|
|
821
827
|
}
|
|
@@ -868,6 +874,14 @@ export function useSimilarProducts(product: Product) {
|
|
|
868
874
|
setFileError('');
|
|
869
875
|
}, []);
|
|
870
876
|
|
|
877
|
+
const clearResults = useCallback(() => {
|
|
878
|
+
setSearchResults(null);
|
|
879
|
+
setResultsKey(0);
|
|
880
|
+
setLastProductIds([]);
|
|
881
|
+
setAllLoadedProducts([]);
|
|
882
|
+
setLoadedPages(new Set([1]));
|
|
883
|
+
}, []);
|
|
884
|
+
|
|
871
885
|
const clearFileInput = useCallback(
|
|
872
886
|
(fileInputRef: React.RefObject<HTMLInputElement>) => {
|
|
873
887
|
if (fileInputRef.current) {
|
|
@@ -934,6 +948,7 @@ export function useSimilarProducts(product: Product) {
|
|
|
934
948
|
loadedPages: Array.from(loadedPages),
|
|
935
949
|
allLoadedProducts,
|
|
936
950
|
clearError,
|
|
951
|
+
clearResults,
|
|
937
952
|
clearFileInput
|
|
938
953
|
};
|
|
939
954
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -316,20 +316,29 @@ export interface SimilarProductsSettings {
|
|
|
316
316
|
maxPagesLoadMore?: number;
|
|
317
317
|
customStyles?: {
|
|
318
318
|
modal?: string;
|
|
319
|
+
modalContent?: string;
|
|
319
320
|
filterSidebar?: string;
|
|
320
321
|
resultsGrid?: string;
|
|
321
322
|
imageSearchModal?: string;
|
|
322
323
|
|
|
323
324
|
activeFiltersContainer?: string;
|
|
325
|
+
activeFiltersWrapper?: string;
|
|
324
326
|
activeFilterTag?: string;
|
|
325
327
|
activeFilterTagButton?: string;
|
|
326
328
|
|
|
327
329
|
controlsContainer?: string;
|
|
330
|
+
controlsInner?: string;
|
|
331
|
+
controlsLeft?: string;
|
|
332
|
+
controlsRight?: string;
|
|
328
333
|
itemCount?: string;
|
|
329
334
|
sortDropdown?: string;
|
|
330
335
|
filterToggleButton?: string;
|
|
331
336
|
|
|
332
337
|
filterSidebarMobileHeader?: string;
|
|
338
|
+
filterSidebarMobileTitle?: string;
|
|
339
|
+
filterSidebarMobileCloseButton?: string;
|
|
340
|
+
filterSidebarMobileCounter?: string;
|
|
341
|
+
filterSidebarMobileCounterText?: string;
|
|
333
342
|
filterGroup?: string;
|
|
334
343
|
filterGroupTitle?: string;
|
|
335
344
|
filterGroupContent?: string;
|
|
@@ -348,6 +357,19 @@ export interface SimilarProductsSettings {
|
|
|
348
357
|
errorMessage?: string;
|
|
349
358
|
cropControls?: string;
|
|
350
359
|
|
|
360
|
+
// Detailed crop styling
|
|
361
|
+
cropComponent?: string;
|
|
362
|
+
cropImage?: string;
|
|
363
|
+
cropImageActive?: string;
|
|
364
|
+
cropOverlay?: string;
|
|
365
|
+
cropSelection?: string;
|
|
366
|
+
cropSelectionBorder?: string;
|
|
367
|
+
cropImageNonCropping?: string;
|
|
368
|
+
cropImageContainer?: string;
|
|
369
|
+
cropImageWrapper?: string;
|
|
370
|
+
cropOverlayBackground?: string;
|
|
371
|
+
cropSelectionHighlight?: string;
|
|
372
|
+
|
|
351
373
|
resultsContainer?: string;
|
|
352
374
|
gridContainer?: string;
|
|
353
375
|
productItem?: string;
|
|
@@ -356,6 +378,7 @@ export interface SimilarProductsSettings {
|
|
|
356
378
|
productInfo?: string;
|
|
357
379
|
productTitle?: string;
|
|
358
380
|
productPrice?: string;
|
|
381
|
+
productPriceContainer?: string;
|
|
359
382
|
productOldPrice?: string;
|
|
360
383
|
|
|
361
384
|
pagination?: string;
|
|
@@ -372,12 +395,25 @@ export interface SimilarProductsSettings {
|
|
|
372
395
|
loadingSpinner?: string;
|
|
373
396
|
loadingOverlay?: string;
|
|
374
397
|
emptyState?: string;
|
|
398
|
+
emptyStateInner?: string;
|
|
375
399
|
emptyStateIcon?: string;
|
|
376
400
|
emptyStateText?: string;
|
|
377
401
|
|
|
402
|
+
imageSearchContent?: string;
|
|
403
|
+
imageSearchUploadSection?: string;
|
|
404
|
+
imageSearchUploadTitle?: string;
|
|
405
|
+
imageSearchUploadArea?: string;
|
|
406
|
+
imageSearchUploadButton?: string;
|
|
407
|
+
imageSearchTipsSection?: string;
|
|
408
|
+
imageSearchTipsTitle?: string;
|
|
409
|
+
imageSearchTipsList?: string;
|
|
410
|
+
imageSearchTipsItem?: string;
|
|
411
|
+
imageSearchModalOverlay?: string;
|
|
412
|
+
|
|
378
413
|
mobileActiveFilters?: string;
|
|
379
414
|
mobileActiveFilterTag?: string;
|
|
380
415
|
mobileClearAllButton?: string;
|
|
416
|
+
filterSidebarMobileOverlay?: string;
|
|
381
417
|
};
|
|
382
418
|
customRenderers?: {
|
|
383
419
|
Modal?: React.ComponentType<SimilarProductsModalProps>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const convertWebPToJPEG = (file: File): Promise<File> => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const canvas = document.createElement('canvas');
|
|
4
|
+
const ctx = canvas.getContext('2d');
|
|
5
|
+
const img = new Image();
|
|
6
|
+
|
|
7
|
+
img.onload = () => {
|
|
8
|
+
canvas.width = img.naturalWidth;
|
|
9
|
+
canvas.height = img.naturalHeight;
|
|
10
|
+
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
reject(new Error('Canvas context not available'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
ctx.drawImage(img, 0, 0);
|
|
17
|
+
|
|
18
|
+
canvas.toBlob(
|
|
19
|
+
(blob) => {
|
|
20
|
+
if (!blob) {
|
|
21
|
+
reject(new Error('Failed to convert image'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const convertedFile = new File(
|
|
26
|
+
[blob],
|
|
27
|
+
file.name.replace(/\.webp$/i, '.jpg'),
|
|
28
|
+
{
|
|
29
|
+
type: 'image/jpeg',
|
|
30
|
+
lastModified: Date.now()
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
resolve(convertedFile);
|
|
35
|
+
},
|
|
36
|
+
'image/jpeg',
|
|
37
|
+
0.9
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
img.onerror = () => reject(new Error('Failed to load image'));
|
|
42
|
+
img.src = URL.createObjectURL(file);
|
|
43
|
+
});
|
|
44
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -21,7 +21,12 @@ export const defaultSettings: Required<SimilarProductsSettings> = {
|
|
|
21
21
|
render: {}
|
|
22
22
|
},
|
|
23
23
|
theme: {},
|
|
24
|
-
cssVariables: {}
|
|
24
|
+
cssVariables: {},
|
|
25
|
+
paginationType: 'pagination',
|
|
26
|
+
loadMoreText: '',
|
|
27
|
+
loadMoreStyle: 'button',
|
|
28
|
+
loadMoreThreshold: 10,
|
|
29
|
+
maxPagesLoadMore: 20
|
|
25
30
|
};
|
|
26
31
|
|
|
27
32
|
export function mergeSettings(
|