@pradip1995/segment-product-card 0.3.24 → 0.3.26
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 +9 -8
- package/src/beauty-product-card.tsx +1 -1
- package/src/default-product-card.tsx +1 -1
- package/src/jewelry-product-card.tsx +5 -2
- package/src/product-card-actions-class.test.ts +67 -0
- package/src/product-card-actions-class.ts +16 -0
- package/src/product-card-actions.tsx +12 -3
- package/src/segment.css +20 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pradip1995/segment-product-card",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
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",
|
|
@@ -30,8 +26,8 @@
|
|
|
30
26
|
},
|
|
31
27
|
"dependencies": {
|
|
32
28
|
"@pradip1995/segment-login-popup": "^0.1.3",
|
|
33
|
-
"@pradip1995/segment-primitives": "^0.4.
|
|
34
|
-
"@pradip1995/segment-tokens": "^0.3.
|
|
29
|
+
"@pradip1995/segment-primitives": "^0.4.11",
|
|
30
|
+
"@pradip1995/segment-tokens": "^0.3.14"
|
|
35
31
|
},
|
|
36
32
|
"devDependencies": {
|
|
37
33
|
"@medusajs/types": "^2.0.0",
|
|
@@ -41,5 +37,10 @@
|
|
|
41
37
|
"next": ">=15",
|
|
42
38
|
"react": "19.0.3",
|
|
43
39
|
"typescript": "^5.7.2"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"lint": "tsc --noEmit",
|
|
44
|
+
"test": "node --experimental-strip-types --test src/*.test.ts"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|
|
@@ -61,7 +61,7 @@ export default function BeautyProductCard({
|
|
|
61
61
|
href={`/products/${product.handle}`}
|
|
62
62
|
className="beauty-product-card__link flex flex-col h-full"
|
|
63
63
|
>
|
|
64
|
-
<div className="beauty-product-card__media relative aspect-square bg-[var(--color-surface-muted)] overflow-hidden">
|
|
64
|
+
<div className="beauty-product-card__media group/media relative aspect-square bg-[var(--color-surface-muted)] overflow-hidden">
|
|
65
65
|
{product.thumbnail?.trim() ? (
|
|
66
66
|
<Image
|
|
67
67
|
src={product.thumbnail.trim()}
|
|
@@ -43,7 +43,7 @@ export default function DefaultProductCard({
|
|
|
43
43
|
className="product-card group flex flex-col w-full h-full relative"
|
|
44
44
|
>
|
|
45
45
|
<LocalizedLink href={`/products/${product.handle}`} className="product-card__link flex flex-col h-full">
|
|
46
|
-
<div className="product-card__media relative aspect-[3/4] bg-surface-muted overflow-hidden rounded-lg">
|
|
46
|
+
<div className="product-card__media group/media relative aspect-[3/4] bg-surface-muted overflow-hidden rounded-lg">
|
|
47
47
|
{product.thumbnail?.trim() ? (
|
|
48
48
|
<Image
|
|
49
49
|
src={product.thumbnail.trim()}
|
|
@@ -11,6 +11,7 @@ type Props = {
|
|
|
11
11
|
product: HttpTypes.StoreProduct
|
|
12
12
|
region?: HttpTypes.StoreRegion
|
|
13
13
|
countryCode?: string
|
|
14
|
+
showWishlist?: boolean
|
|
14
15
|
wishlistProductIds?: string[]
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -18,6 +19,7 @@ export default function JewelryProductCard({
|
|
|
18
19
|
product,
|
|
19
20
|
region,
|
|
20
21
|
countryCode: countryCodeProp,
|
|
22
|
+
showWishlist = true,
|
|
21
23
|
wishlistProductIds,
|
|
22
24
|
}: Props) {
|
|
23
25
|
const variant = product.variants?.[0]
|
|
@@ -40,7 +42,7 @@ export default function JewelryProductCard({
|
|
|
40
42
|
className="jewelry-product-card__link flex flex-col h-full"
|
|
41
43
|
>
|
|
42
44
|
<div
|
|
43
|
-
className={`jewelry-product-card__media relative aspect-square overflow-hidden rounded-2xl bg-[var(--color-surface-muted)]${
|
|
45
|
+
className={`jewelry-product-card__media group/media relative aspect-square overflow-hidden rounded-2xl bg-[var(--color-surface-muted)]${
|
|
44
46
|
hover ? " jewelry-product-card__media--has-hover" : ""
|
|
45
47
|
}`}
|
|
46
48
|
>
|
|
@@ -70,11 +72,12 @@ export default function JewelryProductCard({
|
|
|
70
72
|
<div className="jewelry-product-card__placeholder w-full h-full" />
|
|
71
73
|
)}
|
|
72
74
|
|
|
73
|
-
{product.id ? (
|
|
75
|
+
{showWishlist && product.id ? (
|
|
74
76
|
<ProductCardActions
|
|
75
77
|
product={product}
|
|
76
78
|
countryCode={countryCode}
|
|
77
79
|
wishlistProductIds={wishlistProductIds}
|
|
80
|
+
showWishlist={showWishlist}
|
|
78
81
|
/>
|
|
79
82
|
) : null}
|
|
80
83
|
</div>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it } from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import {
|
|
4
|
+
productCardActionsClass,
|
|
5
|
+
productCardWishlistClass,
|
|
6
|
+
} from "./product-card-actions-class.ts"
|
|
7
|
+
|
|
8
|
+
const WISHLIST_CASES = [
|
|
9
|
+
{
|
|
10
|
+
name: "wishlist wrap is visible without hover",
|
|
11
|
+
wantIncludes: ["product-card__wishlist-wrap", "top-3.5", "right-3.5", "pointer-events-auto"],
|
|
12
|
+
wantExcludes: ["opacity-0", "group-hover/media"],
|
|
13
|
+
},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const CART_CASES = [
|
|
17
|
+
{
|
|
18
|
+
name: "hover hides cart actions until named media group hover",
|
|
19
|
+
visibility: "hover" as const,
|
|
20
|
+
wantIncludes: [
|
|
21
|
+
"opacity-0",
|
|
22
|
+
"group-hover/media:opacity-100",
|
|
23
|
+
"group-hover/media:pointer-events-auto",
|
|
24
|
+
],
|
|
25
|
+
wantExcludes: ["top-3.5"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "always keeps cart actions visible",
|
|
29
|
+
visibility: "always" as const,
|
|
30
|
+
wantIncludes: ["top-3.5", "right-3.5", "pointer-events-auto"],
|
|
31
|
+
wantExcludes: ["opacity-0", "group-hover/media"],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "unknown visibility falls back to hover",
|
|
35
|
+
visibility: undefined,
|
|
36
|
+
wantIncludes: ["opacity-0", "group-hover/media:opacity-100"],
|
|
37
|
+
wantExcludes: ["top-3.5"],
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
describe("productCardWishlistClass", () => {
|
|
42
|
+
for (const tc of WISHLIST_CASES) {
|
|
43
|
+
it(tc.name, () => {
|
|
44
|
+
const className = productCardWishlistClass()
|
|
45
|
+
for (const snippet of tc.wantIncludes) {
|
|
46
|
+
assert.match(className, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))
|
|
47
|
+
}
|
|
48
|
+
for (const snippet of tc.wantExcludes) {
|
|
49
|
+
assert.doesNotMatch(className, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe("productCardActionsClass", () => {
|
|
56
|
+
for (const tc of CART_CASES) {
|
|
57
|
+
it(tc.name, () => {
|
|
58
|
+
const className = productCardActionsClass(tc.visibility)
|
|
59
|
+
for (const snippet of tc.wantIncludes) {
|
|
60
|
+
assert.match(className, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))
|
|
61
|
+
}
|
|
62
|
+
for (const snippet of tc.wantExcludes) {
|
|
63
|
+
assert.doesNotMatch(className, new RegExp(snippet.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ProductCardActionsVisibility = "hover" | "always"
|
|
2
|
+
|
|
3
|
+
export function productCardWishlistClass(): string {
|
|
4
|
+
return "product-card__wishlist-wrap absolute top-3.5 right-3.5 z-30 pointer-events-auto"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function productCardActionsClass(
|
|
8
|
+
visibility?: ProductCardActionsVisibility
|
|
9
|
+
): string {
|
|
10
|
+
const mode = visibility === "always" ? "always" : "hover"
|
|
11
|
+
const base = "product-card__actions absolute z-30"
|
|
12
|
+
if (mode === "always") {
|
|
13
|
+
return `${base} top-3.5 right-3.5 pointer-events-auto`
|
|
14
|
+
}
|
|
15
|
+
return `${base} bottom-3.5 right-3.5 opacity-0 group-hover/media:opacity-100 transition-opacity duration-300 pointer-events-none group-hover/media:pointer-events-auto`
|
|
16
|
+
}
|
|
@@ -12,12 +12,18 @@ import type { HttpTypes } from "@medusajs/types"
|
|
|
12
12
|
import { isNextRedirect } from "@pradip1995/segment-primitives/is-next-redirect"
|
|
13
13
|
import LoginRequiredModal from "@pradip1995/segment-login-popup/required"
|
|
14
14
|
import QuickAddPanel from "./quick-add-panel"
|
|
15
|
+
import {
|
|
16
|
+
productCardActionsClass,
|
|
17
|
+
productCardWishlistClass,
|
|
18
|
+
type ProductCardActionsVisibility,
|
|
19
|
+
} from "./product-card-actions-class"
|
|
15
20
|
|
|
16
21
|
type Props = {
|
|
17
22
|
product: HttpTypes.StoreProduct
|
|
18
23
|
countryCode: string
|
|
19
24
|
wishlistProductIds?: string[]
|
|
20
25
|
showWishlist?: boolean
|
|
26
|
+
actionsVisible?: ProductCardActionsVisibility
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
function findVariantForOptions(
|
|
@@ -44,6 +50,7 @@ export default function ProductCardActions({
|
|
|
44
50
|
countryCode,
|
|
45
51
|
wishlistProductIds,
|
|
46
52
|
showWishlist = true,
|
|
53
|
+
actionsVisible = "hover",
|
|
47
54
|
}: Props) {
|
|
48
55
|
const router = useRouter()
|
|
49
56
|
const [wishlisted, setWishlisted] = useState(false)
|
|
@@ -181,8 +188,8 @@ export default function ProductCardActions({
|
|
|
181
188
|
|
|
182
189
|
return (
|
|
183
190
|
<>
|
|
184
|
-
|
|
185
|
-
{
|
|
191
|
+
{showWishlist ? (
|
|
192
|
+
<div className={productCardWishlistClass()} onClick={stopCardNav}>
|
|
186
193
|
<button
|
|
187
194
|
type="button"
|
|
188
195
|
className={`product-card__wishlist-btn${wishlisted ? " product-card__wishlist-btn--active" : ""}${
|
|
@@ -200,8 +207,10 @@ export default function ProductCardActions({
|
|
|
200
207
|
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
|
201
208
|
</svg>
|
|
202
209
|
</button>
|
|
203
|
-
|
|
210
|
+
</div>
|
|
211
|
+
) : null}
|
|
204
212
|
|
|
213
|
+
<div className={productCardActionsClass(actionsVisible)} onClick={stopCardNav}>
|
|
205
214
|
<button
|
|
206
215
|
type="button"
|
|
207
216
|
className="product-card__cart-btn"
|
package/src/segment.css
CHANGED
|
@@ -58,6 +58,11 @@
|
|
|
58
58
|
right: 0.875rem;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
.beauty-product-card .product-card__wishlist-wrap {
|
|
62
|
+
top: 0.625rem;
|
|
63
|
+
right: 0.625rem;
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
.beauty-product-card .product-card__wishlist-btn,
|
|
62
67
|
.beauty-product-card .product-card__cart-btn {
|
|
63
68
|
width: 2.125rem;
|
|
@@ -304,10 +309,22 @@
|
|
|
304
309
|
background: linear-gradient(135deg, var(--color-surface-muted) 0%, var(--color-surface-alt) 100%);
|
|
305
310
|
}
|
|
306
311
|
|
|
312
|
+
.product-card__wishlist-wrap {
|
|
313
|
+
position: absolute;
|
|
314
|
+
top: 0.75rem;
|
|
315
|
+
right: 0.75rem;
|
|
316
|
+
z-index: 30;
|
|
317
|
+
opacity: 1;
|
|
318
|
+
pointer-events: auto;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.jewelry-product-card .product-card__wishlist-wrap {
|
|
322
|
+
top: 0.75rem;
|
|
323
|
+
right: 0.75rem;
|
|
324
|
+
}
|
|
325
|
+
|
|
307
326
|
.jewelry-product-card .product-card__actions {
|
|
308
|
-
|
|
309
|
-
top: auto;
|
|
310
|
-
right: 0.875rem;
|
|
327
|
+
display: none;
|
|
311
328
|
}
|
|
312
329
|
|
|
313
330
|
.jewelry-product-card .product-card__wishlist-btn {
|