@asteby/storefront-react 0.6.0 → 0.8.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/ui.js ADDED
@@ -0,0 +1,375 @@
1
+ import { useRef, useState, useId, useEffect } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/ui/select.tsx
5
+ var Chevron = ({ open = false, floating = false }) => /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", "data-select-chevron": floating ? "floating" : "", "data-open": open || void 0, children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" }) });
6
+ var Check = () => /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
7
+ function Select({ id, name, options, defaultValue, label, className = "" }) {
8
+ const native = useRef(null);
9
+ const root = useRef(null);
10
+ const [enhanced, setEnhanced] = useState(false);
11
+ const [open, setOpen] = useState(false);
12
+ const [value, setValue] = useState(defaultValue ?? options[0]?.value ?? "");
13
+ const [active, setActive] = useState(0);
14
+ const listId = useId();
15
+ const current2 = options.find((o) => o.value === value) ?? options[0];
16
+ useEffect(() => setEnhanced(true), []);
17
+ useEffect(() => {
18
+ if (defaultValue != null) setValue(defaultValue);
19
+ }, [defaultValue]);
20
+ useEffect(() => {
21
+ if (!open) return;
22
+ const onDown = (e) => {
23
+ if (!root.current?.contains(e.target)) setOpen(false);
24
+ };
25
+ document.addEventListener("pointerdown", onDown);
26
+ return () => document.removeEventListener("pointerdown", onDown);
27
+ }, [open]);
28
+ const openList = () => {
29
+ setActive(Math.max(0, options.findIndex((o) => o.value === value)));
30
+ setOpen(true);
31
+ };
32
+ const choose = (next) => {
33
+ setOpen(false);
34
+ setValue(next);
35
+ const el = native.current;
36
+ if (el && el.value !== next) {
37
+ el.value = next;
38
+ el.dispatchEvent(new Event("change", { bubbles: true }));
39
+ }
40
+ root.current?.querySelector("button")?.focus();
41
+ };
42
+ const onKeyDown = (e) => {
43
+ const last = options.length - 1;
44
+ if (!open) {
45
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
46
+ e.preventDefault();
47
+ openList();
48
+ }
49
+ return;
50
+ }
51
+ const go = (i) => {
52
+ e.preventDefault();
53
+ setActive(i);
54
+ };
55
+ if (e.key === "Escape") {
56
+ e.preventDefault();
57
+ setOpen(false);
58
+ } else if (e.key === "ArrowDown") go(Math.min(last, active + 1));
59
+ else if (e.key === "ArrowUp") go(Math.max(0, active - 1));
60
+ else if (e.key === "Home") go(0);
61
+ else if (e.key === "End") go(last);
62
+ else if (e.key === "Enter" || e.key === " ") {
63
+ e.preventDefault();
64
+ const picked = options[active];
65
+ if (picked) choose(picked.value);
66
+ } else if (e.key === "Tab") setOpen(false);
67
+ };
68
+ return /* @__PURE__ */ jsxs("div", { ref: root, "data-select": "", className: className || void 0, children: [
69
+ /* @__PURE__ */ jsxs("div", { "data-select-native": enhanced ? "hidden" : "", children: [
70
+ /* @__PURE__ */ jsx(
71
+ "select",
72
+ {
73
+ ref: native,
74
+ id: enhanced ? void 0 : id,
75
+ name,
76
+ defaultValue,
77
+ tabIndex: enhanced ? -1 : void 0,
78
+ "aria-hidden": enhanced || void 0,
79
+ "aria-label": enhanced ? label : void 0,
80
+ className: "select",
81
+ children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
82
+ }
83
+ ),
84
+ !enhanced ? /* @__PURE__ */ jsx(Chevron, { floating: true }) : null
85
+ ] }),
86
+ enhanced ? /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ /* @__PURE__ */ jsxs(
88
+ "button",
89
+ {
90
+ type: "button",
91
+ id,
92
+ role: "combobox",
93
+ "aria-haspopup": "listbox",
94
+ "aria-expanded": open,
95
+ "aria-controls": listId,
96
+ "aria-label": `${label}: ${current2?.label}`,
97
+ onClick: () => open ? setOpen(false) : openList(),
98
+ onKeyDown,
99
+ className: "select",
100
+ "data-select-trigger": "",
101
+ children: [
102
+ /* @__PURE__ */ jsx("span", { "data-select-value": "", children: current2?.label }),
103
+ /* @__PURE__ */ jsx(Chevron, { open })
104
+ ]
105
+ }
106
+ ),
107
+ open ? /* @__PURE__ */ jsx("ul", { id: listId, role: "listbox", "aria-label": label, tabIndex: -1, "data-select-list": "", children: options.map((o, i) => /* @__PURE__ */ jsxs(
108
+ "li",
109
+ {
110
+ role: "option",
111
+ "aria-selected": o.value === value,
112
+ "data-active": i === active || void 0,
113
+ onPointerEnter: () => setActive(i),
114
+ onClick: () => choose(o.value),
115
+ children: [
116
+ o.label,
117
+ o.value === value ? /* @__PURE__ */ jsx(Check, {}) : null
118
+ ]
119
+ },
120
+ o.value
121
+ )) }) : null
122
+ ] }) : null
123
+ ] });
124
+ }
125
+
126
+ // src/ui/swatch-colors.ts
127
+ var HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
128
+ var HEX_IN_TEXT = /#(?:[0-9a-f]{6}|[0-9a-f]{3})\b/gi;
129
+ var MAX = 4;
130
+ function normalize(value) {
131
+ return value.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
132
+ }
133
+ var defaultColors = [
134
+ ["azul marino", "#14213d"],
135
+ ["navy", "#14213d"],
136
+ ["azul rey", "#2447d6"],
137
+ ["azul cielo", "#6ec1f0"],
138
+ ["celeste", "#8fd0f5"],
139
+ ["verde militar", "#4b5320"],
140
+ ["verde oliva", "#6b7a2a"],
141
+ ["verde limon", "#b8e03a"],
142
+ ["verde menta", "#8fe0b5"],
143
+ ["rosa mexicano", "#e4007c"],
144
+ ["gris oscuro", "#4a4f57"],
145
+ ["gris claro", "#cfd3d8"],
146
+ ["rojo", "#e0262d"],
147
+ ["red", "#e0262d"],
148
+ ["vino", "#6d1a36"],
149
+ ["guinda", "#7a1f3d"],
150
+ ["burgundy", "#6d1a36"],
151
+ ["azul", "#2f5bea"],
152
+ ["blue", "#2f5bea"],
153
+ ["verde", "#2fa84f"],
154
+ ["green", "#2fa84f"],
155
+ ["amarillo", "#ffd21f"],
156
+ ["yellow", "#ffd21f"],
157
+ ["naranja", "#ff8a1f"],
158
+ ["orange", "#ff8a1f"],
159
+ ["morado", "#7b3fc4"],
160
+ ["purpura", "#7b3fc4"],
161
+ ["purple", "#7b3fc4"],
162
+ ["violeta", "#8a4fd6"],
163
+ ["lila", "#c4a5f0"],
164
+ ["rosa", "#ff7eb6"],
165
+ ["pink", "#ff7eb6"],
166
+ ["fucsia", "#e4007c"],
167
+ ["magenta", "#d4119e"],
168
+ ["negro", "#111111"],
169
+ ["black", "#111111"],
170
+ ["blanco", "#ffffff"],
171
+ ["white", "#ffffff"],
172
+ ["gris", "#9aa0a8"],
173
+ ["gray", "#9aa0a8"],
174
+ ["grey", "#9aa0a8"],
175
+ ["plata", "#c0c4cc"],
176
+ ["plateado", "#c0c4cc"],
177
+ ["silver", "#c0c4cc"],
178
+ ["dorado", "#d4a72c"],
179
+ ["oro", "#d4a72c"],
180
+ ["gold", "#d4a72c"],
181
+ ["cafe", "#7a4a2b"],
182
+ ["marron", "#7a4a2b"],
183
+ ["brown", "#7a4a2b"],
184
+ ["beige", "#e6d3b3"],
185
+ ["crema", "#f4ead2"],
186
+ ["hueso", "#f1ecde"],
187
+ ["arena", "#d9c39a"],
188
+ ["khaki", "#b8a77a"],
189
+ ["camel", "#b9824a"],
190
+ ["turquesa", "#1fc8c8"],
191
+ ["aqua", "#34d3d3"],
192
+ ["coral", "#ff6f61"],
193
+ ["salmon", "#ff8f7a"],
194
+ ["mostaza", "#d9a521"],
195
+ ["terracota", "#c4623f"],
196
+ ["transparente", "#e8edf2"]
197
+ ];
198
+ function scan(text, entries) {
199
+ const found = [];
200
+ let rest = text;
201
+ for (const [phrase, value] of [...entries].sort((a, b) => b[0].length - a[0].length)) {
202
+ const m = new RegExp(`(^|[^a-z])${phrase}s?(?![a-z])`).exec(rest);
203
+ if (!m) continue;
204
+ const start = m.index + (m[1]?.length ?? 0);
205
+ found.push({ index: start, value });
206
+ rest = rest.slice(0, start) + " ".repeat(phrase.length + 1) + rest.slice(start + phrase.length + 1);
207
+ }
208
+ return found.sort((a, b) => a.index - b.index).map((f) => f.value);
209
+ }
210
+ function graphemes(text) {
211
+ return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(text)].map((g) => g.segment);
212
+ }
213
+ function createSwatchResolver({ colors: colorTable = defaultColors, emojis = [], prefer = "auto" } = {}) {
214
+ const colors = (value) => {
215
+ const text = normalize(value);
216
+ if (!text) return null;
217
+ const hex = (text.match(HEX_IN_TEXT) ?? []).filter((h) => HEX.test(h));
218
+ const found = [...hex, ...scan(text, colorTable).flat()];
219
+ const unique = found.filter((c, i) => found.indexOf(c) === i).slice(0, MAX);
220
+ return unique.length > 0 ? unique : null;
221
+ };
222
+ const emoji = (value) => {
223
+ const parts = scan(normalize(value), emojis).flatMap(graphemes);
224
+ const unique = parts.filter((e, i) => parts.indexOf(e) === i).slice(0, 3);
225
+ return unique.length > 0 ? unique.join("") : null;
226
+ };
227
+ const resolve = (value, attribute) => {
228
+ const fromCatalog = attribute?.get(normalize(value));
229
+ if (fromCatalog) return fromCatalog;
230
+ const c = colors(value);
231
+ const e = emoji(value);
232
+ if (prefer === "both" && c && e) return { colors: c, emoji: e };
233
+ if (prefer === "emoji") return e ? { emoji: e } : c ? { colors: c } : null;
234
+ if (prefer === "color") return c ? { colors: c } : e ? { emoji: e } : null;
235
+ return c ? { colors: c } : e ? { emoji: e } : null;
236
+ };
237
+ return { colors, emoji, resolve, hasSwatches: (values, attribute) => values.length > 0 && values.every((v) => resolve(v, attribute) !== null) };
238
+ }
239
+ var basic = createSwatchResolver();
240
+ var resolveSwatch = basic.colors;
241
+ var resolveSwatchSpec = (value, attribute) => basic.resolve(value, attribute);
242
+ var optionHasSwatches = basic.hasSwatches;
243
+ function parseSwatchAttribute(raw) {
244
+ const map = /* @__PURE__ */ new Map();
245
+ if (typeof raw !== "string") return map;
246
+ for (const part of raw.split("|")) {
247
+ const i = part.lastIndexOf(":");
248
+ if (i <= 0) continue;
249
+ const name = normalize(part.slice(0, i));
250
+ const value = part.slice(i + 1).trim();
251
+ const colors = (value.match(HEX_IN_TEXT) ?? []).filter((c) => HEX.test(c)).slice(0, MAX);
252
+ const emoji = value.replace(HEX_IN_TEXT, "").replace(/[+\s]/g, "");
253
+ const hasEmoji = emoji.length > 0 && /\p{Extended_Pictographic}/u.test(emoji);
254
+ if (colors.length > 0 || hasEmoji) map.set(name, { ...colors.length > 0 ? { colors } : {}, ...hasEmoji ? { emoji } : {} });
255
+ }
256
+ return map;
257
+ }
258
+ function swatchBackground(colors) {
259
+ const [first = "transparent", second] = colors;
260
+ if (second === void 0) return first;
261
+ if (colors.length === 2) return `linear-gradient(90deg, ${first} 50%, ${second} 50%)`;
262
+ const step = 360 / colors.length;
263
+ return `conic-gradient(${colors.map((c, i) => `${c} ${i * step}deg ${(i + 1) * step}deg`).join(", ")})`;
264
+ }
265
+ function luminance(hex) {
266
+ const h = hex.replace("#", "");
267
+ const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
268
+ const channel = (i) => {
269
+ const v = parseInt(full.slice(i, i + 2), 16) / 255;
270
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
271
+ };
272
+ return 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
273
+ }
274
+ function pastelBackground(colors) {
275
+ const soft = colors.map((c) => `color-mix(in srgb, ${c} 26%, white)`);
276
+ const [first = "transparent", second] = soft;
277
+ if (second === void 0) return first;
278
+ return `linear-gradient(100deg, ${soft.join(", ")})`;
279
+ }
280
+ function Swatch({ value = "", spec, size = 20, shape = "circle", className = "" }) {
281
+ const resolved = spec ?? resolveSwatchSpec(value);
282
+ if (!resolved) return null;
283
+ const colors = resolved.colors ?? [];
284
+ if (resolved.emoji) {
285
+ const parts = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(resolved.emoji)].map((g) => g.segment);
286
+ const mixed = parts.length > 1;
287
+ const style2 = {
288
+ minWidth: size,
289
+ height: size,
290
+ fontSize: Math.round(size * (mixed ? 0.56 : 0.6)),
291
+ ...colors.length > 0 ? { background: pastelBackground(colors), ["--sw-ring"]: colors[0] } : {}
292
+ };
293
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-swatch": "", "data-kind": "emoji", "data-mixed": mixed || void 0, "data-colored": colors.length > 0 || void 0, "data-shape": shape, className: className || void 0, style: style2, children: parts.map((emoji, i) => /* @__PURE__ */ jsx("span", { style: i > 0 ? { marginInlineStart: "-0.3em", zIndex: parts.length - i } : { zIndex: parts.length }, children: emoji }, `${emoji}-${i}`)) });
294
+ }
295
+ if (colors.length === 0) return null;
296
+ const lums = colors.map(luminance);
297
+ const edge = lums.some((l) => l > 0.82) ? "light" : lums.some((l) => l < 0.04) ? "dark" : void 0;
298
+ const style = { width: size, height: size, background: swatchBackground(colors) };
299
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-swatch": "", "data-edge": edge, "data-shape": shape, className: className || void 0, style });
300
+ }
301
+ var THEME_INIT_SCRIPT = `try{var t=localStorage.getItem("theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}`;
302
+ function current() {
303
+ const attr = document.documentElement.getAttribute("data-theme");
304
+ if (attr === "light" || attr === "dark") return attr;
305
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
306
+ }
307
+ function ThemeToggle({ className = "", themeColors = { light: "#ffffff", dark: "#000000" } }) {
308
+ const [theme, setTheme] = useState(null);
309
+ useEffect(() => setTheme(current()), []);
310
+ const toggle = () => {
311
+ const next = current() === "dark" ? "light" : "dark";
312
+ document.documentElement.setAttribute("data-theme", next);
313
+ try {
314
+ localStorage.setItem("theme", next);
315
+ } catch {
316
+ }
317
+ document.querySelector('meta[name="theme-color"][data-dynamic]')?.setAttribute("content", themeColors[next]);
318
+ setTheme(next);
319
+ };
320
+ const dark = theme === "dark";
321
+ return /* @__PURE__ */ jsx("button", { type: "button", onClick: toggle, className, "aria-label": dark ? "Cambiar a tema claro" : "Cambiar a tema oscuro", "aria-pressed": theme === null ? void 0 : dark, children: dark ? /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", "aria-hidden": "true", children: [
322
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
323
+ /* @__PURE__ */ jsx("path", { d: "M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" })
324
+ ] }) : /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" }) }) });
325
+ }
326
+ function useServiceWorker({ enabled, url = "/sw.js", scope = "/" }) {
327
+ useEffect(() => {
328
+ if (!enabled || !("serviceWorker" in navigator)) return;
329
+ const register = () => navigator.serviceWorker.register(url, { scope }).catch(() => void 0);
330
+ if (document.readyState === "complete") register();
331
+ else window.addEventListener("load", register, { once: true });
332
+ }, [enabled, url, scope]);
333
+ }
334
+ function InstallApp({ className = "", label = "Instalar app", iosHint = "Para instalar: toca Compartir y luego \xABAgregar a pantalla de inicio\xBB." }) {
335
+ const [event, setEvent] = useState(null);
336
+ const [ios, setIos] = useState(false);
337
+ const [installed, setInstalled] = useState(true);
338
+ useEffect(() => {
339
+ const standalone = window.matchMedia("(display-mode: standalone)").matches || navigator.standalone === true;
340
+ setInstalled(standalone);
341
+ setIos(/iphone|ipad|ipod/i.test(navigator.userAgent) && !standalone);
342
+ const onPrompt = (e) => {
343
+ e.preventDefault();
344
+ setEvent(e);
345
+ };
346
+ const onInstalled = () => setInstalled(true);
347
+ window.addEventListener("beforeinstallprompt", onPrompt);
348
+ window.addEventListener("appinstalled", onInstalled);
349
+ return () => {
350
+ window.removeEventListener("beforeinstallprompt", onPrompt);
351
+ window.removeEventListener("appinstalled", onInstalled);
352
+ };
353
+ }, []);
354
+ if (installed) return null;
355
+ if (event) {
356
+ return /* @__PURE__ */ jsx(
357
+ "button",
358
+ {
359
+ type: "button",
360
+ className,
361
+ onClick: async () => {
362
+ await event.prompt();
363
+ await event.userChoice;
364
+ setEvent(null);
365
+ },
366
+ children: label
367
+ }
368
+ );
369
+ }
370
+ return ios ? /* @__PURE__ */ jsx("p", { className, children: iosHint }) : null;
371
+ }
372
+
373
+ export { InstallApp, Select, Swatch, THEME_INIT_SCRIPT, ThemeToggle, createSwatchResolver, defaultColors, luminance, normalize as normalizeSwatchName, optionHasSwatches, parseSwatchAttribute, pastelBackground, resolveSwatch, resolveSwatchSpec, swatchBackground, useServiceWorker };
374
+ //# sourceMappingURL=ui.js.map
375
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/select.tsx","../src/ui/swatch-colors.ts","../src/ui/swatch.tsx","../src/ui/theme-toggle.tsx","../src/ui/pwa.tsx"],"names":["current","style","jsx","useState","useEffect","jsxs"],"mappings":"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","file":"ui.js","sourcesContent":["import { useEffect, useId, useRef, useState } from 'react'\n\nexport type SelectOption = { value: string; label: string }\n\nconst Chevron = ({ open = false, floating = false }: { open?: boolean; floating?: boolean }) => (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\" data-select-chevron={floating ? 'floating' : ''} data-open={open || undefined}>\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n)\nconst Check = () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n)\n\nexport type SelectProps = {\n id: string\n name: string\n options: SelectOption[]\n defaultValue?: string\n /** Etiqueta accesible (la visible la pone el llamador con <label htmlFor={id}>). */\n label: string\n className?: string\n}\n\n/**\n * Selector con mejora progresiva. Sin JavaScript (o antes de hidratar) es un <select> nativo dentro de un formulario;\n * con JavaScript se reemplaza por un listbox propio (teclado completo, objetivo táctil de 44 px, tema claro/oscuro).\n * El <select> nativo sigue en el DOM como fuente de verdad: al elegir se cambia su valor y se emite \"change\", así un\n * formulario con onChange → submit (filtros por URL) no nota la diferencia.\n * Estilos: clases `select` (ver ui.css) y los tokens --background, --foreground, --border, --tint, --primary.\n */\nexport function Select({ id, name, options, defaultValue, label, className = '' }: SelectProps) {\n const native = useRef<HTMLSelectElement>(null)\n const root = useRef<HTMLDivElement>(null)\n const [enhanced, setEnhanced] = useState(false)\n const [open, setOpen] = useState(false)\n const [value, setValue] = useState(defaultValue ?? options[0]?.value ?? '')\n const [active, setActive] = useState(0)\n const listId = useId()\n const current = options.find((o) => o.value === value) ?? options[0]\n\n useEffect(() => setEnhanced(true), [])\n // El valor lo puede cambiar la URL (atrás/adelante): se sincroniza.\n useEffect(() => {\n if (defaultValue != null) setValue(defaultValue)\n }, [defaultValue])\n\n useEffect(() => {\n if (!open) return\n const onDown = (e: PointerEvent) => {\n if (!root.current?.contains(e.target as Node)) setOpen(false)\n }\n document.addEventListener('pointerdown', onDown)\n return () => document.removeEventListener('pointerdown', onDown)\n }, [open])\n\n const openList = () => {\n setActive(Math.max(0, options.findIndex((o) => o.value === value)))\n setOpen(true)\n }\n\n const choose = (next: string) => {\n setOpen(false)\n setValue(next)\n const el = native.current\n if (el && el.value !== next) {\n el.value = next\n el.dispatchEvent(new Event('change', { bubbles: true }))\n }\n root.current?.querySelector<HTMLButtonElement>('button')?.focus()\n }\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n const last = options.length - 1\n if (!open) {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {\n e.preventDefault()\n openList()\n }\n return\n }\n const go = (i: number) => {\n e.preventDefault()\n setActive(i)\n }\n if (e.key === 'Escape') {\n e.preventDefault()\n setOpen(false)\n } else if (e.key === 'ArrowDown') go(Math.min(last, active + 1))\n else if (e.key === 'ArrowUp') go(Math.max(0, active - 1))\n else if (e.key === 'Home') go(0)\n else if (e.key === 'End') go(last)\n else if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n const picked = options[active]\n if (picked) choose(picked.value)\n } else if (e.key === 'Tab') setOpen(false)\n }\n\n return (\n <div ref={root} data-select=\"\" className={className || undefined}>\n <div data-select-native={enhanced ? 'hidden' : ''}>\n <select\n ref={native}\n id={enhanced ? undefined : id}\n name={name}\n defaultValue={defaultValue}\n tabIndex={enhanced ? -1 : undefined}\n aria-hidden={enhanced || undefined}\n aria-label={enhanced ? label : undefined}\n className=\"select\"\n >\n {options.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n {!enhanced ? <Chevron floating /> : null}\n </div>\n\n {enhanced ? (\n <>\n <button\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listId}\n aria-label={`${label}: ${current?.label}`}\n onClick={() => (open ? setOpen(false) : openList())}\n onKeyDown={onKeyDown}\n className=\"select\" data-select-trigger=\"\"\n >\n <span data-select-value=\"\">{current?.label}</span>\n <Chevron open={open} />\n </button>\n {open ? (\n <ul id={listId} role=\"listbox\" aria-label={label} tabIndex={-1} data-select-list=\"\">\n {options.map((o, i) => (\n <li\n key={o.value}\n role=\"option\"\n aria-selected={o.value === value}\n data-active={i === active || undefined}\n onPointerEnter={() => setActive(i)}\n onClick={() => choose(o.value)}\n >\n {o.label}\n {o.value === value ? <Check /> : null}\n </li>\n ))}\n </ul>\n ) : null}\n </>\n ) : null}\n </div>\n )\n}\n","/**\n * Muestras de variante (color, combinación o emoji), pura y sin DOM: sirve en servidor y cliente.\n *\n * El MOTOR es genérico; los DICCIONARIOS no. El SDK solo trae colores básicos (ropa, acabados). Los sabores, emojis y cualquier\n * vocabulario de un giro (suplementos, café, moda…) los inyecta la tienda con `createSwatchResolver`, o llegan del catálogo\n * como atributo (`parseSwatchAttribute`), que siempre manda sobre cualquier diccionario.\n */\n\n/**\n * Lo que dibuja una muestra: colores planos, emoji, o AMBOS (una píldora pastel con la mezcla de colores y los emoji encima).\n * Al menos uno de los dos viene lleno.\n */\nexport type SwatchSpec = { colors?: string[]; emoji?: string }\n\n/** [frase normalizada (minúsculas, sin acentos), un color hex o varios] */\nexport type ColorEntry = [phrase: string, color: string | string[]]\n/** [frase normalizada, emoji o varios emoji] */\nexport type EmojiEntry = [phrase: string, emoji: string]\n\n/** Qué dibujar primero cuando hay color y emoji para el mismo nombre. */\nexport type SwatchPrefer = 'auto' | 'color' | 'emoji' | 'both'\n\nconst HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i\nconst HEX_IN_TEXT = /#(?:[0-9a-f]{6}|[0-9a-f]{3})\\b/gi\nconst MAX = 4\n\n/** Minúsculas, sin acentos, sin puntuación suelta. \"Piña\" → \"pina\". */\nexport function normalize(value: string): string {\n return value.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim()\n}\n\n/** Colores básicos (es/en) para ropa y acabados. Sin vocabulario de ningún giro en particular. */\nexport const defaultColors: ColorEntry[] = [\n ['azul marino', '#14213d'], ['navy', '#14213d'], ['azul rey', '#2447d6'], ['azul cielo', '#6ec1f0'], ['celeste', '#8fd0f5'],\n ['verde militar', '#4b5320'], ['verde oliva', '#6b7a2a'], ['verde limon', '#b8e03a'], ['verde menta', '#8fe0b5'],\n ['rosa mexicano', '#e4007c'], ['gris oscuro', '#4a4f57'], ['gris claro', '#cfd3d8'],\n ['rojo', '#e0262d'], ['red', '#e0262d'], ['vino', '#6d1a36'], ['guinda', '#7a1f3d'], ['burgundy', '#6d1a36'],\n ['azul', '#2f5bea'], ['blue', '#2f5bea'], ['verde', '#2fa84f'], ['green', '#2fa84f'],\n ['amarillo', '#ffd21f'], ['yellow', '#ffd21f'], ['naranja', '#ff8a1f'], ['orange', '#ff8a1f'],\n ['morado', '#7b3fc4'], ['purpura', '#7b3fc4'], ['purple', '#7b3fc4'], ['violeta', '#8a4fd6'], ['lila', '#c4a5f0'],\n ['rosa', '#ff7eb6'], ['pink', '#ff7eb6'], ['fucsia', '#e4007c'], ['magenta', '#d4119e'],\n ['negro', '#111111'], ['black', '#111111'], ['blanco', '#ffffff'], ['white', '#ffffff'],\n ['gris', '#9aa0a8'], ['gray', '#9aa0a8'], ['grey', '#9aa0a8'], ['plata', '#c0c4cc'], ['plateado', '#c0c4cc'], ['silver', '#c0c4cc'],\n ['dorado', '#d4a72c'], ['oro', '#d4a72c'], ['gold', '#d4a72c'], ['cafe', '#7a4a2b'], ['marron', '#7a4a2b'], ['brown', '#7a4a2b'],\n ['beige', '#e6d3b3'], ['crema', '#f4ead2'], ['hueso', '#f1ecde'], ['arena', '#d9c39a'], ['khaki', '#b8a77a'], ['camel', '#b9824a'],\n ['turquesa', '#1fc8c8'], ['aqua', '#34d3d3'], ['coral', '#ff6f61'], ['salmon', '#ff8f7a'], ['mostaza', '#d9a521'], ['terracota', '#c4623f'],\n ['transparente', '#e8edf2'],\n]\n\n/**\n * Busca en el texto COMPLETO las frases del diccionario como palabras (plural con \"s\" opcional), la más larga primero, y las\n * devuelve en el orden en que se leen. Se tacha lo encontrado: \"azul\" no vuelve a salir dentro de \"azul marino\". Al no partir\n * el texto por \"y\"/\"and\", las frases con conectores (\"cookies and cream\") se resuelven como frase.\n */\nfunction scan<T>(text: string, entries: [string, T][]): T[] {\n const found: { index: number; value: T }[] = []\n let rest = text\n for (const [phrase, value] of [...entries].sort((a, b) => b[0].length - a[0].length)) {\n const m = new RegExp(`(^|[^a-z])${phrase}s?(?![a-z])`).exec(rest)\n if (!m) continue\n const start = m.index + (m[1]?.length ?? 0)\n found.push({ index: start, value })\n rest = rest.slice(0, start) + ' '.repeat(phrase.length + 1) + rest.slice(start + phrase.length + 1)\n }\n return found.sort((a, b) => a.index - b.index).map((f) => f.value)\n}\n\nfunction graphemes(text: string): string[] {\n return [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text)].map((g) => g.segment)\n}\n\nexport type SwatchResolverOptions = {\n /** Diccionario de colores. Default: `defaultColors`. Para sumar vocabulario: `[...defaultColors, ...tusColores]`. */\n colors?: ColorEntry[]\n /** Diccionario de emoji (sin default: es vocabulario del giro). */\n emojis?: EmojiEntry[]\n /** Qué dibujar si hay color y emoji: `auto`/`color` = color primero, `emoji` = emoji primero, `both` = píldora pastel con colores y emoji. */\n prefer?: SwatchPrefer\n}\n\nexport type SwatchResolver = {\n /** Colores del nombre (\"Rojo / Negro\", \"#ff8800\"), en orden de lectura, o `null`. */\n colors(value: string): string[] | null\n /** Emoji(s) del nombre (hasta 3, sin repetir), o `null`. */\n emoji(value: string): string | null\n /** Muestra de una variante: primero el atributo del catálogo, luego el nombre según `prefer`. */\n resolve(value: string, attribute?: Map<string, SwatchSpec>): SwatchSpec | null\n /** ¿Todos los valores de una opción tienen muestra? */\n hasSwatches(values: string[], attribute?: Map<string, SwatchSpec>): boolean\n}\n\nexport function createSwatchResolver({ colors: colorTable = defaultColors, emojis = [], prefer = 'auto' }: SwatchResolverOptions = {}): SwatchResolver {\n const colors = (value: string): string[] | null => {\n const text = normalize(value)\n if (!text) return null\n const hex = (text.match(HEX_IN_TEXT) ?? []).filter((h) => HEX.test(h))\n const found = [...hex, ...scan(text, colorTable).flat()]\n const unique = found.filter((c, i) => found.indexOf(c) === i).slice(0, MAX)\n return unique.length > 0 ? unique : null\n }\n const emoji = (value: string): string | null => {\n const parts = scan(normalize(value), emojis).flatMap(graphemes)\n const unique = parts.filter((e, i) => parts.indexOf(e) === i).slice(0, 3)\n return unique.length > 0 ? unique.join('') : null\n }\n const resolve = (value: string, attribute?: Map<string, SwatchSpec>): SwatchSpec | null => {\n const fromCatalog = attribute?.get(normalize(value))\n if (fromCatalog) return fromCatalog\n const c = colors(value)\n const e = emoji(value)\n if (prefer === 'both' && c && e) return { colors: c, emoji: e }\n if (prefer === 'emoji') return e ? { emoji: e } : c ? { colors: c } : null\n if (prefer === 'color') return c ? { colors: c } : e ? { emoji: e } : null\n return c ? { colors: c } : e ? { emoji: e } : null\n }\n return { colors, emoji, resolve, hasSwatches: (values, attribute) => values.length > 0 && values.every((v) => resolve(v, attribute) !== null) }\n}\n\nconst basic = createSwatchResolver()\n\n/** Colores básicos del nombre (sin vocabulario de giro). Para uno propio: `createSwatchResolver`. */\nexport const resolveSwatch = basic.colors\n/** Muestra de una variante con solo colores básicos. Para uno propio: `createSwatchResolver`. */\nexport const resolveSwatchSpec = (value: string, attribute?: Map<string, SwatchSpec>): SwatchSpec | null => basic.resolve(value, attribute)\nexport const optionHasSwatches = basic.hasSwatches\n\n/**\n * Atributo del producto con el color o emoji de cada variante, como lo siembra el catálogo (OPS):\n * `\"Mora Azul:#3d4fd6|Fresa Kiwi:#ff4d6d+#8bc34a 🍓🥝|Natural:🥛\"`. Cada valor es un hex, varios hex con \"+\" (combinación), emoji, o colores y emoji juntos.\n * Es la fuente de verdad: manda sobre cualquier diccionario.\n */\nexport function parseSwatchAttribute(raw: unknown): Map<string, SwatchSpec> {\n const map = new Map<string, SwatchSpec>()\n if (typeof raw !== 'string') return map\n for (const part of raw.split('|')) {\n const i = part.lastIndexOf(':')\n if (i <= 0) continue\n const name = normalize(part.slice(0, i))\n const value = part.slice(i + 1).trim()\n const colors = (value.match(HEX_IN_TEXT) ?? []).filter((c) => HEX.test(c)).slice(0, MAX)\n const emoji = value.replace(HEX_IN_TEXT, '').replace(/[+\\s]/g, '')\n const hasEmoji = emoji.length > 0 && /\\p{Extended_Pictographic}/u.test(emoji)\n if (colors.length > 0 || hasEmoji) map.set(name, { ...(colors.length > 0 ? { colors } : {}), ...(hasEmoji ? { emoji } : {}) })\n }\n return map\n}\n\n/** Fondo CSS plano: un color liso, dos mitades, o sectores iguales (hasta 4). */\nexport function swatchBackground(colors: string[]): string {\n const [first = 'transparent', second] = colors\n if (second === undefined) return first\n if (colors.length === 2) return `linear-gradient(90deg, ${first} 50%, ${second} 50%)`\n const step = 360 / colors.length\n return `conic-gradient(${colors.map((c, i) => `${c} ${i * step}deg ${(i + 1) * step}deg`).join(', ')})`\n}\n\n/** Luminancia relativa (0 negro – 1 blanco) de un color hex, para decidir si hace falta un aro. */\nexport function luminance(hex: string): number {\n const h = hex.replace('#', '')\n const full = h.length === 3 ? h.split('').map((c) => c + c).join('') : h\n const channel = (i: number): number => {\n const v = parseInt(full.slice(i, i + 2), 16) / 255\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4\n }\n return 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4)\n}\n\n/**\n * Fondo pastel de una píldora: cada color mezclado con blanco (queda claro y suave en claro y oscuro), un degradado suave entre\n * ellos si son varios. Con ese fondo el emoji siempre contrasta.\n */\nexport function pastelBackground(colors: string[]): string {\n const soft = colors.map((c) => `color-mix(in srgb, ${c} 26%, white)`)\n const [first = 'transparent', second] = soft\n if (second === undefined) return first\n return `linear-gradient(100deg, ${soft.join(', ')})`\n}\n","import type { CSSProperties } from 'react'\nimport { luminance, pastelBackground, resolveSwatchSpec, swatchBackground, type SwatchSpec } from './swatch-colors'\n\nexport type SwatchProps = {\n /** Nombre de la variante; se resuelve con los colores básicos del SDK. Para vocabulario propio resuelve tú con `createSwatchResolver` y pasa `spec`. */\n value?: string\n /** Lo que se dibuja (color(es) o emoji), ya resuelto: del catálogo (`parseSwatchAttribute`) o de tu `createSwatchResolver`. Manda sobre `value`. */\n spec?: SwatchSpec | null\n size?: number\n shape?: 'circle' | 'square'\n className?: string\n}\n\n/**\n * Muestra de color 2D para variantes: plana, sin sombras ni brillos y sin borde, salvo un aro mínimo cuando el color\n * se perdería contra el fondo (blanco en tema claro, negro en tema oscuro). Las combinaciones se dibujan en partes iguales.\n * Es decorativa (aria-hidden): el nombre de la variante debe ir siempre como texto junto a ella.\n * Con `spec.emoji` dibuja el emoji. Devuelve null si no reconoce nada, para que el llamador muestre solo el texto.\n */\nexport function Swatch({ value = '', spec, size = 20, shape = 'circle', className = '' }: SwatchProps) {\n const resolved = spec ?? resolveSwatchSpec(value)\n if (!resolved) return null\n const colors = resolved.colors ?? []\n if (resolved.emoji) {\n // Badge: círculo (un emoji) o píldora (mezcla) con fondo pastel de los colores del sabor y el emoji encima; sin colores, fondo\n // neutro de superficie. En la mezcla los emoji se montan un poco, el primero arriba. Así se ve qué emoji forman cada grupo.\n const parts = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(resolved.emoji)].map((g) => g.segment)\n const mixed = parts.length > 1\n const style: CSSProperties = {\n minWidth: size,\n height: size,\n fontSize: Math.round(size * (mixed ? 0.56 : 0.6)),\n ...(colors.length > 0 ? { background: pastelBackground(colors), ['--sw-ring' as string]: colors[0] } : {}),\n }\n return (\n <span aria-hidden=\"true\" data-swatch=\"\" data-kind=\"emoji\" data-mixed={mixed || undefined} data-colored={colors.length > 0 || undefined} data-shape={shape} className={className || undefined} style={style}>\n {parts.map((emoji, i) => (\n <span key={`${emoji}-${i}`} style={i > 0 ? { marginInlineStart: '-0.3em', zIndex: parts.length - i } : { zIndex: parts.length }}>\n {emoji}\n </span>\n ))}\n </span>\n )\n }\n if (colors.length === 0) return null\n const lums = colors.map(luminance)\n const edge = lums.some((l) => l > 0.82) ? 'light' : lums.some((l) => l < 0.04) ? 'dark' : undefined\n const style: CSSProperties = { width: size, height: size, background: swatchBackground(colors) }\n return <span aria-hidden=\"true\" data-swatch=\"\" data-edge={edge} data-shape={shape} className={className || undefined} style={style} />\n}\n","import { useEffect, useState } from 'react'\n\ntype Theme = 'light' | 'dark'\n\n/** Script de cabecera: aplica el tema guardado antes de pintar (sin parpadeo). Sin preferencia guardada manda el sistema. */\nexport const THEME_INIT_SCRIPT = `try{var t=localStorage.getItem(\"theme\");if(t===\"light\"||t===\"dark\")document.documentElement.setAttribute(\"data-theme\",t)}catch(e){}`\n\nfunction current(): Theme {\n const attr = document.documentElement.getAttribute('data-theme')\n if (attr === 'light' || attr === 'dark') return attr\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'\n}\n\n/**\n * Botón claro/oscuro. El estado real se lee del DOM tras hidratar (el servidor no sabe la preferencia).\n * Guarda la elección en localStorage (\"theme\") y fija `html[data-theme]`; los tokens de la tienda reaccionan a eso.\n */\nexport function ThemeToggle({ className = '', themeColors = { light: '#ffffff', dark: '#000000' } }: { className?: string; themeColors?: { light: string; dark: string } }) {\n const [theme, setTheme] = useState<Theme | null>(null)\n useEffect(() => setTheme(current()), [])\n\n const toggle = () => {\n const next: Theme = current() === 'dark' ? 'light' : 'dark'\n document.documentElement.setAttribute('data-theme', next)\n try {\n localStorage.setItem('theme', next)\n } catch {\n /* modo privado: el cambio dura la sesión */\n }\n document.querySelector('meta[name=\"theme-color\"][data-dynamic]')?.setAttribute('content', themeColors[next])\n setTheme(next)\n }\n\n const dark = theme === 'dark'\n return (\n <button type=\"button\" onClick={toggle} className={className} aria-label={dark ? 'Cambiar a tema claro' : 'Cambiar a tema oscuro'} aria-pressed={theme === null ? undefined : dark}>\n {dark ? (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\" />\n </svg>\n ) : (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z\" />\n </svg>\n )}\n </button>\n )\n}\n","import { useEffect, useState } from 'react'\n\ntype InstallEvent = Event & { prompt: () => Promise<void>; userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }> }\n\n/** Registra el service worker (solo si `enabled`, normalmente producción; requiere HTTPS o localhost). */\nexport function useServiceWorker({ enabled, url = '/sw.js', scope = '/' }: { enabled: boolean; url?: string; scope?: string }) {\n useEffect(() => {\n if (!enabled || !('serviceWorker' in navigator)) return\n const register = () => navigator.serviceWorker.register(url, { scope }).catch(() => undefined)\n if (document.readyState === 'complete') register()\n else window.addEventListener('load', register, { once: true })\n }, [enabled, url, scope])\n}\n\n/**\n * Botón \"Instalar app\": aparece solo cuando el navegador ofrece la instalación (Chrome, Edge, Android).\n * En iPhone/iPad no existe ese evento: muestra la instrucción de Compartir → Agregar a pantalla de inicio.\n */\nexport function InstallApp({ className = '', label = 'Instalar app', iosHint = 'Para instalar: toca Compartir y luego «Agregar a pantalla de inicio».' }: { className?: string; label?: string; iosHint?: string }) {\n const [event, setEvent] = useState<InstallEvent | null>(null)\n const [ios, setIos] = useState(false)\n const [installed, setInstalled] = useState(true)\n\n useEffect(() => {\n const standalone = window.matchMedia('(display-mode: standalone)').matches || (navigator as Navigator & { standalone?: boolean }).standalone === true\n setInstalled(standalone)\n setIos(/iphone|ipad|ipod/i.test(navigator.userAgent) && !standalone)\n const onPrompt = (e: Event) => {\n e.preventDefault()\n setEvent(e as InstallEvent)\n }\n const onInstalled = () => setInstalled(true)\n window.addEventListener('beforeinstallprompt', onPrompt)\n window.addEventListener('appinstalled', onInstalled)\n return () => {\n window.removeEventListener('beforeinstallprompt', onPrompt)\n window.removeEventListener('appinstalled', onInstalled)\n }\n }, [])\n\n if (installed) return null\n if (event) {\n return (\n <button\n type=\"button\"\n className={className}\n onClick={async () => {\n await event.prompt()\n await event.userChoice\n setEvent(null)\n }}\n >\n {label}\n </button>\n )\n }\n return ios ? <p className={className}>{iosHint}</p> : null\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/storefront-react",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "React bindings for @asteby/storefront — SSR-safe provider, optimistic cart, Money, responsive Image, prices, variant selection, SEO meta and JSON-LD, section registry and server-rendered theme CSS.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -27,6 +27,27 @@
27
27
  "default": "./dist/index.cjs"
28
28
  }
