@pradip1995/segment-animation-shop-grid 0.1.2 → 0.1.4
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 +8 -8
- package/src/animation-shop-grid.css +2 -2
- package/src/animation-shop-grid.tsx +172 -76
- package/src/manifest.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-animation-shop-grid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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",
|
|
@@ -28,9 +24,9 @@
|
|
|
28
24
|
"next": ">=15"
|
|
29
25
|
},
|
|
30
26
|
"dependencies": {
|
|
31
|
-
"@pradip1995/segment-primitives": "^0.4.
|
|
32
|
-
"@pradip1995/segment-product-card": "^0.3.
|
|
33
|
-
"@pradip1995/segment-tokens": "^0.3.
|
|
27
|
+
"@pradip1995/segment-primitives": "^0.4.11",
|
|
28
|
+
"@pradip1995/segment-product-card": "^0.3.26",
|
|
29
|
+
"@pradip1995/segment-tokens": "^0.3.14",
|
|
34
30
|
"framer-motion": "^12.0.0"
|
|
35
31
|
},
|
|
36
32
|
"devDependencies": {
|
|
@@ -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:
|
|
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:
|
|
12
|
+
padding: 0 3rem 6rem 3rem;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -9,6 +9,7 @@ import { loadMoreStoreProducts, type StoreLoadMoreFilters } from "./load-more-pr
|
|
|
9
9
|
import "./animation-shop-grid.css"
|
|
10
10
|
|
|
11
11
|
const PAGE_SIZE = 12
|
|
12
|
+
const EMPTY_PRODUCTS: HttpTypes.StoreProduct[] = []
|
|
12
13
|
|
|
13
14
|
const SORT_OPTIONS = [
|
|
14
15
|
{ value: "created_at_desc", label: "Newest" },
|
|
@@ -119,6 +120,20 @@ function matchLabel(
|
|
|
119
120
|
return hit?.name || hit?.title || undefined
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
function formatSlugToTitle(slug?: string): string | undefined {
|
|
124
|
+
if (!slug) return undefined
|
|
125
|
+
if (slug.includes(",")) {
|
|
126
|
+
return slug
|
|
127
|
+
.split(",")
|
|
128
|
+
.map((s) => formatSlugToTitle(s))
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join(" & ")
|
|
131
|
+
}
|
|
132
|
+
return slug
|
|
133
|
+
.replace(/[-_]+/g, " ")
|
|
134
|
+
.replace(/\b\w/g, (char) => char.toUpperCase())
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
const containerVariants = {
|
|
123
138
|
hidden: { opacity: 0 },
|
|
124
139
|
visible: {
|
|
@@ -135,7 +150,7 @@ const cardVariants = {
|
|
|
135
150
|
visible: {
|
|
136
151
|
opacity: 1,
|
|
137
152
|
y: 0,
|
|
138
|
-
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] },
|
|
153
|
+
transition: { duration: 0.4, ease: [0.16, 1, 0.3, 1] as const },
|
|
139
154
|
},
|
|
140
155
|
}
|
|
141
156
|
|
|
@@ -226,7 +241,7 @@ export default function AnimationShopGrid(props: {
|
|
|
226
241
|
props.maxPrice,
|
|
227
242
|
]
|
|
228
243
|
)
|
|
229
|
-
const initialProducts = props.products ??
|
|
244
|
+
const initialProducts = props.products ?? EMPTY_PRODUCTS
|
|
230
245
|
const totalCount = props.productCount ?? initialProducts.length
|
|
231
246
|
|
|
232
247
|
const [items, setItems] = useState(initialProducts)
|
|
@@ -241,12 +256,12 @@ export default function AnimationShopGrid(props: {
|
|
|
241
256
|
const loadingRef = useRef(false)
|
|
242
257
|
|
|
243
258
|
useEffect(() => {
|
|
244
|
-
setItems(props.products ??
|
|
259
|
+
setItems(props.products ?? EMPTY_PRODUCTS)
|
|
245
260
|
setCurrentPage(initialPage)
|
|
246
261
|
setHasMore(sortBy !== "bestsellers" && totalCount > initialPage * PAGE_SIZE)
|
|
247
262
|
setLoadError(null)
|
|
248
263
|
loadingRef.current = false
|
|
249
|
-
}, [filterSignature, initialPage, sortBy, totalCount])
|
|
264
|
+
}, [filterSignature, initialPage, sortBy, totalCount, props.products])
|
|
250
265
|
|
|
251
266
|
const loadNextPage = useCallback(() => {
|
|
252
267
|
if (loadingRef.current || pending || !hasMore || sortBy === "bestsellers") return
|
|
@@ -297,12 +312,23 @@ export default function AnimationShopGrid(props: {
|
|
|
297
312
|
|
|
298
313
|
const activeCategory = paramValue(props.category)
|
|
299
314
|
const activeCollection = paramValue(props.collection)
|
|
315
|
+
const activeGender = paramValue(props.gender)
|
|
316
|
+
const activeMaterial = paramValue(props.material)
|
|
317
|
+
const activeColor = paramValue(props.color)
|
|
318
|
+
const activeType = paramValue(props.type)
|
|
319
|
+
|
|
300
320
|
const categoryLabel =
|
|
301
321
|
matchLabel(activeCategory, props.categories || []) ||
|
|
302
|
-
(activeCategory
|
|
322
|
+
formatSlugToTitle(activeCategory)
|
|
323
|
+
|
|
303
324
|
const collectionLabel =
|
|
304
325
|
matchLabel(activeCollection, props.collections || []) ||
|
|
305
|
-
(activeCollection
|
|
326
|
+
formatSlugToTitle(activeCollection)
|
|
327
|
+
|
|
328
|
+
const genderLabel = formatSlugToTitle(activeGender)
|
|
329
|
+
const materialLabel = formatSlugToTitle(activeMaterial)
|
|
330
|
+
const colorLabel = formatSlugToTitle(activeColor)
|
|
331
|
+
const typeLabel = formatSlugToTitle(activeType)
|
|
306
332
|
|
|
307
333
|
const appliedFilters: { key: string; label: string; href: string }[] = []
|
|
308
334
|
if (props.q) {
|
|
@@ -327,90 +353,120 @@ export default function AnimationShopGrid(props: {
|
|
|
327
353
|
})
|
|
328
354
|
}
|
|
329
355
|
|
|
356
|
+
const displayTitle = useMemo(() => {
|
|
357
|
+
if (categoryLabel) return categoryLabel
|
|
358
|
+
if (collectionLabel) return collectionLabel
|
|
359
|
+
if (props.q) return `Search: ${props.q}`
|
|
360
|
+
if (genderLabel) return `${genderLabel}'s Collection`
|
|
361
|
+
if (materialLabel) return materialLabel
|
|
362
|
+
if (colorLabel) return colorLabel
|
|
363
|
+
if (typeLabel) return typeLabel
|
|
364
|
+
if (sortBy === "bestsellers") return "Bestsellers"
|
|
365
|
+
if (!props.title || props.title.toLowerCase() === "all beauty" || props.title.toLowerCase() === "all products") {
|
|
366
|
+
return "ALL PRODUCTS"
|
|
367
|
+
}
|
|
368
|
+
return props.title
|
|
369
|
+
}, [categoryLabel, collectionLabel, props.q, genderLabel, materialLabel, colorLabel, typeLabel, sortBy, props.title])
|
|
370
|
+
|
|
371
|
+
const displaySubtitle = useMemo(() => {
|
|
372
|
+
if (categoryLabel) return `Explore our curated selection of ${categoryLabel.toLowerCase()}.`
|
|
373
|
+
if (collectionLabel) return `Discover the latest items in our ${collectionLabel.toLowerCase()} collection.`
|
|
374
|
+
if (props.q) return `Product results matching "${props.q}".`
|
|
375
|
+
if (sortBy === "bestsellers") return "Our most popular and highly-rated pieces."
|
|
376
|
+
if (genderLabel || materialLabel || colorLabel) return "Handpicked items filtered to your taste."
|
|
377
|
+
return "The latest additions to the lineup."
|
|
378
|
+
}, [categoryLabel, collectionLabel, props.q, sortBy, genderLabel, materialLabel, colorLabel])
|
|
379
|
+
|
|
380
|
+
const [showFilters, setShowFilters] = useState(true)
|
|
381
|
+
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
const sidebar = document.querySelector(".store-page__sidebar") as HTMLElement | null
|
|
384
|
+
const mainCol = document.querySelector(".store-page__main") as HTMLElement | null
|
|
385
|
+
if (sidebar) {
|
|
386
|
+
sidebar.style.display = showFilters ? "" : "none"
|
|
387
|
+
}
|
|
388
|
+
if (mainCol) {
|
|
389
|
+
if (!showFilters) {
|
|
390
|
+
mainCol.classList.add("w-full", "col-span-full")
|
|
391
|
+
} else {
|
|
392
|
+
mainCol.classList.remove("w-full", "col-span-full")
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}, [showFilters])
|
|
396
|
+
|
|
330
397
|
return (
|
|
331
|
-
<section className="animation-shop-grid flex-1 min-w-0" aria-label="Product results">
|
|
398
|
+
<section className="animation-shop-grid flex-1 min-w-0 pt-0" aria-label="Product results">
|
|
399
|
+
{/* Simplified, Clean Hero Header */}
|
|
332
400
|
<motion.header
|
|
333
|
-
className="
|
|
334
|
-
initial={{ opacity: 0, y: -
|
|
401
|
+
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"
|
|
402
|
+
initial={{ opacity: 0, y: -6 }}
|
|
335
403
|
animate={{ opacity: 1, y: 0 }}
|
|
336
|
-
transition={{ duration: 0.
|
|
404
|
+
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
|
|
337
405
|
>
|
|
338
|
-
<
|
|
339
|
-
<
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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>
|
|
406
|
+
<nav aria-label="Breadcrumb" className="text-[11px] text-gray-400 font-sans tracking-wide mb-1.5 uppercase">
|
|
407
|
+
<LocalizedLink href="/" className="hover:text-black transition-colors" countryCode={countryCode}>
|
|
408
|
+
Home
|
|
409
|
+
</LocalizedLink>
|
|
410
|
+
<span className="mx-2 text-gray-300">/</span>
|
|
411
|
+
<span className="text-gray-900 font-semibold tracking-wider">{displayTitle}</span>
|
|
412
|
+
</nav>
|
|
352
413
|
|
|
353
|
-
<
|
|
354
|
-
{
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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>
|
|
414
|
+
<h1 className="text-xl sm:text-2xl font-normal tracking-widest text-[#111111] uppercase my-1">
|
|
415
|
+
{displayTitle}
|
|
416
|
+
</h1>
|
|
417
|
+
|
|
418
|
+
<p className="text-xs text-gray-500 font-normal tracking-wide max-w-sm m-0">
|
|
419
|
+
{displaySubtitle}
|
|
420
|
+
</p>
|
|
368
421
|
</motion.header>
|
|
369
422
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
animate={{ opacity: 1, height: "auto" }}
|
|
378
|
-
exit={{ opacity: 0, height: 0 }}
|
|
423
|
+
{/* Filter Toolbar with Hide Filters Option */}
|
|
424
|
+
<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">
|
|
425
|
+
<div className="flex items-center gap-3">
|
|
426
|
+
<button
|
|
427
|
+
type="button"
|
|
428
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
429
|
+
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
430
|
>
|
|
380
|
-
<
|
|
381
|
-
|
|
431
|
+
<svg className="w-3.5 h-3.5 fill-none stroke-current stroke-[1.8]" viewBox="0 0 24 24">
|
|
432
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6h9m-9 12h9m-15-6h15M3 6h3m-3 12h3m3-6h3" />
|
|
433
|
+
</svg>
|
|
434
|
+
<span>{showFilters ? "Hide Filters" : "Show Filters"}</span>
|
|
435
|
+
</button>
|
|
436
|
+
|
|
437
|
+
{appliedFilters.length > 0 ? (
|
|
438
|
+
<div className="hidden sm:flex items-center gap-2">
|
|
439
|
+
<span className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Active:</span>
|
|
382
440
|
{appliedFilters.map((filter) => (
|
|
383
|
-
<
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
>
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
×
|
|
393
|
-
</span>
|
|
394
|
-
</LocalizedLink>
|
|
395
|
-
</li>
|
|
441
|
+
<LocalizedLink
|
|
442
|
+
key={filter.key}
|
|
443
|
+
href={filter.href}
|
|
444
|
+
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"
|
|
445
|
+
countryCode={countryCode}
|
|
446
|
+
>
|
|
447
|
+
<span>{filter.label}</span>
|
|
448
|
+
<span className="text-gray-500 hover:text-black">×</span>
|
|
449
|
+
</LocalizedLink>
|
|
396
450
|
))}
|
|
397
|
-
</
|
|
451
|
+
</div>
|
|
452
|
+
) : null}
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div className="flex items-center gap-3 ml-auto">
|
|
456
|
+
<span className="text-xs text-gray-500 font-medium tracking-wide">
|
|
457
|
+
{showingCount} of {totalCount} {totalCount === 1 ? "Product" : "Products"}
|
|
458
|
+
</span>
|
|
459
|
+
{appliedFilters.length > 0 ? (
|
|
398
460
|
<LocalizedLink
|
|
399
461
|
href={buildClearAllFiltersHref(storeSearchParams)}
|
|
400
|
-
className="
|
|
462
|
+
className="text-xs font-bold text-red-600 hover:text-red-700 uppercase tracking-wider underline ml-2"
|
|
401
463
|
countryCode={countryCode}
|
|
402
464
|
>
|
|
403
|
-
Clear
|
|
465
|
+
Clear All
|
|
404
466
|
</LocalizedLink>
|
|
405
|
-
|
|
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}%` }} />
|
|
467
|
+
) : null}
|
|
412
468
|
</div>
|
|
413
|
-
|
|
469
|
+
</div>
|
|
414
470
|
|
|
415
471
|
{items.length === 0 ? (
|
|
416
472
|
<motion.div
|
|
@@ -437,7 +493,7 @@ export default function AnimationShopGrid(props: {
|
|
|
437
493
|
<ProductCard
|
|
438
494
|
product={product}
|
|
439
495
|
countryCode={countryCode}
|
|
440
|
-
variant={props.productCardVariant ?? "
|
|
496
|
+
variant={props.productCardVariant ?? "shop"}
|
|
441
497
|
/>
|
|
442
498
|
</motion.div>
|
|
443
499
|
))}
|
|
@@ -451,6 +507,46 @@ export default function AnimationShopGrid(props: {
|
|
|
451
507
|
</div>
|
|
452
508
|
) : null}
|
|
453
509
|
|
|
510
|
+
{/* Managed Pagination: Only show page buttons if totalPages > 1 */}
|
|
511
|
+
{Math.ceil(totalCount / PAGE_SIZE) > 1 ? (
|
|
512
|
+
<div className="animation-shop-grid__pagination flex items-center justify-center gap-2 mt-8 mb-2">
|
|
513
|
+
{Array.from({ length: Math.ceil(totalCount / PAGE_SIZE) }, (_, i) => i + 1).map((pageNum) => (
|
|
514
|
+
<button
|
|
515
|
+
key={pageNum}
|
|
516
|
+
type="button"
|
|
517
|
+
onClick={() => {
|
|
518
|
+
if (pageNum > currentPage && hasMore) {
|
|
519
|
+
loadNextPage()
|
|
520
|
+
}
|
|
521
|
+
}}
|
|
522
|
+
className={`w-8 h-8 rounded-full text-xs font-semibold flex items-center justify-center transition-all ${
|
|
523
|
+
currentPage === pageNum
|
|
524
|
+
? "bg-black text-white shadow-sm font-bold scale-105"
|
|
525
|
+
: "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-black"
|
|
526
|
+
}`}
|
|
527
|
+
>
|
|
528
|
+
{pageNum}
|
|
529
|
+
</button>
|
|
530
|
+
))}
|
|
531
|
+
{hasMore && (
|
|
532
|
+
<button
|
|
533
|
+
type="button"
|
|
534
|
+
onClick={loadNextPage}
|
|
535
|
+
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"
|
|
536
|
+
aria-label="Next page"
|
|
537
|
+
>
|
|
538
|
+
<svg className="w-3.5 h-3.5 fill-current" viewBox="0 0 20 20">
|
|
539
|
+
<path
|
|
540
|
+
fillRule="evenodd"
|
|
541
|
+
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"
|
|
542
|
+
clipRule="evenodd"
|
|
543
|
+
/>
|
|
544
|
+
</svg>
|
|
545
|
+
</button>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
) : null}
|
|
549
|
+
|
|
454
550
|
{loadError ? (
|
|
455
551
|
<div className="animation-shop-grid__error">
|
|
456
552
|
<p>{loadError}</p>
|
|
@@ -461,7 +557,7 @@ export default function AnimationShopGrid(props: {
|
|
|
461
557
|
) : null}
|
|
462
558
|
|
|
463
559
|
{!hasMore && items.length > 0 ? (
|
|
464
|
-
<p className="animation-shop-grid__end">
|
|
560
|
+
<p className="animation-shop-grid__end text-xs tracking-wider text-gray-500 uppercase font-medium mt-4">
|
|
465
561
|
You've viewed all {totalCount} {totalCount === 1 ? "product" : "products"}
|
|
466
562
|
</p>
|
|
467
563
|
) : null}
|
package/src/manifest.ts
CHANGED