@pradip1995/segment-product-card 0.3.12 → 0.3.14

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-product-card",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,10 +17,6 @@
17
17
  "./product-scroll": "./src/product-scroll.tsx",
18
18
  "./product-rail-variants": "./src/product-rail-variants.tsx"
19
19
  },
20
- "scripts": {
21
- "typecheck": "tsc --noEmit",
22
- "lint": "tsc --noEmit"
23
- },
24
20
  "peerDependencies": {
25
21
  "@pradip1995/commerce-core": "^4.0.0",
26
22
  "@pradip1995/plugin-sdk": "^0.2.0",
@@ -37,5 +33,9 @@
37
33
  "@types/react": "^19",
38
34
  "react": "19.0.3",
39
35
  "typescript": "^5.7.2"
36
+ },
37
+ "scripts": {
38
+ "typecheck": "tsc --noEmit",
39
+ "lint": "tsc --noEmit"
40
40
  }
41
- }
41
+ }
@@ -1,5 +1,6 @@
1
1
  "use client"
2
2
 
3
+ import Image from "next/image"
3
4
  import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
4
5
  import { formatPrice } from "@pradip1995/segment-primitives/format-price"
5
6
  import type { HttpTypes } from "@medusajs/types"
@@ -57,10 +58,13 @@ export default function BeautyProductCard({
57
58
  >
58
59
  <div className="beauty-product-card__media relative aspect-square bg-[var(--color-surface-muted)] overflow-hidden">
59
60
  {product.thumbnail?.trim() ? (
60
- <img
61
+ <Image
61
62
  src={product.thumbnail.trim()}
62
63
  alt={product.title || ""}
63
- className="beauty-product-card__image w-full h-full object-cover"
64
+ fill
65
+ sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
66
+ className="beauty-product-card__image object-cover"
67
+ loading="lazy"
64
68
  />
65
69
  ) : (
66
70
  <div className="beauty-product-card__placeholder w-full h-full" />
@@ -1,5 +1,6 @@
1
1
  "use client"
2
2
 
3
+ import Image from "next/image"
3
4
  import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
4
5
  import { formatPrice } from "@pradip1995/segment-primitives/format-price"
5
6
  import type { HttpTypes } from "@medusajs/types"
@@ -44,10 +45,13 @@ export default function DefaultProductCard({
44
45
  <LocalizedLink href={`/products/${product.handle}`} className="product-card__link flex flex-col h-full">
45
46
  <div className="product-card__media relative aspect-[3/4] bg-surface-muted overflow-hidden rounded-lg">
46
47
  {product.thumbnail?.trim() ? (
47
- <img
48
+ <Image
48
49
  src={product.thumbnail.trim()}
49
50
  alt={product.title || ""}
50
- className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
51
+ fill
52
+ sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
53
+ className="object-cover group-hover:scale-105 transition-transform duration-300"
54
+ loading="lazy"
51
55
  />
52
56
  ) : (
53
57
  <div className="w-full h-full bg-surface-muted" />
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { default } from "./segment"
2
+ export type { ProductCardVariant } from "./segment"
2
3
  export { default as manifest } from "./manifest"
@@ -0,0 +1,91 @@
1
+ "use client"
2
+
3
+ import Image from "next/image"
4
+ import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
5
+ import { formatPrice } from "@pradip1995/segment-primitives/format-price"
6
+ import type { HttpTypes } from "@medusajs/types"
7
+ import ProductCardActions from "./product-card-actions"
8
+ import { resolveProductCardImages } from "./resolve-product-images"
9
+
10
+ type Props = {
11
+ product: HttpTypes.StoreProduct
12
+ region?: HttpTypes.StoreRegion
13
+ countryCode?: string
14
+ wishlistProductIds?: string[]
15
+ }
16
+
17
+ export default function JewelryProductCard({
18
+ product,
19
+ region,
20
+ countryCode: countryCodeProp,
21
+ wishlistProductIds,
22
+ }: Props) {
23
+ const variant = product.variants?.[0]
24
+ const price = variant?.calculated_price?.calculated_amount
25
+ const currency = region?.currency_code || variant?.calculated_price?.currency_code || "inr"
26
+ const countryCode =
27
+ countryCodeProp?.toLowerCase() ||
28
+ region?.countries?.[0]?.iso_2?.toLowerCase() ||
29
+ "in"
30
+
31
+ const { primary, hover } = resolveProductCardImages(product)
32
+
33
+ return (
34
+ <article
35
+ data-product-card-id={product.id}
36
+ className="jewelry-product-card group flex flex-col w-full h-full relative"
37
+ >
38
+ <LocalizedLink
39
+ href={`/products/${product.handle}`}
40
+ className="jewelry-product-card__link flex flex-col h-full"
41
+ >
42
+ <div
43
+ className={`jewelry-product-card__media relative aspect-square overflow-hidden rounded-2xl bg-[var(--color-surface-muted)]${
44
+ hover ? " jewelry-product-card__media--has-hover" : ""
45
+ }`}
46
+ >
47
+ {primary ? (
48
+ <>
49
+ <Image
50
+ src={primary}
51
+ alt={product.title || ""}
52
+ fill
53
+ sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
54
+ className="jewelry-product-card__image jewelry-product-card__image--primary"
55
+ loading="lazy"
56
+ />
57
+ {hover ? (
58
+ <Image
59
+ src={hover}
60
+ alt=""
61
+ aria-hidden
62
+ fill
63
+ sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
64
+ className="jewelry-product-card__image jewelry-product-card__image--hover"
65
+ loading="lazy"
66
+ />
67
+ ) : null}
68
+ </>
69
+ ) : (
70
+ <div className="jewelry-product-card__placeholder w-full h-full" />
71
+ )}
72
+
73
+ {product.id ? (
74
+ <ProductCardActions
75
+ product={product}
76
+ countryCode={countryCode}
77
+ wishlistProductIds={wishlistProductIds}
78
+ />
79
+ ) : null}
80
+ </div>
81
+
82
+ <div className="jewelry-product-card__body">
83
+ <h3 className="jewelry-product-card__title">{product.title}</h3>
84
+ {price != null ? (
85
+ <p className="jewelry-product-card__price">{formatPrice(price, currency)}</p>
86
+ ) : null}
87
+ </div>
88
+ </LocalizedLink>
89
+ </article>
90
+ )
91
+ }
@@ -1,6 +1,6 @@
1
1
  "use client"
2
2
 
3
- import ProductCard from "./segment"
3
+ import ProductCard, { type ProductCardVariant } from "./segment"
4
4
  import ProductScroll from "./product-scroll"
5
5
  import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
6
6
  import type { ProductRailVariant } from "@pradip1995/segment-primitives/section-variants"
@@ -30,11 +30,13 @@ function ProductRailGrid({
30
30
  region,
31
31
  countryCode,
32
32
  wishlistProductIds,
33
+ productCardVariant,
33
34
  }: {
34
35
  products: HttpTypes.StoreProduct[]
35
36
  region?: HttpTypes.StoreRegion
36
37
  countryCode?: string
37
38
  wishlistProductIds?: string[]
39
+ productCardVariant?: ProductCardVariant
38
40
  }) {
39
41
  return (
40
42
  <div className="product-rail__grid grid grid-cols-2 md:grid-cols-4 gap-4 md:gap-6">
@@ -44,6 +46,7 @@ function ProductRailGrid({
44
46
  product={p}
45
47
  region={region}
46
48
  countryCode={countryCode}
49
+ variant={productCardVariant}
47
50
  wishlistProductIds={wishlistProductIds}
48
51
  />
49
52
  ))}
@@ -56,11 +59,13 @@ function ProductRailFeatured({
56
59
  region,
57
60
  countryCode,
58
61
  wishlistProductIds,
62
+ productCardVariant,
59
63
  }: {
60
64
  products: HttpTypes.StoreProduct[]
61
65
  region?: HttpTypes.StoreRegion
62
66
  countryCode?: string
63
67
  wishlistProductIds?: string[]
68
+ productCardVariant?: ProductCardVariant
64
69
  }) {
65
70
  const [featured, ...rest] = products
66
71
  if (!featured) return null
@@ -72,6 +77,7 @@ function ProductRailFeatured({
72
77
  product={featured}
73
78
  region={region}
74
79
  countryCode={countryCode}
80
+ variant={productCardVariant}
75
81
  wishlistProductIds={wishlistProductIds}
76
82
  />
77
83
  </div>
@@ -82,6 +88,7 @@ function ProductRailFeatured({
82
88
  product={p}
83
89
  region={region}
84
90
  countryCode={countryCode}
91
+ variant={productCardVariant}
85
92
  wishlistProductIds={wishlistProductIds}
86
93
  />
87
94
  ))}
@@ -100,6 +107,7 @@ export function ProductRailByVariant({
100
107
  viewAllLink,
101
108
  href,
102
109
  wishlistProductIds,
110
+ productCardVariant,
103
111
  }: {
104
112
  variant: ProductRailVariant
105
113
  products: HttpTypes.StoreProduct[]
@@ -110,6 +118,7 @@ export function ProductRailByVariant({
110
118
  viewAllLink: string
111
119
  href?: string
112
120
  wishlistProductIds?: string[]
121
+ productCardVariant?: ProductCardVariant
113
122
  }) {
114
123
  return (
115
124
  <>
@@ -121,6 +130,7 @@ export function ProductRailByVariant({
121
130
  ratings={ratings}
122
131
  countryCode={countryCode}
123
132
  wishlistProductIds={wishlistProductIds}
133
+ productCardVariant={productCardVariant}
124
134
  />
125
135
  ) : variant === "featured" ? (
126
136
  <ProductRailFeatured
@@ -128,6 +138,7 @@ export function ProductRailByVariant({
128
138
  region={region}
129
139
  countryCode={countryCode}
130
140
  wishlistProductIds={wishlistProductIds}
141
+ productCardVariant={productCardVariant}
131
142
  />
132
143
  ) : (
133
144
  <ProductRailGrid
@@ -135,6 +146,7 @@ export function ProductRailByVariant({
135
146
  region={region}
136
147
  countryCode={countryCode}
137
148
  wishlistProductIds={wishlistProductIds}
149
+ productCardVariant={productCardVariant}
138
150
  />
139
151
  )}
140
152
  </>
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import { useRef } from "react"
4
- import ProductCard from "./segment"
4
+ import ProductCard, { type ProductCardVariant } from "./segment"
5
5
  import type { HttpTypes } from "@medusajs/types"
6
6
 
7
7
  type ProductScrollProps = {
@@ -10,6 +10,7 @@ type ProductScrollProps = {
10
10
  ratings?: Array<{ product_id?: string; rating?: number }>
11
11
  countryCode?: string
12
12
  wishlistProductIds?: string[]
13
+ productCardVariant?: ProductCardVariant
13
14
  }
14
15
 
15
16
  export default function ProductScroll({
@@ -18,6 +19,7 @@ export default function ProductScroll({
18
19
  ratings,
19
20
  countryCode,
20
21
  wishlistProductIds,
22
+ productCardVariant,
21
23
  }: ProductScrollProps) {
22
24
  const viewportRef = useRef<HTMLDivElement>(null)
23
25
 
@@ -57,6 +59,7 @@ export default function ProductScroll({
57
59
  product={product}
58
60
  region={region ?? undefined}
59
61
  countryCode={countryCode}
62
+ variant={productCardVariant}
60
63
  rating={ratings?.find((r) => r.product_id === product.id)}
61
64
  wishlistProductIds={wishlistProductIds}
62
65
  />
@@ -0,0 +1,15 @@
1
+ import type { HttpTypes } from "@medusajs/types"
2
+ import { resolveMediaUrl } from "@pradip1995/segment-primitives/resolve-media-url"
3
+
4
+ export function resolveProductCardImages(product: HttpTypes.StoreProduct) {
5
+ const images =
6
+ product.images
7
+ ?.map((image) => resolveMediaUrl(image.url))
8
+ .filter((url): url is string => !!url) ?? []
9
+
10
+ const thumbnail = resolveMediaUrl(product.thumbnail) || ""
11
+ const primary = thumbnail || images[0] || ""
12
+ const hover = images.find((url) => url && url !== primary) || ""
13
+
14
+ return { primary, hover }
15
+ }
package/src/segment.css CHANGED
@@ -223,3 +223,151 @@
223
223
  pointer-events: auto;
224
224
  }
225
225
  }
226
+
227
+ /* Jewelry product card — H.Ankit / premium catalogue */
228
+
229
+ .jewelry-product-card {
230
+ background: transparent;
231
+ }
232
+
233
+ .jewelry-product-card__link {
234
+ min-width: 0;
235
+ text-decoration: none;
236
+ color: inherit;
237
+ }
238
+
239
+ .jewelry-product-card__media {
240
+ position: relative;
241
+ }
242
+
243
+ .jewelry-product-card__image {
244
+ position: absolute;
245
+ inset: 0;
246
+ width: 100%;
247
+ height: 100%;
248
+ object-fit: cover;
249
+ transition: opacity 0.4s ease;
250
+ }
251
+
252
+ .jewelry-product-card__image--hover {
253
+ opacity: 0;
254
+ }
255
+
256
+ .jewelry-product-card:hover .jewelry-product-card__image--hover {
257
+ opacity: 1;
258
+ }
259
+
260
+ .jewelry-product-card__media--has-hover:hover .jewelry-product-card__image--primary {
261
+ opacity: 0;
262
+ }
263
+
264
+ .jewelry-product-card__placeholder {
265
+ background: linear-gradient(135deg, var(--color-surface-muted) 0%, var(--color-surface-alt) 100%);
266
+ }
267
+
268
+ .jewelry-product-card .product-card__actions {
269
+ display: none;
270
+ }
271
+
272
+ .jewelry-product-card .product-card__quick-add-wrap {
273
+ left: 0;
274
+ right: 0;
275
+ bottom: 0;
276
+ top: auto;
277
+ opacity: 0;
278
+ transform: translateY(100%);
279
+ pointer-events: none;
280
+ transition: opacity 0.25s ease, transform 0.25s ease;
281
+ }
282
+
283
+ .jewelry-product-card:hover .product-card__quick-add-wrap {
284
+ opacity: 1;
285
+ transform: translateY(0);
286
+ pointer-events: auto;
287
+ }
288
+
289
+ .jewelry-product-card .product-card__quick-add {
290
+ width: 100%;
291
+ border-radius: 0;
292
+ background: rgba(26, 26, 26, 0.88);
293
+ color: var(--color-text-inverse);
294
+ letter-spacing: 0.08em;
295
+ text-transform: uppercase;
296
+ font-size: 0.6875rem;
297
+ font-weight: 600;
298
+ min-height: 2.75rem;
299
+ backdrop-filter: blur(4px);
300
+ }
301
+
302
+ .jewelry-product-card .product-card__quick-add:hover:not(:disabled) {
303
+ background: var(--color-brand-primary);
304
+ }
305
+
306
+ .jewelry-product-card .product-card__quick-add-label {
307
+ font-size: 0;
308
+ }
309
+
310
+ .jewelry-product-card .product-card__quick-add-label::after {
311
+ content: "Add to cart";
312
+ font-size: 0.6875rem;
313
+ }
314
+
315
+ .jewelry-product-card__body {
316
+ display: flex;
317
+ flex-direction: column;
318
+ align-items: center;
319
+ gap: 0.5rem;
320
+ padding: 1rem 0.25rem 0;
321
+ text-align: center;
322
+ min-width: 0;
323
+ }
324
+
325
+ .jewelry-product-card__title {
326
+ margin: 0;
327
+ font-family: var(--font-heading);
328
+ font-size: 0.875rem;
329
+ font-weight: 400;
330
+ line-height: 1.45;
331
+ color: var(--color-text-body);
332
+ display: -webkit-box;
333
+ -webkit-line-clamp: 2;
334
+ -webkit-box-orient: vertical;
335
+ overflow: hidden;
336
+ min-height: 2.5rem;
337
+ }
338
+
339
+ .jewelry-product-card__price {
340
+ margin: 0;
341
+ font-size: 0.9375rem;
342
+ font-weight: 700;
343
+ color: var(--color-text-heading);
344
+ }
345
+
346
+ .product-grid .jewelry-product-card,
347
+ .product-rail__grid .jewelry-product-card,
348
+ .beauty-shop-grid__grid .jewelry-product-card {
349
+ height: 100%;
350
+ }
351
+
352
+ .product-scroll__slide:has(.jewelry-product-card) {
353
+ flex: 0 0 clamp(210px, 38vw, 280px);
354
+ }
355
+
356
+ @media (min-width: 768px) {
357
+ .product-scroll__slide:has(.jewelry-product-card) {
358
+ flex: 0 0 clamp(240px, 22vw, 280px);
359
+ }
360
+ }
361
+
362
+ @media (prefers-reduced-motion: reduce) {
363
+ .jewelry-product-card__image,
364
+ .jewelry-product-card .product-card__quick-add-wrap {
365
+ transition: none;
366
+ }
367
+
368
+ .jewelry-product-card .product-card__quick-add-wrap {
369
+ opacity: 1;
370
+ transform: none;
371
+ pointer-events: auto;
372
+ }
373
+ }
package/src/segment.tsx CHANGED
@@ -4,8 +4,9 @@ import "./segment.css"
4
4
  import type { HttpTypes } from "@medusajs/types"
5
5
  import BeautyProductCard from "./beauty-product-card"
6
6
  import DefaultProductCard from "./default-product-card"
7
+ import JewelryProductCard from "./jewelry-product-card"
7
8
 
8
- export type ProductCardVariant = "default" | "beauty"
9
+ export type ProductCardVariant = "default" | "beauty" | "jewelry"
9
10
 
10
11
  type Props = {
11
12
  product: HttpTypes.StoreProduct
@@ -18,12 +19,13 @@ type Props = {
18
19
  }
19
20
 
20
21
  function resolveProductCardVariant(variant?: ProductCardVariant): ProductCardVariant {
21
- if (variant === "beauty" || variant === "default") return variant
22
+ if (variant === "beauty" || variant === "default" || variant === "jewelry") return variant
22
23
  return "default"
23
24
  }
24
25
 
25
26
  export default function ProductCard({ variant, ...props }: Props) {
26
27
  const layout = resolveProductCardVariant(variant)
27
28
  if (layout === "beauty") return <BeautyProductCard {...props} />
29
+ if (layout === "jewelry") return <JewelryProductCard {...props} />
28
30
  return <DefaultProductCard {...props} />
29
31
  }