@pradip1995/segment-animation-shop-grid 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pradip1995/segment-animation-shop-grid",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,10 +15,6 @@
15
15
  ".": "./src/index.ts",
16
16
  "./manifest": "./src/manifest.ts"
17
17
  },
18
- "scripts": {
19
- "typecheck": "tsc --noEmit",
20
- "lint": "tsc --noEmit"
21
- },
22
18
  "peerDependencies": {
23
19
  "@pradip1995/commerce-core": "^4.0.0",
24
20
  "@pradip1995/medusa-connector": "^0.2.0",
@@ -38,5 +34,9 @@
38
34
  "@types/react": "^19",
39
35
  "react": "19.0.3",
40
36
  "typescript": "^5.7.2"
37
+ },
38
+ "scripts": {
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "tsc --noEmit"
41
41
  }
42
42
  }
@@ -1,6 +1,6 @@
1
1
  .animation-shop-grid {
2
2
  width: 100%;
3
- padding: 2.5rem 1.5rem 4rem 1.5rem;
3
+ padding: 0 1.5rem 4rem 1.5rem;
4
4
  background-color: var(--page-bg, #ffffff);
5
5
  color: var(--text-color, #111111);
6
6
  font-family: inherit;
@@ -9,7 +9,7 @@
9
9
 
10
10
  @media (min-width: 1024px) {
11
11
  .animation-shop-grid {
12
- padding: 3.5rem 3rem 6rem 3rem;
12
+ padding: 0 3rem 6rem 3rem;
13
13
  }
14
14
  }
15
15
 
@@ -119,6 +119,20 @@ function matchLabel(
119
119
  return hit?.name || hit?.title || undefined
120
120
  }
121
121
 
122
+ function formatSlugToTitle(slug?: string): string | undefined {
123
+ if (!slug) return undefined
124
+ if (slug.includes(",")) {
125
+ return slug
126
+ .split(",")
127
+ .map((s) => formatSlugToTitle(s))
128
+ .filter(Boolean)
129
+ .join(" & ")
130
+ }
131
+ return slug
132
+ .replace(/[-_]+/g, " ")
133
+ .replace(/\b\w/g, (char) => char.toUpperCase())
134
+ }
135
+
122
136
  const containerVariants = {
123
137
  hidden: { opacity: 0 },
124
138
  visible: {
@@ -135,7 +149,7 @@ const cardVariants = {
135
149
  visible: {
136
150
  opacity: 1,
137
151
  y: 0,
138
- transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
152
+ transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] as const },
139
153
  },
140
154
  }
141
155
 
@@ -297,12 +311,23 @@ export default function AnimationShopGrid(props: {
297
311
 
298
312
  const activeCategory = paramValue(props.category)
299
313
  const activeCollection = paramValue(props.collection)
314
+ const activeGender = paramValue(props.gender)
315
+ const activeMaterial = paramValue(props.material)
316
+ const activeColor = paramValue(props.color)
317
+ const activeType = paramValue(props.type)
318
+
300
319
  const categoryLabel =
301
320
  matchLabel(activeCategory, props.categories || []) ||
302
- (activeCategory ? "Category" : undefined)
321
+ formatSlugToTitle(activeCategory)
322
+
303
323
  const collectionLabel =
304
324
  matchLabel(activeCollection, props.collections || []) ||
305
- (activeCollection ? "Collection" : undefined)
325
+ formatSlugToTitle(activeCollection)
326
+
327
+ const genderLabel = formatSlugToTitle(activeGender)
328
+ const materialLabel = formatSlugToTitle(activeMaterial)
329
+ const colorLabel = formatSlugToTitle(activeColor)
330
+ const typeLabel = formatSlugToTitle(activeType)
306
331
 
307
332
  const appliedFilters: { key: string; label: string; href: string }[] = []
308
333
  if (props.q) {
@@ -327,90 +352,120 @@ export default function AnimationShopGrid(props: {
327
352
  })
328
353
  }
329
354
 
355
+ const displayTitle = useMemo(() => {
356
+ if (categoryLabel) return categoryLabel
357
+ if (collectionLabel) return collectionLabel
358
+ if (props.q) return `Search: ${props.q}`
359
+ if (genderLabel) return `${genderLabel}'s Collection`
360
+ if (materialLabel) return materialLabel
361
+ if (colorLabel) return colorLabel
362
+ if (typeLabel) return typeLabel
363
+ if (sortBy === "bestsellers") return "Bestsellers"
364
+ if (!props.title || props.title.toLowerCase() === "all beauty" || props.title.toLowerCase() === "all products") {
365
+ return "ALL PRODUCTS"
366
+ }
367
+ return props.title
368
+ }, [categoryLabel, collectionLabel, props.q, genderLabel, materialLabel, colorLabel, typeLabel, sortBy, props.title])
369
+
370
+ const displaySubtitle = useMemo(() => {
371
+ if (categoryLabel) return `Explore our curated selection of ${categoryLabel.toLowerCase()}.`
372
+ if (collectionLabel) return `Discover the latest items in our ${collectionLabel.toLowerCase()} collection.`
373
+ if (props.q) return `Product results matching "${props.q}".`
374
+ if (sortBy === "bestsellers") return "Our most popular and highly-rated pieces."
375
+ if (genderLabel || materialLabel || colorLabel) return "Handpicked items filtered to your taste."
376
+ return "The latest additions to the lineup."
377
+ }, [categoryLabel, collectionLabel, props.q, sortBy, genderLabel, materialLabel, colorLabel])
378
+
379
+ const [showFilters, setShowFilters] = useState(true)
380
+
381
+ useEffect(() => {
382
+ const sidebar = document.querySelector(".store-page__sidebar") as HTMLElement | null
383
+ const mainCol = document.querySelector(".store-page__main") as HTMLElement | null
384
+ if (sidebar) {
385
+ sidebar.style.display = showFilters ? "" : "none"
386
+ }
387
+ if (mainCol) {
388
+ if (!showFilters) {
389
+ mainCol.classList.add("w-full", "col-span-full")
390
+ } else {
391
+ mainCol.classList.remove("w-full", "col-span-full")
392
+ }
393
+ }
394
+ }, [showFilters])
395
+
330
396
  return (
331
- <section className="animation-shop-grid flex-1 min-w-0" aria-label="Product results">
397
+ <section className="animation-shop-grid flex-1 min-w-0 pt-0" aria-label="Product results">
398
+ {/* Simplified, Clean Hero Header */}
332
399
  <motion.header
333
- className="animation-shop-grid__header"
334
- initial={{ opacity: 0, y: -10 }}
400
+ className="w-full bg-[#FAF9F6] py-6 px-4 mb-4 flex flex-col justify-center items-center text-center border-b border-gray-100 rounded-sm"
401
+ initial={{ opacity: 0, y: -6 }}
335
402
  animate={{ opacity: 1, y: 0 }}
336
- transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
403
+ transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
337
404
  >
338
- <div className="animation-shop-grid__intro">
339
- <p className="animation-shop-grid__eyebrow">{eyebrow}</p>
340
- <h2 className="animation-shop-grid__title">{title}</h2>
341
- {props.q ? (
342
- <p className="animation-shop-grid__search">
343
- Results for &ldquo;{props.q}&rdquo;
344
- </p>
345
- ) : null}
346
- {totalCount > 0 ? (
347
- <p className="animation-shop-grid__count">
348
- Showing {showingCount} of {totalCount} {totalCount === 1 ? "product" : "products"}
349
- </p>
350
- ) : null}
351
- </div>
405
+ <nav aria-label="Breadcrumb" className="text-[11px] text-gray-400 font-sans tracking-wide mb-1.5 uppercase">
406
+ <LocalizedLink href="/" className="hover:text-black transition-colors" countryCode={countryCode}>
407
+ Home
408
+ </LocalizedLink>
409
+ <span className="mx-2 text-gray-300">/</span>
410
+ <span className="text-gray-900 font-semibold tracking-wider">{displayTitle}</span>
411
+ </nav>
352
412
 
353
- <div className="animation-shop-grid__sort" role="group" aria-label="Sort products">
354
- {SORT_OPTIONS.map((opt) => {
355
- const active = sortBy === opt.value
356
- return (
357
- <LocalizedLink
358
- key={opt.value}
359
- href={buildSortHref(storeSearchParams, opt.value)}
360
- className={`animation-shop-grid__sort-pill${active ? " animation-shop-grid__sort-pill--active" : ""}`}
361
- countryCode={countryCode}
362
- >
363
- {opt.label}
364
- </LocalizedLink>
365
- )
366
- })}
367
- </div>
413
+ <h1 className="text-xl sm:text-2xl font-normal tracking-widest text-[#111111] uppercase my-1">
414
+ {displayTitle}
415
+ </h1>
416
+
417
+ <p className="text-xs text-gray-500 font-normal tracking-wide max-w-sm m-0">
418
+ {displaySubtitle}
419
+ </p>
368
420
  </motion.header>
369
421
 
370
- <AnimatePresence>
371
- {appliedFilters.length > 0 ? (
372
- <motion.div
373
- className="animation-shop-grid__applied"
374
- role="group"
375
- aria-label="Applied filters"
376
- initial={{ opacity: 0, height: 0 }}
377
- animate={{ opacity: 1, height: "auto" }}
378
- exit={{ opacity: 0, height: 0 }}
422
+ {/* Filter Toolbar with Hide Filters Option */}
423
+ <div className="w-full flex flex-wrap items-center justify-between gap-3 mb-5 py-2.5 px-4 bg-white border border-gray-200/80 rounded-md shadow-2xs">
424
+ <div className="flex items-center gap-3">
425
+ <button
426
+ type="button"
427
+ onClick={() => setShowFilters(!showFilters)}
428
+ className="inline-flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-gray-800 hover:text-black transition-colors py-1 px-2.5 bg-gray-50 hover:bg-gray-100 border border-gray-200 rounded cursor-pointer"
379
429
  >
380
- <span className="animation-shop-grid__applied-label">Filters</span>
381
- <ul className="animation-shop-grid__applied-list">
430
+ <svg className="w-3.5 h-3.5 fill-none stroke-current stroke-[1.8]" viewBox="0 0 24 24">
431
+ <path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6h9m-9 12h9m-15-6h15M3 6h3m-3 12h3m3-6h3" />
432
+ </svg>
433
+ <span>{showFilters ? "Hide Filters" : "Show Filters"}</span>
434
+ </button>
435
+
436
+ {appliedFilters.length > 0 ? (
437
+ <div className="hidden sm:flex items-center gap-2">
438
+ <span className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Active:</span>
382
439
  {appliedFilters.map((filter) => (
383
- <li key={filter.key}>
384
- <LocalizedLink
385
- href={filter.href}
386
- className="animation-shop-grid__applied-chip"
387
- countryCode={countryCode}
388
- aria-label={`Remove ${filter.label} filter`}
389
- >
390
- <span>{filter.label}</span>
391
- <span className="animation-shop-grid__applied-x" aria-hidden>
392
- ×
393
- </span>
394
- </LocalizedLink>
395
- </li>
440
+ <LocalizedLink
441
+ key={filter.key}
442
+ href={filter.href}
443
+ className="inline-flex items-center gap-1 text-xs font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 px-2 py-0.5 rounded transition-colors"
444
+ countryCode={countryCode}
445
+ >
446
+ <span>{filter.label}</span>
447
+ <span className="text-gray-500 hover:text-black">×</span>
448
+ </LocalizedLink>
396
449
  ))}
397
- </ul>
450
+ </div>
451
+ ) : null}
452
+ </div>
453
+
454
+ <div className="flex items-center gap-3 ml-auto">
455
+ <span className="text-xs text-gray-500 font-medium tracking-wide">
456
+ {showingCount} of {totalCount} {totalCount === 1 ? "Product" : "Products"}
457
+ </span>
458
+ {appliedFilters.length > 0 ? (
398
459
  <LocalizedLink
399
460
  href={buildClearAllFiltersHref(storeSearchParams)}
400
- className="animation-shop-grid__applied-clear"
461
+ className="text-xs font-bold text-red-600 hover:text-red-700 uppercase tracking-wider underline ml-2"
401
462
  countryCode={countryCode}
402
463
  >
403
- Clear all
464
+ Clear All
404
465
  </LocalizedLink>
405
- </motion.div>
406
- ) : null}
407
- </AnimatePresence>
408
-
409
- {totalCount > 0 && sortBy !== "bestsellers" ? (
410
- <div className="animation-shop-grid__progress" aria-hidden>
411
- <span className="animation-shop-grid__progress-fill" style={{ width: `${progressPct}%` }} />
466
+ ) : null}
412
467
  </div>
413
- ) : null}
468
+ </div>
414
469
 
415
470
  {items.length === 0 ? (
416
471
  <motion.div
@@ -437,7 +492,7 @@ export default function AnimationShopGrid(props: {
437
492
  <ProductCard
438
493
  product={product}
439
494
  countryCode={countryCode}
440
- variant={props.productCardVariant ?? "motion"}
495
+ variant={props.productCardVariant ?? "shop"}
441
496
  />
442
497
  </motion.div>
443
498
  ))}
@@ -451,6 +506,46 @@ export default function AnimationShopGrid(props: {
451
506
  </div>
452
507
  ) : null}
453
508
 
509
+ {/* Managed Pagination: Only show page buttons if totalPages > 1 */}
510
+ {Math.ceil(totalCount / PAGE_SIZE) > 1 ? (
511
+ <div className="animation-shop-grid__pagination flex items-center justify-center gap-2 mt-8 mb-2">
512
+ {Array.from({ length: Math.ceil(totalCount / PAGE_SIZE) }, (_, i) => i + 1).map((pageNum) => (
513
+ <button
514
+ key={pageNum}
515
+ type="button"
516
+ onClick={() => {
517
+ if (pageNum > currentPage && hasMore) {
518
+ loadNextPage()
519
+ }
520
+ }}
521
+ className={`w-8 h-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all ${
522
+ currentPage === pageNum
523
+ ? "bg-black text-white shadow-sm font-bold scale-105"
524
+ : "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-black"
525
+ }`}
526
+ >
527
+ {pageNum}
528
+ </button>
529
+ ))}
530
+ {hasMore && (
531
+ <button
532
+ type="button"
533
+ onClick={loadNextPage}
534
+ className="w-8 h-8 rounded-full bg-black hover:bg-gray-800 text-white flex items-center justify-center transition-all hover:scale-105 shadow-sm ml-1"
535
+ aria-label="Next page"
536
+ >
537
+ <svg className="w-3.5 h-3.5 fill-current" viewBox="0 0 20 20">
538
+ <path
539
+ fillRule="evenodd"
540
+ d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
541
+ clipRule="evenodd"
542
+ />
543
+ </svg>
544
+ </button>
545
+ )}
546
+ </div>
547
+ ) : null}
548
+
454
549
  {loadError ? (
455
550
  <div className="animation-shop-grid__error">
456
551
  <p>{loadError}</p>
@@ -461,7 +556,7 @@ export default function AnimationShopGrid(props: {
461
556
  ) : null}
462
557
 
463
558
  {!hasMore && items.length > 0 ? (
464
- <p className="animation-shop-grid__end">
559
+ <p className="animation-shop-grid__end text-xs tracking-wider text-gray-500 uppercase font-medium mt-4">
465
560
  You&apos;ve viewed all {totalCount} {totalCount === 1 ? "product" : "products"}
466
561
  </p>
467
562
  ) : null}
package/src/manifest.ts CHANGED
@@ -3,7 +3,7 @@ import type { SegmentManifest } from "@pradip1995/plugin-sdk"
3
3
  const manifest: SegmentManifest = {
4
4
  id: "animation-shop-grid",
5
5
  type: "segment",
6
- version: "0.1.0",
6
+ version: "0.1.1",
7
7
  compatibleFramework: ["^1.0.0"],
8
8
  dataKey: "store",
9
9
  }