@akinon/pz-similar-products 1.92.0-snapshot-ZERO-3457-20250627121541 → 1.93.0-rc.47

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.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
- import { Modal, Button, Icon, Select } from '@akinon/next/components';
4
+ import { Modal, Button, Icon, Select, Input } from '@akinon/next/components';
5
5
  import { useLocalization } from '@akinon/next/hooks';
6
6
  import { SimilarProductsFilterSidebar } from './filters';
7
7
  import { SimilarProductsResultsGrid } from './results';
@@ -43,7 +43,11 @@ export function SimilarProductsModal({
43
43
  fileError,
44
44
  showResetButton = true,
45
45
  settings,
46
- className
46
+ className,
47
+ searchText,
48
+ setSearchText,
49
+ handleTextSearch,
50
+ handleClearText
47
51
  }: SimilarProductsModalProps) {
48
52
  const { t } = useLocalization();
49
53
 
@@ -164,6 +168,24 @@ export function SimilarProductsModal({
164
168
  })) || []
165
169
  ) || [];
166
170
 
171
+ const modalCloseIconName = settings?.iconNames?.modalClose || 'close';
172
+ const filterIconName = settings?.iconNames?.filter || 'filter';
173
+ const modalSearchIconName = settings?.iconNames?.modalSearch || 'search';
174
+ const modalSearchClearIconName =
175
+ settings?.iconNames?.modalSearchClear || 'close';
176
+
177
+ const modalCloseIconClassName = twMerge(
178
+ '',
179
+ settings?.customStyles?.modalCloseIcon
180
+ );
181
+
182
+ const filterIconClassName = twMerge('', settings?.customStyles?.filterIcon);
183
+
184
+ const modalSearchIconClassName = twMerge(
185
+ 'text-gray-500',
186
+ settings?.customStyles?.modalSearchIcon
187
+ );
188
+
167
189
  const renderHeader = () => {
168
190
  if (settings?.customRenderers?.render?.modal?.renderHeader) {
169
191
  return settings.customRenderers.render.modal.renderHeader({
@@ -246,7 +268,11 @@ export function SimilarProductsModal({
246
268
  disabled={isLoading}
247
269
  className={buttonClassName}
248
270
  >
249
- <Icon name="close" size={12} />
271
+ <Icon
272
+ name={modalCloseIconName}
273
+ size={12}
274
+ className={modalCloseIconClassName}
275
+ />
250
276
  </Button>
251
277
  </div>
252
278
  );
@@ -266,7 +292,9 @@ export function SimilarProductsModal({
266
292
  '',
267
293
  onSortChange: handleSortChange,
268
294
  onFilterMenuToggle: () => setIsFilterMenuOpen(true),
269
- isLoading
295
+ isLoading,
296
+ searchText,
297
+ setSearchText
270
298
  });
271
299
  }
272
300
 
@@ -281,15 +309,30 @@ export function SimilarProductsModal({
281
309
  );
282
310
 
283
311
  const filterToggleClassName = twMerge(
284
- 'md:hidden text-xs',
312
+ 'md:hidden text-xs px-3 py-2',
285
313
  settings?.customStyles?.filterToggleButton
286
314
  );
287
315
 
288
316
  const sortDropdownClassName = twMerge(
289
- 'h-10 px-4 text-md md:text-xs bg-gray-200 hover:bg-gray-400 transition-colors duration-200 border-gray-300 focus:border-primary focus:ring-1 focus:ring-primary w-full md:w-40 min-w-[120px]',
317
+ 'h-10 px-3 md:px-4 text-sm md:text-xs bg-gray-200 hover:bg-gray-400 transition-colors duration-200 border-gray-300 focus:border-primary focus:ring-1 focus:ring-primary w-full md:w-40 min-w-[120px]',
290
318
  settings?.customStyles?.sortDropdown
291
319
  );
292
320
 
321
+ const modalSearchInputClassName = twMerge(
322
+ 'h-10 px-3 md:px-4 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-48 md:w-60 min-w-[180px]',
323
+ settings?.customStyles?.modalSearchInput
324
+ );
325
+
326
+ const modalSearchContainerClassName = twMerge(
327
+ 'relative flex items-center',
328
+ settings?.customStyles?.modalSearchContainer
329
+ );
330
+
331
+ const modalSearchButtonClassName = twMerge(
332
+ 'absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
333
+ settings?.customStyles?.modalSearchButton
334
+ );
335
+
293
336
  const renderItemCount = () => {
294
337
  if (settings?.customRenderers?.render?.modal?.renderItemCount) {
295
338
  return settings.customRenderers.render.modal.renderItemCount({
@@ -299,7 +342,9 @@ export function SimilarProductsModal({
299
342
  }
300
343
  return (
301
344
  <div className={itemCountClassName}>
302
- {searchResults?.pagination?.total_count || 0} items
345
+ <span className="hidden md:inline">
346
+ {searchResults?.pagination?.total_count || 0} items
347
+ </span>
303
348
  </div>
304
349
  );
305
350
  };
@@ -319,13 +364,21 @@ export function SimilarProductsModal({
319
364
  onClick={() => setIsFilterMenuOpen(true)}
320
365
  data-testid="similar-products-filter"
321
366
  >
322
- <Icon name="filter" size={14} />
367
+ <Icon
368
+ name={filterIconName}
369
+ size={14}
370
+ className={filterIconClassName}
371
+ />
323
372
  {t('common.product.filters')}
324
373
  </Button>
325
374
  );
326
375
  };
327
376
 
328
377
  const renderSortDropdown = () => {
378
+ if (!searchResults?.products?.length || !searchResults?.sorters?.length) {
379
+ return null;
380
+ }
381
+
329
382
  if (settings?.customRenderers?.render?.modal?.renderSortDropdown) {
330
383
  return settings.customRenderers.render.modal.renderSortDropdown({
331
384
  sorters: searchResults?.sorters || [],
@@ -351,26 +404,134 @@ export function SimilarProductsModal({
351
404
  );
352
405
  };
353
406
 
407
+ const renderModalSearchInput = () => {
408
+ if (
409
+ !settings?.enableTextSearch ||
410
+ searchText === undefined ||
411
+ !setSearchText ||
412
+ !searchResults?.products?.length ||
413
+ !searchResults?.sorters?.length
414
+ ) {
415
+ return null;
416
+ }
417
+
418
+ if (settings?.customRenderers?.render?.modal?.renderModalSearchInput) {
419
+ return settings.customRenderers.render.modal.renderModalSearchInput({
420
+ searchText,
421
+ setSearchText,
422
+ isLoading,
423
+ placeholder: t('common.search.placeholder'),
424
+ onSearch: handleTextSearch
425
+ });
426
+ }
427
+
428
+ return (
429
+ <div className={modalSearchContainerClassName}>
430
+ <Input
431
+ type="text"
432
+ value={searchText}
433
+ onChange={(e) => setSearchText(e.currentTarget.value)}
434
+ onKeyDown={(e) => {
435
+ if (e.key === 'Enter' && handleTextSearch) {
436
+ e.preventDefault();
437
+ handleTextSearch();
438
+ }
439
+ }}
440
+ placeholder={t('common.search.placeholder')}
441
+ className={twMerge(modalSearchInputClassName, 'pr-20')}
442
+ disabled={isLoading}
443
+ />
444
+ {searchText && (
445
+ <button
446
+ onClick={() => {
447
+ if (handleClearText) {
448
+ handleClearText();
449
+ }
450
+ }}
451
+ disabled={isLoading}
452
+ className={twMerge(
453
+ 'absolute right-12 top-1/2 -translate-y-1/2 p-2 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
454
+ settings?.customStyles?.modalSearchClearButton
455
+ )}
456
+ >
457
+ <Icon
458
+ name={modalSearchClearIconName}
459
+ size={16}
460
+ className={twMerge(
461
+ 'text-gray-400 hover:text-gray-600',
462
+ settings?.customStyles?.modalSearchClearIcon
463
+ )}
464
+ />
465
+ </button>
466
+ )}
467
+ <button
468
+ onClick={() => {
469
+ if (handleTextSearch) {
470
+ handleTextSearch();
471
+ }
472
+ }}
473
+ disabled={isLoading}
474
+ className={modalSearchButtonClassName}
475
+ >
476
+ {settings?.customRenderers?.render?.modal?.renderSearchIcon ? (
477
+ settings.customRenderers.render.modal.renderSearchIcon({
478
+ disabled: isLoading,
479
+ onClick: handleTextSearch
480
+ })
481
+ ) : (
482
+ <Icon
483
+ name={modalSearchIconName}
484
+ size={18}
485
+ className={modalSearchIconClassName}
486
+ />
487
+ )}
488
+ </button>
489
+ </div>
490
+ );
491
+ };
492
+
354
493
  return (
355
494
  <div className={containerClassName}>
356
495
  <div
357
496
  className={twMerge(
358
- 'flex items-center justify-between p-3 md:p-4',
497
+ 'p-3 md:p-4',
498
+ settings?.enableTextSearch &&
499
+ searchText !== undefined &&
500
+ setSearchText
501
+ ? 'flex gap-3 md:grid md:grid-cols-3 md:gap-4 md:items-center'
502
+ : 'flex items-center justify-between',
359
503
  settings?.customStyles?.controlsInner
360
504
  )}
361
505
  >
362
506
  <div
363
507
  className={twMerge(
364
- 'flex items-center gap-3',
508
+ 'flex items-center gap-2 md:gap-3',
365
509
  settings?.customStyles?.controlsLeft
366
510
  )}
367
511
  >
368
512
  {renderItemCount()}
369
513
  {renderFilterToggle()}
370
514
  </div>
515
+ {settings?.enableTextSearch &&
516
+ searchText !== undefined &&
517
+ setSearchText && (
518
+ <div
519
+ className={twMerge(
520
+ 'flex items-center justify-center w-full',
521
+ settings?.customStyles?.controlsCenter
522
+ )}
523
+ >
524
+ {renderModalSearchInput()}
525
+ </div>
526
+ )}
371
527
  <div
372
528
  className={twMerge(
373
529
  'relative',
530
+ settings?.enableTextSearch &&
531
+ searchText !== undefined &&
532
+ setSearchText
533
+ ? 'flex justify-end md:mt-0'
534
+ : '',
374
535
  settings?.customStyles?.controlsRight
375
536
  )}
376
537
  >
@@ -406,6 +567,8 @@ export function SimilarProductsModal({
406
567
  isLoading={isLoading}
407
568
  handleFacetChange={handleFacetChange}
408
569
  removeFacetFilter={removeFacetFilter}
570
+ searchText={searchText}
571
+ setSearchText={setSearchText}
409
572
  currentImageUrl={currentImageUrl}
410
573
  isCropping={isCropping}
411
574
  imageRef={imageRef}