@lit-roa/custom-header 0.1.5 → 0.2.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 +61 -5
- package/dist/custom-header.d.ts +102 -2
- package/dist/custom-header.d.ts.map +1 -1
- package/dist/custom-header.js +744 -35
- package/dist/custom-header.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/custom-header.ts +743 -34
- package/src/index.ts +1 -0
package/src/custom-header.ts
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
|
-
import { LitElement, css, html } from 'lit';
|
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
3
|
-
import {
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
3
|
+
import {
|
|
4
|
+
emitir,
|
|
5
|
+
estilosBase,
|
|
6
|
+
iconoChevronDown,
|
|
7
|
+
iconoMenu2,
|
|
8
|
+
iconoShoppingBag,
|
|
9
|
+
iconoUser,
|
|
10
|
+
iconoX,
|
|
11
|
+
tokens,
|
|
12
|
+
} from '@lit-roa/base';
|
|
13
|
+
import '@lit-roa/buscador';
|
|
14
|
+
import type { Sugerencia } from '@lit-roa/buscador';
|
|
15
|
+
|
|
16
|
+
export interface LigaHeader {
|
|
17
|
+
texto: string;
|
|
18
|
+
href?: string;
|
|
19
|
+
id?: string;
|
|
20
|
+
/** Submenú sencillo. */
|
|
21
|
+
hijos?: LigaHeader[];
|
|
22
|
+
/** Mega menú: columnas con título. Si hay `columnas` se ignoran `hijos`. */
|
|
23
|
+
columnas?: { titulo: string; ligas: LigaHeader[] }[];
|
|
24
|
+
/** Tarjeta destacada a la derecha del mega menú. */
|
|
25
|
+
promo?: { titulo: string; texto?: string; imagen?: string; href?: string; id?: string };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DetalleNavegar {
|
|
29
|
+
liga: LigaHeader;
|
|
30
|
+
}
|
|
4
31
|
|
|
5
32
|
/**
|
|
6
|
-
*
|
|
33
|
+
* Encabezado de la tienda: logo, ligas con submenús o mega menú, buscador,
|
|
34
|
+
* cuenta y carrito. En pantallas angostas muestra un menú lateral. Puede
|
|
35
|
+
* quedarse pegado arriba y hacerse más bajo al hacer scroll.
|
|
36
|
+
*
|
|
37
|
+
* Las ligas no navegan solas: el sitio escucha `navegar` y decide.
|
|
38
|
+
*
|
|
39
|
+
* @fires navegar - Al tocar una liga, el logo o una promoción. `detail`: `{ liga }`.
|
|
40
|
+
* @fires abrir-carrito - Al tocar el carrito.
|
|
41
|
+
* @fires abrir-cuenta - Al tocar "Entrar" o el nombre de la persona.
|
|
42
|
+
* @fires buscar-texto - (del buscador) Al escribir. `detail`: `{ texto }`.
|
|
43
|
+
* @fires buscar - (del buscador) Al presionar Enter. `detail`: `{ texto }`.
|
|
44
|
+
* @fires elegir-sugerencia - (del buscador) `detail`: `{ sugerencia }`.
|
|
45
|
+
* @slot anuncio - Arriba de todo, por ejemplo `<lr-barra-anuncio>`.
|
|
46
|
+
* @slot carrito - Reemplaza el botón del carrito (por ejemplo con `<lr-mini-carrito>`).
|
|
47
|
+
* @slot menu-pie - Contenido extra abajo del menú de celular.
|
|
7
48
|
*/
|
|
8
49
|
@customElement('lr-custom-header')
|
|
9
50
|
export class LrCustomHeader extends LitElement {
|
|
@@ -13,55 +54,723 @@ export class LrCustomHeader extends LitElement {
|
|
|
13
54
|
css`
|
|
14
55
|
:host {
|
|
15
56
|
display: block;
|
|
57
|
+
--_fondo: var(--lr-header-fondo, #292929);
|
|
58
|
+
--_texto: var(--lr-header-texto, #ffffff);
|
|
59
|
+
--_alto: var(--lr-header-alto, 4.75rem);
|
|
60
|
+
--_alto-compacto: var(--lr-header-alto-compacto, 3.5rem);
|
|
61
|
+
position: relative;
|
|
62
|
+
z-index: var(--lr-header-capa, 50);
|
|
63
|
+
}
|
|
64
|
+
:host([pegajoso]) {
|
|
65
|
+
position: sticky;
|
|
66
|
+
top: 0;
|
|
67
|
+
transition: transform 0.25s ease;
|
|
68
|
+
}
|
|
69
|
+
:host([escondido]) {
|
|
70
|
+
transform: translateY(-100%);
|
|
71
|
+
}
|
|
72
|
+
header {
|
|
73
|
+
background: var(--_fondo);
|
|
74
|
+
color: var(--_texto);
|
|
75
|
+
transition: box-shadow 0.2s;
|
|
76
|
+
}
|
|
77
|
+
:host([compacto]) header {
|
|
78
|
+
box-shadow: 0 4px 14px rgb(0 0 0 / 0.25);
|
|
79
|
+
}
|
|
80
|
+
:host([compacto]) .anuncio {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
.fila {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 1.5rem;
|
|
87
|
+
max-width: var(--lr-header-ancho, 1200px);
|
|
88
|
+
height: var(--_alto);
|
|
89
|
+
margin: 0 auto;
|
|
90
|
+
padding: 0 1.5rem;
|
|
91
|
+
transition: height 0.2s;
|
|
92
|
+
}
|
|
93
|
+
:host([compacto]) .fila {
|
|
94
|
+
height: var(--_alto-compacto);
|
|
95
|
+
}
|
|
96
|
+
button,
|
|
97
|
+
a {
|
|
98
|
+
font: inherit;
|
|
99
|
+
color: inherit;
|
|
100
|
+
}
|
|
101
|
+
button {
|
|
102
|
+
all: unset;
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
}
|
|
106
|
+
a {
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
}
|
|
109
|
+
button:focus-visible,
|
|
110
|
+
a:focus-visible {
|
|
111
|
+
outline: 2px solid currentColor;
|
|
112
|
+
outline-offset: 2px;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
}
|
|
115
|
+
.logo {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 0.625rem;
|
|
119
|
+
flex: none;
|
|
120
|
+
font-weight: 800;
|
|
121
|
+
font-size: 1.1875rem;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
}
|
|
124
|
+
.logo img {
|
|
125
|
+
height: 2.5rem;
|
|
126
|
+
width: auto;
|
|
127
|
+
max-width: 10rem;
|
|
128
|
+
object-fit: contain;
|
|
129
|
+
transition: height 0.2s;
|
|
130
|
+
}
|
|
131
|
+
:host([compacto]) .logo img {
|
|
132
|
+
height: 2rem;
|
|
133
|
+
}
|
|
134
|
+
.fila nav > ul {
|
|
135
|
+
display: flex;
|
|
136
|
+
gap: 0.25rem;
|
|
137
|
+
margin: 0;
|
|
138
|
+
padding: 0;
|
|
139
|
+
list-style: none;
|
|
140
|
+
}
|
|
141
|
+
.liga {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 0.25rem;
|
|
145
|
+
padding: 0.5rem 0.75rem;
|
|
146
|
+
border-radius: 8px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
font-size: 0.9375rem;
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
opacity: 0.9;
|
|
151
|
+
}
|
|
152
|
+
.liga:hover,
|
|
153
|
+
.liga[aria-expanded='true'] {
|
|
154
|
+
opacity: 1;
|
|
155
|
+
background: rgb(255 255 255 / 0.1);
|
|
156
|
+
}
|
|
157
|
+
.liga svg {
|
|
158
|
+
transition: transform 0.2s;
|
|
159
|
+
}
|
|
160
|
+
.liga[aria-expanded='true'] svg {
|
|
161
|
+
transform: rotate(180deg);
|
|
162
|
+
}
|
|
163
|
+
li {
|
|
164
|
+
position: relative;
|
|
165
|
+
}
|
|
166
|
+
.submenu {
|
|
167
|
+
position: absolute;
|
|
168
|
+
top: calc(100% + 0.5rem);
|
|
169
|
+
left: 0;
|
|
170
|
+
min-width: 13rem;
|
|
171
|
+
margin: 0;
|
|
172
|
+
padding: 0.5rem;
|
|
173
|
+
list-style: none;
|
|
174
|
+
background: #fff;
|
|
175
|
+
color: #111827;
|
|
176
|
+
border-radius: 12px;
|
|
177
|
+
box-shadow: 0 18px 30px rgb(0 0 0 / 0.18);
|
|
178
|
+
}
|
|
179
|
+
.submenu a,
|
|
180
|
+
.mega a {
|
|
181
|
+
display: block;
|
|
182
|
+
padding: 0.5rem 0.75rem;
|
|
183
|
+
border-radius: 8px;
|
|
184
|
+
}
|
|
185
|
+
.submenu a:hover,
|
|
186
|
+
.mega a:hover {
|
|
187
|
+
background: #f3f4f6;
|
|
188
|
+
}
|
|
189
|
+
.mega {
|
|
190
|
+
position: absolute;
|
|
191
|
+
left: 0;
|
|
192
|
+
right: 0;
|
|
193
|
+
top: 100%;
|
|
194
|
+
background: #fff;
|
|
195
|
+
color: #111827;
|
|
196
|
+
box-shadow: 0 18px 30px rgb(0 0 0 / 0.15);
|
|
197
|
+
}
|
|
198
|
+
.mega .dentro {
|
|
199
|
+
display: grid;
|
|
200
|
+
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
|
201
|
+
gap: 2rem;
|
|
202
|
+
max-width: var(--lr-header-ancho, 1200px);
|
|
203
|
+
margin: 0 auto;
|
|
204
|
+
padding: 1.75rem 1.5rem;
|
|
205
|
+
}
|
|
206
|
+
.mega .dentro.con-promo {
|
|
207
|
+
grid-template-columns: repeat(var(--_columnas), minmax(9rem, 1fr)) minmax(14rem, 1.3fr);
|
|
208
|
+
}
|
|
209
|
+
.mega h3 {
|
|
210
|
+
margin: 0 0 0.375rem 0.75rem;
|
|
211
|
+
font-size: 0.75rem;
|
|
212
|
+
text-transform: uppercase;
|
|
213
|
+
letter-spacing: 0.06em;
|
|
214
|
+
color: #6b7280;
|
|
215
|
+
}
|
|
216
|
+
.mega ul {
|
|
217
|
+
margin: 0;
|
|
218
|
+
padding: 0;
|
|
219
|
+
list-style: none;
|
|
220
|
+
}
|
|
221
|
+
.mega .promo {
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-direction: column;
|
|
224
|
+
justify-content: flex-end;
|
|
225
|
+
gap: 0.25rem;
|
|
226
|
+
min-height: 10rem;
|
|
227
|
+
padding: 1.25rem;
|
|
228
|
+
border-radius: 12px;
|
|
229
|
+
background: var(--lr-header-promo-fondo, linear-gradient(135deg, #7c2d12, #c2410c)) center / cover;
|
|
230
|
+
color: #fff;
|
|
231
|
+
}
|
|
232
|
+
.promo b {
|
|
233
|
+
font-size: 1.1875rem;
|
|
234
|
+
}
|
|
235
|
+
.promo span {
|
|
236
|
+
font-size: 0.8125rem;
|
|
237
|
+
opacity: 0.9;
|
|
238
|
+
}
|
|
239
|
+
lr-buscador {
|
|
240
|
+
flex: 1;
|
|
241
|
+
min-width: 9rem;
|
|
242
|
+
max-width: 22rem;
|
|
243
|
+
margin-left: auto;
|
|
244
|
+
--lr-color-texto: #111827;
|
|
245
|
+
--lr-buscador-borde: transparent;
|
|
246
|
+
}
|
|
247
|
+
.acciones {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 0.375rem;
|
|
251
|
+
flex: none;
|
|
252
|
+
}
|
|
253
|
+
.solo-acciones {
|
|
254
|
+
margin-left: auto;
|
|
255
|
+
}
|
|
256
|
+
.icono {
|
|
257
|
+
position: relative;
|
|
258
|
+
display: grid;
|
|
259
|
+
place-items: center;
|
|
260
|
+
width: 2.625rem;
|
|
261
|
+
height: 2.625rem;
|
|
262
|
+
border-radius: 999px;
|
|
263
|
+
font-size: 1.375rem;
|
|
264
|
+
}
|
|
265
|
+
.icono:hover,
|
|
266
|
+
.cuenta:hover {
|
|
267
|
+
background: rgb(255 255 255 / 0.1);
|
|
268
|
+
}
|
|
269
|
+
.cuenta {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 0.5rem;
|
|
273
|
+
height: 2.625rem;
|
|
274
|
+
padding: 0 0.75rem 0 0.5rem;
|
|
275
|
+
border-radius: 999px;
|
|
276
|
+
font-size: 0.875rem;
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
}
|
|
279
|
+
.nombre-usuario {
|
|
280
|
+
max-width: 9rem;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
text-overflow: ellipsis;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|
|
285
|
+
.cuenta svg {
|
|
286
|
+
font-size: 1.25rem;
|
|
287
|
+
}
|
|
288
|
+
.avatar {
|
|
289
|
+
display: grid;
|
|
290
|
+
place-items: center;
|
|
291
|
+
width: 1.875rem;
|
|
292
|
+
height: 1.875rem;
|
|
293
|
+
border-radius: 999px;
|
|
294
|
+
background: var(--lr-header-avatar, #fbbf24);
|
|
295
|
+
color: #292929;
|
|
296
|
+
font-size: 0.75rem;
|
|
297
|
+
font-weight: 800;
|
|
298
|
+
}
|
|
299
|
+
.globo {
|
|
300
|
+
position: absolute;
|
|
301
|
+
top: 0.1875rem;
|
|
302
|
+
right: 0;
|
|
303
|
+
min-width: 1.1875rem;
|
|
304
|
+
height: 1.1875rem;
|
|
305
|
+
padding: 0 0.3125rem;
|
|
306
|
+
border-radius: 999px;
|
|
307
|
+
border: 2px solid var(--_fondo);
|
|
308
|
+
background: var(--lr-header-globo, #ea580c);
|
|
309
|
+
color: #fff;
|
|
310
|
+
font-size: 0.6875rem;
|
|
311
|
+
font-weight: 800;
|
|
312
|
+
line-height: 0.8rem;
|
|
313
|
+
text-align: center;
|
|
314
|
+
}
|
|
315
|
+
.solo-lectores {
|
|
316
|
+
position: absolute;
|
|
317
|
+
width: 1px;
|
|
318
|
+
height: 1px;
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
clip: rect(0 0 0 0);
|
|
16
321
|
}
|
|
17
322
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
padding:
|
|
22
|
-
height:
|
|
323
|
+
/* Celular */
|
|
324
|
+
.celular .fila {
|
|
325
|
+
gap: 0.25rem;
|
|
326
|
+
padding: 0 0.5rem;
|
|
327
|
+
height: 3.75rem;
|
|
328
|
+
}
|
|
329
|
+
:host([compacto]) .celular .fila {
|
|
330
|
+
height: 3.25rem;
|
|
331
|
+
}
|
|
332
|
+
.celular .logo {
|
|
333
|
+
flex: 1;
|
|
334
|
+
justify-content: center;
|
|
335
|
+
font-size: 1.0625rem;
|
|
336
|
+
min-width: 0;
|
|
337
|
+
}
|
|
338
|
+
.celular .logo img {
|
|
339
|
+
height: 2rem;
|
|
340
|
+
}
|
|
341
|
+
.celular .acciones {
|
|
342
|
+
gap: 0;
|
|
343
|
+
}
|
|
344
|
+
.celular .barra-buscar {
|
|
345
|
+
padding: 0 0.75rem 0.75rem;
|
|
346
|
+
}
|
|
347
|
+
:host([compacto]) .celular .barra-buscar {
|
|
348
|
+
display: none;
|
|
349
|
+
}
|
|
350
|
+
.celular lr-buscador {
|
|
351
|
+
max-width: none;
|
|
352
|
+
margin: 0;
|
|
23
353
|
}
|
|
24
354
|
|
|
25
|
-
|
|
355
|
+
dialog {
|
|
356
|
+
position: fixed;
|
|
357
|
+
inset: 0 auto 0 0;
|
|
358
|
+
width: min(86vw, 21rem);
|
|
359
|
+
max-width: none;
|
|
360
|
+
height: 100%;
|
|
361
|
+
max-height: none;
|
|
362
|
+
margin: 0;
|
|
363
|
+
padding: 0;
|
|
364
|
+
border: 0;
|
|
365
|
+
background: #fff;
|
|
366
|
+
color: #111827;
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
animation: entrar 0.25s ease-out;
|
|
370
|
+
}
|
|
371
|
+
dialog:not([open]) {
|
|
372
|
+
display: none;
|
|
373
|
+
}
|
|
374
|
+
dialog::backdrop {
|
|
375
|
+
background: rgb(0 0 0 / 0.45);
|
|
376
|
+
}
|
|
377
|
+
@keyframes entrar {
|
|
378
|
+
from {
|
|
379
|
+
transform: translateX(-100%);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
.cab {
|
|
26
383
|
display: flex;
|
|
27
384
|
align-items: center;
|
|
28
385
|
justify-content: space-between;
|
|
386
|
+
gap: 0.5rem;
|
|
387
|
+
padding: 0.75rem 0.75rem 0.75rem 1.125rem;
|
|
388
|
+
border-bottom: 1px solid #e5e7eb;
|
|
29
389
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
margin-right: var(--_espacio);
|
|
390
|
+
.cab .logo {
|
|
391
|
+
flex: initial;
|
|
33
392
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
font-size: 1.5rem;
|
|
393
|
+
.cab .icono:hover {
|
|
394
|
+
background: #f3f4f6;
|
|
37
395
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
396
|
+
.menu {
|
|
397
|
+
flex: 1;
|
|
398
|
+
overflow-y: auto;
|
|
399
|
+
margin: 0;
|
|
400
|
+
padding: 0.25rem 0;
|
|
401
|
+
list-style: none;
|
|
402
|
+
}
|
|
403
|
+
.menu > li > a,
|
|
404
|
+
.menu > li > button {
|
|
405
|
+
display: flex;
|
|
406
|
+
justify-content: space-between;
|
|
407
|
+
align-items: center;
|
|
408
|
+
width: 100%;
|
|
409
|
+
padding: 0.875rem 1.125rem;
|
|
410
|
+
border-bottom: 1px solid #f3f4f6;
|
|
411
|
+
font-weight: 600;
|
|
412
|
+
}
|
|
413
|
+
.menu button svg {
|
|
414
|
+
transition: transform 0.2s;
|
|
415
|
+
}
|
|
416
|
+
.menu button[aria-expanded='true'] svg {
|
|
417
|
+
transform: rotate(180deg);
|
|
418
|
+
}
|
|
419
|
+
.menu ul {
|
|
420
|
+
margin: 0;
|
|
421
|
+
padding: 0.25rem 0;
|
|
422
|
+
list-style: none;
|
|
423
|
+
}
|
|
424
|
+
.menu ul a {
|
|
425
|
+
display: block;
|
|
426
|
+
padding: 0.6875rem 1.125rem 0.6875rem 2.125rem;
|
|
427
|
+
color: #374151;
|
|
428
|
+
}
|
|
429
|
+
.menu ul .grupo {
|
|
430
|
+
padding: 0.75rem 1.125rem 0.25rem 2.125rem;
|
|
431
|
+
font-size: 0.75rem;
|
|
432
|
+
font-weight: 700;
|
|
433
|
+
text-transform: uppercase;
|
|
434
|
+
letter-spacing: 0.05em;
|
|
435
|
+
color: #6b7280;
|
|
436
|
+
}
|
|
437
|
+
.pie {
|
|
438
|
+
display: grid;
|
|
439
|
+
gap: 0.75rem;
|
|
440
|
+
padding: 1rem 1.125rem;
|
|
441
|
+
border-top: 1px solid #e5e7eb;
|
|
442
|
+
}
|
|
443
|
+
.boton-cuenta {
|
|
444
|
+
display: flex;
|
|
445
|
+
justify-content: center;
|
|
446
|
+
align-items: center;
|
|
447
|
+
gap: 0.5rem;
|
|
448
|
+
height: 2.875rem;
|
|
449
|
+
border-radius: 10px;
|
|
450
|
+
background: var(--_fondo);
|
|
451
|
+
color: var(--_texto);
|
|
452
|
+
font-weight: 700;
|
|
453
|
+
}
|
|
454
|
+
@media (prefers-reduced-motion: reduce) {
|
|
455
|
+
:host([pegajoso]),
|
|
456
|
+
.fila,
|
|
457
|
+
dialog {
|
|
458
|
+
transition: none;
|
|
459
|
+
animation: none;
|
|
460
|
+
}
|
|
44
461
|
}
|
|
45
462
|
`,
|
|
46
463
|
];
|
|
47
464
|
|
|
48
|
-
|
|
465
|
+
/** URL de la imagen del logo. */
|
|
466
|
+
@property() logo = '';
|
|
467
|
+
|
|
468
|
+
/** Nombre de la tienda (texto junto al logo y texto alternativo). */
|
|
469
|
+
@property() nombre = '';
|
|
470
|
+
|
|
471
|
+
/** Liga del logo. */
|
|
472
|
+
@property({ attribute: 'href-inicio' }) hrefInicio = '/';
|
|
473
|
+
|
|
474
|
+
/** Ligas del menú. Como atributo, en JSON. */
|
|
475
|
+
@property({ type: Array }) ligas: LigaHeader[] = [];
|
|
476
|
+
|
|
477
|
+
/** Piezas en el carrito (globito). */
|
|
478
|
+
@property({ type: Number }) piezas = 0;
|
|
479
|
+
|
|
480
|
+
/** Nombre de la persona con sesión iniciada; vacío muestra "Entrar". */
|
|
481
|
+
@property() usuario = '';
|
|
482
|
+
|
|
483
|
+
@property({ attribute: 'texto-entrar' }) textoEntrar = 'Entrar';
|
|
484
|
+
|
|
485
|
+
@property({ attribute: 'texto-cuenta' }) textoCuenta = 'Mi cuenta y pedidos';
|
|
486
|
+
|
|
487
|
+
/** Oculta el buscador. */
|
|
488
|
+
@property({ type: Boolean, attribute: 'sin-buscador' }) sinBuscador = false;
|
|
489
|
+
|
|
490
|
+
@property({ attribute: 'placeholder-buscador' }) placeholderBuscador = 'Buscar en la tienda';
|
|
491
|
+
|
|
492
|
+
/** Sugerencias para el buscador (ver `lr-buscador`). */
|
|
493
|
+
@property({ type: Array }) sugerencias: Sugerencia[] = [];
|
|
494
|
+
|
|
495
|
+
@property({ type: Boolean, attribute: 'buscando' }) buscando = false;
|
|
496
|
+
|
|
497
|
+
/** Se queda pegado arriba y se hace más bajo al hacer scroll. */
|
|
498
|
+
@property({ type: Boolean, reflect: true }) pegajoso = false;
|
|
499
|
+
|
|
500
|
+
/** Con `pegajoso`, se esconde al bajar y aparece al subir. */
|
|
501
|
+
@property({ type: Boolean, attribute: 'ocultar-al-bajar' }) ocultarAlBajar = false;
|
|
502
|
+
|
|
503
|
+
/** Ancho (px) del header debajo del cual se usa la vista de celular. */
|
|
504
|
+
@property({ type: Number, attribute: 'ancho-celular' }) anchoCelular = 900;
|
|
505
|
+
|
|
506
|
+
@property({ type: Boolean, reflect: true }) compacto = false;
|
|
507
|
+
@property({ type: Boolean, reflect: true }) escondido = false;
|
|
508
|
+
|
|
509
|
+
@state() private celular = false;
|
|
510
|
+
@state() private abierta = -1;
|
|
511
|
+
@state() private abiertasMenu = new Set<number>();
|
|
512
|
+
@state() private conAnuncio = false;
|
|
513
|
+
|
|
514
|
+
@query('dialog') private dialogo!: HTMLDialogElement;
|
|
515
|
+
|
|
516
|
+
private observador?: ResizeObserver;
|
|
517
|
+
private ultimoScroll = 0;
|
|
518
|
+
|
|
519
|
+
override connectedCallback() {
|
|
520
|
+
super.connectedCallback();
|
|
521
|
+
this.observador = new ResizeObserver(([e]) => (this.celular = e.contentRect.width < this.anchoCelular));
|
|
522
|
+
this.observador.observe(this);
|
|
523
|
+
window.addEventListener('scroll', this.alScroll, { passive: true });
|
|
524
|
+
document.addEventListener('click', this.alClicFuera);
|
|
525
|
+
document.addEventListener('keydown', this.alEscape);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
override disconnectedCallback() {
|
|
529
|
+
super.disconnectedCallback();
|
|
530
|
+
this.observador?.disconnect();
|
|
531
|
+
window.removeEventListener('scroll', this.alScroll);
|
|
532
|
+
document.removeEventListener('click', this.alClicFuera);
|
|
533
|
+
document.removeEventListener('keydown', this.alEscape);
|
|
534
|
+
}
|
|
49
535
|
|
|
50
536
|
override render() {
|
|
51
|
-
return html
|
|
52
|
-
<div class="
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
537
|
+
return html`<header class=${this.celular ? 'celular' : ''} part="header">
|
|
538
|
+
<div class="anuncio" ?hidden=${!this.conAnuncio}>
|
|
539
|
+
<slot name="anuncio" @slotchange=${this.alCambiarAnuncio}></slot>
|
|
540
|
+
</div>
|
|
541
|
+
${this.celular ? this.vistaCelular() : this.vistaEscritorio()}
|
|
542
|
+
</header>`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Abre el menú de celular. */
|
|
546
|
+
abrirMenu() {
|
|
547
|
+
this.dialogo?.showModal();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Cierra el menú de celular y los submenús. */
|
|
551
|
+
cerrarMenu() {
|
|
552
|
+
this.dialogo?.close();
|
|
553
|
+
this.abierta = -1;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private vistaEscritorio() {
|
|
557
|
+
const liga = this.ligas[this.abierta];
|
|
558
|
+
return html`<div class="fila">
|
|
559
|
+
${this.logoTpl()}
|
|
560
|
+
${this.ligas.length
|
|
561
|
+
? html`<nav aria-label="Principal">
|
|
562
|
+
<ul>
|
|
563
|
+
${this.ligas.map((l, i) => this.ligaEscritorio(l, i))}
|
|
564
|
+
</ul>
|
|
565
|
+
</nav>`
|
|
566
|
+
: nothing}
|
|
567
|
+
${this.buscadorTpl()}
|
|
568
|
+
<div class="acciones ${this.sinBuscador ? 'solo-acciones' : ''}">${this.cuentaTpl(true)} ${this.carritoTpl()}</div>
|
|
569
|
+
</div>
|
|
570
|
+
${liga?.columnas?.length ? this.megaTpl(liga) : nothing}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
private vistaCelular() {
|
|
574
|
+
return html`<div class="fila">
|
|
575
|
+
<button class="icono" aria-label="Abrir menú" aria-haspopup="dialog" @click=${this.abrirMenu}>${iconoMenu2}</button>
|
|
576
|
+
${this.logoTpl()}
|
|
577
|
+
<div class="acciones">${this.cuentaTpl(false)} ${this.carritoTpl()}</div>
|
|
578
|
+
</div>
|
|
579
|
+
${this.sinBuscador ? nothing : html`<div class="barra-buscar">${this.buscadorTpl()}</div>`}
|
|
580
|
+
<dialog aria-label="Menú" @click=${this.alClicDialogo} @close=${() => (this.abierta = -1)}>
|
|
581
|
+
<div class="cab">
|
|
582
|
+
${this.logoTpl()}
|
|
583
|
+
<button class="icono" aria-label="Cerrar menú" @click=${this.cerrarMenu}>${iconoX}</button>
|
|
61
584
|
</div>
|
|
585
|
+
<nav aria-label="Principal" style="display:contents">
|
|
586
|
+
<ul class="menu">
|
|
587
|
+
${this.ligas.map((l, i) => this.ligaCelular(l, i))}
|
|
588
|
+
</ul>
|
|
589
|
+
</nav>
|
|
590
|
+
<div class="pie">
|
|
591
|
+
<slot name="menu-pie"></slot>
|
|
592
|
+
<button class="boton-cuenta" @click=${this.alCuenta}>
|
|
593
|
+
${this.usuario ? html`${iconoUser} ${this.textoCuenta}` : this.textoEntrar}
|
|
594
|
+
</button>
|
|
595
|
+
</div>
|
|
596
|
+
</dialog>`;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
private logoTpl() {
|
|
600
|
+
const inicio: LigaHeader = { texto: this.nombre || 'Inicio', href: this.hrefInicio, id: 'inicio' };
|
|
601
|
+
return html`<a class="logo" href=${this.hrefInicio} part="logo" @click=${(e: MouseEvent) => this.alNavegar(e, inicio)}>
|
|
602
|
+
${this.logo ? html`<img src=${this.logo} alt=${this.nombre ? '' : 'Inicio'} />` : nothing}
|
|
603
|
+
${this.nombre ? html`<span>${this.nombre}</span>` : nothing}
|
|
604
|
+
</a>`;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private buscadorTpl() {
|
|
608
|
+
if (this.sinBuscador) return nothing;
|
|
609
|
+
return html`<lr-buscador
|
|
610
|
+
placeholder=${this.placeholderBuscador}
|
|
611
|
+
.sugerencias=${this.sugerencias}
|
|
612
|
+
?cargando=${this.buscando}
|
|
613
|
+
ver-todo
|
|
614
|
+
></lr-buscador>`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private cuentaTpl(conTexto: boolean) {
|
|
618
|
+
if (!conTexto) {
|
|
619
|
+
return html`<button class="icono" aria-label=${this.usuario ? `${this.textoCuenta}: ${this.usuario}` : this.textoEntrar} @click=${this.alCuenta}>
|
|
620
|
+
${this.usuario ? html`<span class="avatar">${this.iniciales()}</span>` : iconoUser}
|
|
621
|
+
</button>`;
|
|
622
|
+
}
|
|
623
|
+
return html`<button class="cuenta" @click=${this.alCuenta}>
|
|
624
|
+
${this.usuario
|
|
625
|
+
? html`<span class="avatar" aria-hidden="true">${this.iniciales()}</span><span class="solo-lectores">${this.textoCuenta}:</span><span class="nombre-usuario">${this.usuario.split(/\s+/)[0]}</span>`
|
|
626
|
+
: html`${iconoUser}${this.textoEntrar}`}
|
|
627
|
+
</button>`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private carritoTpl() {
|
|
631
|
+
return html`<slot name="carrito">
|
|
632
|
+
<button class="icono" aria-label="Carrito, ${this.piezas} ${this.piezas === 1 ? 'pieza' : 'piezas'}" @click=${this.alCarrito}>
|
|
633
|
+
${iconoShoppingBag}${this.piezas > 0 ? html`<span class="globo" aria-hidden="true">${this.piezas > 99 ? '99+' : this.piezas}</span>` : nothing}
|
|
634
|
+
</button>
|
|
635
|
+
</slot>`;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private ligaEscritorio(liga: LigaHeader, i: number) {
|
|
639
|
+
const conSub = !!(liga.columnas?.length || liga.hijos?.length);
|
|
640
|
+
if (!conSub) {
|
|
641
|
+
return html`<li><a class="liga" href=${liga.href || '#'} @click=${(e: MouseEvent) => this.alNavegar(e, liga)}>${liga.texto}</a></li>`;
|
|
642
|
+
}
|
|
643
|
+
const abierta = this.abierta === i;
|
|
644
|
+
return html`<li @mouseenter=${() => (this.abierta = i)} @mouseleave=${() => this.alSalirLiga(i)}>
|
|
645
|
+
<button class="liga" aria-expanded=${abierta} @click=${(e: MouseEvent) => this.alClicLiga(e, i)}>${liga.texto} ${iconoChevronDown}</button>
|
|
646
|
+
${abierta && !liga.columnas?.length
|
|
647
|
+
? html`<ul class="submenu">
|
|
648
|
+
${liga.hijos!.map((h) => html`<li><a href=${h.href || '#'} @click=${(e: MouseEvent) => this.alNavegar(e, h)}>${h.texto}</a></li>`)}
|
|
649
|
+
</ul>`
|
|
650
|
+
: nothing}
|
|
651
|
+
</li>`;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private megaTpl(liga: LigaHeader) {
|
|
655
|
+
const promo = liga.promo;
|
|
656
|
+
const indice = this.abierta;
|
|
657
|
+
return html`<div class="mega" @mouseenter=${() => (this.abierta = indice)} @mouseleave=${() => (this.abierta = -1)}>
|
|
658
|
+
<div class="dentro ${promo ? 'con-promo' : ''}" style="--_columnas: ${liga.columnas!.length}">
|
|
659
|
+
${liga.columnas!.map(
|
|
660
|
+
(c) => html`<div>
|
|
661
|
+
<h3>${c.titulo}</h3>
|
|
662
|
+
<ul>
|
|
663
|
+
${c.ligas.map((l) => html`<li><a href=${l.href || '#'} @click=${(e: MouseEvent) => this.alNavegar(e, l)}>${l.texto}</a></li>`)}
|
|
664
|
+
</ul>
|
|
665
|
+
</div>`,
|
|
666
|
+
)}
|
|
667
|
+
${promo
|
|
668
|
+
? html`<a
|
|
669
|
+
class="promo"
|
|
670
|
+
href=${promo.href || '#'}
|
|
671
|
+
style=${promo.imagen ? `background-image: linear-gradient(0deg, rgb(0 0 0 / .55), rgb(0 0 0 / 0) 70%), url(${promo.imagen})` : ''}
|
|
672
|
+
@click=${(e: MouseEvent) => this.alNavegar(e, { texto: promo.titulo, href: promo.href, id: promo.id })}
|
|
673
|
+
>
|
|
674
|
+
<b>${promo.titulo}</b>${promo.texto ? html`<span>${promo.texto}</span>` : nothing}
|
|
675
|
+
</a>`
|
|
676
|
+
: nothing}
|
|
62
677
|
</div>
|
|
63
|
-
|
|
678
|
+
</div>`;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private ligaCelular(liga: LigaHeader, i: number) {
|
|
682
|
+
const hijos: (LigaHeader | { grupo: string })[] = liga.columnas?.length
|
|
683
|
+
? liga.columnas.flatMap((c) => [{ grupo: c.titulo }, ...c.ligas])
|
|
684
|
+
: (liga.hijos ?? []);
|
|
685
|
+
if (!hijos.length) {
|
|
686
|
+
return html`<li><a href=${liga.href || '#'} @click=${(e: MouseEvent) => this.alNavegar(e, liga)}>${liga.texto}</a></li>`;
|
|
687
|
+
}
|
|
688
|
+
const abierta = this.abiertasMenu.has(i);
|
|
689
|
+
return html`<li>
|
|
690
|
+
<button aria-expanded=${abierta} @click=${() => this.alternarMenu(i)}>${liga.texto} ${iconoChevronDown}</button>
|
|
691
|
+
${abierta
|
|
692
|
+
? html`<ul>
|
|
693
|
+
${hijos.map((h) =>
|
|
694
|
+
'grupo' in h
|
|
695
|
+
? html`<li class="grupo" aria-hidden="true">${h.grupo}</li>`
|
|
696
|
+
: html`<li><a href=${h.href || '#'} @click=${(e: MouseEvent) => this.alNavegar(e, h)}>${h.texto}</a></li>`,
|
|
697
|
+
)}
|
|
698
|
+
${liga.href
|
|
699
|
+
? html`<li><a href=${liga.href} @click=${(e: MouseEvent) => this.alNavegar(e, liga)}>Ver todo ${liga.texto.toLowerCase()}</a></li>`
|
|
700
|
+
: nothing}
|
|
701
|
+
</ul>`
|
|
702
|
+
: nothing}
|
|
703
|
+
</li>`;
|
|
64
704
|
}
|
|
705
|
+
|
|
706
|
+
private alternarMenu(i: number) {
|
|
707
|
+
const s = new Set(this.abiertasMenu);
|
|
708
|
+
if (s.has(i)) s.delete(i);
|
|
709
|
+
else s.add(i);
|
|
710
|
+
this.abiertasMenu = s;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private iniciales() {
|
|
714
|
+
return this.usuario
|
|
715
|
+
.split(/\s+/)
|
|
716
|
+
.filter(Boolean)
|
|
717
|
+
.slice(0, 2)
|
|
718
|
+
.map((p) => p[0]!.toUpperCase())
|
|
719
|
+
.join('');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private alNavegar(evento: MouseEvent, liga: LigaHeader) {
|
|
723
|
+
if (liga.href && (evento.ctrlKey || evento.metaKey)) return;
|
|
724
|
+
evento.preventDefault();
|
|
725
|
+
this.cerrarMenu();
|
|
726
|
+
emitir<DetalleNavegar>(this, 'navegar', { liga });
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private alCuenta = () => {
|
|
730
|
+
this.cerrarMenu();
|
|
731
|
+
emitir(this, 'abrir-cuenta');
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
private alCarrito = () => {
|
|
735
|
+
this.cerrarMenu();
|
|
736
|
+
emitir(this, 'abrir-carrito');
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
private alClicLiga(evento: MouseEvent, i: number) {
|
|
740
|
+
// Con mouse el hover ya lo abrió: el clic no debe cerrarlo. Con teclado (detail 0) alterna.
|
|
741
|
+
if (evento.detail === 0) this.abierta = this.abierta === i ? -1 : i;
|
|
742
|
+
else this.abierta = i;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private alSalirLiga(i: number) {
|
|
746
|
+
// El mega menú vive fuera del <li>; se cierra cuando el mouse sale de él.
|
|
747
|
+
if (!this.ligas[i]?.columnas?.length) this.abierta = -1;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private alCambiarAnuncio(e: Event) {
|
|
751
|
+
this.conAnuncio = (e.target as HTMLSlotElement).assignedElements().length > 0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private alClicDialogo(e: MouseEvent) {
|
|
755
|
+
// Clic en el fondo (fuera del panel) cierra el menú.
|
|
756
|
+
if (e.target === this.dialogo) this.cerrarMenu();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
private alClicFuera = (e: MouseEvent) => {
|
|
760
|
+
if (this.abierta >= 0 && !e.composedPath().includes(this)) this.abierta = -1;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
private alEscape = (e: KeyboardEvent) => {
|
|
764
|
+
if (e.key === 'Escape' && this.abierta >= 0) this.abierta = -1;
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
private alScroll = () => {
|
|
768
|
+
if (!this.pegajoso) return;
|
|
769
|
+
const y = window.scrollY;
|
|
770
|
+
this.compacto = y > 40;
|
|
771
|
+
if (this.ocultarAlBajar) this.escondido = y > 200 && y > this.ultimoScroll && this.abierta < 0;
|
|
772
|
+
this.ultimoScroll = y;
|
|
773
|
+
};
|
|
65
774
|
}
|
|
66
775
|
|
|
67
776
|
declare global {
|