@asteby/storefront-react 0.20.0 → 0.22.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/checkout.css CHANGED
@@ -328,3 +328,145 @@
328
328
  zoom: 0.78;
329
329
  }
330
330
  }
331
+
332
+ /*
333
+ * <ProductThumb>, <ProductPreview>, <ProductThumbStack>: la foto del producto y la línea de producto de la tienda.
334
+ * CSS plano sobre los tokens de la tienda (--foreground, --background, --border, --muted-foreground, --radius); en tema
335
+ * oscuro la superficie y el brillo salen de color-mix, sin colores escritos aquí. Va incluido en cart.css, checkout.css,
336
+ * account.css y ui.css (misma regla, un solo origen: src/ui/product-preview.css).
337
+ */
338
+ [data-sf-thumb] {
339
+ --_surface: color-mix(in srgb, var(--foreground, #111) 5%, var(--background, #fff));
340
+ position: relative;
341
+ display: inline-block;
342
+ flex: none;
343
+ box-sizing: border-box;
344
+ width: var(--sf-preview-size, 3.5rem);
345
+ aspect-ratio: var(--sf-preview-ratio, 1 / 1);
346
+ overflow: hidden;
347
+ border-radius: calc(var(--radius, 0.5rem) * 1.25);
348
+ background: var(--_surface);
349
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground, #111) 9%, transparent);
350
+ vertical-align: middle;
351
+ }
352
+ :root[data-theme="dark"] [data-sf-thumb] { --_surface: color-mix(in srgb, var(--foreground, #fff) 9%, var(--background, #111)); }
353
+ [data-sf-thumb] > img {
354
+ display: block;
355
+ width: 100%;
356
+ height: 100%;
357
+ box-sizing: border-box;
358
+ object-fit: contain;
359
+ padding: 6%;
360
+ transition: transform 220ms cubic-bezier(0.2, 0.7, 0.2, 1), opacity 180ms;
361
+ }
362
+ [data-sf-thumb][data-fit="cover"] > img { object-fit: cover; padding: 0; }
363
+ /* Fotos sobre fondo blanco: se funden con la superficie en tema claro (no hay caja blanca dentro del marco). */
364
+ [data-sf-thumb][data-fit="contain"] > img { mix-blend-mode: multiply; }
365
+ :root[data-theme="dark"] [data-sf-thumb] > img { mix-blend-mode: normal; }
366
+ [data-sf-thumb][data-state="loading"]::before {
367
+ content: "";
368
+ position: absolute;
369
+ inset: 0;
370
+ background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, var(--foreground, #111) 8%, transparent) 50%, transparent 80%);
371
+ background-size: 200% 100%;
372
+ animation: sf-thumb-shimmer 1.4s linear infinite;
373
+ pointer-events: none;
374
+ }
375
+ @keyframes sf-thumb-shimmer { from { background-position: 150% 0; } to { background-position: -50% 0; } }
376
+ [data-sf-thumb][data-zoom]:hover > img { transform: scale(1.07); }
377
+ @media (prefers-reduced-motion: reduce) {
378
+ [data-sf-thumb][data-state="loading"]::before { animation: none; }
379
+ [data-sf-thumb] > img { transition: none; }
380
+ [data-sf-thumb][data-zoom]:hover > img { transform: none; }
381
+ }
382
+
383
+ [data-sf-thumb-fallback] {
384
+ position: absolute;
385
+ inset: 0;
386
+ display: grid;
387
+ place-items: center;
388
+ color: color-mix(in srgb, var(--foreground, #111) 42%, transparent);
389
+ background: linear-gradient(145deg, color-mix(in srgb, var(--foreground, #111) 3%, transparent), color-mix(in srgb, var(--foreground, #111) 9%, transparent));
390
+ }
391
+ [data-sf-thumb-initial] {
392
+ font-size: calc(var(--sf-preview-size, 3.5rem) * 0.36);
393
+ font-weight: 600;
394
+ letter-spacing: 0.02em;
395
+ line-height: 1;
396
+ }
397
+
398
+ [data-sf-thumb-qty] {
399
+ position: absolute;
400
+ top: 0.2rem;
401
+ right: 0.2rem;
402
+ min-width: 1.25rem;
403
+ height: 1.25rem;
404
+ padding: 0 0.3rem;
405
+ box-sizing: border-box;
406
+ display: inline-grid;
407
+ place-items: center;
408
+ border-radius: 999px;
409
+ font-size: 0.7rem;
410
+ font-weight: 700;
411
+ font-variant-numeric: tabular-nums;
412
+ background: var(--sf-summary-qty-bg, color-mix(in srgb, var(--foreground, #111) 78%, transparent));
413
+ color: var(--sf-summary-qty-fg, var(--background, #fff));
414
+ }
415
+
416
+ /* Línea de producto: foto | texto | importe. */
417
+ [data-sf-preview] {
418
+ display: flex;
419
+ align-items: center;
420
+ gap: 0.875rem;
421
+ min-width: 0;
422
+ }
423
+ [data-sf-preview][data-missing] [data-sf-preview-name] { font-style: italic; color: var(--muted-foreground, color-mix(in srgb, currentColor 60%, transparent)); }
424
+ [data-sf-preview-media] { flex: none; display: inline-flex; }
425
+ [data-sf-preview-body] { display: grid; flex: 1 1 auto; gap: 0.125rem; min-width: 0; }
426
+ [data-sf-preview-name] {
427
+ display: -webkit-box;
428
+ -webkit-line-clamp: 2;
429
+ -webkit-box-orient: vertical;
430
+ overflow: hidden;
431
+ overflow-wrap: anywhere;
432
+ font-size: 0.9375rem;
433
+ font-weight: 600;
434
+ line-height: 1.3;
435
+ }
436
+ [data-sf-preview-name] a { color: inherit; text-decoration: none; }
437
+ [data-sf-preview-name] a:hover { text-decoration: underline; }
438
+ [data-sf-preview-variant],
439
+ [data-sf-preview-sku],
440
+ [data-sf-preview-meta] {
441
+ font-size: 0.8125rem;
442
+ line-height: 1.35;
443
+ color: var(--muted-foreground, color-mix(in srgb, currentColor 66%, transparent));
444
+ }
445
+ [data-sf-preview-sku] { font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
446
+ [data-sf-preview-total] {
447
+ flex: none;
448
+ align-self: flex-start;
449
+ font-size: 0.9375rem;
450
+ font-weight: 700;
451
+ font-variant-numeric: tabular-nums;
452
+ text-align: right;
453
+ }
454
+
455
+ /* Fotos apiladas (tarjeta de pedido). */
456
+ [data-sf-thumb-stack] { display: inline-flex; align-items: center; }
457
+ [data-sf-thumb-stack] > * { box-shadow: 0 0 0 2px var(--card, var(--background, #fff)), inset 0 0 0 1px color-mix(in srgb, var(--foreground, #111) 9%, transparent); }
458
+ [data-sf-thumb-stack] > * + * { margin-inline-start: -0.625rem; }
459
+ [data-sf-thumb-more] {
460
+ display: inline-grid;
461
+ place-items: center;
462
+ flex: none;
463
+ box-sizing: border-box;
464
+ width: var(--sf-preview-size, 2.75rem);
465
+ aspect-ratio: 1 / 1;
466
+ border-radius: calc(var(--radius, 0.5rem) * 1.25);
467
+ font-size: 0.8125rem;
468
+ font-weight: 600;
469
+ font-variant-numeric: tabular-nums;
470
+ color: var(--muted-foreground, color-mix(in srgb, currentColor 66%, transparent));
471
+ background: color-mix(in srgb, var(--foreground, #111) 7%, var(--background, #fff));
472
+ }
@@ -14,6 +14,10 @@ type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> &
14
14
  emailHint: string;
15
15
  phoneHint: string;
16
16
  lookupFilled: string;
17
+ /** Tooltip and note of a state the postal code defined (locked). */
18
+ provinceLocked: string;
19
+ /** Discreet action that releases the locked state so the buyer can fix the postal code. */
20
+ changePostalCode: string;
17
21
  };
18
22
  declare function addressFieldsMessages(locale?: string | null, overrides?: Partial<AddressFieldsMessages> | null): AddressFieldsMessages;
19
23
  type FieldLayoutClassNames = CheckoutFieldClassNames & {
@@ -88,8 +92,11 @@ type AddressFieldsProps = Common & {
88
92
  /**
89
93
  * Once `lookupPostalCode` fills the state field (never over what the buyer already typed), lock it: a plain
90
94
  * `<input>` turns read-only; a `<select>` (from `provinces`) turns disabled with a hidden field that keeps
91
- * submitting its value. The buyer corrects the state by correcting the postal code. Default false: the filled
92
- * state stays as editable as any other field. Marked with `data-sf-locked="1"` for the shop's own styling.
95
+ * submitting its value. The state is the postal code's: the field shows a lock, the tooltip `provinceLocked` and a
96
+ * discreet `changePostalCode` action; editing the postal code releases the lock and clears what the lookup filled
97
+ * (also when the new code resolves nothing), so a stale state is never left behind. Default TRUE (the server
98
+ * validates the pair again; set `false` to keep the filled state as editable as any field). Marked with
99
+ * `data-sf-locked="1"` for the shop's own styling.
93
100
  */
94
101
  lockProvinceOnFill?: boolean;
95
102
  /** Rendered after the fields (e.g. "save this address"). */
@@ -131,8 +138,10 @@ type OrderSummaryProps = {
131
138
  cost: CartCost;
132
139
  /** The delivery is being saved and the cart re-read: shipping and total say so (`aria-busy`). */
133
140
  updating?: boolean;
134
- /** Picture of a line (e.g. `<ProductImage>`), 48-64 px. The quantity badge sits on it. */
141
+ /** Picture of a line (e.g. `<ProductImage>`), 48-64 px. The quantity badge sits on it. Default: `<ProductThumb>` of the line's image (fallback initial when it has none). */
135
142
  renderLineMedia?: (line: CartLine) => ReactNode;
143
+ /** Show the default picture of each line. Default true; `false` gives the text-only "2 × Name" list. */
144
+ media?: boolean;
136
145
  renderAmount?: (amount: Money) => ReactNode;
137
146
  /** Heading. `false` hides it (the disclosure already names the block). */
138
147
  title?: ReactNode | false;
@@ -14,6 +14,10 @@ type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> &
14
14
  emailHint: string;
15
15
  phoneHint: string;
16
16
  lookupFilled: string;
17
+ /** Tooltip and note of a state the postal code defined (locked). */
18
+ provinceLocked: string;
19
+ /** Discreet action that releases the locked state so the buyer can fix the postal code. */
20
+ changePostalCode: string;
17
21
  };
18
22
  declare function addressFieldsMessages(locale?: string | null, overrides?: Partial<AddressFieldsMessages> | null): AddressFieldsMessages;
19
23
  type FieldLayoutClassNames = CheckoutFieldClassNames & {
@@ -88,8 +92,11 @@ type AddressFieldsProps = Common & {
88
92
  /**
89
93
  * Once `lookupPostalCode` fills the state field (never over what the buyer already typed), lock it: a plain
90
94
  * `<input>` turns read-only; a `<select>` (from `provinces`) turns disabled with a hidden field that keeps
91
- * submitting its value. The buyer corrects the state by correcting the postal code. Default false: the filled
92
- * state stays as editable as any other field. Marked with `data-sf-locked="1"` for the shop's own styling.
95
+ * submitting its value. The state is the postal code's: the field shows a lock, the tooltip `provinceLocked` and a
96
+ * discreet `changePostalCode` action; editing the postal code releases the lock and clears what the lookup filled
97
+ * (also when the new code resolves nothing), so a stale state is never left behind. Default TRUE (the server
98
+ * validates the pair again; set `false` to keep the filled state as editable as any field). Marked with
99
+ * `data-sf-locked="1"` for the shop's own styling.
93
100
  */
94
101
  lockProvinceOnFill?: boolean;
95
102
  /** Rendered after the fields (e.g. "save this address"). */
@@ -131,8 +138,10 @@ type OrderSummaryProps = {
131
138
  cost: CartCost;
132
139
  /** The delivery is being saved and the cart re-read: shipping and total say so (`aria-busy`). */
133
140
  updating?: boolean;
134
- /** Picture of a line (e.g. `<ProductImage>`), 48-64 px. The quantity badge sits on it. */
141
+ /** Picture of a line (e.g. `<ProductImage>`), 48-64 px. The quantity badge sits on it. Default: `<ProductThumb>` of the line's image (fallback initial when it has none). */
135
142
  renderLineMedia?: (line: CartLine) => ReactNode;
143
+ /** Show the default picture of each line. Default true; `false` gives the text-only "2 × Name" list. */
144
+ media?: boolean;
136
145
  renderAmount?: (amount: Money) => ReactNode;
137
146
  /** Heading. `false` hides it (the disclosure already names the block). */
138
147
  title?: ReactNode | false;
package/dist/checkout.js CHANGED
@@ -2,6 +2,8 @@ import { brandName, BrandMark, brandKey } from './chunk-JNUJGPJC.js';
2
2
  export { BrandMark, brandKey, brandLogo, brandName, findBrand, listBrands } from './chunk-JNUJGPJC.js';
3
3
  import { CheckoutField } from './chunk-DFGLCIEG.js';
4
4
  export { CheckoutField, ErrorSummary, fieldMessages, useFieldErrors, validationMessage } from './chunk-DFGLCIEG.js';
5
+ import { ProductThumb } from './chunk-VHEY5AU3.js';
6
+ import './chunk-6CVKW7F3.js';
5
7
  import { useResolvedLocale } from './chunk-T52H5HL3.js';
6
8
  import './chunk-DQBL2DFJ.js';
7
9
  import { useId, useState, useRef, useEffect, useMemo, useSyncExternalStore } from 'react';
@@ -26,7 +28,9 @@ var ES = {
26
28
  referenceHint: "Entre qu\xE9 calles, color de la fachada\u2026",
27
29
  emailHint: "Te enviamos aqu\xED la confirmaci\xF3n y el seguimiento.",
28
30
  phoneHint: "Solo para la paqueter\xEDa, por si necesita contactarte.",
29
- lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal."
31
+ lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal.",
32
+ provinceLocked: "Definido por tu c\xF3digo postal",
33
+ changePostalCode: "Cambiar c\xF3digo postal"
30
34
  };
31
35
  var EN = {
32
36
  email: "Email",
@@ -46,7 +50,9 @@ var EN = {
46
50
  referenceHint: "Cross streets, building color\u2026",
47
51
  emailHint: "We send the confirmation and tracking here.",
48
52
  phoneHint: "Only for the carrier, in case they need to reach you.",
49
- lookupFilled: "We filled in state and city from your postal code."
53
+ lookupFilled: "We filled in state and city from your postal code.",
54
+ provinceLocked: "Set by your postal code",
55
+ changePostalCode: "Change postal code"
50
56
  };
51
57
  function addressFieldsMessages(locale, overrides) {
52
58
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN : ES;
@@ -72,8 +78,10 @@ function lockField(el) {
72
78
  if (el instanceof HTMLSelectElement) {
73
79
  const name = el.getAttribute("name");
74
80
  if (name) {
81
+ el.dataset.sfName = name;
75
82
  const hidden = document.createElement("input");
76
83
  hidden.type = "hidden";
84
+ hidden.dataset.sfLockMirror = "1";
77
85
  hidden.name = name;
78
86
  hidden.value = el.value;
79
87
  el.insertAdjacentElement("afterend", hidden);
@@ -84,12 +92,27 @@ function lockField(el) {
84
92
  el.readOnly = true;
85
93
  }
86
94
  }
95
+ function unlockField(el) {
96
+ if (el.dataset.sfLocked !== "1") return;
97
+ delete el.dataset.sfLocked;
98
+ el.removeAttribute("aria-readonly");
99
+ el.removeAttribute("title");
100
+ if (el instanceof HTMLSelectElement) {
101
+ const mirror = el.nextElementSibling;
102
+ if (mirror instanceof HTMLInputElement && mirror.dataset.sfLockMirror === "1") mirror.remove();
103
+ if (el.dataset.sfName) el.setAttribute("name", el.dataset.sfName);
104
+ delete el.dataset.sfName;
105
+ el.disabled = false;
106
+ } else {
107
+ el.readOnly = false;
108
+ }
109
+ }
87
110
  var ADDRESS_FILL_EVENT_FLAG = "sfSynthetic";
88
111
  function isAddressFillEvent(event) {
89
112
  return event[ADDRESS_FILL_EVENT_FLAG] === true;
90
113
  }
91
114
  function AddressFields(props) {
92
- const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
115
+ const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill = true, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
93
116
  const showNeighborhood = props.neighborhood ?? true;
94
117
  const showReference = props.reference ?? true;
95
118
  const m = addressFieldsMessages(locale, messages);
@@ -98,6 +121,7 @@ function AddressFields(props) {
98
121
  const listId = useId();
99
122
  const [colonias, setColonias] = useState([]);
100
123
  const [filled, setFilled] = useState(false);
124
+ const [provinceLocked, setProvinceLocked] = useState(false);
101
125
  const [coloniaSelect, setColoniaSelect] = useState(false);
102
126
  const coloniaSelectRef = useRef(false);
103
127
  const coloniaCarry = useRef(null);
@@ -128,6 +152,31 @@ function AddressFields(props) {
128
152
  form.removeEventListener("change", mark, true);
129
153
  };
130
154
  }, []);
155
+ const releaseLookup = () => {
156
+ const form = zipRef.current?.closest("form");
157
+ if (!form) return;
158
+ const locked = form.querySelector('[data-sf-locked="1"]');
159
+ const touched = [];
160
+ if (locked) {
161
+ unlockField(locked);
162
+ touched.push(locked);
163
+ }
164
+ for (const key of ["province", "city", "neighborhood"]) {
165
+ const el = form.elements.namedItem(n(key));
166
+ if (el && !("length" in el && !(el instanceof HTMLSelectElement)) && !touched.includes(el)) touched.push(el);
167
+ }
168
+ for (const el of touched) {
169
+ if (el.dataset.sfAutofilled !== "1") continue;
170
+ el.value = "";
171
+ delete el.dataset.sfAutofilled;
172
+ const event = new Event("input", { bubbles: true });
173
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
174
+ el.dispatchEvent(event);
175
+ }
176
+ lastZip.current = null;
177
+ setProvinceLocked(false);
178
+ setFilled(false);
179
+ };
131
180
  const runLookup = (zip) => {
132
181
  const lookup = lookupRef.current;
133
182
  if (!lookup || !zip || zip === lastZip.current) return;
@@ -151,7 +200,11 @@ function AddressFields(props) {
151
200
  const event = new Event("input", { bubbles: true });
152
201
  Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
153
202
  el2.dispatchEvent(event);
154
- if (lock) lockField(el2);
203
+ if (lock) {
204
+ lockField(el2);
205
+ el2.title = m.provinceLocked;
206
+ setProvinceLocked(true);
207
+ }
155
208
  return true;
156
209
  };
157
210
  const a = fill2(n("province"), info.province, lockProvinceOnFill);
@@ -199,6 +252,22 @@ function AddressFields(props) {
199
252
  const zip = defaults?.zip;
200
253
  if (zip) runLookup(zip);
201
254
  }, []);
255
+ const hasLock = (root) => !!root?.closest("form")?.querySelector('[data-sf-locked="1"]');
256
+ const changeZip = () => {
257
+ releaseLookup();
258
+ const zipEl = zipRef.current?.querySelector("input");
259
+ zipEl?.focus();
260
+ zipEl?.select();
261
+ };
262
+ const provinceHint = provinceLocked ? /* @__PURE__ */ jsxs("span", { "data-sf-province-locked": "", title: m.provinceLocked, children: [
263
+ /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { display: "inline-block", verticalAlign: "-1px", marginRight: 4 }, children: [
264
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "11", width: "16", height: "10", rx: "2" }),
265
+ /* @__PURE__ */ jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
266
+ ] }),
267
+ m.provinceLocked,
268
+ " \xB7 ",
269
+ /* @__PURE__ */ jsx("button", { type: "button", "data-sf-change-zip": "", onClick: changeZip, style: { background: "none", border: 0, padding: 0, font: "inherit", color: "inherit", textDecoration: "underline", cursor: "pointer" }, children: m.changePostalCode })
270
+ ] }) : void 0;
202
271
  const numericZip = postalCode?.inputMode === "numeric";
203
272
  return /* @__PURE__ */ jsxs(Fragment, { children: [
204
273
  /* @__PURE__ */ jsx("div", { ref: zipRef, className: join(cx.half), "data-sf-zip": true, children: /* @__PURE__ */ jsx(
@@ -218,15 +287,16 @@ function AddressFields(props) {
218
287
  classNames: { ...cx, root: cx.root },
219
288
  onInput: (e) => {
220
289
  const v = e.currentTarget.value.trim();
290
+ if (v !== lastZip.current && hasLock(zipRef.current)) releaseLookup();
221
291
  if (postalCode?.maxLength && v.length === postalCode.maxLength) runLookup(v);
222
292
  },
223
293
  onBlur: (e) => runLookup(e.currentTarget.value.trim())
224
294
  }
225
295
  ) }),
226
- provinces && provinces.length ? /* @__PURE__ */ jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), children: [
296
+ provinces && provinces.length ? /* @__PURE__ */ jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint, children: [
227
297
  /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: m.provincePlaceholder }),
228
298
  provinces.map((p) => /* @__PURE__ */ jsx("option", { value: p.name, children: p.name }, p.code ?? p.name))
229
- ] }) : /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half") }),
299
+ ] }) : /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint }),
230
300
  /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("city"), label: m.city, autoComplete: "address-level2", defaultValue: defaults?.city ?? "", error: errors[n("city")] ?? null, classNames: field(cx, "half") }),
231
301
  showNeighborhood && coloniaSelect ? /* @__PURE__ */ jsxs(
232
302
  CheckoutField,
@@ -302,13 +372,18 @@ function orderSummaryMessages(locale, overrides) {
302
372
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN2 : ES2;
303
373
  return overrides ? { ...base, ...overrides } : base;
304
374
  }
375
+ function defaultLineMedia(l) {
376
+ const img = l.merchandise.image ?? l.merchandise.product.featured_image ?? null;
377
+ return /* @__PURE__ */ jsx(ProductThumb, { image: img, name: l.merchandise.product.title, size: 56, decorative: true, zoom: false });
378
+ }
305
379
  var isZero = (m) => !!m && Number(m.amount) === 0;
306
380
  var positive = (m) => !!m && Number(m.amount) > 0;
307
381
  function OrderSummary(props) {
308
- const { lines, cost, updating = false, renderLineMedia, renderAmount, title, headingLevel = 2, classNames: cx = {}, children } = props;
382
+ const { lines, cost, updating = false, media = true, renderAmount, title, headingLevel = 2, classNames: cx = {}, children } = props;
309
383
  const locale = useResolvedLocale(props.locale);
310
384
  const m = orderSummaryMessages(locale, props.messages);
311
385
  const money = (v) => renderAmount ? renderAmount(v) : formatMoney(v, locale);
386
+ const renderLineMedia = props.renderLineMedia ?? (media ? defaultLineMedia : void 0);
312
387
  const H = headingLevel === 3 ? "h3" : "h2";
313
388
  const discounts = cost.discounts?.filter((d) => positive(d.amount)) ?? [];
314
389
  return /* @__PURE__ */ jsxs("div", { className: cx.root, "data-sf-summary": true, children: [