@asteby/storefront-react 0.6.0 → 0.7.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 +14 -0
- package/dist/ui.cjs +392 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.css +166 -0
- package/dist/ui.d.cts +137 -0
- package/dist/ui.d.ts +137 -0
- package/dist/ui.js +375 -0
- package/dist/ui.js.map +1 -0
- package/package.json +14 -3
package/dist/ui.css
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Scroll moderno compartido (admin de ops, SDK y tiendas): barras delgadas, redondeadas y del color del texto del
|
|
3
|
+
* contexto (currentColor), así funcionan en claro y oscuro con cualquier sistema de tokens sin definir colores nuevos.
|
|
4
|
+
* Estándar en Chrome 121+/Firefox (scrollbar-width/color); prefijo WebKit para Safari. Las listas internas y los diálogos
|
|
5
|
+
* no arrastran la página de fondo al llegar a su límite (overscroll-behavior).
|
|
6
|
+
*/
|
|
7
|
+
@layer base {
|
|
8
|
+
* {
|
|
9
|
+
scrollbar-width: thin;
|
|
10
|
+
scrollbar-color: color-mix(in srgb, currentColor 32%, transparent) transparent;
|
|
11
|
+
}
|
|
12
|
+
*::-webkit-scrollbar {
|
|
13
|
+
width: 10px;
|
|
14
|
+
height: 10px;
|
|
15
|
+
}
|
|
16
|
+
*::-webkit-scrollbar-track {
|
|
17
|
+
background: transparent;
|
|
18
|
+
}
|
|
19
|
+
*::-webkit-scrollbar-thumb {
|
|
20
|
+
background-color: color-mix(in srgb, currentColor 32%, transparent);
|
|
21
|
+
border: 3px solid transparent;
|
|
22
|
+
border-radius: 9999px;
|
|
23
|
+
background-clip: content-box;
|
|
24
|
+
}
|
|
25
|
+
*::-webkit-scrollbar-thumb:hover {
|
|
26
|
+
background-color: color-mix(in srgb, currentColor 55%, transparent);
|
|
27
|
+
}
|
|
28
|
+
*::-webkit-scrollbar-corner {
|
|
29
|
+
background: transparent;
|
|
30
|
+
}
|
|
31
|
+
dialog,
|
|
32
|
+
[role='dialog'],
|
|
33
|
+
[role='listbox'],
|
|
34
|
+
[data-radix-scroll-area-viewport],
|
|
35
|
+
.overflow-y-auto,
|
|
36
|
+
.overflow-x-auto {
|
|
37
|
+
overscroll-behavior: contain;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* @asteby/storefront-react/ui.css: estilos de las piezas de /ui (Select, casillas, radios, Swatch).
|
|
43
|
+
* CSS plano, sin Tailwind. Se pintan con los tokens de la tienda: --background, --foreground, --border, --tint,
|
|
44
|
+
* --primary, --primary-foreground, --ring. Cada tienda los define (y los cambia en tema oscuro).
|
|
45
|
+
*/
|
|
46
|
+
/*
|
|
47
|
+
* <Swatch>: muestra plana (sin brillo ni sombra). Solo cuando el color se confunde con el fondo (blanco en tema claro,
|
|
48
|
+
* negro en tema oscuro) lleva un aro fino del color primario de la tienda (--primary), así se ve de marca y no genérico.
|
|
49
|
+
*/
|
|
50
|
+
[data-swatch] {
|
|
51
|
+
display: inline-block;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
border-radius: 9999px;
|
|
54
|
+
}
|
|
55
|
+
[data-swatch][data-shape="square"] {
|
|
56
|
+
border-radius: 0.3125rem;
|
|
57
|
+
}
|
|
58
|
+
[data-swatch][data-edge] {
|
|
59
|
+
box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--primary, #111111) 70%, transparent);
|
|
60
|
+
}
|
|
61
|
+
/* Donde el color sí contrasta con el fondo, el aro sobra. */
|
|
62
|
+
[data-swatch][data-edge="dark"] {
|
|
63
|
+
box-shadow: none;
|
|
64
|
+
}
|
|
65
|
+
:root[data-theme="dark"] [data-swatch][data-edge="dark"] {
|
|
66
|
+
box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--primary, #ffffff) 70%, transparent);
|
|
67
|
+
}
|
|
68
|
+
:root[data-theme="dark"] [data-swatch][data-edge="light"] {
|
|
69
|
+
box-shadow: none;
|
|
70
|
+
}
|
|
71
|
+
@media (prefers-color-scheme: dark) {
|
|
72
|
+
:root:not([data-theme="light"]) [data-swatch][data-edge="dark"] {
|
|
73
|
+
box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--primary, #ffffff) 70%, transparent);
|
|
74
|
+
}
|
|
75
|
+
:root:not([data-theme="light"]) [data-swatch][data-edge="light"] {
|
|
76
|
+
box-shadow: none;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/*
|
|
80
|
+
* Emoji: cada sabor en su círculo (uno) o píldora (mezcla), con fondo suave del color de superficie (--tint) y un aro fino,
|
|
81
|
+
* como un badge. Así, en una fila de sabores se distingue qué emoji forman cada grupo.
|
|
82
|
+
*/
|
|
83
|
+
[data-swatch][data-kind="emoji"] {
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
justify-content: center;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
line-height: 1;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
border-radius: 9999px;
|
|
91
|
+
background: var(--tint, color-mix(in srgb, currentColor 10%, transparent));
|
|
92
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 16%, transparent);
|
|
93
|
+
color: inherit;
|
|
94
|
+
}
|
|
95
|
+
/* Con colores del sabor: píldora pastel clara (el emoji siempre contrasta, también en tema oscuro) y aro del color principal. */
|
|
96
|
+
[data-swatch][data-kind="emoji"][data-colored] {
|
|
97
|
+
color: #111111;
|
|
98
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sw-ring) 42%, transparent);
|
|
99
|
+
}
|
|
100
|
+
[data-swatch][data-kind="emoji"][data-mixed] {
|
|
101
|
+
padding-inline: 0.42em;
|
|
102
|
+
}
|
|
103
|
+
[data-swatch][data-kind="emoji"] > span {
|
|
104
|
+
position: relative;
|
|
105
|
+
display: inline-block;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
.select {
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
display: block;
|
|
112
|
+
width: 100%;
|
|
113
|
+
min-height: 3rem;
|
|
114
|
+
padding: 0.625rem 0.875rem;
|
|
115
|
+
font: inherit;
|
|
116
|
+
color: var(--foreground);
|
|
117
|
+
background-color: var(--background);
|
|
118
|
+
border: 1px solid color-mix(in srgb, var(--foreground) 25%, transparent);
|
|
119
|
+
border-radius: var(--radius, 0.375rem);
|
|
120
|
+
cursor: pointer;
|
|
121
|
+
appearance: none;
|
|
122
|
+
transition: border-color 120ms;
|
|
123
|
+
}
|
|
124
|
+
.select:hover { border-color: var(--foreground); }
|
|
125
|
+
.select:focus-visible { outline: 2px solid var(--ring, var(--primary)); outline-offset: 2px; }
|
|
126
|
+
.select[aria-expanded="true"] { border-color: var(--primary); }
|
|
127
|
+
|
|
128
|
+
[data-select] { position: relative; }
|
|
129
|
+
[data-select-native="hidden"] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
130
|
+
[data-select-native=""] { position: relative; }
|
|
131
|
+
[data-select-trigger] { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; text-align: left; }
|
|
132
|
+
[data-select-value] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
133
|
+
[data-select-chevron] { flex-shrink: 0; opacity: 0.6; transition: transform 150ms; }
|
|
134
|
+
[data-select-chevron][data-open] { transform: rotate(180deg); }
|
|
135
|
+
[data-select-chevron="floating"] { position: absolute; right: 0.875rem; top: 50%; translate: 0 -50%; pointer-events: none; }
|
|
136
|
+
|
|
137
|
+
[data-select-list] {
|
|
138
|
+
position: absolute; inset-inline: 0; top: 100%; z-index: 30; margin: 0.375rem 0 0; padding: 0.25rem; list-style: none;
|
|
139
|
+
overflow: hidden; background: var(--background); color: var(--foreground);
|
|
140
|
+
border: 1px solid var(--border); border-radius: calc(var(--radius, 0.375rem) * 1.5);
|
|
141
|
+
box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);
|
|
142
|
+
}
|
|
143
|
+
[data-select-list] > li {
|
|
144
|
+
display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; min-height: 2.75rem; padding: 0 0.75rem;
|
|
145
|
+
font-size: 0.875rem; cursor: pointer; border-radius: var(--radius, 0.375rem);
|
|
146
|
+
}
|
|
147
|
+
[data-select-list] > li[data-active] { background: var(--tint, color-mix(in srgb, var(--foreground) 8%, transparent)); }
|
|
148
|
+
[data-select-list] > li[aria-selected="true"] { font-weight: 600; }
|
|
149
|
+
[data-select-list] > li[aria-selected="true"] svg { color: var(--primary); }
|
|
150
|
+
|
|
151
|
+
.check, .radio {
|
|
152
|
+
appearance: none; flex-shrink: 0; width: 1.25rem; height: 1.25rem; margin: 0; cursor: pointer;
|
|
153
|
+
background-color: var(--background); border: 1.5px solid color-mix(in srgb, var(--foreground) 40%, transparent);
|
|
154
|
+
transition: background-color 120ms, border-color 120ms;
|
|
155
|
+
}
|
|
156
|
+
.check { border-radius: 0.3125rem; background-position: center; background-repeat: no-repeat; background-size: 0.8rem; }
|
|
157
|
+
.radio { border-radius: 9999px; }
|
|
158
|
+
.check:hover, .radio:hover { border-color: var(--foreground); }
|
|
159
|
+
.check:focus-visible, .radio:focus-visible { outline: 2px solid var(--ring, var(--primary)); outline-offset: 2px; }
|
|
160
|
+
.check:checked { border-color: var(--primary); background-color: var(--primary); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E"); }
|
|
161
|
+
.radio:checked { border-color: var(--primary); border-width: 6px; }
|
|
162
|
+
/* Con primario claro (tema oscuro) la palomita va oscura. */
|
|
163
|
+
:root[data-theme="dark"] .check:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23080808' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E"); }
|
|
164
|
+
@media (prefers-color-scheme: dark) {
|
|
165
|
+
:root:not([data-theme="light"]) .check:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23080808' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E"); }
|
|
166
|
+
}
|
package/dist/ui.d.cts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
type SelectOption = {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
type SelectProps = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
options: SelectOption[];
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
/** Etiqueta accesible (la visible la pone el llamador con <label htmlFor={id}>). */
|
|
13
|
+
label: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Selector con mejora progresiva. Sin JavaScript (o antes de hidratar) es un <select> nativo dentro de un formulario;
|
|
18
|
+
* con JavaScript se reemplaza por un listbox propio (teclado completo, objetivo táctil de 44 px, tema claro/oscuro).
|
|
19
|
+
* El <select> nativo sigue en el DOM como fuente de verdad: al elegir se cambia su valor y se emite "change", así un
|
|
20
|
+
* formulario con onChange → submit (filtros por URL) no nota la diferencia.
|
|
21
|
+
* Estilos: clases `select` (ver ui.css) y los tokens --background, --foreground, --border, --tint, --primary.
|
|
22
|
+
*/
|
|
23
|
+
declare function Select({ id, name, options, defaultValue, label, className }: SelectProps): react.JSX.Element;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Muestras de variante (color, combinación o emoji), pura y sin DOM: sirve en servidor y cliente.
|
|
27
|
+
*
|
|
28
|
+
* El MOTOR es genérico; los DICCIONARIOS no. El SDK solo trae colores básicos (ropa, acabados). Los sabores, emojis y cualquier
|
|
29
|
+
* vocabulario de un giro (suplementos, café, moda…) los inyecta la tienda con `createSwatchResolver`, o llegan del catálogo
|
|
30
|
+
* como atributo (`parseSwatchAttribute`), que siempre manda sobre cualquier diccionario.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Lo que dibuja una muestra: colores planos, emoji, o AMBOS (una píldora pastel con la mezcla de colores y los emoji encima).
|
|
34
|
+
* Al menos uno de los dos viene lleno.
|
|
35
|
+
*/
|
|
36
|
+
type SwatchSpec = {
|
|
37
|
+
colors?: string[];
|
|
38
|
+
emoji?: string;
|
|
39
|
+
};
|
|
40
|
+
/** [frase normalizada (minúsculas, sin acentos), un color hex o varios] */
|
|
41
|
+
type ColorEntry = [phrase: string, color: string | string[]];
|
|
42
|
+
/** [frase normalizada, emoji o varios emoji] */
|
|
43
|
+
type EmojiEntry = [phrase: string, emoji: string];
|
|
44
|
+
/** Qué dibujar primero cuando hay color y emoji para el mismo nombre. */
|
|
45
|
+
type SwatchPrefer = 'auto' | 'color' | 'emoji' | 'both';
|
|
46
|
+
/** Minúsculas, sin acentos, sin puntuación suelta. "Piña" → "pina". */
|
|
47
|
+
declare function normalize(value: string): string;
|
|
48
|
+
/** Colores básicos (es/en) para ropa y acabados. Sin vocabulario de ningún giro en particular. */
|
|
49
|
+
declare const defaultColors: ColorEntry[];
|
|
50
|
+
type SwatchResolverOptions = {
|
|
51
|
+
/** Diccionario de colores. Default: `defaultColors`. Para sumar vocabulario: `[...defaultColors, ...tusColores]`. */
|
|
52
|
+
colors?: ColorEntry[];
|
|
53
|
+
/** Diccionario de emoji (sin default: es vocabulario del giro). */
|
|
54
|
+
emojis?: EmojiEntry[];
|
|
55
|
+
/** Qué dibujar si hay color y emoji: `auto`/`color` = color primero, `emoji` = emoji primero, `both` = píldora pastel con colores y emoji. */
|
|
56
|
+
prefer?: SwatchPrefer;
|
|
57
|
+
};
|
|
58
|
+
type SwatchResolver = {
|
|
59
|
+
/** Colores del nombre ("Rojo / Negro", "#ff8800"), en orden de lectura, o `null`. */
|
|
60
|
+
colors(value: string): string[] | null;
|
|
61
|
+
/** Emoji(s) del nombre (hasta 3, sin repetir), o `null`. */
|
|
62
|
+
emoji(value: string): string | null;
|
|
63
|
+
/** Muestra de una variante: primero el atributo del catálogo, luego el nombre según `prefer`. */
|
|
64
|
+
resolve(value: string, attribute?: Map<string, SwatchSpec>): SwatchSpec | null;
|
|
65
|
+
/** ¿Todos los valores de una opción tienen muestra? */
|
|
66
|
+
hasSwatches(values: string[], attribute?: Map<string, SwatchSpec>): boolean;
|
|
67
|
+
};
|
|
68
|
+
declare function createSwatchResolver({ colors: colorTable, emojis, prefer }?: SwatchResolverOptions): SwatchResolver;
|
|
69
|
+
/** Colores básicos del nombre (sin vocabulario de giro). Para uno propio: `createSwatchResolver`. */
|
|
70
|
+
declare const resolveSwatch: (value: string) => string[] | null;
|
|
71
|
+
/** Muestra de una variante con solo colores básicos. Para uno propio: `createSwatchResolver`. */
|
|
72
|
+
declare const resolveSwatchSpec: (value: string, attribute?: Map<string, SwatchSpec>) => SwatchSpec | null;
|
|
73
|
+
declare const optionHasSwatches: (values: string[], attribute?: Map<string, SwatchSpec>) => boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Atributo del producto con el color o emoji de cada variante, como lo siembra el catálogo (OPS):
|
|
76
|
+
* `"Mora Azul:#3d4fd6|Fresa Kiwi:#ff4d6d+#8bc34a 🍓🥝|Natural:🥛"`. Cada valor es un hex, varios hex con "+" (combinación), emoji, o colores y emoji juntos.
|
|
77
|
+
* Es la fuente de verdad: manda sobre cualquier diccionario.
|
|
78
|
+
*/
|
|
79
|
+
declare function parseSwatchAttribute(raw: unknown): Map<string, SwatchSpec>;
|
|
80
|
+
/** Fondo CSS plano: un color liso, dos mitades, o sectores iguales (hasta 4). */
|
|
81
|
+
declare function swatchBackground(colors: string[]): string;
|
|
82
|
+
/** Luminancia relativa (0 negro – 1 blanco) de un color hex, para decidir si hace falta un aro. */
|
|
83
|
+
declare function luminance(hex: string): number;
|
|
84
|
+
/**
|
|
85
|
+
* Fondo pastel de una píldora: cada color mezclado con blanco (queda claro y suave en claro y oscuro), un degradado suave entre
|
|
86
|
+
* ellos si son varios. Con ese fondo el emoji siempre contrasta.
|
|
87
|
+
*/
|
|
88
|
+
declare function pastelBackground(colors: string[]): string;
|
|
89
|
+
|
|
90
|
+
type SwatchProps = {
|
|
91
|
+
/** Nombre de la variante; se resuelve con los colores básicos del SDK. Para vocabulario propio resuelve tú con `createSwatchResolver` y pasa `spec`. */
|
|
92
|
+
value?: string;
|
|
93
|
+
/** Lo que se dibuja (color(es) o emoji), ya resuelto: del catálogo (`parseSwatchAttribute`) o de tu `createSwatchResolver`. Manda sobre `value`. */
|
|
94
|
+
spec?: SwatchSpec | null;
|
|
95
|
+
size?: number;
|
|
96
|
+
shape?: 'circle' | 'square';
|
|
97
|
+
className?: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Muestra de color 2D para variantes: plana, sin sombras ni brillos y sin borde, salvo un aro mínimo cuando el color
|
|
101
|
+
* se perdería contra el fondo (blanco en tema claro, negro en tema oscuro). Las combinaciones se dibujan en partes iguales.
|
|
102
|
+
* Es decorativa (aria-hidden): el nombre de la variante debe ir siempre como texto junto a ella.
|
|
103
|
+
* Con `spec.emoji` dibuja el emoji. Devuelve null si no reconoce nada, para que el llamador muestre solo el texto.
|
|
104
|
+
*/
|
|
105
|
+
declare function Swatch({ value, spec, size, shape, className }: SwatchProps): react.JSX.Element | null;
|
|
106
|
+
|
|
107
|
+
/** Script de cabecera: aplica el tema guardado antes de pintar (sin parpadeo). Sin preferencia guardada manda el sistema. */
|
|
108
|
+
declare const THEME_INIT_SCRIPT = "try{var t=localStorage.getItem(\"theme\");if(t===\"light\"||t===\"dark\")document.documentElement.setAttribute(\"data-theme\",t)}catch(e){}";
|
|
109
|
+
/**
|
|
110
|
+
* Botón claro/oscuro. El estado real se lee del DOM tras hidratar (el servidor no sabe la preferencia).
|
|
111
|
+
* Guarda la elección en localStorage ("theme") y fija `html[data-theme]`; los tokens de la tienda reaccionan a eso.
|
|
112
|
+
*/
|
|
113
|
+
declare function ThemeToggle({ className, themeColors }: {
|
|
114
|
+
className?: string;
|
|
115
|
+
themeColors?: {
|
|
116
|
+
light: string;
|
|
117
|
+
dark: string;
|
|
118
|
+
};
|
|
119
|
+
}): react.JSX.Element;
|
|
120
|
+
|
|
121
|
+
/** Registra el service worker (solo si `enabled`, normalmente producción; requiere HTTPS o localhost). */
|
|
122
|
+
declare function useServiceWorker({ enabled, url, scope }: {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
url?: string;
|
|
125
|
+
scope?: string;
|
|
126
|
+
}): void;
|
|
127
|
+
/**
|
|
128
|
+
* Botón "Instalar app": aparece solo cuando el navegador ofrece la instalación (Chrome, Edge, Android).
|
|
129
|
+
* En iPhone/iPad no existe ese evento: muestra la instrucción de Compartir → Agregar a pantalla de inicio.
|
|
130
|
+
*/
|
|
131
|
+
declare function InstallApp({ className, label, iosHint }: {
|
|
132
|
+
className?: string;
|
|
133
|
+
label?: string;
|
|
134
|
+
iosHint?: string;
|
|
135
|
+
}): react.JSX.Element | null;
|
|
136
|
+
|
|
137
|
+
export { type ColorEntry, type EmojiEntry, InstallApp, Select, type SelectOption, type SelectProps, Swatch, type SwatchPrefer, type SwatchProps, type SwatchResolver, type SwatchResolverOptions, type SwatchSpec, THEME_INIT_SCRIPT, ThemeToggle, createSwatchResolver, defaultColors, luminance, normalize as normalizeSwatchName, optionHasSwatches, parseSwatchAttribute, pastelBackground, resolveSwatch, resolveSwatchSpec, swatchBackground, useServiceWorker };
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
|
|
3
|
+
type SelectOption = {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
type SelectProps = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
options: SelectOption[];
|
|
11
|
+
defaultValue?: string;
|
|
12
|
+
/** Etiqueta accesible (la visible la pone el llamador con <label htmlFor={id}>). */
|
|
13
|
+
label: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Selector con mejora progresiva. Sin JavaScript (o antes de hidratar) es un <select> nativo dentro de un formulario;
|
|
18
|
+
* con JavaScript se reemplaza por un listbox propio (teclado completo, objetivo táctil de 44 px, tema claro/oscuro).
|
|
19
|
+
* El <select> nativo sigue en el DOM como fuente de verdad: al elegir se cambia su valor y se emite "change", así un
|
|
20
|
+
* formulario con onChange → submit (filtros por URL) no nota la diferencia.
|
|
21
|
+
* Estilos: clases `select` (ver ui.css) y los tokens --background, --foreground, --border, --tint, --primary.
|
|
22
|
+
*/
|
|
23
|
+
declare function Select({ id, name, options, defaultValue, label, className }: SelectProps): react.JSX.Element;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Muestras de variante (color, combinación o emoji), pura y sin DOM: sirve en servidor y cliente.
|
|
27
|
+
*
|
|
28
|
+
* El MOTOR es genérico; los DICCIONARIOS no. El SDK solo trae colores básicos (ropa, acabados). Los sabores, emojis y cualquier
|
|
29
|
+
* vocabulario de un giro (suplementos, café, moda…) los inyecta la tienda con `createSwatchResolver`, o llegan del catálogo
|
|
30
|
+
* como atributo (`parseSwatchAttribute`), que siempre manda sobre cualquier diccionario.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Lo que dibuja una muestra: colores planos, emoji, o AMBOS (una píldora pastel con la mezcla de colores y los emoji encima).
|
|
34
|
+
* Al menos uno de los dos viene lleno.
|
|
35
|
+
*/
|
|
36
|
+
type SwatchSpec = {
|
|
37
|
+
colors?: string[];
|
|
38
|
+
emoji?: string;
|
|
39
|
+
};
|
|
40
|
+
/** [frase normalizada (minúsculas, sin acentos), un color hex o varios] */
|
|
41
|
+
type ColorEntry = [phrase: string, color: string | string[]];
|
|
42
|
+
/** [frase normalizada, emoji o varios emoji] */
|
|
43
|
+
type EmojiEntry = [phrase: string, emoji: string];
|
|
44
|
+
/** Qué dibujar primero cuando hay color y emoji para el mismo nombre. */
|
|
45
|
+
type SwatchPrefer = 'auto' | 'color' | 'emoji' | 'both';
|
|
46
|
+
/** Minúsculas, sin acentos, sin puntuación suelta. "Piña" → "pina". */
|
|
47
|
+
declare function normalize(value: string): string;
|
|
48
|
+
/** Colores básicos (es/en) para ropa y acabados. Sin vocabulario de ningún giro en particular. */
|
|
49
|
+
declare const defaultColors: ColorEntry[];
|
|
50
|
+
type SwatchResolverOptions = {
|
|
51
|
+
/** Diccionario de colores. Default: `defaultColors`. Para sumar vocabulario: `[...defaultColors, ...tusColores]`. */
|
|
52
|
+
colors?: ColorEntry[];
|
|
53
|
+
/** Diccionario de emoji (sin default: es vocabulario del giro). */
|
|
54
|
+
emojis?: EmojiEntry[];
|
|
55
|
+
/** Qué dibujar si hay color y emoji: `auto`/`color` = color primero, `emoji` = emoji primero, `both` = píldora pastel con colores y emoji. */
|
|
56
|
+
prefer?: SwatchPrefer;
|
|
57
|
+
};
|
|
58
|
+
type SwatchResolver = {
|
|
59
|
+
/** Colores del nombre ("Rojo / Negro", "#ff8800"), en orden de lectura, o `null`. */
|
|
60
|
+
colors(value: string): string[] | null;
|
|
61
|
+
/** Emoji(s) del nombre (hasta 3, sin repetir), o `null`. */
|
|
62
|
+
emoji(value: string): string | null;
|
|
63
|
+
/** Muestra de una variante: primero el atributo del catálogo, luego el nombre según `prefer`. */
|
|
64
|
+
resolve(value: string, attribute?: Map<string, SwatchSpec>): SwatchSpec | null;
|
|
65
|
+
/** ¿Todos los valores de una opción tienen muestra? */
|
|
66
|
+
hasSwatches(values: string[], attribute?: Map<string, SwatchSpec>): boolean;
|
|
67
|
+
};
|
|
68
|
+
declare function createSwatchResolver({ colors: colorTable, emojis, prefer }?: SwatchResolverOptions): SwatchResolver;
|
|
69
|
+
/** Colores básicos del nombre (sin vocabulario de giro). Para uno propio: `createSwatchResolver`. */
|
|
70
|
+
declare const resolveSwatch: (value: string) => string[] | null;
|
|
71
|
+
/** Muestra de una variante con solo colores básicos. Para uno propio: `createSwatchResolver`. */
|
|
72
|
+
declare const resolveSwatchSpec: (value: string, attribute?: Map<string, SwatchSpec>) => SwatchSpec | null;
|
|
73
|
+
declare const optionHasSwatches: (values: string[], attribute?: Map<string, SwatchSpec>) => boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Atributo del producto con el color o emoji de cada variante, como lo siembra el catálogo (OPS):
|
|
76
|
+
* `"Mora Azul:#3d4fd6|Fresa Kiwi:#ff4d6d+#8bc34a 🍓🥝|Natural:🥛"`. Cada valor es un hex, varios hex con "+" (combinación), emoji, o colores y emoji juntos.
|
|
77
|
+
* Es la fuente de verdad: manda sobre cualquier diccionario.
|
|
78
|
+
*/
|
|
79
|
+
declare function parseSwatchAttribute(raw: unknown): Map<string, SwatchSpec>;
|
|
80
|
+
/** Fondo CSS plano: un color liso, dos mitades, o sectores iguales (hasta 4). */
|
|
81
|
+
declare function swatchBackground(colors: string[]): string;
|
|
82
|
+
/** Luminancia relativa (0 negro – 1 blanco) de un color hex, para decidir si hace falta un aro. */
|
|
83
|
+
declare function luminance(hex: string): number;
|
|
84
|
+
/**
|
|
85
|
+
* Fondo pastel de una píldora: cada color mezclado con blanco (queda claro y suave en claro y oscuro), un degradado suave entre
|
|
86
|
+
* ellos si son varios. Con ese fondo el emoji siempre contrasta.
|
|
87
|
+
*/
|
|
88
|
+
declare function pastelBackground(colors: string[]): string;
|
|
89
|
+
|
|
90
|
+
type SwatchProps = {
|
|
91
|
+
/** Nombre de la variante; se resuelve con los colores básicos del SDK. Para vocabulario propio resuelve tú con `createSwatchResolver` y pasa `spec`. */
|
|
92
|
+
value?: string;
|
|
93
|
+
/** Lo que se dibuja (color(es) o emoji), ya resuelto: del catálogo (`parseSwatchAttribute`) o de tu `createSwatchResolver`. Manda sobre `value`. */
|
|
94
|
+
spec?: SwatchSpec | null;
|
|
95
|
+
size?: number;
|
|
96
|
+
shape?: 'circle' | 'square';
|
|
97
|
+
className?: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Muestra de color 2D para variantes: plana, sin sombras ni brillos y sin borde, salvo un aro mínimo cuando el color
|
|
101
|
+
* se perdería contra el fondo (blanco en tema claro, negro en tema oscuro). Las combinaciones se dibujan en partes iguales.
|
|
102
|
+
* Es decorativa (aria-hidden): el nombre de la variante debe ir siempre como texto junto a ella.
|
|
103
|
+
* Con `spec.emoji` dibuja el emoji. Devuelve null si no reconoce nada, para que el llamador muestre solo el texto.
|
|
104
|
+
*/
|
|
105
|
+
declare function Swatch({ value, spec, size, shape, className }: SwatchProps): react.JSX.Element | null;
|
|
106
|
+
|
|
107
|
+
/** Script de cabecera: aplica el tema guardado antes de pintar (sin parpadeo). Sin preferencia guardada manda el sistema. */
|
|
108
|
+
declare const THEME_INIT_SCRIPT = "try{var t=localStorage.getItem(\"theme\");if(t===\"light\"||t===\"dark\")document.documentElement.setAttribute(\"data-theme\",t)}catch(e){}";
|
|
109
|
+
/**
|
|
110
|
+
* Botón claro/oscuro. El estado real se lee del DOM tras hidratar (el servidor no sabe la preferencia).
|
|
111
|
+
* Guarda la elección en localStorage ("theme") y fija `html[data-theme]`; los tokens de la tienda reaccionan a eso.
|
|
112
|
+
*/
|
|
113
|
+
declare function ThemeToggle({ className, themeColors }: {
|
|
114
|
+
className?: string;
|
|
115
|
+
themeColors?: {
|
|
116
|
+
light: string;
|
|
117
|
+
dark: string;
|
|
118
|
+
};
|
|
119
|
+
}): react.JSX.Element;
|
|
120
|
+
|
|
121
|
+
/** Registra el service worker (solo si `enabled`, normalmente producción; requiere HTTPS o localhost). */
|
|
122
|
+
declare function useServiceWorker({ enabled, url, scope }: {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
url?: string;
|
|
125
|
+
scope?: string;
|
|
126
|
+
}): void;
|
|
127
|
+
/**
|
|
128
|
+
* Botón "Instalar app": aparece solo cuando el navegador ofrece la instalación (Chrome, Edge, Android).
|
|
129
|
+
* En iPhone/iPad no existe ese evento: muestra la instrucción de Compartir → Agregar a pantalla de inicio.
|
|
130
|
+
*/
|
|
131
|
+
declare function InstallApp({ className, label, iosHint }: {
|
|
132
|
+
className?: string;
|
|
133
|
+
label?: string;
|
|
134
|
+
iosHint?: string;
|
|
135
|
+
}): react.JSX.Element | null;
|
|
136
|
+
|
|
137
|
+
export { type ColorEntry, type EmojiEntry, InstallApp, Select, type SelectOption, type SelectProps, Swatch, type SwatchPrefer, type SwatchProps, type SwatchResolver, type SwatchResolverOptions, type SwatchSpec, THEME_INIT_SCRIPT, ThemeToggle, createSwatchResolver, defaultColors, luminance, normalize as normalizeSwatchName, optionHasSwatches, parseSwatchAttribute, pastelBackground, resolveSwatch, resolveSwatchSpec, swatchBackground, useServiceWorker };
|