@shopify/shop-minis-react 0.2.5 → 0.2.6
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/commerce/product-card.js +61 -58
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/commerce/product-link.js +144 -131
- package/dist/components/commerce/product-link.js.map +1 -1
- package/eslint/rules/validate-manifest.cjs +99 -1
- package/package.json +1 -1
- package/src/components/commerce/product-card.test.tsx +68 -0
- package/src/components/commerce/product-card.tsx +10 -2
- package/src/components/commerce/product-link.test.tsx +81 -0
- package/src/components/commerce/product-link.tsx +36 -10
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import * as
|
|
3
|
-
import { cva as
|
|
4
|
-
import { useShopNavigation as
|
|
5
|
-
import { useSavedProductsActions as
|
|
6
|
-
import { formatMoney as
|
|
7
|
-
import { cn as
|
|
8
|
-
import { Touchable as
|
|
9
|
-
import { Card as
|
|
10
|
-
import { FavoriteButton as
|
|
11
|
-
import
|
|
12
|
-
import { Root as
|
|
13
|
-
const
|
|
1
|
+
import { jsxs as d, jsx as t, Fragment as S } from "react/jsx-runtime";
|
|
2
|
+
import * as k from "react";
|
|
3
|
+
import { cva as D } 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 q } from "../../hooks/navigation/useShopNavigation.js";
|
|
5
|
+
import { useSavedProductsActions as B } from "../../hooks/user/useSavedProductsActions.js";
|
|
6
|
+
import { formatMoney as R } from "../../lib/formatMoney.js";
|
|
7
|
+
import { cn as a } from "../../lib/utils.js";
|
|
8
|
+
import { Touchable as V } from "../atoms/touchable.js";
|
|
9
|
+
import { Card as U, CardContent as _, CardAction as E } from "../ui/card.js";
|
|
10
|
+
import { FavoriteButton as G } from "./favorite-button.js";
|
|
11
|
+
import H from "../../shop-minis-react/node_modules/.pnpm/lucide-react@0.513.0_react@19.1.0/node_modules/lucide-react/dist/esm/icons/star.js";
|
|
12
|
+
import { Root as J } 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";
|
|
13
|
+
const K = D("", {
|
|
14
14
|
variants: {
|
|
15
15
|
layout: {
|
|
16
16
|
horizontal: "w-full !flex-row items-center gap-3 px-4 py-3",
|
|
@@ -27,163 +27,166 @@ const H = O("", {
|
|
|
27
27
|
discount: "none"
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
function
|
|
30
|
+
function Q({
|
|
31
31
|
className: e,
|
|
32
|
-
layout:
|
|
33
|
-
discount:
|
|
34
|
-
asChild:
|
|
35
|
-
onPress:
|
|
36
|
-
...
|
|
32
|
+
layout: r,
|
|
33
|
+
discount: o,
|
|
34
|
+
asChild: i = !1,
|
|
35
|
+
onPress: n,
|
|
36
|
+
...l
|
|
37
37
|
}) {
|
|
38
38
|
return /* @__PURE__ */ t(
|
|
39
|
-
|
|
39
|
+
V,
|
|
40
40
|
{
|
|
41
|
-
onClick:
|
|
41
|
+
onClick: n,
|
|
42
42
|
whileTap: { opacity: 0.7 },
|
|
43
43
|
transition: {
|
|
44
44
|
opacity: { type: "tween", duration: 0.08, ease: "easeInOut" }
|
|
45
45
|
},
|
|
46
46
|
children: /* @__PURE__ */ t(
|
|
47
|
-
|
|
47
|
+
i ? J : U,
|
|
48
48
|
{
|
|
49
|
-
className:
|
|
50
|
-
|
|
49
|
+
className: a(
|
|
50
|
+
K({ layout: r, discount: o }),
|
|
51
51
|
"border-0 bg-white rounded-xl shadow-lg shadow-black/10",
|
|
52
52
|
e
|
|
53
53
|
),
|
|
54
|
-
...
|
|
54
|
+
...l
|
|
55
55
|
}
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
|
-
function
|
|
60
|
+
function W({
|
|
61
61
|
className: e,
|
|
62
|
-
layout:
|
|
63
|
-
...
|
|
62
|
+
layout: r = "horizontal",
|
|
63
|
+
...o
|
|
64
64
|
}) {
|
|
65
65
|
return /* @__PURE__ */ t(
|
|
66
66
|
"div",
|
|
67
67
|
{
|
|
68
68
|
"data-slot": "product-link-image",
|
|
69
|
-
className:
|
|
69
|
+
className: a(
|
|
70
70
|
"overflow-hidden rounded-md bg-muted",
|
|
71
|
-
|
|
71
|
+
r === "horizontal" ? "h-16 w-16 flex-shrink-0" : "aspect-square w-full",
|
|
72
72
|
e
|
|
73
73
|
),
|
|
74
|
-
...
|
|
74
|
+
...o
|
|
75
75
|
}
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
|
-
function
|
|
78
|
+
function X({
|
|
79
79
|
className: e,
|
|
80
|
-
layout:
|
|
81
|
-
...
|
|
80
|
+
layout: r = "horizontal",
|
|
81
|
+
...o
|
|
82
82
|
}) {
|
|
83
83
|
return /* @__PURE__ */ t(
|
|
84
|
-
|
|
84
|
+
_,
|
|
85
85
|
{
|
|
86
|
-
className:
|
|
87
|
-
|
|
86
|
+
className: a(
|
|
87
|
+
r === "horizontal" ? "flex-1 min-w-0 space-y-1 px-0 py-0" : "space-y-2",
|
|
88
88
|
e
|
|
89
89
|
),
|
|
90
|
-
...
|
|
90
|
+
...o
|
|
91
91
|
}
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
|
-
function
|
|
94
|
+
function Y({
|
|
95
95
|
className: e,
|
|
96
|
-
children:
|
|
97
|
-
...
|
|
96
|
+
children: r,
|
|
97
|
+
...o
|
|
98
98
|
}) {
|
|
99
99
|
return /* @__PURE__ */ t(
|
|
100
100
|
"h3",
|
|
101
101
|
{
|
|
102
102
|
"data-slot": "product-link-title",
|
|
103
|
-
className:
|
|
103
|
+
className: a(
|
|
104
104
|
"text-sm font-medium leading-tight text-gray-900 truncate overflow-hidden whitespace-nowrap text-ellipsis",
|
|
105
105
|
e
|
|
106
106
|
),
|
|
107
|
-
...
|
|
108
|
-
children:
|
|
107
|
+
...o,
|
|
108
|
+
children: r
|
|
109
109
|
}
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
-
function
|
|
112
|
+
function Z({ className: e, ...r }) {
|
|
113
113
|
return /* @__PURE__ */ t(
|
|
114
114
|
"div",
|
|
115
115
|
{
|
|
116
116
|
"data-slot": "product-link-price",
|
|
117
|
-
className:
|
|
118
|
-
...
|
|
117
|
+
className: a("flex items-center gap-2", e),
|
|
118
|
+
...r
|
|
119
119
|
}
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
|
-
function
|
|
122
|
+
function $({
|
|
123
123
|
className: e,
|
|
124
|
-
...
|
|
124
|
+
...r
|
|
125
125
|
}) {
|
|
126
126
|
return /* @__PURE__ */ t(
|
|
127
127
|
"span",
|
|
128
128
|
{
|
|
129
129
|
"data-slot": "product-link-current-price",
|
|
130
|
-
className:
|
|
131
|
-
...
|
|
130
|
+
className: a("text-sm font-semibold text-gray-900", e),
|
|
131
|
+
...r
|
|
132
132
|
}
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
|
-
function
|
|
135
|
+
function tt({
|
|
136
136
|
className: e,
|
|
137
|
-
...
|
|
137
|
+
...r
|
|
138
138
|
}) {
|
|
139
139
|
return /* @__PURE__ */ t(
|
|
140
140
|
"span",
|
|
141
141
|
{
|
|
142
142
|
"data-slot": "product-link-original-price",
|
|
143
|
-
className:
|
|
144
|
-
...
|
|
143
|
+
className: a("text-sm text-gray-500 line-through", e),
|
|
144
|
+
...r
|
|
145
145
|
}
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
|
-
function
|
|
148
|
+
function et({
|
|
149
149
|
className: e,
|
|
150
|
-
...
|
|
150
|
+
...r
|
|
151
151
|
}) {
|
|
152
152
|
return /* @__PURE__ */ t(
|
|
153
153
|
"span",
|
|
154
154
|
{
|
|
155
155
|
"data-slot": "product-link-discount-price",
|
|
156
|
-
className:
|
|
157
|
-
...
|
|
156
|
+
className: a("text-sm font-semibold text-red-600", e),
|
|
157
|
+
...r
|
|
158
158
|
}
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
|
-
function
|
|
161
|
+
function rt({ className: e, ...r }) {
|
|
162
162
|
return /* @__PURE__ */ t(
|
|
163
163
|
"div",
|
|
164
164
|
{
|
|
165
165
|
"data-slot": "product-link-rating",
|
|
166
|
-
className:
|
|
166
|
+
className: a(
|
|
167
167
|
"flex items-center gap-1 text-xs text-muted-foreground",
|
|
168
168
|
e
|
|
169
169
|
),
|
|
170
|
-
...
|
|
170
|
+
...r
|
|
171
171
|
}
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
|
-
function
|
|
174
|
+
function ot({
|
|
175
175
|
className: e,
|
|
176
|
+
hideFavoriteAction: r = !1,
|
|
176
177
|
onPress: o,
|
|
177
|
-
filled:
|
|
178
|
-
|
|
178
|
+
filled: i = !1,
|
|
179
|
+
customAction: n,
|
|
180
|
+
...l
|
|
179
181
|
}) {
|
|
182
|
+
const c = r ? null : /* @__PURE__ */ t(G, { filled: i, onClick: o });
|
|
180
183
|
return /* @__PURE__ */ t(
|
|
181
|
-
|
|
184
|
+
E,
|
|
182
185
|
{
|
|
183
|
-
className:
|
|
184
|
-
...
|
|
186
|
+
className: a("flex-shrink-0 self-center px-0 py-0", e),
|
|
187
|
+
...l,
|
|
185
188
|
children: /* @__PURE__ */ t(
|
|
186
|
-
|
|
189
|
+
V,
|
|
187
190
|
{
|
|
188
191
|
stopPropagation: !0,
|
|
189
192
|
onClick: o,
|
|
@@ -192,102 +195,112 @@ function et({
|
|
|
192
195
|
opacity: { type: "tween", duration: 0.08, ease: "easeInOut" },
|
|
193
196
|
scale: { type: "tween", duration: 0.08, ease: "easeInOut" }
|
|
194
197
|
},
|
|
195
|
-
children: /* @__PURE__ */ t(
|
|
198
|
+
children: n ? /* @__PURE__ */ t(S, { children: n }) : c
|
|
196
199
|
}
|
|
197
200
|
)
|
|
198
201
|
}
|
|
199
202
|
);
|
|
200
203
|
}
|
|
201
|
-
function
|
|
204
|
+
function ht({
|
|
202
205
|
product: e,
|
|
203
|
-
hideFavoriteAction:
|
|
204
|
-
onClick:
|
|
206
|
+
hideFavoriteAction: r = !1,
|
|
207
|
+
onClick: o,
|
|
208
|
+
customAction: i,
|
|
209
|
+
onCustomActionClick: n
|
|
205
210
|
}) {
|
|
206
|
-
const { navigateToProduct:
|
|
207
|
-
id:
|
|
208
|
-
title:
|
|
209
|
-
featuredImage:
|
|
211
|
+
const { navigateToProduct: l } = q(), { saveProduct: c, unsaveProduct: N } = B(), {
|
|
212
|
+
id: s,
|
|
213
|
+
title: I,
|
|
214
|
+
featuredImage: L,
|
|
210
215
|
reviewAnalytics: C,
|
|
211
|
-
price:
|
|
212
|
-
compareAtPrice:
|
|
213
|
-
isFavorited:
|
|
214
|
-
selectedVariant:
|
|
215
|
-
defaultVariantId:
|
|
216
|
-
shop:
|
|
217
|
-
} = e, [
|
|
218
|
-
|
|
219
|
-
productId:
|
|
220
|
-
}),
|
|
221
|
-
}, [
|
|
222
|
-
|
|
223
|
-
|
|
216
|
+
price: p,
|
|
217
|
+
compareAtPrice: f,
|
|
218
|
+
isFavorited: F,
|
|
219
|
+
selectedVariant: h,
|
|
220
|
+
defaultVariantId: g,
|
|
221
|
+
shop: x
|
|
222
|
+
} = e, [y, b] = k.useState(F), v = C?.averageRating, z = C?.reviewCount, w = p?.amount ? R(p?.amount, p?.currencyCode) : void 0, T = L?.url, j = L?.altText || I, P = f?.amount ? R(f?.amount, f?.currencyCode) : void 0, A = P && P !== w, O = k.useCallback(() => {
|
|
223
|
+
l({
|
|
224
|
+
productId: s
|
|
225
|
+
}), o?.(e);
|
|
226
|
+
}, [l, s, o, e]), M = k.useCallback(async () => {
|
|
227
|
+
if (i || n) {
|
|
228
|
+
n?.();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const u = y;
|
|
232
|
+
b(!u);
|
|
224
233
|
try {
|
|
225
|
-
|
|
226
|
-
productId:
|
|
227
|
-
shopId:
|
|
228
|
-
productVariantId:
|
|
229
|
-
}) : await
|
|
230
|
-
productId:
|
|
231
|
-
shopId:
|
|
232
|
-
productVariantId:
|
|
234
|
+
u ? await N({
|
|
235
|
+
productId: s,
|
|
236
|
+
shopId: x.id,
|
|
237
|
+
productVariantId: h?.id || g
|
|
238
|
+
}) : await c({
|
|
239
|
+
productId: s,
|
|
240
|
+
shopId: x.id,
|
|
241
|
+
productVariantId: h?.id || g
|
|
233
242
|
});
|
|
234
243
|
} catch {
|
|
235
|
-
|
|
244
|
+
b(u);
|
|
236
245
|
}
|
|
237
246
|
}, [
|
|
238
|
-
x,
|
|
239
247
|
i,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
n,
|
|
249
|
+
y,
|
|
250
|
+
N,
|
|
251
|
+
s,
|
|
252
|
+
x.id,
|
|
253
|
+
h?.id,
|
|
254
|
+
g,
|
|
255
|
+
c
|
|
245
256
|
]);
|
|
246
|
-
return /* @__PURE__ */
|
|
247
|
-
|
|
257
|
+
return /* @__PURE__ */ d(
|
|
258
|
+
Q,
|
|
248
259
|
{
|
|
249
260
|
layout: "horizontal",
|
|
250
261
|
discount: A ? "small" : "none",
|
|
251
|
-
onPress:
|
|
262
|
+
onPress: O,
|
|
252
263
|
children: [
|
|
253
|
-
/* @__PURE__ */ t(
|
|
264
|
+
/* @__PURE__ */ t(W, { layout: "horizontal", children: T ? /* @__PURE__ */ t(
|
|
254
265
|
"img",
|
|
255
266
|
{
|
|
256
|
-
src:
|
|
257
|
-
alt:
|
|
267
|
+
src: T,
|
|
268
|
+
alt: j,
|
|
258
269
|
className: "h-full w-full object-cover"
|
|
259
270
|
}
|
|
260
271
|
) : /* @__PURE__ */ t("div", { className: "h-full w-full bg-muted flex items-center justify-center text-muted-foreground text-xs", children: "No Image" }) }),
|
|
261
|
-
/* @__PURE__ */
|
|
262
|
-
/* @__PURE__ */ t(
|
|
263
|
-
|
|
264
|
-
Array.from({ length: 5 }, (
|
|
265
|
-
|
|
272
|
+
/* @__PURE__ */ d(X, { layout: "horizontal", children: [
|
|
273
|
+
/* @__PURE__ */ t(Y, { children: I }),
|
|
274
|
+
z && v ? /* @__PURE__ */ t(rt, { children: /* @__PURE__ */ d("div", { className: "flex items-center gap-1", children: [
|
|
275
|
+
Array.from({ length: 5 }, (u, m) => /* @__PURE__ */ t(
|
|
276
|
+
H,
|
|
266
277
|
{
|
|
267
|
-
fill:
|
|
268
|
-
className:
|
|
278
|
+
fill: m < Math.floor(v) ? "currentColor" : "none",
|
|
279
|
+
className: a(
|
|
269
280
|
"h-3 w-3",
|
|
270
|
-
|
|
281
|
+
m < Math.floor(v) ? "text-primary" : "text-gray-300"
|
|
271
282
|
)
|
|
272
283
|
},
|
|
273
|
-
|
|
284
|
+
m
|
|
274
285
|
)),
|
|
275
|
-
/* @__PURE__ */
|
|
286
|
+
/* @__PURE__ */ d("span", { className: "text-xs text-gray-600 ml-1", children: [
|
|
276
287
|
"(",
|
|
277
|
-
|
|
288
|
+
z,
|
|
278
289
|
")"
|
|
279
290
|
] })
|
|
280
291
|
] }) }) : null,
|
|
281
|
-
/* @__PURE__ */ t(
|
|
282
|
-
/* @__PURE__ */ t(
|
|
283
|
-
/* @__PURE__ */ t(
|
|
284
|
-
] }) : /* @__PURE__ */ t(
|
|
292
|
+
/* @__PURE__ */ t(Z, { children: A ? /* @__PURE__ */ d(S, { children: [
|
|
293
|
+
/* @__PURE__ */ t(et, { children: w }),
|
|
294
|
+
/* @__PURE__ */ t(tt, { children: P })
|
|
295
|
+
] }) : /* @__PURE__ */ t($, { children: w }) })
|
|
285
296
|
] }),
|
|
286
|
-
|
|
287
|
-
|
|
297
|
+
/* @__PURE__ */ t(
|
|
298
|
+
ot,
|
|
288
299
|
{
|
|
289
|
-
filled:
|
|
290
|
-
onPress:
|
|
300
|
+
filled: y,
|
|
301
|
+
onPress: M,
|
|
302
|
+
customAction: i,
|
|
303
|
+
hideFavoriteAction: r
|
|
291
304
|
}
|
|
292
305
|
)
|
|
293
306
|
]
|
|
@@ -295,6 +308,6 @@ function pt({
|
|
|
295
308
|
);
|
|
296
309
|
}
|
|
297
310
|
export {
|
|
298
|
-
|
|
311
|
+
ht as ProductLink
|
|
299
312
|
};
|
|
300
313
|
//# 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 {Star} from 'lucide-react'\nimport {Slot as SlotPrimitive} from 'radix-ui'\n\nimport {useShopNavigation} from '../../hooks/navigation/useShopNavigation'\nimport {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'\nimport {formatMoney} from '../../lib/formatMoney'\nimport {cn} from '../../lib/utils'\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(\n 'flex items-center gap-1 text-xs text-muted-foreground',\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ProductLinkActions({\n className,\n onPress,\n filled = false,\n ...props\n}: React.ComponentProps<typeof CardAction> & {\n onPress?: () => void\n filled?: boolean\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 <FavoriteButton filled={filled} onClick={onPress} />\n </Touchable>\n </CardAction>\n )\n}\n\nexport interface ProductLinkProps {\n product: Product\n hideFavoriteAction?: boolean\n onClick?: (product: Product) => void\n}\n\n// Composed ProductLink component\nfunction ProductLink({\n product,\n hideFavoriteAction = false,\n onClick,\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 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 isFavoritedLocal,\n id,\n shop.id,\n selectedVariant?.id,\n defaultVariantId,\n saveProduct,\n unsaveProduct,\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 {reviewCount && averageRating ? (\n <ProductLinkRating>\n <div className=\"flex items-center gap-1\">\n {Array.from({length: 5}, (_, i) => (\n <Star\n key={i}\n fill={\n i < Math.floor(averageRating!) ? 'currentColor' : 'none'\n }\n className={cn(\n 'h-3 w-3',\n i < Math.floor(averageRating!)\n ? 'text-primary'\n : 'text-gray-300'\n )}\n />\n ))}\n <span className=\"text-xs text-gray-600 ml-1\">\n ({reviewCount})\n </span>\n </div>\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 {hideFavoriteAction ? null : (\n <ProductLinkActions\n filled={isFavoritedLocal}\n onPress={handleActionPress}\n />\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","filled","CardAction","FavoriteButton","ProductLink","product","hideFavoriteAction","onClick","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","_","i","Star","Fragment"],"mappings":";;;;;;;;;;;;AAgBA,MAAMA,IAAsBC,EAAI,IAAI;AAAA,EAClC,UAAU;AAAA,IACR,QAAQ;AAAA,MACN,YAAY;AAAA,MACZ,UAAU;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,OAAO;AAAA,IAAA;AAAA,EAEX;AAAA,EACA,iBAAiB;AAAA,IACf,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAEd,CAAC;AAWD,SAASC,EAAgB;AAAA,EACvB,WAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,SAAAC,IAAU;AAAA,EACV,SAAAC;AAAA,EACA,GAAGC;AACL,GAAyB;AAIrB,SAAA,gBAAAC;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,SAASH;AAAA,MACT,UAAU,EAAC,SAAS,IAAG;AAAA,MACvB,YAAY;AAAA,QACV,SAAS,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,YAAW;AAAA,MAC5D;AAAA,MAEA,UAAA,gBAAAE;AAAA,QAVSH,IAAUK,IAAqBC;AAAA,QAUvC;AAAA,UACC,WAAWC;AAAA,YACTb,EAAoB,EAAC,QAAAI,GAAQ,UAAAC,GAAS;AAAA,YACtC;AAAA,YACAF;AAAA,UACF;AAAA,UACC,GAAGK;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ;AAEA,SAASM,EAAiB;AAAA,EACxB,WAAAX;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,GAAGI;AACL,GAAuE;AAEnE,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI;AAAA,QACT;AAAA,QACAT,MAAW,eACP,4BACA;AAAA,QACJD;AAAA,MACF;AAAA,MACC,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASO,EAAgB;AAAA,EACvB,WAAAZ;AAAA,EACA,QAAAC,IAAS;AAAA,EACT,GAAGI;AACL,GAEG;AAEC,SAAA,gBAAAC;AAAA,IAACO;AAAA,IAAA;AAAA,MACC,WAAWH;AAAA,QACTT,MAAW,eACP,uCACA;AAAA,QACJD;AAAA,MACF;AAAA,MACC,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASS,EAAiB;AAAA,EACxB,WAAAd;AAAA,EACA,UAAAe;AAAA,EACA,GAAGV;AACL,GAA+B;AAE3B,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI;AAAA,QACT;AAAA,QACAV;AAAA,MACF;AAAA,MACC,GAAGK;AAAA,MAEH,UAAAU;AAAA,IAAA;AAAA,EACH;AAEJ;AAEA,SAASC,EAAiB,EAAC,WAAAhB,GAAW,GAAGK,KAAqC;AAE1E,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI,EAAG,2BAA2BV,CAAS;AAAA,MACjD,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASY,EAAwB;AAAA,EAC/B,WAAAjB;AAAA,EACA,GAAGK;AACL,GAAiC;AAE7B,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI,EAAG,uCAAuCV,CAAS;AAAA,MAC7D,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASa,EAAyB;AAAA,EAChC,WAAAlB;AAAA,EACA,GAAGK;AACL,GAAiC;AAE7B,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI,EAAG,sCAAsCV,CAAS;AAAA,MAC5D,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASc,EAAyB;AAAA,EAChC,WAAAnB;AAAA,EACA,GAAGK;AACL,GAAiC;AAE7B,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI,EAAG,sCAAsCV,CAAS;AAAA,MAC5D,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASe,GAAkB,EAAC,WAAApB,GAAW,GAAGK,KAAqC;AAE3E,SAAA,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAWI;AAAA,QACT;AAAA,QACAV;AAAA,MACF;AAAA,MACC,GAAGK;AAAA,IAAA;AAAA,EACN;AAEJ;AAEA,SAASgB,GAAmB;AAAA,EAC1B,WAAArB;AAAA,EACA,SAAAI;AAAA,EACA,QAAAkB,IAAS;AAAA,EACT,GAAGjB;AACL,GAGG;AAEC,SAAA,gBAAAC;AAAA,IAACiB;AAAA,IAAA;AAAA,MACC,WAAWb,EAAG,uCAAuCV,CAAS;AAAA,MAC7D,GAAGK;AAAA,MAEJ,UAAA,gBAAAC;AAAA,QAACC;AAAA,QAAA;AAAA,UACC,iBAAe;AAAA,UACf,SAASH;AAAA,UACT,UAAU,EAAC,SAAS,KAAK,OAAO,KAAI;AAAA,UACpC,YAAY;AAAA,YACV,SAAS,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,YAAW;AAAA,YAC1D,OAAO,EAAC,MAAM,SAAS,UAAU,MAAM,MAAM,YAAW;AAAA,UAC1D;AAAA,UAEA,UAAC,gBAAAE,EAAAkB,GAAA,EAAe,QAAAF,GAAgB,SAASlB,EAAS,CAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACpD;AAAA,EACF;AAEJ;AASA,SAASqB,GAAY;AAAA,EACnB,SAAAC;AAAA,EACA,oBAAAC,IAAqB;AAAA,EACrB,SAAAC;AACF,GAAqB;AACb,QAAA,EAAC,mBAAAC,EAAiB,IAAIC,EAAkB,GACxC,EAAC,aAAAC,GAAa,eAAAC,EAAa,IAAIC,EAAwB,GAEvD;AAAA,IACJ,IAAAC;AAAA,IACA,OAAAC;AAAA,IACA,eAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,OAAAC;AAAA,IACA,gBAAAC;AAAA,IACA,aAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,kBAAAC;AAAA,IACA,MAAAC;AAAA,EAAA,IACEjB,GAGE,CAACkB,GAAkBC,CAAmB,IAAIC,EAAM,SAASN,CAAW,GAEpEO,IAAgBV,GAAiB,eACjCW,IAAcX,GAAiB,aAC/BY,IAASX,GAAO,SAClBY,EAAYZ,GAAO,QAAQA,GAAO,YAAY,IAC9C,QACEa,IAAWf,GAAe,KAC1BgB,IAAehB,GAAe,WAAWD,GACzCkB,IAAuBd,GAAgB,SACzCW,EAAYX,GAAgB,QAAQA,GAAgB,YAAY,IAChE,QACEe,IAAcD,KAAwBA,MAAyBJ,GAE/DM,IAAcT,EAAM,YAAY,MAAM;AACxB,IAAAjB,EAAA;AAAA,MAChB,WAAWK;AAAA,IAAA,CACZ,GACDN,IAAUF,CAAO;AAAA,KAChB,CAACG,GAAmBK,GAAIN,GAASF,CAAO,CAAC,GAEtC8B,IAAoBV,EAAM,YAAY,YAAY;AACtD,UAAMW,IAAgBb;AAGtB,IAAAC,EAAoB,CAACY,CAAa;AAE9B,QAAA;AACF,MAAIA,IACF,MAAMzB,EAAc;AAAA,QAClB,WAAWE;AAAA,QACX,QAAQS,EAAK;AAAA,QACb,kBAAkBF,GAAiB,MAAMC;AAAA,MAAA,CAC1C,IAED,MAAMX,EAAY;AAAA,QAChB,WAAWG;AAAA,QACX,QAAQS,EAAK;AAAA,QACb,kBAAkBF,GAAiB,MAAMC;AAAA,MAAA,CAC1C;AAAA,YAEW;AAEd,MAAAG,EAAoBY,CAAa;AAAA,IAAA;AAAA,EACnC,GACC;AAAA,IACDb;AAAA,IACAV;AAAA,IACAS,EAAK;AAAA,IACLF,GAAiB;AAAA,IACjBC;AAAA,IACAX;AAAA,IACAC;AAAA,EAAA,CACD;AAGC,SAAA,gBAAA0B;AAAA,IAAC3D;AAAA,IAAA;AAAA,MACC,QAAO;AAAA,MACP,UAAUuD,IAAc,UAAU;AAAA,MAClC,SAASC;AAAA,MAET,UAAA;AAAA,QAAC,gBAAAjD,EAAAK,GAAA,EAAiB,QAAO,cACtB,UACCwC,IAAA,gBAAA7C;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK6C;AAAA,YACL,KAAKC;AAAA,YACL,WAAU;AAAA,UAAA;AAAA,QAAA,IAGX,gBAAA9C,EAAA,OAAA,EAAI,WAAU,yFAAwF,qBAEvG,CAAA,GAEJ;AAAA,QAEA,gBAAAoD,EAAC9C,GAAgB,EAAA,QAAO,cACtB,UAAA;AAAA,UAAA,gBAAAN,EAACQ,KAAkB,UAAMqB,EAAA,CAAA;AAAA,UAExBa,KAAeD,IACd,gBAAAzC,EAACc,MACC,UAAC,gBAAAsC,EAAA,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,YAAA,MAAM,KAAK,EAAC,QAAQ,KAAI,CAACC,GAAGC,MAC3B,gBAAAtD;AAAA,cAACuD;AAAA,cAAA;AAAA,gBAEC,MACED,IAAI,KAAK,MAAMb,CAAc,IAAI,iBAAiB;AAAA,gBAEpD,WAAWrC;AAAA,kBACT;AAAA,kBACAkD,IAAI,KAAK,MAAMb,CAAc,IACzB,iBACA;AAAA,gBAAA;AAAA,cACN;AAAA,cATKa;AAAA,YAAA,CAWR;AAAA,YACD,gBAAAF,EAAC,QAAK,EAAA,WAAU,8BAA6B,UAAA;AAAA,cAAA;AAAA,cACzCV;AAAA,cAAY;AAAA,YAAA,EAChB,CAAA;AAAA,UAAA,EACF,CAAA,EACF,CAAA,IACE;AAAA,UAEJ,gBAAA1C,EAACU,GACE,EAAA,UAAAsC,IAEG,gBAAAI,EAAAI,GAAA,EAAA,UAAA;AAAA,YAAA,gBAAAxD,EAACa,KAA0B,UAAO8B,EAAA,CAAA;AAAA,YAClC,gBAAA3C,EAACY,KACE,UACHmC,EAAA,CAAA;AAAA,UAAA,EACF,CAAA,IAEA,gBAAA/C,EAACW,GAAyB,EAAA,UAAAgC,EAAA,CAAO,EAErC,CAAA;AAAA,QAAA,GACF;AAAA,QAECtB,IAAqB,OACpB,gBAAArB;AAAA,UAACe;AAAA,UAAA;AAAA,YACC,QAAQuB;AAAA,YACR,SAASY;AAAA,UAAA;AAAA,QAAA;AAAA,MACX;AAAA,IAAA;AAAA,EAEJ;AAEJ;"}
|
|
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 {Star} from 'lucide-react'\nimport {Slot as SlotPrimitive} from 'radix-ui'\n\nimport {useShopNavigation} from '../../hooks/navigation/useShopNavigation'\nimport {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'\nimport {formatMoney} from '../../lib/formatMoney'\nimport {cn} from '../../lib/utils'\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(\n 'flex items-center gap-1 text-xs text-muted-foreground',\n className\n )}\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 type ProductLinkProps = {\n product: Product\n hideFavoriteAction?: boolean\n onClick?: (product: Product) => void\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}: 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 {reviewCount && averageRating ? (\n <ProductLinkRating>\n <div className=\"flex items-center gap-1\">\n {Array.from({length: 5}, (_, i) => (\n <Star\n key={i}\n fill={\n i < Math.floor(averageRating!) ? 'currentColor' : 'none'\n }\n className={cn(\n 'h-3 w-3',\n i < Math.floor(averageRating!)\n ? 'text-primary'\n : 'text-gray-300'\n )}\n />\n ))}\n <span className=\"text-xs text-gray-600 ml-1\">\n ({reviewCount})\n </span>\n </div>\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","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","_","i","Star"],"mappings":"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"}
|
|
@@ -82,6 +82,10 @@ module.exports = {
|
|
|
82
82
|
messages: {
|
|
83
83
|
missingScope:
|
|
84
84
|
'{{source}} requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array.',
|
|
85
|
+
missingScopeProductCard:
|
|
86
|
+
'Component "ProductCard" requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array or set favoriteButtonDisabled to true on all ProductCard instances.',
|
|
87
|
+
missingScopeProductLink:
|
|
88
|
+
'Component "ProductLink" requires scope "{{scope}}" in src/manifest.json. Add "{{scope}}" to the "scopes" array or set hideFavoriteAction to true (or provide a customAction) on all ProductLink instances.',
|
|
85
89
|
missingPermission:
|
|
86
90
|
'{{reason}} requires permission "{{permission}}" in src/manifest.json. Add "{{permission}}" to the "permissions" array.',
|
|
87
91
|
missingTrustedDomain:
|
|
@@ -105,6 +109,8 @@ module.exports = {
|
|
|
105
109
|
const requiredPermissions = new Set()
|
|
106
110
|
const requiredDomains = new Set()
|
|
107
111
|
const fixedIssues = new Set()
|
|
112
|
+
// Track how components are actually used (e.g., with specific props)
|
|
113
|
+
const componentUsagePatterns = new Map()
|
|
108
114
|
|
|
109
115
|
// Check module-level cache first to avoid repeated file I/O
|
|
110
116
|
if (manifestPathCache && fs.existsSync(manifestPathCache)) {
|
|
@@ -361,6 +367,78 @@ module.exports = {
|
|
|
361
367
|
}
|
|
362
368
|
},
|
|
363
369
|
|
|
370
|
+
// Track ProductCard and ProductLink usage with disabled favorite functionality
|
|
371
|
+
JSXElement(node) {
|
|
372
|
+
const elementName = node.openingElement.name.name
|
|
373
|
+
|
|
374
|
+
// Handle ProductCard with favoriteButtonDisabled prop
|
|
375
|
+
if (elementName === 'ProductCard') {
|
|
376
|
+
// Check if favoriteButtonDisabled prop is present and true
|
|
377
|
+
const favoriteDisabledProp = node.openingElement.attributes.find(
|
|
378
|
+
attr =>
|
|
379
|
+
attr.type === 'JSXAttribute' &&
|
|
380
|
+
attr.name?.name === 'favoriteButtonDisabled'
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
const isDisabled =
|
|
384
|
+
favoriteDisabledProp &&
|
|
385
|
+
// Shorthand syntax: <ProductCard favoriteButtonDisabled />
|
|
386
|
+
(favoriteDisabledProp.value === null ||
|
|
387
|
+
// Explicit true: <ProductCard favoriteButtonDisabled={true} />
|
|
388
|
+
(favoriteDisabledProp.value?.type === 'JSXExpressionContainer' &&
|
|
389
|
+
favoriteDisabledProp.value?.expression?.type === 'Literal' &&
|
|
390
|
+
favoriteDisabledProp.value?.expression?.value === true))
|
|
391
|
+
|
|
392
|
+
// Track usage pattern
|
|
393
|
+
const componentPath = 'commerce/product-card'
|
|
394
|
+
if (!componentUsagePatterns.has(componentPath)) {
|
|
395
|
+
componentUsagePatterns.set(componentPath, {
|
|
396
|
+
allDisabled: true,
|
|
397
|
+
hasUsage: true,
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
const pattern = componentUsagePatterns.get(componentPath)
|
|
401
|
+
pattern.allDisabled = pattern.allDisabled && isDisabled
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Handle ProductLink with hideFavoriteAction or customAction props
|
|
405
|
+
if (elementName === 'ProductLink') {
|
|
406
|
+
// Check if hideFavoriteAction prop is present and true
|
|
407
|
+
const hideFavoriteProp = node.openingElement.attributes.find(
|
|
408
|
+
attr =>
|
|
409
|
+
attr.type === 'JSXAttribute' &&
|
|
410
|
+
attr.name?.name === 'hideFavoriteAction'
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
// Check if customAction prop is present (replaces favorite action)
|
|
414
|
+
const customActionProp = node.openingElement.attributes.find(
|
|
415
|
+
attr =>
|
|
416
|
+
attr.type === 'JSXAttribute' && attr.name?.name === 'customAction'
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
const isFavoriteDisabled =
|
|
420
|
+
// hideFavoriteAction={true} or shorthand
|
|
421
|
+
(hideFavoriteProp &&
|
|
422
|
+
(hideFavoriteProp.value === null || // shorthand
|
|
423
|
+
(hideFavoriteProp.value?.type === 'JSXExpressionContainer' &&
|
|
424
|
+
hideFavoriteProp.value?.expression?.type === 'Literal' &&
|
|
425
|
+
hideFavoriteProp.value?.expression?.value === true))) ||
|
|
426
|
+
// customAction is provided (any truthy value replaces favorites)
|
|
427
|
+
customActionProp !== undefined
|
|
428
|
+
|
|
429
|
+
// Track usage pattern
|
|
430
|
+
const componentPath = 'commerce/product-link'
|
|
431
|
+
if (!componentUsagePatterns.has(componentPath)) {
|
|
432
|
+
componentUsagePatterns.set(componentPath, {
|
|
433
|
+
allDisabled: true,
|
|
434
|
+
hasUsage: true,
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
const pattern = componentUsagePatterns.get(componentPath)
|
|
438
|
+
pattern.allDisabled = pattern.allDisabled && isFavoriteDisabled
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
|
|
364
442
|
// Check JSX attributes for external URLs
|
|
365
443
|
JSXAttribute(node) {
|
|
366
444
|
if (!node.value || node.value.type !== 'Literal') {
|
|
@@ -489,6 +567,18 @@ module.exports = {
|
|
|
489
567
|
// Check scopes for components
|
|
490
568
|
usedComponents.forEach(
|
|
491
569
|
({path: componentPath, name: componentName, node}) => {
|
|
570
|
+
// Special handling for components with conditional favorite functionality
|
|
571
|
+
if (
|
|
572
|
+
componentPath === 'commerce/product-card' ||
|
|
573
|
+
componentPath === 'commerce/product-link'
|
|
574
|
+
) {
|
|
575
|
+
const usagePattern = componentUsagePatterns.get(componentPath)
|
|
576
|
+
// Skip scope requirement if all usages have favorites disabled
|
|
577
|
+
if (usagePattern?.hasUsage && usagePattern?.allDisabled) {
|
|
578
|
+
return // No scope required when favorite functionality is disabled
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
492
582
|
const componentData = componentScopesMap[componentPath]
|
|
493
583
|
if (
|
|
494
584
|
!componentData ||
|
|
@@ -597,9 +687,17 @@ module.exports = {
|
|
|
597
687
|
const sourceName = issue.hookName || issue.componentName
|
|
598
688
|
const sourceType = issue.hookName ? 'Hook' : 'Component'
|
|
599
689
|
|
|
690
|
+
// Use custom message for ProductCard and ProductLink
|
|
691
|
+
let messageId = 'missingScope'
|
|
692
|
+
if (issue.componentName === 'ProductCard') {
|
|
693
|
+
messageId = 'missingScopeProductCard'
|
|
694
|
+
} else if (issue.componentName === 'ProductLink') {
|
|
695
|
+
messageId = 'missingScopeProductLink'
|
|
696
|
+
}
|
|
697
|
+
|
|
600
698
|
context.report({
|
|
601
699
|
loc: {line: 1, column: 0},
|
|
602
|
-
messageId
|
|
700
|
+
messageId,
|
|
603
701
|
data: {
|
|
604
702
|
source: `${sourceType} "${sourceName}"`,
|
|
605
703
|
scope: issue.scope,
|