@asteby/storefront-react 0.1.2 → 0.3.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/README.md +171 -0
- package/dist/chunk-C6QZNKRE.js +162 -0
- package/dist/chunk-C6QZNKRE.js.map +1 -0
- package/dist/components-B5XhSH-m.d.cts +54 -0
- package/dist/components-B5XhSH-m.d.ts +54 -0
- package/dist/index.cjs +380 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -54
- package/dist/index.d.ts +228 -54
- package/dist/index.js +373 -171
- package/dist/index.js.map +1 -1
- package/dist/mockups.cjs +525 -0
- package/dist/mockups.cjs.map +1 -0
- package/dist/mockups.d.cts +203 -0
- package/dist/mockups.d.ts +203 -0
- package/dist/mockups.js +401 -0
- package/dist/mockups.js.map +1 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -158,6 +158,176 @@ metacore-ui components pick it up), fonts as `--font-<role>` and `--radius`.
|
|
|
158
158
|
Unsafe values (`;`, `{}`, `<`, `url(`) are dropped. `<ThemeStyle theme />`
|
|
159
159
|
renders it as a `<style>`; `themeFontsHref(theme)` gives a Google Fonts URL.
|
|
160
160
|
|
|
161
|
+
## Sucursales (headless)
|
|
162
|
+
|
|
163
|
+
Primitivos sin estilos ni clases (render props), accesibles y SSR-safe. Con cero
|
|
164
|
+
sucursales todos renderizan `null`. Leen `shop.branches` del `StorefrontProvider`
|
|
165
|
+
si no recibes `locations`.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const [origin, setOrigin] = useState<GeoPoint | null>(null)
|
|
169
|
+
const cp = new URLSearchParams(search).get('cp')
|
|
170
|
+
const { locations } = useNearestLocations({ origin, postalCode: cp, limit: 3 })
|
|
171
|
+
const mine = useSelectedLocation({ initialId: urlSucursal }) // ?sucursal= + localStorage, nunca cookie
|
|
172
|
+
|
|
173
|
+
<NearestLocationForm action="/sucursales" onLocate={setOrigin} /> {/* GET ?cp=06000 sin JS */}
|
|
174
|
+
<LocationList locations={locations}>
|
|
175
|
+
{(l, { selected }) => (
|
|
176
|
+
<>
|
|
177
|
+
<strong>{l.name}</strong> <OpenStatus location={l} /> <DirectionsLink location={l} />
|
|
178
|
+
<PickupOption location={l} />
|
|
179
|
+
<button onClick={() => mine.select(l.id)} aria-pressed={selected}>Mi sucursal</button>
|
|
180
|
+
</>
|
|
181
|
+
)}
|
|
182
|
+
</LocationList>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- `useGeolocation()`: solo pide la ubicación al llamar `request()` desde un gesto
|
|
186
|
+
del usuario; expone `supported`, `permission`, `status`, `position` y un error
|
|
187
|
+
tipado (`denied | unavailable | timeout | unsupported | insecure`). La posición
|
|
188
|
+
no se guarda ni se envía.
|
|
189
|
+
- `useNearestLocations({ origin, postalCode, locations, limit, radiusKm, remote })`:
|
|
190
|
+
con `origin` ordena en el navegador (haversine; las coordenadas no salen). Con
|
|
191
|
+
`postalCode` (o `remote`) pregunta al servidor (`client.locations`).
|
|
192
|
+
- `useSelectedLocation()`: "Mi sucursal" en `?sucursal=` y `localStorage`
|
|
193
|
+
(`sf_sucursal`); valida contra las sucursales y olvida ids obsoletos. Pasa
|
|
194
|
+
`initialId` desde el loader para que SSR e hidratación coincidan.
|
|
195
|
+
- `LocationList`, `LocationPicker` (radios nativos, `name="sucursal"`),
|
|
196
|
+
`OpenStatus` (calcula tras montar; `now` fija el instante), `DirectionsLink`,
|
|
197
|
+
`PickupOption` (muestra lo que manda OPS: costo y días), `NearestLocationForm`.
|
|
198
|
+
|
|
199
|
+
## Mockups de respaldo (`@asteby/storefront-react/mockups`)
|
|
200
|
+
|
|
201
|
+
Cuando un producto de OPS no trae imagen, `ProductImage` dibuja una ilustración
|
|
202
|
+
SVG según el tipo de producto en lugar de un hueco: **llanta** (medida en cifras,
|
|
203
|
+
marca, modelo, índice de carga/velocidad, LT, run flat), **refacción** (ficha con
|
|
204
|
+
número de parte, OEM, compatibilidad y posición), **suplemento** (bote, frasco o
|
|
205
|
+
bolsa con nombre, sabor y presentación) y **genérico** (caja con monograma).
|
|
206
|
+
SSR, sin JavaScript en el cliente, sin red, sin `id`s; ~1–2 KB gzip por mockup.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
import { ProductImage } from '@asteby/storefront-react/mockups'
|
|
210
|
+
|
|
211
|
+
// Sustituye el `product.featured_image ? <Image …/> : null` de las tarjetas:
|
|
212
|
+
<ProductImage
|
|
213
|
+
product={product}
|
|
214
|
+
variant={selectedVariant} // opcional: su imagen gana; su sabor/presentación alimenta el mockup
|
|
215
|
+
widths={[240, 360, 480, 720]}
|
|
216
|
+
sizes="(min-width: 1024px) 25vw, 50vw"
|
|
217
|
+
priority={index < 4} // LCP (solo aplica a la imagen real)
|
|
218
|
+
className="h-full w-full object-cover"
|
|
219
|
+
/>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- **Con imagen** renderiza `<Image>` (srcset, `width`/`height`, `priority`); el
|
|
223
|
+
`alt` es el de OPS, o el título del producto si viene vacío.
|
|
224
|
+
- **Sin imagen** renderiza `<svg role="img" aria-label="Ilustración de referencia: …">`
|
|
225
|
+
con `<title>`: nunca se anuncia como foto. `alt=""` lo vuelve decorativo
|
|
226
|
+
(`aria-hidden`), útil cuando el nombre ya está junto a la imagen.
|
|
227
|
+
- **Nunca inventa ni muestra precios**: solo pinta lo que trae el producto
|
|
228
|
+
(atributos, `vendor`, título, opciones, variante). Sin medida no hay medida;
|
|
229
|
+
el sabor solo aparece con variante elegida o si la opción tiene un único valor.
|
|
230
|
+
Todo texto se limpia (controles/bidi), se recorta con `…` y React lo escapa.
|
|
231
|
+
- `image={null}` fuerza el mockup (p. ej. en una galería); `kind="tire"` fuerza el tipo.
|
|
232
|
+
|
|
233
|
+
### Qué tipo se dibuja
|
|
234
|
+
|
|
235
|
+
`kind` → `resolveKind` (prop o `MockupProvider`) → `resolveMockupKind(product)`:
|
|
236
|
+
|
|
237
|
+
1. Atributo `mockup_kind` (o `mockup`) en OPS, si ese tipo está registrado: el
|
|
238
|
+
dueño puede elegirlo sin tocar código.
|
|
239
|
+
2. Atributos: `size_key`, `section_width_mm`, `rim_diameter_in`, `speed_rating`… → `tire`;
|
|
240
|
+
`part_number`, `oem_number`, `mpn` → `part`.
|
|
241
|
+
3. Palabras en `product_type`, categoría y etiquetas (no en el título): llanta,
|
|
242
|
+
neumático, refacción, autoparte, proteína, creatina, colágeno, suplemento…
|
|
243
|
+
4. `generic`.
|
|
244
|
+
|
|
245
|
+
Atributos que lee cada uno (los del pack del giro, más alias comunes):
|
|
246
|
+
llanta `size_key|size`, `section_width_mm|width`, `aspect_ratio|aspect`,
|
|
247
|
+
`rim_diameter_in|rim`, `load_index`/`load_service`, `speed_rating`, `model`,
|
|
248
|
+
`light_truck`, `runflat`; refacción `part_number|mpn`, `oem_number`,
|
|
249
|
+
`fitment|compatibility|compatibilidad`, `position`; suplemento opción
|
|
250
|
+
`Sabor`/`Presentación` o atributos `flavor|sabor`, `presentation|net_content`,
|
|
251
|
+
`servings` (número) y `form|envase` (`cápsulas` → frasco, `bolsa` → bolsa).
|
|
252
|
+
|
|
253
|
+
### Personalizar sin tocar el SDK
|
|
254
|
+
|
|
255
|
+
**Colores y tipografía** con variables CSS. Por defecto toman el tema de la tienda
|
|
256
|
+
(`--color-muted`, `--color-foreground`, `--color-background`, `--color-primary`,
|
|
257
|
+
`--color-primary-foreground`, `--font-heading`/`--font-sans`, los que emite
|
|
258
|
+
`ThemeStyle`) y si no hay, grises neutros con `light-dark()` (modo oscuro cuando
|
|
259
|
+
la página declara `color-scheme: dark` o `light dark`).
|
|
260
|
+
|
|
261
|
+
```css
|
|
262
|
+
/* app/app.css de la tienda */
|
|
263
|
+
:root {
|
|
264
|
+
--mockup-bg: var(--color-muted); /* fondo del cuadro */
|
|
265
|
+
--mockup-ink: var(--color-foreground); /* texto sobre el fondo */
|
|
266
|
+
--mockup-muted: #6b7280; /* texto secundario sobre el fondo */
|
|
267
|
+
--mockup-surface: #001963; /* hule, envase, caja */
|
|
268
|
+
--mockup-surface-ink: #ffffff; /* texto sobre el envase */
|
|
269
|
+
--mockup-label: #ffffff; /* etiqueta / ficha */
|
|
270
|
+
--mockup-label-ink: #000c3c; /* texto sobre la etiqueta */
|
|
271
|
+
--mockup-accent: #f66123; /* acento: R16, banda de la ficha, tapa */
|
|
272
|
+
--mockup-accent-ink: #ffffff;
|
|
273
|
+
--mockup-font: "Archivo", sans-serif;
|
|
274
|
+
--mockup-font-mono: ui-monospace, monospace; /* números de parte */
|
|
275
|
+
--mockup-text-transform: uppercase; /* p. ej. nombres en MAYÚSCULAS */
|
|
276
|
+
}
|
|
277
|
+
.dark { --mockup-surface: #0b1440; }
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Tipo por tienda** (sin recompilar el SDK) y textos de la interfaz:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// app/root.tsx
|
|
284
|
+
import { MockupProvider } from '@asteby/storefront-react/mockups'
|
|
285
|
+
|
|
286
|
+
<MockupProvider
|
|
287
|
+
resolveKind={(product, guess) => (product.category?.handle === 'shakers' ? 'generic' : guess)}
|
|
288
|
+
labels={{ illustration: 'Imagen ilustrativa', servings: 'servicios' }}
|
|
289
|
+
>
|
|
290
|
+
<Outlet />
|
|
291
|
+
</MockupProvider>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Un mockup propio** (o reemplazar uno base). `MockupFrame` da el `<svg>`, el
|
|
295
|
+
fondo, las variables y la accesibilidad; `MockupText`, `wrapMockupText` y
|
|
296
|
+
`fitMockupFontSize` acomodan el texto sin medir el DOM.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import {
|
|
300
|
+
BUILTIN_MOCKUPS, MockupFrame, MockupText, MockupProvider, createMockupRegistry,
|
|
301
|
+
cleanMockupText, tone, wrapMockupText, type MockupComponentProps,
|
|
302
|
+
} from '@asteby/storefront-react/mockups'
|
|
303
|
+
|
|
304
|
+
type Helmet = { title: string; shell: string | null }
|
|
305
|
+
|
|
306
|
+
function HelmetMockup({ data, label, labels, ...svg }: MockupComponentProps<Helmet>) {
|
|
307
|
+
return (
|
|
308
|
+
<MockupFrame kind="helmet" label={label} {...svg}>
|
|
309
|
+
<path d="M90 230a110 110 0 0 1 220 0v40H90z" style={tone('surface')} />
|
|
310
|
+
<MockupText lines={wrapMockupText(data.title, 24, 2)} x={200} y={330} size={22} />
|
|
311
|
+
{data.shell ? <MockupText lines={[data.shell]} x={200} y={372} size={14} tone="muted" /> : null}
|
|
312
|
+
</MockupFrame>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const registry = createMockupRegistry(BUILTIN_MOCKUPS).register('helmet', {
|
|
317
|
+
component: HelmetMockup,
|
|
318
|
+
fromProduct: (p) => ({ title: cleanMockupText(p.title) ?? '', shell: cleanMockupText(p.attributes?.shell, 24) }),
|
|
319
|
+
// describe: (d, labels) => `${labels.illustration}: ${d.title}` (opcional)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
<MockupProvider registry={registry} resolveKind={(p, guess) => (p.product_type === 'Casco' ? 'helmet' : guess)}>…</MockupProvider>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`registerMockup(kind, renderer)` hace lo mismo sobre el registro por defecto
|
|
326
|
+
(llámalo una vez a nivel de módulo). Si registras solo un componente para un
|
|
327
|
+
tipo base (`registerMockup('tire', MiLlanta)`), recibe los datos del extractor
|
|
328
|
+
base (`TireMockupData`). Para dibujar sin producto completo:
|
|
329
|
+
`<ProductMockup kind="part" data={partMockupData(product)} />`.
|
|
330
|
+
|
|
161
331
|
## API
|
|
162
332
|
|
|
163
333
|
| Export | |
|
|
@@ -171,3 +341,4 @@ renders it as a `<style>`; `themeFontsHref(theme)` gives a Google Fonts URL.
|
|
|
171
341
|
| `productJsonLd`, `organizationJsonLd`, `localBusinessJsonLd`, `breadcrumbJsonLd`, `faqJsonLd`, `collectionJsonLd`, `JsonLd`, `serializeJsonLd` | schema.org JSON-LD |
|
|
172
342
|
| `defineSection`, `SectionRegistry`, `createSectionRegistry`, `Sections`, `resolveSectionProps`, `sectionJsonSchema` | Content sections |
|
|
173
343
|
| `themeToCss`, `themeToCssVariables`, `ThemeStyle`, `themeFontsHref`, `tokenName` | SSR theme |
|
|
344
|
+
| `ProductImage`, `ProductMockup`, `MockupProvider`, `createMockupRegistry`, `registerMockup`, `BUILTIN_MOCKUPS`, `MockupFrame`, `MockupText`, `TireMockup`, `PartMockup`, `SupplementMockup`, `GenericMockup` | Mockups de respaldo (subpath `/mockups`) |
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createContext, useMemo, useContext, createElement } from 'react';
|
|
2
|
+
import { createStorefrontClient, formatMoney, parseMoney } from '@asteby/storefront';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/context.tsx
|
|
6
|
+
var StorefrontContext = createContext(null);
|
|
7
|
+
function StorefrontProvider({ client, config, shop = null, locale, currency, children }) {
|
|
8
|
+
const configKey = config ? `${config.baseUrl}|${config.token}|${config.locale ?? ""}|${config.customerToken ?? ""}` : "";
|
|
9
|
+
const resolvedClient = useMemo(
|
|
10
|
+
() => client ?? (config ? createStorefrontClient(config) : null),
|
|
11
|
+
[client, configKey]
|
|
12
|
+
);
|
|
13
|
+
const value = useMemo(
|
|
14
|
+
() => ({
|
|
15
|
+
client: resolvedClient,
|
|
16
|
+
shop,
|
|
17
|
+
locale: locale ?? config?.locale ?? shop?.locale ?? "es-MX",
|
|
18
|
+
currency: currency ?? shop?.currency_code ?? "MXN"
|
|
19
|
+
}),
|
|
20
|
+
[resolvedClient, shop, locale, currency, config?.locale]
|
|
21
|
+
);
|
|
22
|
+
return /* @__PURE__ */ jsx(StorefrontContext.Provider, { value, children });
|
|
23
|
+
}
|
|
24
|
+
function useStorefront() {
|
|
25
|
+
const ctx = useContext(StorefrontContext);
|
|
26
|
+
if (!ctx) throw new Error("useStorefront must be used inside <StorefrontProvider>");
|
|
27
|
+
return ctx;
|
|
28
|
+
}
|
|
29
|
+
function useOptionalStorefront() {
|
|
30
|
+
return useContext(StorefrontContext);
|
|
31
|
+
}
|
|
32
|
+
function useStorefrontClient() {
|
|
33
|
+
const { client } = useStorefront();
|
|
34
|
+
if (!client) throw new Error("<StorefrontProvider> needs a `client` or `config` for browser-side calls");
|
|
35
|
+
return client;
|
|
36
|
+
}
|
|
37
|
+
function Money({
|
|
38
|
+
data,
|
|
39
|
+
as,
|
|
40
|
+
locale,
|
|
41
|
+
withoutTrailingZeros,
|
|
42
|
+
withoutCurrency,
|
|
43
|
+
currencyDisplay,
|
|
44
|
+
...rest
|
|
45
|
+
}) {
|
|
46
|
+
const sf = useOptionalStorefront();
|
|
47
|
+
const text = formatMoney(data, locale ?? sf?.locale, {
|
|
48
|
+
...withoutTrailingZeros !== void 0 ? { withoutTrailingZeros } : {},
|
|
49
|
+
...withoutCurrency !== void 0 ? { withoutCurrency } : {},
|
|
50
|
+
...currencyDisplay !== void 0 ? { currencyDisplay } : {}
|
|
51
|
+
});
|
|
52
|
+
return createElement(as ?? "span", rest, text);
|
|
53
|
+
}
|
|
54
|
+
var DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600];
|
|
55
|
+
var defaultImageLoader = (src, width, height) => {
|
|
56
|
+
try {
|
|
57
|
+
const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src);
|
|
58
|
+
const url = new URL(src, "http://relative.invalid");
|
|
59
|
+
url.searchParams.set("width", String(width));
|
|
60
|
+
if (height) url.searchParams.set("height", String(height));
|
|
61
|
+
return isAbsolute ? url.toString() : url.pathname + url.search + url.hash;
|
|
62
|
+
} catch {
|
|
63
|
+
return src;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function parseRatio(ratio) {
|
|
67
|
+
if (!ratio) return null;
|
|
68
|
+
const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n));
|
|
69
|
+
return w && h ? w / h : null;
|
|
70
|
+
}
|
|
71
|
+
function Image({
|
|
72
|
+
data,
|
|
73
|
+
sizes = "100vw",
|
|
74
|
+
widths = DEFAULT_IMAGE_WIDTHS,
|
|
75
|
+
loader = defaultImageLoader,
|
|
76
|
+
priority = false,
|
|
77
|
+
aspectRatio,
|
|
78
|
+
alt,
|
|
79
|
+
loading,
|
|
80
|
+
decoding,
|
|
81
|
+
...rest
|
|
82
|
+
}) {
|
|
83
|
+
const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null);
|
|
84
|
+
let candidates = [...widths].sort((a, b) => a - b);
|
|
85
|
+
if (data.width) {
|
|
86
|
+
const capped = candidates.filter((w) => w <= data.width);
|
|
87
|
+
candidates = capped.length ? capped : [data.width];
|
|
88
|
+
}
|
|
89
|
+
const srcSet = loader ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : void 0)} ${w}w`).join(", ") : void 0;
|
|
90
|
+
const width = data.width ?? candidates[candidates.length - 1];
|
|
91
|
+
const height = data.height ?? (ratio && width ? Math.round(width / ratio) : void 0);
|
|
92
|
+
return /* @__PURE__ */ jsx(
|
|
93
|
+
"img",
|
|
94
|
+
{
|
|
95
|
+
src: data.url,
|
|
96
|
+
alt: alt ?? data.alt ?? "",
|
|
97
|
+
...srcSet ? { srcSet, sizes } : {},
|
|
98
|
+
...width ? { width } : {},
|
|
99
|
+
...height ? { height } : {},
|
|
100
|
+
loading: loading ?? (priority ? "eager" : "lazy"),
|
|
101
|
+
decoding: decoding ?? (priority ? "auto" : "async"),
|
|
102
|
+
...priority ? { fetchPriority: "high" } : {},
|
|
103
|
+
...rest
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
var VISUALLY_HIDDEN = {
|
|
108
|
+
position: "absolute",
|
|
109
|
+
width: 1,
|
|
110
|
+
height: 1,
|
|
111
|
+
padding: 0,
|
|
112
|
+
margin: -1,
|
|
113
|
+
overflow: "hidden",
|
|
114
|
+
clip: "rect(0, 0, 0, 0)",
|
|
115
|
+
whiteSpace: "nowrap",
|
|
116
|
+
border: 0
|
|
117
|
+
};
|
|
118
|
+
function ProductPrice({
|
|
119
|
+
product,
|
|
120
|
+
variant,
|
|
121
|
+
fromLabel = "Desde",
|
|
122
|
+
compareAtLabel = "Precio anterior",
|
|
123
|
+
locale,
|
|
124
|
+
withoutTrailingZeros,
|
|
125
|
+
...rest
|
|
126
|
+
}) {
|
|
127
|
+
let price = null;
|
|
128
|
+
let compareAt = null;
|
|
129
|
+
let isRange = false;
|
|
130
|
+
if (variant) {
|
|
131
|
+
price = variant.price;
|
|
132
|
+
compareAt = variant.compare_at_price;
|
|
133
|
+
} else if (product) {
|
|
134
|
+
price = product.price_range.min;
|
|
135
|
+
compareAt = product.compare_at_price_range?.min ?? null;
|
|
136
|
+
isRange = product.price_range.min.amount !== product.price_range.max.amount;
|
|
137
|
+
}
|
|
138
|
+
if (!price) return null;
|
|
139
|
+
const onSale = compareAt !== null && parseMoney(compareAt) > parseMoney(price);
|
|
140
|
+
const opts = { ...locale ? { locale } : {}, ...withoutTrailingZeros !== void 0 ? { withoutTrailingZeros } : {} };
|
|
141
|
+
return /* @__PURE__ */ jsxs("div", { "data-on-sale": onSale || void 0, ...rest, children: [
|
|
142
|
+
isRange ? /* @__PURE__ */ jsxs("span", { "data-price-prefix": "", children: [
|
|
143
|
+
fromLabel,
|
|
144
|
+
" "
|
|
145
|
+
] }) : null,
|
|
146
|
+
/* @__PURE__ */ jsx(Money, { data: price, "data-price": "", ...opts }),
|
|
147
|
+
onSale && compareAt ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
148
|
+
" ",
|
|
149
|
+
/* @__PURE__ */ jsxs("s", { "data-compare-at-price": "", children: [
|
|
150
|
+
/* @__PURE__ */ jsxs("span", { style: VISUALLY_HIDDEN, children: [
|
|
151
|
+
compareAtLabel,
|
|
152
|
+
": "
|
|
153
|
+
] }),
|
|
154
|
+
/* @__PURE__ */ jsx(Money, { data: compareAt, ...opts })
|
|
155
|
+
] })
|
|
156
|
+
] }) : null
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { DEFAULT_IMAGE_WIDTHS, Image, Money, ProductPrice, StorefrontProvider, defaultImageLoader, useOptionalStorefront, useStorefront, useStorefrontClient };
|
|
161
|
+
//# sourceMappingURL=chunk-C6QZNKRE.js.map
|
|
162
|
+
//# sourceMappingURL=chunk-C6QZNKRE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/components.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-C6QZNKRE.js","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react'\nimport { createStorefrontClient, type Shop, type StorefrontClient, type StorefrontClientOptions } from '@asteby/storefront'\n\nexport type StorefrontContextValue = {\n /** Client for browser-side calls (use a public `pk_` token). */\n client: StorefrontClient | null\n shop: Shop | null\n /** BCP 47 locale used for formatting (defaults to `shop.locale`, then `es-MX`). */\n locale: string\n /** ISO 4217 currency (defaults to `shop.currency_code`, then `MXN`). */\n currency: string\n}\n\nconst StorefrontContext = createContext<StorefrontContextValue | null>(null)\n\nexport type StorefrontProviderProps = {\n /** A ready client. Takes precedence over `config`. */\n client?: StorefrontClient\n /** Options to create the browser client (use a `pk_` token). */\n config?: StorefrontClientOptions\n /** Shop info loaded on the server (root loader). */\n shop?: Shop | null\n locale?: string\n currency?: string\n children?: ReactNode\n}\n\n/**\n * Makes the storefront client, shop, locale and currency available to the\n * tree. SSR-safe: creating the client does not touch `window`.\n */\nexport function StorefrontProvider({ client, config, shop = null, locale, currency, children }: StorefrontProviderProps) {\n const configKey = config ? `${config.baseUrl}|${config.token}|${config.locale ?? ''}|${config.customerToken ?? ''}` : ''\n const resolvedClient = useMemo(\n () => client ?? (config ? createStorefrontClient(config) : null),\n [client, configKey],\n )\n const value = useMemo<StorefrontContextValue>(\n () => ({\n client: resolvedClient,\n shop,\n locale: locale ?? config?.locale ?? shop?.locale ?? 'es-MX',\n currency: currency ?? shop?.currency_code ?? 'MXN',\n }),\n [resolvedClient, shop, locale, currency, config?.locale],\n )\n return <StorefrontContext.Provider value={value}>{children}</StorefrontContext.Provider>\n}\n\n/** Storefront context. Throws outside `<StorefrontProvider>`. */\nexport function useStorefront(): StorefrontContextValue {\n const ctx = useContext(StorefrontContext)\n if (!ctx) throw new Error('useStorefront must be used inside <StorefrontProvider>')\n return ctx\n}\n\n/** Storefront context or `null` outside a provider (for components that work standalone). */\nexport function useOptionalStorefront(): StorefrontContextValue | null {\n return useContext(StorefrontContext)\n}\n\n/** The client from context; throws when the provider has none. */\nexport function useStorefrontClient(): StorefrontClient {\n const { client } = useStorefront()\n if (!client) throw new Error('<StorefrontProvider> needs a `client` or `config` for browser-side calls')\n return client\n}\n","import { createElement, type ComponentPropsWithoutRef, type ElementType, type ReactNode } from 'react'\nimport { formatMoney, parseMoney, type FormatMoneyOptions, type Image as ImageData, type Money as MoneyData, type Product, type ProductVariant } from '@asteby/storefront'\nimport { useOptionalStorefront } from './context.js'\n\n// ---------------------------------------------------------------------------\n// Money\n// ---------------------------------------------------------------------------\n\nexport type MoneyProps<As extends ElementType = 'span'> = FormatMoneyOptions & {\n data: MoneyData\n /** Element to render (default `span`). */\n as?: As\n /** Overrides the provider locale. */\n locale?: string\n} & Omit<ComponentPropsWithoutRef<As>, 'children' | 'as'>\n\n/** Formats `Money` with the provider locale (or `locale`). */\nexport function Money<As extends ElementType = 'span'>({\n data,\n as,\n locale,\n withoutTrailingZeros,\n withoutCurrency,\n currencyDisplay,\n ...rest\n}: MoneyProps<As>) {\n const sf = useOptionalStorefront()\n const text = formatMoney(data, locale ?? sf?.locale, {\n ...(withoutTrailingZeros !== undefined ? { withoutTrailingZeros } : {}),\n ...(withoutCurrency !== undefined ? { withoutCurrency } : {}),\n ...(currencyDisplay !== undefined ? { currencyDisplay } : {}),\n })\n return createElement(as ?? 'span', rest, text)\n}\n\n// ---------------------------------------------------------------------------\n// Image\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600] as const\n\n/** Builds the URL for one srcset width. */\nexport type ImageLoader = (src: string, width: number, height?: number) => string\n\n/**\n * Default loader: adds `?width=<w>` (the convention of most image CDNs and of\n * the OPS image proxy). URLs that cannot be parsed are returned unchanged.\n */\nexport const defaultImageLoader: ImageLoader = (src, width, height) => {\n try {\n const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src)\n const url = new URL(src, 'http://relative.invalid')\n url.searchParams.set('width', String(width))\n if (height) url.searchParams.set('height', String(height))\n return isAbsolute ? url.toString() : url.pathname + url.search + url.hash\n } catch {\n return src\n }\n}\n\nexport type ImageProps = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'srcSet' | 'width' | 'height'> & {\n data: ImageData\n /** `sizes` attribute (default `100vw`). */\n sizes?: string\n /** Candidate widths for `srcset` (capped at the intrinsic width when known). */\n widths?: readonly number[]\n /** URL builder per width (default adds `?width=`). Pass `null` to disable srcset. */\n loader?: ImageLoader | null\n /** Above-the-fold image: eager loading and `fetchpriority=\"high\"`. */\n priority?: boolean\n /** Forces an aspect ratio, e.g. `1/1` or `4/3`, used for width/height and srcset heights. */\n aspectRatio?: string\n}\n\nfunction parseRatio(ratio: string | undefined): number | null {\n if (!ratio) return null\n const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n))\n return w && h ? w / h : null\n}\n\n/** Responsive `<img>` with `srcset`, lazy by default, intrinsic size to avoid layout shift. */\nexport function Image({\n data,\n sizes = '100vw',\n widths = DEFAULT_IMAGE_WIDTHS,\n loader = defaultImageLoader,\n priority = false,\n aspectRatio,\n alt,\n loading,\n decoding,\n ...rest\n}: ImageProps) {\n const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null)\n let candidates = [...widths].sort((a, b) => a - b)\n if (data.width) {\n const capped = candidates.filter((w) => w <= data.width!)\n candidates = capped.length ? capped : [data.width]\n }\n const srcSet = loader\n ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : undefined)} ${w}w`).join(', ')\n : undefined\n const width = data.width ?? candidates[candidates.length - 1]\n const height = data.height ?? (ratio && width ? Math.round(width / ratio) : undefined)\n return (\n <img\n src={data.url}\n alt={alt ?? data.alt ?? ''}\n {...(srcSet ? { srcSet, sizes } : {})}\n {...(width ? { width } : {})}\n {...(height ? { height } : {})}\n loading={loading ?? (priority ? 'eager' : 'lazy')}\n decoding={decoding ?? (priority ? 'auto' : 'async')}\n {...(priority ? { fetchPriority: 'high' as const } : {})}\n {...rest}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// ProductPrice\n// ---------------------------------------------------------------------------\n\nconst VISUALLY_HIDDEN = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n} as const\n\nexport type ProductPriceProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {\n product?: Pick<Product, 'price_range' | 'compare_at_price_range'> | null\n variant?: Pick<ProductVariant, 'price' | 'compare_at_price'> | null\n /** Prefix shown when the product has a price range and no variant is selected (default `Desde`). */\n fromLabel?: ReactNode\n /** Accessible label for the compare-at price (default `Precio anterior`). */\n compareAtLabel?: string\n locale?: string\n withoutTrailingZeros?: boolean\n}\n\n/**\n * Price with optional compare-at (strikethrough). With a `variant` it shows\n * that variant; otherwise the product's minimum price (prefixed with\n * `fromLabel` when prices vary).\n */\nexport function ProductPrice({\n product,\n variant,\n fromLabel = 'Desde',\n compareAtLabel = 'Precio anterior',\n locale,\n withoutTrailingZeros,\n ...rest\n}: ProductPriceProps) {\n let price: MoneyData | null = null\n let compareAt: MoneyData | null = null\n let isRange = false\n if (variant) {\n price = variant.price\n compareAt = variant.compare_at_price\n } else if (product) {\n price = product.price_range.min\n compareAt = product.compare_at_price_range?.min ?? null\n isRange = product.price_range.min.amount !== product.price_range.max.amount\n }\n if (!price) return null\n const onSale = compareAt !== null && parseMoney(compareAt) > parseMoney(price)\n const opts = { ...(locale ? { locale } : {}), ...(withoutTrailingZeros !== undefined ? { withoutTrailingZeros } : {}) }\n return (\n <div data-on-sale={onSale || undefined} {...rest}>\n {isRange ? <span data-price-prefix=\"\">{fromLabel} </span> : null}\n <Money data={price} data-price=\"\" {...opts} />\n {onSale && compareAt ? (\n <>\n {' '}\n <s data-compare-at-price=\"\">\n <span style={VISUALLY_HIDDEN}>{compareAtLabel}: </span>\n <Money data={compareAt} {...opts} />\n </s>\n </>\n ) : null}\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
3
|
+
import { Image as Image$1, FormatMoneyOptions, Money as Money$1, Product, ProductVariant } from '@asteby/storefront';
|
|
4
|
+
|
|
5
|
+
type MoneyProps<As extends ElementType = 'span'> = FormatMoneyOptions & {
|
|
6
|
+
data: Money$1;
|
|
7
|
+
/** Element to render (default `span`). */
|
|
8
|
+
as?: As;
|
|
9
|
+
/** Overrides the provider locale. */
|
|
10
|
+
locale?: string;
|
|
11
|
+
} & Omit<ComponentPropsWithoutRef<As>, 'children' | 'as'>;
|
|
12
|
+
/** Formats `Money` with the provider locale (or `locale`). */
|
|
13
|
+
declare function Money<As extends ElementType = 'span'>({ data, as, locale, withoutTrailingZeros, withoutCurrency, currencyDisplay, ...rest }: MoneyProps<As>): react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
14
|
+
declare const DEFAULT_IMAGE_WIDTHS: readonly [320, 480, 640, 768, 1024, 1280, 1600];
|
|
15
|
+
/** Builds the URL for one srcset width. */
|
|
16
|
+
type ImageLoader = (src: string, width: number, height?: number) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Default loader: adds `?width=<w>` (the convention of most image CDNs and of
|
|
19
|
+
* the OPS image proxy). URLs that cannot be parsed are returned unchanged.
|
|
20
|
+
*/
|
|
21
|
+
declare const defaultImageLoader: ImageLoader;
|
|
22
|
+
type ImageProps = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'srcSet' | 'width' | 'height'> & {
|
|
23
|
+
data: Image$1;
|
|
24
|
+
/** `sizes` attribute (default `100vw`). */
|
|
25
|
+
sizes?: string;
|
|
26
|
+
/** Candidate widths for `srcset` (capped at the intrinsic width when known). */
|
|
27
|
+
widths?: readonly number[];
|
|
28
|
+
/** URL builder per width (default adds `?width=`). Pass `null` to disable srcset. */
|
|
29
|
+
loader?: ImageLoader | null;
|
|
30
|
+
/** Above-the-fold image: eager loading and `fetchpriority="high"`. */
|
|
31
|
+
priority?: boolean;
|
|
32
|
+
/** Forces an aspect ratio, e.g. `1/1` or `4/3`, used for width/height and srcset heights. */
|
|
33
|
+
aspectRatio?: string;
|
|
34
|
+
};
|
|
35
|
+
/** Responsive `<img>` with `srcset`, lazy by default, intrinsic size to avoid layout shift. */
|
|
36
|
+
declare function Image({ data, sizes, widths, loader, priority, aspectRatio, alt, loading, decoding, ...rest }: ImageProps): react.JSX.Element;
|
|
37
|
+
type ProductPriceProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
|
|
38
|
+
product?: Pick<Product, 'price_range' | 'compare_at_price_range'> | null;
|
|
39
|
+
variant?: Pick<ProductVariant, 'price' | 'compare_at_price'> | null;
|
|
40
|
+
/** Prefix shown when the product has a price range and no variant is selected (default `Desde`). */
|
|
41
|
+
fromLabel?: ReactNode;
|
|
42
|
+
/** Accessible label for the compare-at price (default `Precio anterior`). */
|
|
43
|
+
compareAtLabel?: string;
|
|
44
|
+
locale?: string;
|
|
45
|
+
withoutTrailingZeros?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Price with optional compare-at (strikethrough). With a `variant` it shows
|
|
49
|
+
* that variant; otherwise the product's minimum price (prefixed with
|
|
50
|
+
* `fromLabel` when prices vary).
|
|
51
|
+
*/
|
|
52
|
+
declare function ProductPrice({ product, variant, fromLabel, compareAtLabel, locale, withoutTrailingZeros, ...rest }: ProductPriceProps): react.JSX.Element | null;
|
|
53
|
+
|
|
54
|
+
export { DEFAULT_IMAGE_WIDTHS as D, Image as I, Money as M, ProductPrice as P, type ImageLoader as a, type ImageProps as b, type MoneyProps as c, type ProductPriceProps as d, defaultImageLoader as e };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
3
|
+
import { Image as Image$1, FormatMoneyOptions, Money as Money$1, Product, ProductVariant } from '@asteby/storefront';
|
|
4
|
+
|
|
5
|
+
type MoneyProps<As extends ElementType = 'span'> = FormatMoneyOptions & {
|
|
6
|
+
data: Money$1;
|
|
7
|
+
/** Element to render (default `span`). */
|
|
8
|
+
as?: As;
|
|
9
|
+
/** Overrides the provider locale. */
|
|
10
|
+
locale?: string;
|
|
11
|
+
} & Omit<ComponentPropsWithoutRef<As>, 'children' | 'as'>;
|
|
12
|
+
/** Formats `Money` with the provider locale (or `locale`). */
|
|
13
|
+
declare function Money<As extends ElementType = 'span'>({ data, as, locale, withoutTrailingZeros, withoutCurrency, currencyDisplay, ...rest }: MoneyProps<As>): react.ReactElement<any, string | react.JSXElementConstructor<any>>;
|
|
14
|
+
declare const DEFAULT_IMAGE_WIDTHS: readonly [320, 480, 640, 768, 1024, 1280, 1600];
|
|
15
|
+
/** Builds the URL for one srcset width. */
|
|
16
|
+
type ImageLoader = (src: string, width: number, height?: number) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Default loader: adds `?width=<w>` (the convention of most image CDNs and of
|
|
19
|
+
* the OPS image proxy). URLs that cannot be parsed are returned unchanged.
|
|
20
|
+
*/
|
|
21
|
+
declare const defaultImageLoader: ImageLoader;
|
|
22
|
+
type ImageProps = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'srcSet' | 'width' | 'height'> & {
|
|
23
|
+
data: Image$1;
|
|
24
|
+
/** `sizes` attribute (default `100vw`). */
|
|
25
|
+
sizes?: string;
|
|
26
|
+
/** Candidate widths for `srcset` (capped at the intrinsic width when known). */
|
|
27
|
+
widths?: readonly number[];
|
|
28
|
+
/** URL builder per width (default adds `?width=`). Pass `null` to disable srcset. */
|
|
29
|
+
loader?: ImageLoader | null;
|
|
30
|
+
/** Above-the-fold image: eager loading and `fetchpriority="high"`. */
|
|
31
|
+
priority?: boolean;
|
|
32
|
+
/** Forces an aspect ratio, e.g. `1/1` or `4/3`, used for width/height and srcset heights. */
|
|
33
|
+
aspectRatio?: string;
|
|
34
|
+
};
|
|
35
|
+
/** Responsive `<img>` with `srcset`, lazy by default, intrinsic size to avoid layout shift. */
|
|
36
|
+
declare function Image({ data, sizes, widths, loader, priority, aspectRatio, alt, loading, decoding, ...rest }: ImageProps): react.JSX.Element;
|
|
37
|
+
type ProductPriceProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
|
|
38
|
+
product?: Pick<Product, 'price_range' | 'compare_at_price_range'> | null;
|
|
39
|
+
variant?: Pick<ProductVariant, 'price' | 'compare_at_price'> | null;
|
|
40
|
+
/** Prefix shown when the product has a price range and no variant is selected (default `Desde`). */
|
|
41
|
+
fromLabel?: ReactNode;
|
|
42
|
+
/** Accessible label for the compare-at price (default `Precio anterior`). */
|
|
43
|
+
compareAtLabel?: string;
|
|
44
|
+
locale?: string;
|
|
45
|
+
withoutTrailingZeros?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Price with optional compare-at (strikethrough). With a `variant` it shows
|
|
49
|
+
* that variant; otherwise the product's minimum price (prefixed with
|
|
50
|
+
* `fromLabel` when prices vary).
|
|
51
|
+
*/
|
|
52
|
+
declare function ProductPrice({ product, variant, fromLabel, compareAtLabel, locale, withoutTrailingZeros, ...rest }: ProductPriceProps): react.JSX.Element | null;
|
|
53
|
+
|
|
54
|
+
export { DEFAULT_IMAGE_WIDTHS as D, Image as I, Money as M, ProductPrice as P, type ImageLoader as a, type ImageProps as b, type MoneyProps as c, type ProductPriceProps as d, defaultImageLoader as e };
|