29
29
  },
30
+ "./cart": {
31
+ "import": {
32
+ "types": "./dist/cart.d.ts",
33
+ "default": "./dist/cart.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/cart.d.cts",
37
+ "default": "./dist/cart.cjs"
38
+ }
39
+ },
40
+ "./cart.css": "./dist/cart.css",
41
+ "./fitment": {
42
+ "import": {
43
+ "types": "./dist/fitment.d.ts",
44
+ "default": "./dist/fitment.js"
45
+ },
46
+ "require": {
47
+ "types": "./dist/fitment.d.cts",
48
+ "default": "./dist/fitment.cjs"
49
+ }
50
+ },
30
51
  "./mockups": {
31
52
  "import": {
32
53
  "types": "./dist/mockups.d.ts",
@@ -57,6 +78,17 @@
57
78
  "default": "./dist/promotions.cjs"
58
79
  }
59
80
  },
81
+ "./ui": {
82
+ "import": {
83
+ "types": "./dist/ui.d.ts",
84
+ "default": "./dist/ui.js"
85
+ },
86
+ "require": {
87
+ "types": "./dist/ui.d.cts",
88
+ "default": "./dist/ui.cjs"
89
+ }
90
+ },
91
+ "./ui.css": "./dist/ui.css",
60
92
  "./package.json": "./package.json"
61
93
  },
62
94
  "sideEffects": false,
@@ -67,7 +99,7 @@
67
99
  "react": "^19.0.0"
68
100
  },
69
101
  "dependencies": {
70
- "@asteby/storefront": "0.6.0"
102
+ "@asteby/storefront": "0.7.0"
71
103
  },
72
104
  "devDependencies": {
73
105
  "@testing-library/react": "^16.3.2",
@@ -85,7 +117,7 @@
85
117
  "access": "public"
86
118
  },
87
119
  "scripts": {
88
- "build": "tsup",
120
+ "build": "tsup && node scripts/copy-ui-css.mjs",
89
121
  "dev": "tsup --watch",
90
122
  "typecheck": "tsc -p tsconfig.json --noEmit",
91
123
  "test": "vitest run",