@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.
@@ -1,16 +1,16 @@
1
- import { jsxs as c, jsx as t, Fragment as j } from "react/jsx-runtime";
2
- import * as w from "react";
3
- import { cva as O } 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 M } from "../../hooks/navigation/useShopNavigation.js";
5
- import { useSavedProductsActions as D } from "../../hooks/user/useSavedProductsActions.js";
6
- import { formatMoney as z } from "../../lib/formatMoney.js";
7
- import { cn as n } from "../../lib/utils.js";
8
- import { Touchable as T } from "../atoms/touchable.js";
9
- import { Card as q, CardContent as B, CardAction as U } from "../ui/card.js";
10
- import { FavoriteButton as _ } from "./favorite-button.js";
11
- import E 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 G } 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 H = O("", {
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 J({
30
+ function Q({
31
31
  className: e,
32
- layout: o,
33
- discount: r,
34
- asChild: a = !1,
35
- onPress: l,
36
- ...s
32
+ layout: r,
33
+ discount: o,
34
+ asChild: i = !1,
35
+ onPress: n,
36
+ ...l
37
37
  }) {
38
38
  return /* @__PURE__ */ t(
39
- T,
39
+ V,
40
40
  {
41
- onClick: l,
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
- a ? G : q,
47
+ i ? J : U,
48
48
  {
49
- className: n(
50
- H({ layout: o, discount: r }),
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
- ...s
54
+ ...l
55
55
  }
56
56
  )
57
57
  }
58
58
  );
59
59
  }
60
- function K({
60
+ function W({
61
61
  className: e,
62
- layout: o = "horizontal",
63
- ...r
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: n(
69
+ className: a(
70
70
  "overflow-hidden rounded-md bg-muted",
71
- o === "horizontal" ? "h-16 w-16 flex-shrink-0" : "aspect-square w-full",
71
+ r === "horizontal" ? "h-16 w-16 flex-shrink-0" : "aspect-square w-full",
72
72
  e
73
73
  ),
74
- ...r
74
+ ...o
75
75
  }
76
76
  );
77
77
  }
78
- function Q({
78
+ function X({
79
79
  className: e,
80
- layout: o = "horizontal",
81
- ...r
80
+ layout: r = "horizontal",
81
+ ...o
82
82
  }) {
83
83
  return /* @__PURE__ */ t(
84
- B,
84
+ _,
85
85
  {
86
- className: n(
87
- o === "horizontal" ? "flex-1 min-w-0 space-y-1 px-0 py-0" : "space-y-2",
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
- ...r
90
+ ...o
91
91
  }
92
92
  );
93
93
  }
94
- function W({
94
+ function Y({
95
95
  className: e,
96
- children: o,
97
- ...r
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: n(
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
- ...r,
108
- children: o
107
+ ...o,
108
+ children: r
109
109
  }
110
110
  );
111
111
  }
112
- function X({ className: e, ...o }) {
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: n("flex items-center gap-2", e),
118
- ...o
117
+ className: a("flex items-center gap-2", e),
118
+ ...r
119
119
  }
120
120
  );
121
121
  }
122
- function Y({
122
+ function $({
123
123
  className: e,
124
- ...o
124
+ ...r
125
125
  }) {
126
126
  return /* @__PURE__ */ t(
127
127
  "span",
128
128
  {
129
129
  "data-slot": "product-link-current-price",
130
- className: n("text-sm font-semibold text-gray-900", e),
131
- ...o
130
+ className: a("text-sm font-semibold text-gray-900", e),
131
+ ...r
132
132
  }
133
133
  );
134
134
  }
135
- function Z({
135
+ function tt({
136
136
  className: e,
137
- ...o
137
+ ...r
138
138
  }) {
139
139
  return /* @__PURE__ */ t(
140
140
  "span",
141
141
  {
142
142
  "data-slot": "product-link-original-price",
143
- className: n("text-sm text-gray-500 line-through", e),
144
- ...o
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
- ...o
150
+ ...r
151
151
  }) {
152
152
  return /* @__PURE__ */ t(
153
153
  "span",
154
154
  {
155
155
  "data-slot": "product-link-discount-price",
156
- className: n("text-sm font-semibold text-red-600", e),
157
- ...o
156
+ className: a("text-sm font-semibold text-red-600", e),
157
+ ...r
158
158
  }
159
159
  );
160
160
  }
161
- function tt({ className: e, ...o }) {
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: n(
166
+ className: a(
167
167
  "flex items-center gap-1 text-xs text-muted-foreground",
168
168
  e
169
169
  ),
170
- ...o
170
+ ...r
171
171
  }
172
172
  );
173
173
  }
174
- function et({
174
+ function ot({
175
175
  className: e,
176
+ hideFavoriteAction: r = !1,
176
177
  onPress: o,
177
- filled: r = !1,
178
- ...a
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
- U,
184
+ E,
182
185
  {
183
- className: n("flex-shrink-0 self-center px-0 py-0", e),
184
- ...a,
186
+ className: a("flex-shrink-0 self-center px-0 py-0", e),
187
+ ...l,
185
188
  children: /* @__PURE__ */ t(
186
- T,
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(_, { filled: r, onClick: o })
198
+ children: n ? /* @__PURE__ */ t(S, { children: n }) : c
196
199
  }
197
200
  )
198
201
  }
199
202
  );
200
203
  }
201
- function pt({
204
+ function ht({
202
205
  product: e,
203
- hideFavoriteAction: o = !1,
204
- onClick: r
206
+ hideFavoriteAction: r = !1,
207
+ onClick: o,
208
+ customAction: i,
209
+ onCustomActionClick: n
205
210
  }) {
206
- const { navigateToProduct: a } = M(), { saveProduct: l, unsaveProduct: s } = D(), {
207
- id: i,
208
- title: P,
209
- featuredImage: N,
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: m,
212
- compareAtPrice: p,
213
- isFavorited: R,
214
- selectedVariant: f,
215
- defaultVariantId: h,
216
- shop: g
217
- } = e, [x, I] = w.useState(R), y = C?.averageRating, L = C?.reviewCount, v = m?.amount ? z(m?.amount, m?.currencyCode) : void 0, b = N?.url, S = N?.altText || P, k = p?.amount ? z(p?.amount, p?.currencyCode) : void 0, A = k && k !== v, V = w.useCallback(() => {
218
- a({
219
- productId: i
220
- }), r?.(e);
221
- }, [a, i, r, e]), F = w.useCallback(async () => {
222
- const d = x;
223
- I(!d);
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
- d ? await s({
226
- productId: i,
227
- shopId: g.id,
228
- productVariantId: f?.id || h
229
- }) : await l({
230
- productId: i,
231
- shopId: g.id,
232
- productVariantId: f?.id || h
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
- I(d);
244
+ b(u);
236
245
  }
237
246
  }, [
238
- x,
239
247
  i,
240
- g.id,
241
- f?.id,
242
- h,
243
- l,
244
- s
248
+ n,
249
+ y,
250
+ N,
251
+ s,
252
+ x.id,
253
+ h?.id,
254
+ g,
255
+ c
245
256
  ]);
246
- return /* @__PURE__ */ c(
247
- J,
257
+ return /* @__PURE__ */ d(
258
+ Q,
248
259
  {
249
260
  layout: "horizontal",
250
261
  discount: A ? "small" : "none",
251
- onPress: V,
262
+ onPress: O,
252
263
  children: [
253
- /* @__PURE__ */ t(K, { layout: "horizontal", children: b ? /* @__PURE__ */ t(
264
+ /* @__PURE__ */ t(W, { layout: "horizontal", children: T ? /* @__PURE__ */ t(
254
265
  "img",
255
266
  {
256
- src: b,
257
- alt: S,
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__ */ c(Q, { layout: "horizontal", children: [
262
- /* @__PURE__ */ t(W, { children: P }),
263
- L && y ? /* @__PURE__ */ t(tt, { children: /* @__PURE__ */ c("div", { className: "flex items-center gap-1", children: [
264
- Array.from({ length: 5 }, (d, u) => /* @__PURE__ */ t(
265
- E,
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: u < Math.floor(y) ? "currentColor" : "none",
268
- className: n(
278
+ fill: m < Math.floor(v) ? "currentColor" : "none",
279
+ className: a(
269
280
  "h-3 w-3",
270
- u < Math.floor(y) ? "text-primary" : "text-gray-300"
281
+ m < Math.floor(v) ? "text-primary" : "text-gray-300"
271
282
  )
272
283
  },
273
- u
284
+ m
274
285
  )),
275
- /* @__PURE__ */ c("span", { className: "text-xs text-gray-600 ml-1", children: [
286
+ /* @__PURE__ */ d("span", { className: "text-xs text-gray-600 ml-1", children: [
276
287
  "(",
277
- L,
288
+ z,
278
289
  ")"
279
290
  ] })
280
291
  ] }) }) : null,
281
- /* @__PURE__ */ t(X, { children: A ? /* @__PURE__ */ c(j, { children: [
282
- /* @__PURE__ */ t($, { children: v }),
283
- /* @__PURE__ */ t(Z, { children: k })
284
- ] }) : /* @__PURE__ */ t(Y, { children: v }) })
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
- o ? null : /* @__PURE__ */ t(
287
- et,
297
+ /* @__PURE__ */ t(
298
+ ot,
288
299
  {
289
- filled: x,
290
- onPress: F
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
- pt as ProductLink
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":"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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 {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":";;;;;;;;;;;;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,GAAyB;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,GAAyB;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,oBAAAsB,IAAqB;AAAA,EACrB,SAAAlB;AAAA,EACA,QAAAmB,IAAS;AAAA,EACT,cAAAC;AAAA,EACA,GAAGnB;AACL,GAKG;AACD,QAAMoB,IAAiBH,IAAqB,yBACzCI,GAAe,EAAA,QAAAH,GAAgB,SAASnB,GAAS;AAIlD,SAAA,gBAAAE;AAAA,IAACqB;AAAA,IAAA;AAAA,MACC,WAAWjB,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,UAEC,UAAAoB,IAAkB,gBAAAlB,EAAAsB,GAAA,EAAA,UAAAJ,EAAA,CAAa,IAAMC;AAAA,QAAA;AAAA,MAAA;AAAA,IACxC;AAAA,EACF;AAEJ;AAkBA,SAASI,GAAY;AAAA,EACnB,SAAAC;AAAA,EACA,oBAAAR,IAAqB;AAAA,EACrB,SAAAS;AAAA,EACA,cAAAP;AAAA,EACA,qBAAAQ;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,GACDP,IAAUD,CAAO;AAAA,KAChB,CAACG,GAAmBK,GAAIP,GAASD,CAAO,CAAC,GAEtC8B,IAAoBV,EAAM,YAAY,YAAY;AACtD,QAAI1B,KAAgBQ,GAAqB;AACjB,MAAAA,IAAA;AACtB;AAAA,IAAA;AAGF,UAAM6B,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,IACDrC;AAAA,IACAQ;AAAA,IACAgB;AAAA,IACAZ;AAAA,IACAE;AAAA,IACAS,EAAK;AAAA,IACLF,GAAiB;AAAA,IACjBC;AAAA,IACAX;AAAA,EAAA,CACD;AAGC,SAAA,gBAAA2B;AAAA,IAAC/D;AAAA,IAAA;AAAA,MACC,QAAO;AAAA,MACP,UAAU2D,IAAc,UAAU;AAAA,MAClC,SAASC;AAAA,MAET,UAAA;AAAA,QAAC,gBAAArD,EAAAK,GAAA,EAAiB,QAAO,cACtB,UACC4C,IAAA,gBAAAjD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAKiD;AAAA,YACL,KAAKC;AAAA,YACL,WAAU;AAAA,UAAA;AAAA,QAAA,IAGX,gBAAAlD,EAAA,OAAA,EAAI,WAAU,yFAAwF,qBAEvG,CAAA,GAEJ;AAAA,QAEA,gBAAAwD,EAAClD,GAAgB,EAAA,QAAO,cACtB,UAAA;AAAA,UAAA,gBAAAN,EAACQ,KAAkB,UAAMyB,EAAA,CAAA;AAAA,UAExBa,KAAeD,IACd,gBAAA7C,EAACc,MACC,UAAC,gBAAA0C,EAAA,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,YAAA,MAAM,KAAK,EAAC,QAAQ,KAAI,CAACC,GAAGC,MAC3B,gBAAA1D;AAAA,cAAC2D;AAAA,cAAA;AAAA,gBAEC,MACED,IAAI,KAAK,MAAMb,CAAc,IAAI,iBAAiB;AAAA,gBAEpD,WAAWzC;AAAA,kBACT;AAAA,kBACAsD,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,gBAAA9C,EAACU,GACE,EAAA,UAAA0C,IAEG,gBAAAI,EAAAlC,GAAA,EAAA,UAAA;AAAA,YAAA,gBAAAtB,EAACa,MAA0B,UAAOkC,EAAA,CAAA;AAAA,YAClC,gBAAA/C,EAACY,MACE,UACHuC,EAAA,CAAA;AAAA,UAAA,EACF,CAAA,IAEA,gBAAAnD,EAACW,GAAyB,EAAA,UAAAoC,EAAA,CAAO,EAErC,CAAA;AAAA,QAAA,GACF;AAAA,QAEA,gBAAA/C;AAAA,UAACe;AAAA,UAAA;AAAA,YACC,QAAQ2B;AAAA,YACR,SAASY;AAAA,YACT,cAAApC;AAAA,YACA,oBAAAF;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EACF;AAEJ;"}
@@ -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: 'missingScope',
700
+ messageId,
603
701
  data: {
604
702
  source: `${sourceType} "${sourceName}"`,
605
703
  scope: issue.scope,