@shopify/shop-minis-react 0.11.0 → 0.12.0
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/dist/components/atoms/content-monitor.js +16 -12
- package/dist/components/atoms/content-monitor.js.map +1 -1
- package/dist/components/commerce/product-card.js +83 -78
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/commerce/product-link.js +98 -93
- package/dist/components/commerce/product-link.js.map +1 -1
- package/dist/internal/useContentImpression.js +33 -0
- package/dist/internal/useContentImpression.js.map +1 -0
- package/dist/internal/useProductImpression.js +34 -0
- package/dist/internal/useProductImpression.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +1 -1
- package/package.json +2 -2
- package/src/components/atoms/content-monitor.tsx +15 -8
- package/src/components/commerce/product-card.tsx +27 -17
- package/src/components/commerce/product-link.tsx +60 -49
- package/src/internal/useContentImpression.ts +75 -0
- package/src/internal/useProductImpression.ts +88 -0
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as t, jsxs as y, Fragment as V } from "react/jsx-runtime";
|
|
2
2
|
import * as P from "react";
|
|
3
|
-
import { cva as
|
|
4
|
-
import { useShopNavigation as
|
|
5
|
-
import { useSavedProductsActions as
|
|
6
|
-
import { ProductReviewStars as
|
|
3
|
+
import { cva as M } from "../../shop-minis-react/node_modules/.pnpm/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.js";
|
|
4
|
+
import { useShopNavigation as U } from "../../hooks/navigation/useShopNavigation.js";
|
|
5
|
+
import { useSavedProductsActions as E } from "../../hooks/user/useSavedProductsActions.js";
|
|
6
|
+
import { ProductReviewStars as G } from "../../internal/components/product-review-stars.js";
|
|
7
|
+
import { useProductImpression as H } from "../../internal/useProductImpression.js";
|
|
7
8
|
import { cn as n } from "../../lib/utils.js";
|
|
8
|
-
import { formatMoney as
|
|
9
|
-
import { Touchable as
|
|
10
|
-
import { Card as
|
|
11
|
-
import { FavoriteButton as
|
|
12
|
-
import { Root as
|
|
13
|
-
const
|
|
9
|
+
import { formatMoney as S } from "../../utils/formatMoney.js";
|
|
10
|
+
import { Touchable as A } from "../atoms/touchable.js";
|
|
11
|
+
import { Card as J, CardContent as K, CardAction as Q } from "../ui/card.js";
|
|
12
|
+
import { FavoriteButton as W } from "./favorite-button.js";
|
|
13
|
+
import { Root as X } from "../../shop-minis-react/node_modules/.pnpm/@radix-ui_react-slot@1.2.3_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-slot/dist/index.js";
|
|
14
|
+
const Y = M("", {
|
|
14
15
|
variants: {
|
|
15
16
|
layout: {
|
|
16
17
|
horizontal: "w-full !flex-row items-center gap-3 px-4 py-3",
|
|
@@ -27,16 +28,16 @@ const Q = q("", {
|
|
|
27
28
|
discount: "none"
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
|
-
function
|
|
31
|
+
function Z({
|
|
31
32
|
className: e,
|
|
32
|
-
layout:
|
|
33
|
-
discount:
|
|
33
|
+
layout: r,
|
|
34
|
+
discount: o,
|
|
34
35
|
asChild: i = !1,
|
|
35
36
|
onPress: a,
|
|
36
37
|
...c
|
|
37
38
|
}) {
|
|
38
39
|
return /* @__PURE__ */ t(
|
|
39
|
-
|
|
40
|
+
A,
|
|
40
41
|
{
|
|
41
42
|
onClick: a,
|
|
42
43
|
whileTap: { opacity: 0.7 },
|
|
@@ -44,10 +45,10 @@ function W({
|
|
|
44
45
|
opacity: { type: "tween", duration: 0.08, ease: "easeInOut" }
|
|
45
46
|
},
|
|
46
47
|
children: /* @__PURE__ */ t(
|
|
47
|
-
i ?
|
|
48
|
+
i ? X : J,
|
|
48
49
|
{
|
|
49
50
|
className: n(
|
|
50
|
-
|
|
51
|
+
Y({ layout: r, discount: o }),
|
|
51
52
|
"border-0 bg-white rounded-xl shadow-lg shadow-black/10",
|
|
52
53
|
e
|
|
53
54
|
),
|
|
@@ -57,10 +58,10 @@ function W({
|
|
|
57
58
|
}
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
+
function _({
|
|
61
62
|
className: e,
|
|
62
|
-
layout:
|
|
63
|
-
...
|
|
63
|
+
layout: r = "horizontal",
|
|
64
|
+
...o
|
|
64
65
|
}) {
|
|
65
66
|
return /* @__PURE__ */ t(
|
|
66
67
|
"div",
|
|
@@ -68,33 +69,33 @@ function X({
|
|
|
68
69
|
"data-slot": "product-link-image",
|
|
69
70
|
className: n(
|
|
70
71
|
"overflow-hidden rounded-md bg-muted",
|
|
71
|
-
|
|
72
|
+
r === "horizontal" ? "h-16 w-16 flex-shrink-0" : "aspect-square w-full",
|
|
72
73
|
e
|
|
73
74
|
),
|
|
74
|
-
...
|
|
75
|
+
...o
|
|
75
76
|
}
|
|
76
77
|
);
|
|
77
78
|
}
|
|
78
|
-
function
|
|
79
|
+
function $({
|
|
79
80
|
className: e,
|
|
80
|
-
layout:
|
|
81
|
-
...
|
|
81
|
+
layout: r = "horizontal",
|
|
82
|
+
...o
|
|
82
83
|
}) {
|
|
83
84
|
return /* @__PURE__ */ t(
|
|
84
|
-
|
|
85
|
+
K,
|
|
85
86
|
{
|
|
86
87
|
className: n(
|
|
87
|
-
|
|
88
|
+
r === "horizontal" ? "flex-1 min-w-0 space-y-1 px-0 py-0" : "space-y-2",
|
|
88
89
|
e
|
|
89
90
|
),
|
|
90
|
-
...
|
|
91
|
+
...o
|
|
91
92
|
}
|
|
92
93
|
);
|
|
93
94
|
}
|
|
94
|
-
function
|
|
95
|
+
function tt({
|
|
95
96
|
className: e,
|
|
96
|
-
children:
|
|
97
|
-
...
|
|
97
|
+
children: r,
|
|
98
|
+
...o
|
|
98
99
|
}) {
|
|
99
100
|
return /* @__PURE__ */ t(
|
|
100
101
|
"h3",
|
|
@@ -104,132 +105,136 @@ function Z({
|
|
|
104
105
|
"text-sm font-medium leading-tight text-gray-900 truncate overflow-hidden whitespace-nowrap text-ellipsis",
|
|
105
106
|
e
|
|
106
107
|
),
|
|
107
|
-
...
|
|
108
|
-
children:
|
|
108
|
+
...o,
|
|
109
|
+
children: r
|
|
109
110
|
}
|
|
110
111
|
);
|
|
111
112
|
}
|
|
112
|
-
function
|
|
113
|
+
function et({ className: e, ...r }) {
|
|
113
114
|
return /* @__PURE__ */ t(
|
|
114
115
|
"div",
|
|
115
116
|
{
|
|
116
117
|
"data-slot": "product-link-price",
|
|
117
118
|
className: n("flex items-center gap-2", e),
|
|
118
|
-
...
|
|
119
|
+
...r
|
|
119
120
|
}
|
|
120
121
|
);
|
|
121
122
|
}
|
|
122
|
-
function
|
|
123
|
+
function rt({
|
|
123
124
|
className: e,
|
|
124
|
-
...
|
|
125
|
+
...r
|
|
125
126
|
}) {
|
|
126
127
|
return /* @__PURE__ */ t(
|
|
127
128
|
"span",
|
|
128
129
|
{
|
|
129
130
|
"data-slot": "product-link-current-price",
|
|
130
131
|
className: n("text-sm font-semibold text-gray-900", e),
|
|
131
|
-
...
|
|
132
|
+
...r
|
|
132
133
|
}
|
|
133
134
|
);
|
|
134
135
|
}
|
|
135
|
-
function
|
|
136
|
+
function ot({
|
|
136
137
|
className: e,
|
|
137
|
-
...
|
|
138
|
+
...r
|
|
138
139
|
}) {
|
|
139
140
|
return /* @__PURE__ */ t(
|
|
140
141
|
"span",
|
|
141
142
|
{
|
|
142
143
|
"data-slot": "product-link-original-price",
|
|
143
144
|
className: n("text-sm text-gray-500 line-through", e),
|
|
144
|
-
...
|
|
145
|
+
...r
|
|
145
146
|
}
|
|
146
147
|
);
|
|
147
148
|
}
|
|
148
|
-
function
|
|
149
|
+
function nt({
|
|
149
150
|
className: e,
|
|
150
|
-
...
|
|
151
|
+
...r
|
|
151
152
|
}) {
|
|
152
153
|
return /* @__PURE__ */ t(
|
|
153
154
|
"span",
|
|
154
155
|
{
|
|
155
156
|
"data-slot": "product-link-discount-price",
|
|
156
157
|
className: n("text-sm font-semibold text-red-600", e),
|
|
157
|
-
...
|
|
158
|
+
...r
|
|
158
159
|
}
|
|
159
160
|
);
|
|
160
161
|
}
|
|
161
|
-
function
|
|
162
|
+
function at({ className: e, ...r }) {
|
|
162
163
|
return /* @__PURE__ */ t(
|
|
163
164
|
"div",
|
|
164
165
|
{
|
|
165
166
|
"data-slot": "product-link-rating",
|
|
166
167
|
className: n("", e),
|
|
167
|
-
...
|
|
168
|
+
...r
|
|
168
169
|
}
|
|
169
170
|
);
|
|
170
171
|
}
|
|
171
|
-
function
|
|
172
|
+
function it({
|
|
172
173
|
className: e,
|
|
173
|
-
hideFavoriteAction:
|
|
174
|
-
onPress:
|
|
174
|
+
hideFavoriteAction: r = !1,
|
|
175
|
+
onPress: o,
|
|
175
176
|
filled: i = !1,
|
|
176
177
|
customAction: a,
|
|
177
178
|
...c
|
|
178
179
|
}) {
|
|
179
|
-
const l =
|
|
180
|
+
const l = r ? null : /* @__PURE__ */ t(W, { filled: i, onClick: o });
|
|
180
181
|
return /* @__PURE__ */ t(
|
|
181
|
-
|
|
182
|
+
Q,
|
|
182
183
|
{
|
|
183
184
|
className: n("flex-shrink-0 self-center px-0 py-0", e),
|
|
184
185
|
...c,
|
|
185
186
|
children: /* @__PURE__ */ t(
|
|
186
|
-
|
|
187
|
+
A,
|
|
187
188
|
{
|
|
188
189
|
stopPropagation: !0,
|
|
189
|
-
onClick:
|
|
190
|
+
onClick: o,
|
|
190
191
|
whileTap: { opacity: 0.7, scale: 0.95 },
|
|
191
192
|
transition: {
|
|
192
193
|
opacity: { type: "tween", duration: 0.08, ease: "easeInOut" },
|
|
193
194
|
scale: { type: "tween", duration: 0.08, ease: "easeInOut" }
|
|
194
195
|
},
|
|
195
|
-
children: a ? /* @__PURE__ */ t(
|
|
196
|
+
children: a ? /* @__PURE__ */ t(V, { children: a }) : l
|
|
196
197
|
}
|
|
197
198
|
)
|
|
198
199
|
}
|
|
199
200
|
);
|
|
200
201
|
}
|
|
201
|
-
function
|
|
202
|
+
function Pt({
|
|
202
203
|
product: e,
|
|
203
|
-
hideFavoriteAction:
|
|
204
|
-
onClick:
|
|
204
|
+
hideFavoriteAction: r = !1,
|
|
205
|
+
onClick: o,
|
|
205
206
|
customAction: i,
|
|
206
207
|
onCustomActionClick: a,
|
|
207
|
-
reviewsDisabled: c = !1
|
|
208
|
+
reviewsDisabled: c = !1,
|
|
209
|
+
impressionTrackingDisabled: l = !1
|
|
208
210
|
}) {
|
|
209
|
-
const { navigateToProduct:
|
|
211
|
+
const { navigateToProduct: k } = U(), { saveProduct: w, unsaveProduct: I } = E(), { ref: F } = H({
|
|
212
|
+
productId: e.id,
|
|
213
|
+
skip: l
|
|
214
|
+
}), {
|
|
210
215
|
id: s,
|
|
211
|
-
title:
|
|
212
|
-
featuredImage:
|
|
213
|
-
reviewAnalytics:
|
|
216
|
+
title: L,
|
|
217
|
+
featuredImage: N,
|
|
218
|
+
reviewAnalytics: b,
|
|
214
219
|
price: d,
|
|
215
220
|
compareAtPrice: u,
|
|
216
|
-
isFavorited:
|
|
221
|
+
isFavorited: j,
|
|
217
222
|
selectedVariant: m,
|
|
218
223
|
defaultVariantId: p,
|
|
219
224
|
shop: f
|
|
220
|
-
} = e, [h,
|
|
221
|
-
|
|
225
|
+
} = e, [h, C] = P.useState(j), z = b?.averageRating, O = b?.reviewCount, g = d?.amount ? S(d?.amount, d?.currencyCode) : void 0, R = N?.url, D = N?.altText || L, v = u?.amount ? S(u?.amount, u?.currencyCode) : void 0, T = v && v !== g, q = P.useCallback(() => {
|
|
226
|
+
k({
|
|
222
227
|
productId: s
|
|
223
|
-
}),
|
|
224
|
-
}, [
|
|
228
|
+
}), o?.(e);
|
|
229
|
+
}, [k, s, o, e]), B = P.useCallback(async () => {
|
|
225
230
|
if (i || a) {
|
|
226
231
|
a?.();
|
|
227
232
|
return;
|
|
228
233
|
}
|
|
229
234
|
const x = h;
|
|
230
|
-
|
|
235
|
+
C(!x);
|
|
231
236
|
try {
|
|
232
|
-
x ? await
|
|
237
|
+
x ? await I({
|
|
233
238
|
productId: s,
|
|
234
239
|
shopId: f.id,
|
|
235
240
|
productVariantId: m?.id || p
|
|
@@ -239,62 +244,62 @@ function gt({
|
|
|
239
244
|
productVariantId: m?.id || p
|
|
240
245
|
});
|
|
241
246
|
} catch {
|
|
242
|
-
|
|
247
|
+
C(x);
|
|
243
248
|
}
|
|
244
249
|
}, [
|
|
245
250
|
i,
|
|
246
251
|
a,
|
|
247
252
|
h,
|
|
248
|
-
|
|
253
|
+
I,
|
|
249
254
|
s,
|
|
250
255
|
f.id,
|
|
251
256
|
m?.id,
|
|
252
257
|
p,
|
|
253
258
|
w
|
|
254
259
|
]);
|
|
255
|
-
return /* @__PURE__ */ y(
|
|
256
|
-
|
|
260
|
+
return /* @__PURE__ */ t("div", { ref: F, children: /* @__PURE__ */ y(
|
|
261
|
+
Z,
|
|
257
262
|
{
|
|
258
263
|
layout: "horizontal",
|
|
259
|
-
discount:
|
|
260
|
-
onPress:
|
|
264
|
+
discount: T ? "small" : "none",
|
|
265
|
+
onPress: q,
|
|
261
266
|
children: [
|
|
262
|
-
/* @__PURE__ */ t(
|
|
267
|
+
/* @__PURE__ */ t(_, { layout: "horizontal", children: R ? /* @__PURE__ */ t(
|
|
263
268
|
"img",
|
|
264
269
|
{
|
|
265
|
-
src:
|
|
266
|
-
alt:
|
|
270
|
+
src: R,
|
|
271
|
+
alt: D,
|
|
267
272
|
className: "h-full w-full object-cover"
|
|
268
273
|
}
|
|
269
274
|
) : /* @__PURE__ */ t("div", { className: "h-full w-full bg-muted flex items-center justify-center text-muted-foreground text-xs", children: "No Image" }) }),
|
|
270
|
-
/* @__PURE__ */ y(
|
|
271
|
-
/* @__PURE__ */ t(
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
/* @__PURE__ */ y($, { layout: "horizontal", children: [
|
|
276
|
+
/* @__PURE__ */ t(tt, { children: L }),
|
|
277
|
+
z && !c ? /* @__PURE__ */ t(at, { children: /* @__PURE__ */ t(
|
|
278
|
+
G,
|
|
274
279
|
{
|
|
275
|
-
averageRating:
|
|
276
|
-
reviewCount:
|
|
280
|
+
averageRating: z,
|
|
281
|
+
reviewCount: O
|
|
277
282
|
}
|
|
278
283
|
) }) : null,
|
|
279
|
-
/* @__PURE__ */ t(
|
|
280
|
-
/* @__PURE__ */ t(
|
|
281
|
-
/* @__PURE__ */ t(
|
|
282
|
-
] }) : /* @__PURE__ */ t(
|
|
284
|
+
/* @__PURE__ */ t(et, { children: T ? /* @__PURE__ */ y(V, { children: [
|
|
285
|
+
/* @__PURE__ */ t(nt, { children: g }),
|
|
286
|
+
/* @__PURE__ */ t(ot, { children: v })
|
|
287
|
+
] }) : /* @__PURE__ */ t(rt, { children: g }) })
|
|
283
288
|
] }),
|
|
284
289
|
/* @__PURE__ */ t(
|
|
285
|
-
|
|
290
|
+
it,
|
|
286
291
|
{
|
|
287
292
|
filled: h,
|
|
288
|
-
onPress:
|
|
293
|
+
onPress: B,
|
|
289
294
|
customAction: i,
|
|
290
|
-
hideFavoriteAction:
|
|
295
|
+
hideFavoriteAction: r
|
|
291
296
|
}
|
|
292
297
|
)
|
|
293
298
|
]
|
|
294
299
|
}
|
|
295
|
-
);
|
|
300
|
+
) });
|
|
296
301
|
}
|
|
297
302
|
export {
|
|
298
|
-
|
|
303
|
+
Pt as ProductLink
|
|
299
304
|
};
|
|
300
305
|
//# sourceMappingURL=product-link.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product-link.js","sources":["../../../src/components/commerce/product-link.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {type Product} from '@shopify/shop-minis-platform'\nimport {cva, type VariantProps} from 'class-variance-authority'\nimport {Slot as SlotPrimitive} from 'radix-ui'\n\nimport {useShopNavigation} from '../../hooks/navigation/useShopNavigation'\nimport {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'\nimport {ProductReviewStars} from '../../internal/components/product-review-stars'\nimport {cn} from '../../lib/utils'\nimport {formatMoney} from '../../utils/formatMoney'\nimport {Touchable} from '../atoms/touchable'\nimport {Card, CardContent, CardAction} from '../ui/card'\n\nimport {FavoriteButton} from './favorite-button'\n\nconst productLinkVariants = cva('', {\n variants: {\n layout: {\n horizontal: 'w-full !flex-row items-center gap-3 px-4 py-3',\n vertical: 'flex-col',\n },\n discount: {\n none: '',\n small: '',\n large: '',\n },\n },\n defaultVariants: {\n layout: 'horizontal',\n discount: 'none',\n },\n})\n\n// Primitive components (building blocks)\nexport interface ProductLinkRootProps\n extends React.ComponentProps<typeof Card>,\n VariantProps<typeof productLinkVariants> {\n layout?: 'horizontal' | 'vertical'\n asChild?: boolean\n onPress?: () => void\n}\n\nfunction ProductLinkRoot({\n className,\n layout,\n discount,\n asChild = false,\n onPress,\n ...props\n}: ProductLinkRootProps) {\n const Comp = asChild ? SlotPrimitive.Root : Card\n\n return (\n <Touchable\n onClick={onPress}\n whileTap={{opacity: 0.7}}\n transition={{\n opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n }}\n >\n <Comp\n className={cn(\n productLinkVariants({layout, discount}),\n 'border-0 bg-white rounded-xl shadow-lg shadow-black/10',\n className\n )}\n {...props}\n />\n </Touchable>\n )\n}\n\nfunction ProductLinkImage({\n className,\n layout = 'horizontal',\n ...props\n}: React.ComponentProps<'div'> & {layout?: 'horizontal' | 'vertical'}) {\n return (\n <div\n data-slot=\"product-link-image\"\n className={cn(\n 'overflow-hidden rounded-md bg-muted',\n layout === 'horizontal'\n ? 'h-16 w-16 flex-shrink-0'\n : 'aspect-square w-full',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ProductLinkInfo({\n className,\n layout = 'horizontal',\n ...props\n}: React.ComponentProps<typeof CardContent> & {\n layout?: 'horizontal' | 'vertical'\n}) {\n return (\n <CardContent\n className={cn(\n layout === 'horizontal'\n ? 'flex-1 min-w-0 space-y-1 px-0 py-0'\n : 'space-y-2',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ProductLinkTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'h3'>) {\n return (\n <h3\n data-slot=\"product-link-title\"\n className={cn(\n 'text-sm font-medium leading-tight text-gray-900 truncate overflow-hidden whitespace-nowrap text-ellipsis',\n className\n )}\n {...props}\n >\n {children}\n </h3>\n )\n}\n\nfunction ProductLinkPrice({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"product-link-price\"\n className={cn('flex items-center gap-2', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkCurrentPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-current-price\"\n className={cn('text-sm font-semibold text-gray-900', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkOriginalPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-original-price\"\n className={cn('text-sm text-gray-500 line-through', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkDiscountPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-discount-price\"\n className={cn('text-sm font-semibold text-red-600', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkRating({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"product-link-rating\"\n className={cn('', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkActions({\n className,\n hideFavoriteAction = false,\n onPress,\n filled = false,\n customAction,\n ...props\n}: React.ComponentProps<typeof CardAction> & {\n hideFavoriteAction?: boolean\n onPress?: () => void\n filled?: boolean\n customAction?: React.ReactNode\n}) {\n const favoriteAction = hideFavoriteAction ? null : (\n <FavoriteButton filled={filled} onClick={onPress} />\n )\n\n return (\n <CardAction\n className={cn('flex-shrink-0 self-center px-0 py-0', className)}\n {...props}\n >\n <Touchable\n stopPropagation\n onClick={onPress}\n whileTap={{opacity: 0.7, scale: 0.95}}\n transition={{\n opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n scale: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n }}\n >\n {customAction ? <>{customAction}</> : favoriteAction}\n </Touchable>\n </CardAction>\n )\n}\n\nexport interface ProductLinkDocProps {\n /** The product to display */\n product: Product\n /** Hide the favorite/save button */\n hideFavoriteAction?: boolean\n /** Callback when the product link is clicked */\n onClick?: (product: Product) => void\n /** Hide the review stars */\n reviewsDisabled?: boolean\n /** Custom action element to replace the favorite button. Must be provided with `onCustomActionClick`. */\n customAction?: React.ReactNode\n /** Callback when the custom action is clicked. Must be provided with `customAction`. */\n onCustomActionClick?: () => void\n}\n\nexport type ProductLinkProps = {\n product: Product\n hideFavoriteAction?: boolean\n onClick?: (product: Product) => void\n reviewsDisabled?: boolean\n} & (\n | {\n customAction?: never\n onCustomActionClick?: never\n }\n | {\n customAction: React.ReactNode\n onCustomActionClick: () => void\n }\n)\n\n// Composed ProductLink component\nfunction ProductLink({\n product,\n hideFavoriteAction = false,\n onClick,\n customAction,\n onCustomActionClick,\n reviewsDisabled = false,\n}: ProductLinkProps) {\n const {navigateToProduct} = useShopNavigation()\n const {saveProduct, unsaveProduct} = useSavedProductsActions()\n\n const {\n id,\n title,\n featuredImage,\n reviewAnalytics,\n price,\n compareAtPrice,\n isFavorited,\n selectedVariant,\n defaultVariantId,\n shop,\n } = product\n\n // Local state for optimistic UI updates\n const [isFavoritedLocal, setIsFavoritedLocal] = React.useState(isFavorited)\n\n const averageRating = reviewAnalytics?.averageRating\n const reviewCount = reviewAnalytics?.reviewCount\n const amount = price?.amount\n ? formatMoney(price?.amount, price?.currencyCode)\n : undefined\n const imageUrl = featuredImage?.url\n const imageAltText = featuredImage?.altText || title\n const compareAtPriceAmount = compareAtPrice?.amount\n ? formatMoney(compareAtPrice?.amount, compareAtPrice?.currencyCode)\n : undefined\n const hasDiscount = compareAtPriceAmount && compareAtPriceAmount !== amount\n\n const handlePress = React.useCallback(() => {\n navigateToProduct({\n productId: id,\n })\n onClick?.(product)\n }, [navigateToProduct, id, onClick, product])\n\n const handleActionPress = React.useCallback(async () => {\n if (customAction || onCustomActionClick) {\n onCustomActionClick?.()\n return\n }\n\n const previousState = isFavoritedLocal\n\n // Optimistic update\n setIsFavoritedLocal(!previousState)\n\n try {\n if (previousState) {\n await unsaveProduct({\n productId: id,\n shopId: shop.id,\n productVariantId: selectedVariant?.id || defaultVariantId,\n })\n } else {\n await saveProduct({\n productId: id,\n shopId: shop.id,\n productVariantId: selectedVariant?.id || defaultVariantId,\n })\n }\n } catch (error) {\n // Revert optimistic update on error\n setIsFavoritedLocal(previousState)\n }\n }, [\n customAction,\n onCustomActionClick,\n isFavoritedLocal,\n unsaveProduct,\n id,\n shop.id,\n selectedVariant?.id,\n defaultVariantId,\n saveProduct,\n ])\n\n return (\n <ProductLinkRoot\n layout=\"horizontal\"\n discount={hasDiscount ? 'small' : 'none'}\n onPress={handlePress}\n >\n <ProductLinkImage layout=\"horizontal\">\n {imageUrl ? (\n <img\n src={imageUrl}\n alt={imageAltText}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"h-full w-full bg-muted flex items-center justify-center text-muted-foreground text-xs\">\n No Image\n </div>\n )}\n </ProductLinkImage>\n\n <ProductLinkInfo layout=\"horizontal\">\n <ProductLinkTitle>{title}</ProductLinkTitle>\n\n {averageRating && !reviewsDisabled ? (\n <ProductLinkRating>\n <ProductReviewStars\n averageRating={averageRating}\n reviewCount={reviewCount}\n />\n </ProductLinkRating>\n ) : null}\n\n <ProductLinkPrice>\n {hasDiscount ? (\n <>\n <ProductLinkDiscountPrice>{amount}</ProductLinkDiscountPrice>\n <ProductLinkOriginalPrice>\n {compareAtPriceAmount}\n </ProductLinkOriginalPrice>\n </>\n ) : (\n <ProductLinkCurrentPrice>{amount}</ProductLinkCurrentPrice>\n )}\n </ProductLinkPrice>\n </ProductLinkInfo>\n\n <ProductLinkActions\n filled={isFavoritedLocal}\n onPress={handleActionPress}\n customAction={customAction}\n hideFavoriteAction={hideFavoriteAction}\n />\n </ProductLinkRoot>\n )\n}\n\nexport {ProductLink}\n"],"names":["productLinkVariants","cva","ProductLinkRoot","className","layout","discount","asChild","onPress","props","jsx","Touchable","SlotPrimitive.Root","Card","cn","ProductLinkImage","ProductLinkInfo","CardContent","ProductLinkTitle","children","ProductLinkPrice","ProductLinkCurrentPrice","ProductLinkOriginalPrice","ProductLinkDiscountPrice","ProductLinkRating","ProductLinkActions","hideFavoriteAction","filled","customAction","favoriteAction","FavoriteButton","CardAction","Fragment","ProductLink","product","onClick","onCustomActionClick","reviewsDisabled","navigateToProduct","useShopNavigation","saveProduct","unsaveProduct","useSavedProductsActions","id","title","featuredImage","reviewAnalytics","price","compareAtPrice","isFavorited","selectedVariant","defaultVariantId","shop","isFavoritedLocal","setIsFavoritedLocal","React","averageRating","reviewCount","amount","formatMoney","imageUrl","imageAltText","compareAtPriceAmount","hasDiscount","handlePress","handleActionPress","previousState","jsxs","ProductReviewStars"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"product-link.js","sources":["../../../src/components/commerce/product-link.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport {type Product} from '@shopify/shop-minis-platform'\nimport {cva, type VariantProps} from 'class-variance-authority'\nimport {Slot as SlotPrimitive} from 'radix-ui'\n\nimport {useShopNavigation} from '../../hooks/navigation/useShopNavigation'\nimport {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'\nimport {ProductReviewStars} from '../../internal/components/product-review-stars'\nimport {useProductImpression} from '../../internal/useProductImpression'\nimport {cn} from '../../lib/utils'\nimport {formatMoney} from '../../utils/formatMoney'\nimport {Touchable} from '../atoms/touchable'\nimport {Card, CardContent, CardAction} from '../ui/card'\n\nimport {FavoriteButton} from './favorite-button'\n\nconst productLinkVariants = cva('', {\n variants: {\n layout: {\n horizontal: 'w-full !flex-row items-center gap-3 px-4 py-3',\n vertical: 'flex-col',\n },\n discount: {\n none: '',\n small: '',\n large: '',\n },\n },\n defaultVariants: {\n layout: 'horizontal',\n discount: 'none',\n },\n})\n\n// Primitive components (building blocks)\nexport interface ProductLinkRootProps\n extends React.ComponentProps<typeof Card>,\n VariantProps<typeof productLinkVariants> {\n layout?: 'horizontal' | 'vertical'\n asChild?: boolean\n onPress?: () => void\n}\n\nfunction ProductLinkRoot({\n className,\n layout,\n discount,\n asChild = false,\n onPress,\n ...props\n}: ProductLinkRootProps) {\n const Comp = asChild ? SlotPrimitive.Root : Card\n\n return (\n <Touchable\n onClick={onPress}\n whileTap={{opacity: 0.7}}\n transition={{\n opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n }}\n >\n <Comp\n className={cn(\n productLinkVariants({layout, discount}),\n 'border-0 bg-white rounded-xl shadow-lg shadow-black/10',\n className\n )}\n {...props}\n />\n </Touchable>\n )\n}\n\nfunction ProductLinkImage({\n className,\n layout = 'horizontal',\n ...props\n}: React.ComponentProps<'div'> & {layout?: 'horizontal' | 'vertical'}) {\n return (\n <div\n data-slot=\"product-link-image\"\n className={cn(\n 'overflow-hidden rounded-md bg-muted',\n layout === 'horizontal'\n ? 'h-16 w-16 flex-shrink-0'\n : 'aspect-square w-full',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ProductLinkInfo({\n className,\n layout = 'horizontal',\n ...props\n}: React.ComponentProps<typeof CardContent> & {\n layout?: 'horizontal' | 'vertical'\n}) {\n return (\n <CardContent\n className={cn(\n layout === 'horizontal'\n ? 'flex-1 min-w-0 space-y-1 px-0 py-0'\n : 'space-y-2',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ProductLinkTitle({\n className,\n children,\n ...props\n}: React.ComponentProps<'h3'>) {\n return (\n <h3\n data-slot=\"product-link-title\"\n className={cn(\n 'text-sm font-medium leading-tight text-gray-900 truncate overflow-hidden whitespace-nowrap text-ellipsis',\n className\n )}\n {...props}\n >\n {children}\n </h3>\n )\n}\n\nfunction ProductLinkPrice({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"product-link-price\"\n className={cn('flex items-center gap-2', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkCurrentPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-current-price\"\n className={cn('text-sm font-semibold text-gray-900', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkOriginalPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-original-price\"\n className={cn('text-sm text-gray-500 line-through', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkDiscountPrice({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"product-link-discount-price\"\n className={cn('text-sm font-semibold text-red-600', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkRating({className, ...props}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"product-link-rating\"\n className={cn('', className)}\n {...props}\n />\n )\n}\n\nfunction ProductLinkActions({\n className,\n hideFavoriteAction = false,\n onPress,\n filled = false,\n customAction,\n ...props\n}: React.ComponentProps<typeof CardAction> & {\n hideFavoriteAction?: boolean\n onPress?: () => void\n filled?: boolean\n customAction?: React.ReactNode\n}) {\n const favoriteAction = hideFavoriteAction ? null : (\n <FavoriteButton filled={filled} onClick={onPress} />\n )\n\n return (\n <CardAction\n className={cn('flex-shrink-0 self-center px-0 py-0', className)}\n {...props}\n >\n <Touchable\n stopPropagation\n onClick={onPress}\n whileTap={{opacity: 0.7, scale: 0.95}}\n transition={{\n opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n scale: {type: 'tween', duration: 0.08, ease: 'easeInOut'},\n }}\n >\n {customAction ? <>{customAction}</> : favoriteAction}\n </Touchable>\n </CardAction>\n )\n}\n\nexport interface ProductLinkDocProps {\n /** The product to display */\n product: Product\n /** Hide the favorite/save button */\n hideFavoriteAction?: boolean\n /** Callback when the product link is clicked */\n onClick?: (product: Product) => void\n /** Hide the review stars */\n reviewsDisabled?: boolean\n /** Custom action element to replace the favorite button. Must be provided with `onCustomActionClick`. */\n customAction?: React.ReactNode\n /** Callback when the custom action is clicked. Must be provided with `customAction`. */\n onCustomActionClick?: () => void\n /** Whether to disable impression tracking */\n impressionTrackingDisabled?: boolean\n}\n\nexport type ProductLinkProps = {\n product: Product\n hideFavoriteAction?: boolean\n onClick?: (product: Product) => void\n reviewsDisabled?: boolean\n impressionTrackingDisabled?: boolean\n} & (\n | {\n customAction?: never\n onCustomActionClick?: never\n }\n | {\n customAction: React.ReactNode\n onCustomActionClick: () => void\n }\n)\n\n// Composed ProductLink component\nfunction ProductLink({\n product,\n hideFavoriteAction = false,\n onClick,\n customAction,\n onCustomActionClick,\n reviewsDisabled = false,\n impressionTrackingDisabled = false,\n}: ProductLinkProps) {\n const {navigateToProduct} = useShopNavigation()\n const {saveProduct, unsaveProduct} = useSavedProductsActions()\n const {ref: impressionRef} = useProductImpression({\n productId: product.id,\n skip: impressionTrackingDisabled,\n })\n\n const {\n id,\n title,\n featuredImage,\n reviewAnalytics,\n price,\n compareAtPrice,\n isFavorited,\n selectedVariant,\n defaultVariantId,\n shop,\n } = product\n\n // Local state for optimistic UI updates\n const [isFavoritedLocal, setIsFavoritedLocal] = React.useState(isFavorited)\n\n const averageRating = reviewAnalytics?.averageRating\n const reviewCount = reviewAnalytics?.reviewCount\n const amount = price?.amount\n ? formatMoney(price?.amount, price?.currencyCode)\n : undefined\n const imageUrl = featuredImage?.url\n const imageAltText = featuredImage?.altText || title\n const compareAtPriceAmount = compareAtPrice?.amount\n ? formatMoney(compareAtPrice?.amount, compareAtPrice?.currencyCode)\n : undefined\n const hasDiscount = compareAtPriceAmount && compareAtPriceAmount !== amount\n\n const handlePress = React.useCallback(() => {\n navigateToProduct({\n productId: id,\n })\n onClick?.(product)\n }, [navigateToProduct, id, onClick, product])\n\n const handleActionPress = React.useCallback(async () => {\n if (customAction || onCustomActionClick) {\n onCustomActionClick?.()\n return\n }\n\n const previousState = isFavoritedLocal\n\n // Optimistic update\n setIsFavoritedLocal(!previousState)\n\n try {\n if (previousState) {\n await unsaveProduct({\n productId: id,\n shopId: shop.id,\n productVariantId: selectedVariant?.id || defaultVariantId,\n })\n } else {\n await saveProduct({\n productId: id,\n shopId: shop.id,\n productVariantId: selectedVariant?.id || defaultVariantId,\n })\n }\n } catch (error) {\n // Revert optimistic update on error\n setIsFavoritedLocal(previousState)\n }\n }, [\n customAction,\n onCustomActionClick,\n isFavoritedLocal,\n unsaveProduct,\n id,\n shop.id,\n selectedVariant?.id,\n defaultVariantId,\n saveProduct,\n ])\n\n return (\n <div ref={impressionRef}>\n <ProductLinkRoot\n layout=\"horizontal\"\n discount={hasDiscount ? 'small' : 'none'}\n onPress={handlePress}\n >\n <ProductLinkImage layout=\"horizontal\">\n {imageUrl ? (\n <img\n src={imageUrl}\n alt={imageAltText}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"h-full w-full bg-muted flex items-center justify-center text-muted-foreground text-xs\">\n No Image\n </div>\n )}\n </ProductLinkImage>\n\n <ProductLinkInfo layout=\"horizontal\">\n <ProductLinkTitle>{title}</ProductLinkTitle>\n\n {averageRating && !reviewsDisabled ? (\n <ProductLinkRating>\n <ProductReviewStars\n averageRating={averageRating}\n reviewCount={reviewCount}\n />\n </ProductLinkRating>\n ) : null}\n\n <ProductLinkPrice>\n {hasDiscount ? (\n <>\n <ProductLinkDiscountPrice>{amount}</ProductLinkDiscountPrice>\n <ProductLinkOriginalPrice>\n {compareAtPriceAmount}\n </ProductLinkOriginalPrice>\n </>\n ) : (\n <ProductLinkCurrentPrice>{amount}</ProductLinkCurrentPrice>\n )}\n </ProductLinkPrice>\n </ProductLinkInfo>\n\n <ProductLinkActions\n filled={isFavoritedLocal}\n onPress={handleActionPress}\n customAction={customAction}\n hideFavoriteAction={hideFavoriteAction}\n />\n </ProductLinkRoot>\n </div>\n )\n}\n\nexport {ProductLink}\n"],"names":["productLinkVariants","cva","ProductLinkRoot","className","layout","discount","asChild","onPress","props","jsx","Touchable","SlotPrimitive.Root","Card","cn","ProductLinkImage","ProductLinkInfo","CardContent","ProductLinkTitle","children","ProductLinkPrice","ProductLinkCurrentPrice","ProductLinkOriginalPrice","ProductLinkDiscountPrice","ProductLinkRating","ProductLinkActions","hideFavoriteAction","filled","customAction","favoriteAction","FavoriteButton","CardAction","Fragment","ProductLink","product","onClick","onCustomActionClick","reviewsDisabled","impressionTrackingDisabled","navigateToProduct","useShopNavigation","saveProduct","unsaveProduct","useSavedProductsActions","impressionRef","useProductImpression","id","title","featuredImage","reviewAnalytics","price","compareAtPrice","isFavorited","selectedVariant","defaultVariantId","shop","isFavoritedLocal","setIsFavoritedLocal","React","averageRating","reviewCount","amount","formatMoney","imageUrl","imageAltText","compareAtPriceAmount","hasDiscount","handlePress","handleActionPress","previousState","jsxs","ProductReviewStars"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useRef as u, useCallback as p, useEffect as f } from "react";
|
|
2
|
+
import { useHandleAction as h } from "./useHandleAction.js";
|
|
3
|
+
import { useShopActions as d } from "./useShopActions.js";
|
|
4
|
+
function C({
|
|
5
|
+
publicId: e,
|
|
6
|
+
skip: n = !1
|
|
7
|
+
}) {
|
|
8
|
+
const { reportContentImpression: i } = d(), s = h(i), t = u(null), r = u(!1), c = p(() => {
|
|
9
|
+
r.current || n || !e || (r.current = !0, s({ publicId: e, pageValue: window.location.pathname }));
|
|
10
|
+
}, [s, e, n]);
|
|
11
|
+
return f(() => {
|
|
12
|
+
r.current = !1;
|
|
13
|
+
}, [e]), f(() => {
|
|
14
|
+
if (n || !t.current)
|
|
15
|
+
return;
|
|
16
|
+
const a = t.current, o = new IntersectionObserver(
|
|
17
|
+
(m) => {
|
|
18
|
+
const [l] = m;
|
|
19
|
+
l?.isIntersecting && (c(), o.disconnect());
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
threshold: 0.5
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
return o.observe(a), () => {
|
|
26
|
+
o.disconnect();
|
|
27
|
+
};
|
|
28
|
+
}, [c, n]), { ref: t };
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
C as useContentImpression
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=useContentImpression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useContentImpression.js","sources":["../../src/internal/useContentImpression.ts"],"sourcesContent":["import {useCallback, useEffect, useRef} from 'react'\n\nimport {useHandleAction} from './useHandleAction'\nimport {useShopActions} from './useShopActions'\n\nexport interface UseContentImpressionParams {\n /**\n * The public ID of the content to report impressions for\n */\n publicId: string\n /**\n * Whether to skip reporting impressions\n */\n skip?: boolean\n}\n\nexport interface UseContentImpressionReturns {\n /**\n * Ref to attach to the content element for visibility tracking.\n * When the element becomes visible, an impression will be reported.\n */\n ref: {current: HTMLDivElement | null}\n}\n\nexport function useContentImpression({\n publicId,\n skip = false,\n}: UseContentImpressionParams): UseContentImpressionReturns {\n const {reportContentImpression} = useShopActions()\n const handleAction = useHandleAction(reportContentImpression)\n const ref = useRef<HTMLDivElement>(null)\n const hasReportedRef = useRef(false)\n\n const reportImpression = useCallback(() => {\n if (hasReportedRef.current || skip || !publicId) {\n return\n }\n\n hasReportedRef.current = true\n handleAction({publicId, pageValue: window.location.pathname})\n }, [handleAction, publicId, skip])\n\n useEffect(() => {\n hasReportedRef.current = false\n }, [publicId])\n\n useEffect(() => {\n if (skip || !ref.current) {\n return\n }\n\n const element = ref.current\n\n const observer = new IntersectionObserver(\n entries => {\n const [entry] = entries\n if (entry?.isIntersecting) {\n reportImpression()\n observer.disconnect()\n }\n },\n {\n threshold: 0.5,\n }\n )\n\n observer.observe(element)\n\n return () => {\n observer.disconnect()\n }\n }, [reportImpression, skip])\n\n return {ref}\n}\n"],"names":["useContentImpression","publicId","skip","reportContentImpression","useShopActions","handleAction","useHandleAction","ref","useRef","hasReportedRef","reportImpression","useCallback","useEffect","element","observer","entries","entry"],"mappings":";;;AAwBO,SAASA,EAAqB;AAAA,EACnC,UAAAC;AAAA,EACA,MAAAC,IAAO;AACT,GAA4D;AACpD,QAAA,EAAC,yBAAAC,EAAuB,IAAIC,EAAe,GAC3CC,IAAeC,EAAgBH,CAAuB,GACtDI,IAAMC,EAAuB,IAAI,GACjCC,IAAiBD,EAAO,EAAK,GAE7BE,IAAmBC,EAAY,MAAM;AACzC,IAAIF,EAAe,WAAWP,KAAQ,CAACD,MAIvCQ,EAAe,UAAU,IACzBJ,EAAa,EAAC,UAAAJ,GAAU,WAAW,OAAO,SAAS,UAAS;AAAA,EAC3D,GAAA,CAACI,GAAcJ,GAAUC,CAAI,CAAC;AAEjC,SAAAU,EAAU,MAAM;AACd,IAAAH,EAAe,UAAU;AAAA,EAAA,GACxB,CAACR,CAAQ,CAAC,GAEbW,EAAU,MAAM;AACV,QAAAV,KAAQ,CAACK,EAAI;AACf;AAGF,UAAMM,IAAUN,EAAI,SAEdO,IAAW,IAAI;AAAA,MACnB,CAAWC,MAAA;AACH,cAAA,CAACC,CAAK,IAAID;AAChB,QAAIC,GAAO,mBACQN,EAAA,GACjBI,EAAS,WAAW;AAAA,MAExB;AAAA,MACA;AAAA,QACE,WAAW;AAAA,MAAA;AAAA,IAEf;AAEA,WAAAA,EAAS,QAAQD,CAAO,GAEjB,MAAM;AACX,MAAAC,EAAS,WAAW;AAAA,IACtB;AAAA,EAAA,GACC,CAACJ,GAAkBR,CAAI,CAAC,GAEpB,EAAC,KAAAK,EAAG;AACb;"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useRef as u, useCallback as h, useEffect as i } from "react";
|
|
2
|
+
import { useHandleAction as p } from "./useHandleAction.js";
|
|
3
|
+
import { useShopActions as b } from "./useShopActions.js";
|
|
4
|
+
function A({
|
|
5
|
+
productId: e,
|
|
6
|
+
skip: n = !1
|
|
7
|
+
}) {
|
|
8
|
+
const { productRecommendationImpression: f } = b(), s = p(f), r = u(null), t = u(!1), c = h(() => {
|
|
9
|
+
t.current || n || !e || (t.current = !0, s({ productId: e }));
|
|
10
|
+
}, [s, e, n]);
|
|
11
|
+
return i(() => {
|
|
12
|
+
t.current = !1;
|
|
13
|
+
}, [e]), i(() => {
|
|
14
|
+
if (n || !r.current)
|
|
15
|
+
return;
|
|
16
|
+
const m = r.current, o = new IntersectionObserver(
|
|
17
|
+
(l) => {
|
|
18
|
+
const [a] = l;
|
|
19
|
+
a?.isIntersecting && (c(), o.disconnect());
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
threshold: 0.5
|
|
23
|
+
// Report when 50% of the element is visible
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
return o.observe(m), () => {
|
|
27
|
+
o.disconnect();
|
|
28
|
+
};
|
|
29
|
+
}, [c, n]), { ref: r };
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
A as useProductImpression
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=useProductImpression.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useProductImpression.js","sources":["../../src/internal/useProductImpression.ts"],"sourcesContent":["import {useCallback, useEffect, useRef} from 'react'\n\nimport {useHandleAction} from './useHandleAction'\nimport {useShopActions} from './useShopActions'\n\nexport interface UseProductImpressionParams {\n /**\n * The product GID string to report impressions for\n */\n productId: string\n /**\n * Whether to skip reporting impressions (e.g., when product data is loading)\n */\n skip?: boolean\n}\n\nexport interface UseProductImpressionReturns {\n /**\n * Ref to attach to the product element for visibility tracking.\n * When the element becomes visible, an impression will be reported.\n */\n ref: {current: HTMLDivElement | null}\n}\n\n/**\n * Hook to report product impressions when a product becomes visible.\n * Attach the returned ref to the product container element.\n *\n * @example\n * ```tsx\n * function ProductCard({ product }) {\n * const { ref } = useProductImpression({ productId: product.id });\n * return <div ref={ref}>...</div>;\n * }\n * ```\n */\nexport function useProductImpression({\n productId,\n skip = false,\n}: UseProductImpressionParams): UseProductImpressionReturns {\n const {productRecommendationImpression} = useShopActions()\n const handleAction = useHandleAction(productRecommendationImpression)\n const ref = useRef<HTMLDivElement>(null)\n const hasReportedRef = useRef(false)\n\n const reportImpression = useCallback(() => {\n if (hasReportedRef.current || skip || !productId) {\n return\n }\n\n hasReportedRef.current = true\n handleAction({productId})\n }, [handleAction, productId, skip])\n\n useEffect(() => {\n // Reset reported state when productId changes\n hasReportedRef.current = false\n }, [productId])\n\n useEffect(() => {\n if (skip || !ref.current) {\n return\n }\n\n const element = ref.current\n\n const observer = new IntersectionObserver(\n entries => {\n const [entry] = entries\n if (entry?.isIntersecting) {\n reportImpression()\n observer.disconnect()\n }\n },\n {\n threshold: 0.5, // Report when 50% of the element is visible\n }\n )\n\n observer.observe(element)\n\n return () => {\n observer.disconnect()\n }\n }, [reportImpression, skip])\n\n return {ref}\n}\n"],"names":["useProductImpression","productId","skip","productRecommendationImpression","useShopActions","handleAction","useHandleAction","ref","useRef","hasReportedRef","reportImpression","useCallback","useEffect","element","observer","entries","entry"],"mappings":";;;AAoCO,SAASA,EAAqB;AAAA,EACnC,WAAAC;AAAA,EACA,MAAAC,IAAO;AACT,GAA4D;AACpD,QAAA,EAAC,iCAAAC,EAA+B,IAAIC,EAAe,GACnDC,IAAeC,EAAgBH,CAA+B,GAC9DI,IAAMC,EAAuB,IAAI,GACjCC,IAAiBD,EAAO,EAAK,GAE7BE,IAAmBC,EAAY,MAAM;AACzC,IAAIF,EAAe,WAAWP,KAAQ,CAACD,MAIvCQ,EAAe,UAAU,IACZJ,EAAA,EAAC,WAAAJ,GAAU;AAAA,EACvB,GAAA,CAACI,GAAcJ,GAAWC,CAAI,CAAC;AAElC,SAAAU,EAAU,MAAM;AAEd,IAAAH,EAAe,UAAU;AAAA,EAAA,GACxB,CAACR,CAAS,CAAC,GAEdW,EAAU,MAAM;AACV,QAAAV,KAAQ,CAACK,EAAI;AACf;AAGF,UAAMM,IAAUN,EAAI,SAEdO,IAAW,IAAI;AAAA,MACnB,CAAWC,MAAA;AACH,cAAA,CAACC,CAAK,IAAID;AAChB,QAAIC,GAAO,mBACQN,EAAA,GACjBI,EAAS,WAAW;AAAA,MAExB;AAAA,MACA;AAAA,QACE,WAAW;AAAA;AAAA,MAAA;AAAA,IAEf;AAEA,WAAAA,EAAS,QAAQD,CAAO,GAEjB,MAAM;AACX,MAAAC,EAAS,WAAW;AAAA,IACtB;AAAA,EAAA,GACC,CAACJ,GAAkBR,CAAI,CAAC,GAEpB,EAAC,KAAAK,EAAG;AACb;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __module as r } from "../../../../../../../_virtual/
|
|
1
|
+
import { __module as r } from "../../../../../../../_virtual/index10.js";
|
|
2
2
|
import { __require as o } from "../cjs/use-sync-external-store-shim.production.js";
|
|
3
3
|
import { __require as i } from "../cjs/use-sync-external-store-shim.development.js";
|
|
4
4
|
var e;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopify/shop-minis-react",
|
|
3
3
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"typescript": ">=5.0.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@shopify/shop-minis-platform": "0.12.
|
|
49
|
+
"@shopify/shop-minis-platform": "0.12.1",
|
|
50
50
|
"@tailwindcss/vite": "4.1.8",
|
|
51
51
|
"@tanstack/react-query": "5.86.0",
|
|
52
52
|
"@types/color": "3.0.6",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {useContentImpression} from '../../internal/useContentImpression'
|
|
1
2
|
import {useShopActions} from '../../internal/useShopActions'
|
|
2
3
|
|
|
3
4
|
import {LongPressDetector} from './long-press-detector'
|
|
@@ -10,16 +11,22 @@ export function ContentMonitor({
|
|
|
10
11
|
children: React.ReactNode
|
|
11
12
|
}) {
|
|
12
13
|
const {showFeedbackSheet} = useShopActions()
|
|
14
|
+
const {ref} = useContentImpression({
|
|
15
|
+
publicId: publicId ?? '',
|
|
16
|
+
skip: !publicId,
|
|
17
|
+
})
|
|
13
18
|
|
|
14
19
|
return (
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
<div ref={ref}>
|
|
21
|
+
<LongPressDetector
|
|
22
|
+
onLongPress={() => {
|
|
23
|
+
if (!publicId) return
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
showFeedbackSheet({publicId})
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</LongPressDetector>
|
|
30
|
+
</div>
|
|
24
31
|
)
|
|
25
32
|
}
|