feat(store-header): implement immersive categories and update header properties

This commit is contained in:
2026-09-18 09:49:51 -03:00
parent 7f4aa81d4b
commit 6ce734b2c2
8 changed files with 99 additions and 24 deletions

View File

@@ -1,16 +1,11 @@
<header
class="immersive-header"
[class.immersive-header--compact]="!showHero()"
[class.immersive-header--has-image]="showHero() && !!backgroundImageUrl()"
>
<div
class="immersive-header__backdrop"
[style.background-image]="backgroundImageUrl() ? 'url(' + backgroundImageUrl() + ')' : null"
>
@if (showHero() && backgroundImageUrl(); as imageUrl) {
<img class="immersive-header__backdrop-size" [src]="imageUrl" alt="" aria-hidden="true" />
}
</div>
></div>
<div class="immersive-header__content">
<nav class="immersive-header__nav" aria-label="Navegación de la tienda">

View File

@@ -5,20 +5,18 @@
.immersive-header {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr);
isolation: isolate;
min-height: 26rem;
min-height: 640px;
color: #fff;
background: #f5f5f5;
}
.immersive-header--has-image {
min-height: 0;
}
.immersive-header__content {
grid-area: 1 / 1;
position: relative;
z-index: 1;
min-width: 0;
padding-top: clamp(1.75rem, 4vw, 2.5rem);
padding-bottom: 0.5rem;
}
@@ -32,13 +30,6 @@
background-size: cover;
}
.immersive-header__backdrop-size {
display: block;
width: 100%;
height: auto;
visibility: hidden;
}
.immersive-header__backdrop::after {
content: '';
position: absolute;
@@ -47,7 +38,7 @@
180deg,
rgba(0, 0, 0, 0.48) 0%,
rgba(0, 0, 0, 0.4) 47%,
rgba(245, 245, 245, 0.88) 100%
#f5f5f5 100%
);
}

View File

@@ -50,3 +50,19 @@
(categorySelect)="categorySelect.emit($event)"
/>
</ng-template>
@if (headerType() === 'immersive' && displayCategories() && categories().length) {
<nav class="immersive-categories" aria-label="Categorías">
<div class="immersive-categories__list">
@for (category of categories(); track category.id) {
<button
type="button"
class="immersive-categories__item"
(click)="categorySelect.emit(category)"
>
{{ category.nombre | uppercase }}
</button>
}
</div>
</nav>
}

View File

@@ -0,0 +1,37 @@
:host {
display: block;
}
.immersive-categories {
width: 100%;
overflow-x: auto;
background: #f5f5f5;
}
.immersive-categories__list {
display: flex;
align-items: center;
justify-content: center;
gap: clamp(1rem, 3vw, 2.5rem);
width: max-content;
min-width: 100%;
min-height: 3.5rem;
padding: 0.75rem 1rem;
}
.immersive-categories__item {
flex: none;
border: 0;
padding: 0;
background: transparent;
color: #666666;
font-size: 15px;
font-weight: 700;
white-space: nowrap;
cursor: pointer;
}
.immersive-categories__item:hover,
.immersive-categories__item:focus-visible {
text-decoration: underline;
}

View File

@@ -1,4 +1,4 @@
import { NgTemplateOutlet } from '@angular/common';
import { NgTemplateOutlet, UpperCasePipe } from '@angular/common';
import { Component, input, output } from '@angular/core';
import { AuthUser } from '../../../services/auth/auth.interfaces';
import { Category, Menu, WebsiteExtras } from '../../../services/tenant.interface';
@@ -9,8 +9,9 @@ export type StoreHeaderType = 'standard' | 'immersive';
@Component({
selector: 'app-store-header',
imports: [NgTemplateOutlet, StandardStoreHeaderComponent, ImmersiveStoreHeaderComponent],
imports: [NgTemplateOutlet, UpperCasePipe, StandardStoreHeaderComponent, ImmersiveStoreHeaderComponent],
templateUrl: './store-header.component.html',
styleUrl: './store-header.component.scss',
})
export class StoreHeaderComponent {
readonly headerType = input<StoreHeaderType>('standard');

View File

@@ -3,7 +3,7 @@
ngSkipHydration
[headerType]="tenant()?.header_type ?? 'standard'"
[heroContent]="tenant()?.extras?.immersiveHero"
[carouselImages]="tenant()?.extras?.heroCarousel ?? []"
[carouselImages]="tenant()?.extras?.immersiveHeroCarousel ?? []"
[showHero]="isHomeRoute()"
[cartQuantity]="cartQuantity()"
[logoUrl]="tenant()?.header_logo ?? null"

View File

@@ -266,7 +266,7 @@ describe('StoreLayoutComponent', () => {
title: 'experiencia',
description: 'Reservá tu entrada',
},
heroCarousel: ['/first.jpg', '/second.jpg'],
immersiveHeroCarousel: ['/first.jpg', '/second.jpg'],
},
});
fixture.detectChanges();
@@ -288,6 +288,41 @@ describe('StoreLayoutComponent', () => {
expect(element.querySelector('app-cart-icon')).not.toBeNull();
});
it('shows immersive categories below the header and navigates on selection', () => {
tenantState.set({
...tenant,
header_type: 'immersive',
display_categories: true,
categories: [{ id: 11, nombre: 'Música', subcategories: [] }],
});
const fixture = TestBed.createComponent(StoreLayoutComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const header = element.querySelector('app-immersive-store-header header');
const categories = element.querySelector('app-store-header .immersive-categories');
const categoryButton = categories?.querySelector<HTMLButtonElement>('button');
expect(header).not.toBeNull();
expect(categories).not.toBeNull();
expect(header?.contains(categories)).toBe(false);
expect(categoryButton?.textContent?.trim()).toBe('MÚSICA');
const navigate = vi.spyOn(TestBed.inject(Router), 'navigate');
categoryButton?.click();
expect(navigate).toHaveBeenCalledWith(['/categoria', 11], { queryParams: { page: 1 } });
tenantState.set({
...tenant,
header_type: 'immersive',
display_categories: false,
categories: [{ id: 11, nombre: 'Música', subcategories: [] }],
});
fixture.detectChanges();
expect(element.querySelector('.immersive-categories')).toBeNull();
});
it('opens the cart when requested through the openCart query parameter', () => {
const fixture = TestBed.createComponent(StoreLayoutComponent);
fixture.detectChanges();

View File

@@ -71,7 +71,7 @@ export interface TenantEvent {
export interface WebsiteExtras {
carousel?: string[];
heroCarousel?: string[];
immersiveHeroCarousel?: string[];
immersiveHero?: {
eyebrow: string;
title: string;