Merge branch 'feature/responsive' into develop
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<footer class="store-layout__footer mt-auto text-light">
|
||||
<div class="container-xl px-3 px-md-4 py-4 py-md-5">
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<div class="container-xl px-3 px-md-4 py-4 py-md-5 store-layout__footer-content">
|
||||
<div class="row g-4 store-layout__footer-grid">
|
||||
<div class="col-12 store-layout__brand-column">
|
||||
<div
|
||||
class="store-layout__brand-slot d-inline-flex align-items-center gap-3"
|
||||
data-testid="store-footer-brand-slot"
|
||||
@@ -17,30 +17,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-xl-5 d-grid gap-3 align-content-start">
|
||||
<div class="d-grid gap-2">
|
||||
<div class="d-flex align-items-center gap-2 ">
|
||||
<div class="col-12 col-md-6 col-xl-5 d-grid gap-3 align-content-start store-layout__contact">
|
||||
<div class="d-grid gap-2 store-layout__contact-details">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="fa-solid fa-location-dot" aria-hidden="true"></i>
|
||||
<span>Av. San Lorenzo 1542, Rosario</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 ">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="fa-solid fa-phone" aria-hidden="true"></i>
|
||||
<span>54 9 (0341) 6658247</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-0 small store-layout__muted-text">Copyright © {{ currentYear }} - Sonder</p>
|
||||
<p class="mb-0 small store-layout__muted-text store-layout__copyright">
|
||||
Copyright © {{ currentYear }} - {{ storeName || 'Sonder' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@for (section of footerSections; track section.heading) {
|
||||
<div class="col-12 col-md-6 col-xl-2 d-grid align-content-start">
|
||||
<div class="col-12 col-md-6 col-xl-2 d-grid align-content-start store-layout__links-column">
|
||||
<div class="d-grid gap-2">
|
||||
@for (link of section.links; track link.label) {
|
||||
@if (link.routerLink) {
|
||||
<a
|
||||
class="store-layout__footer-link"
|
||||
[routerLink]="link.routerLink"
|
||||
>
|
||||
<a class="store-layout__footer-link" [routerLink]="link.routerLink">
|
||||
{{ link.label }}
|
||||
</a>
|
||||
} @else if (link.action === 'logout') {
|
||||
@@ -59,8 +58,10 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="col-12 col-md-6 col-xl-3 d-grid align-content-start">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="store-layout__mobile-divider" aria-hidden="true"></div>
|
||||
|
||||
<div class="col-12 col-md-6 col-xl-3 d-grid align-content-start store-layout__social-column">
|
||||
<div class="d-flex align-items-center gap-3 store-layout__social-list">
|
||||
@for (social of socialMedia; track social.code) {
|
||||
<app-social-media
|
||||
class="text-white"
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
background-color: var(--tenant-footer-bg);
|
||||
}
|
||||
|
||||
.store-layout__mobile-divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.store-layout__brand-slot {
|
||||
width: min(100%, 12rem);
|
||||
min-width: 10rem;
|
||||
@@ -92,11 +96,98 @@
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.store-layout__footer-content {
|
||||
padding: 1.45rem 2.5rem 1.8rem !important;
|
||||
}
|
||||
|
||||
.store-layout__footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.store-layout__footer-grid > * {
|
||||
margin-top: 0;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.store-layout__links-column {
|
||||
order: 1;
|
||||
width: auto;
|
||||
padding-bottom: 1.65rem;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
|
||||
&:nth-of-type(even) {
|
||||
padding-left: 0.65rem;
|
||||
}
|
||||
|
||||
> div {
|
||||
gap: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.store-layout__mobile-divider {
|
||||
display: block;
|
||||
order: 2;
|
||||
grid-column: 1 / -1;
|
||||
width: 100vw;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.store-layout__brand-column,
|
||||
.store-layout__social-column {
|
||||
order: 3;
|
||||
width: auto;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.store-layout__brand-slot {
|
||||
width: min(100%, 7.75rem);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.store-layout__brand-logo {
|
||||
max-height: 2.5rem;
|
||||
}
|
||||
|
||||
.store-layout__brand-logo-shell {
|
||||
width: min(100%, 7.75rem);
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.store-layout__brand-text {
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.store-layout__social-column {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.store-layout__social-list {
|
||||
justify-content: flex-end;
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.store-layout__contact {
|
||||
order: 4;
|
||||
grid-column: 1 / -1;
|
||||
width: auto;
|
||||
gap: 1.25rem !important;
|
||||
padding-top: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.store-layout__contact-details {
|
||||
justify-items: center;
|
||||
gap: 0.4rem !important;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.store-layout__copyright {
|
||||
font-size: 0.6rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,6 @@ export class StoreFooterComponent {
|
||||
@Input({ required: true }) footerSections: StoreFooterSection[] = [];
|
||||
@Input({ required: true }) socialMedia: SocialMedia[] = [];
|
||||
@Input() logoUrl: string | null = null;
|
||||
@Input() storeName: string | null = null;
|
||||
readonly logoutClick = output<void>();
|
||||
}
|
||||
|
||||
@@ -1,81 +1,83 @@
|
||||
<header class="store-layout__header">
|
||||
<div class="store-layout__header-main">
|
||||
<div
|
||||
class="container-xl px-3 px-md-4 py-3 py-lg-4 d-flex flex-row flex-wrap align-items-center justify-content-between gap-3"
|
||||
>
|
||||
<a
|
||||
routerLink="/"
|
||||
class="store-layout__brand-slot d-inline-flex align-items-center gap-3 flex-shrink-0 text-decoration-none"
|
||||
data-testid="store-header-brand-slot"
|
||||
aria-label="Logo del comercio"
|
||||
>
|
||||
@if (logoUrl()) {
|
||||
<img class="store-layout__brand-logo" [src]="logoUrl()" alt="Logo del comercio" />
|
||||
} @else {
|
||||
<div class="store-layout__brand-logo-shell" aria-hidden="true">
|
||||
<span class="store-layout__brand-logo-skeleton"></span>
|
||||
</div>
|
||||
}
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="d-flex flex-row align-items-center gap-3 ms-auto flex-nowrap flex-grow-1 justify-content-between justify-content-md-end"
|
||||
>
|
||||
<div class="store-layout__search-wrapper">
|
||||
<div class="input-group store-layout__search" role="search">
|
||||
<label class="visually-hidden" for="store-search-input">Buscar productos</label>
|
||||
<input
|
||||
id="store-search-input"
|
||||
type="search"
|
||||
class="form-control store-layout__search-input border-end-0 rounded-start"
|
||||
placeholder="Buscar productos"
|
||||
autocomplete="off"
|
||||
[attr.aria-describedby]="showSearchError() ? 'store-search-error' : null"
|
||||
[attr.aria-invalid]="showSearchError()"
|
||||
[minlength]="minSearchLength"
|
||||
[formControl]="searchControl"
|
||||
(input)="clearSearchError()"
|
||||
(keydown.enter)="submitSearch($event)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn store-layout__search-button border-start-0 rounded-end px-3"
|
||||
aria-label="Buscar"
|
||||
(click)="submitSearch()"
|
||||
>
|
||||
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (showSearchError()) {
|
||||
<p id="store-search-error" class="store-layout__search-error mb-0" role="alert">
|
||||
Ingresá al menos {{ minSearchLength }} caracteres para buscar.
|
||||
</p>
|
||||
}
|
||||
<div class="container-xl px-3 px-md-4 py-3 py-lg-4">
|
||||
<div class="row align-items-center gy-3 gy-md-0">
|
||||
<div class="col-auto col-md-auto order-1 store-layout__brand-column">
|
||||
<a
|
||||
routerLink="/"
|
||||
class="store-layout__brand-slot d-inline-flex align-items-center gap-3 text-decoration-none"
|
||||
data-testid="store-header-brand-slot"
|
||||
aria-label="Logo del comercio"
|
||||
>
|
||||
@if (logoUrl()) {
|
||||
<img class="store-layout__brand-logo" [src]="logoUrl()" alt="Logo del comercio" />
|
||||
} @else {
|
||||
<div class="store-layout__brand-logo-shell" aria-hidden="true">
|
||||
<span class="store-layout__brand-logo-skeleton"></span>
|
||||
</div>
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (ticketsMenu(); as menu) {
|
||||
<app-button
|
||||
variant="primary"
|
||||
hostClass="flex-shrink-0"
|
||||
data-testid="store-header-tickets"
|
||||
(click)="ticketsClick.emit()"
|
||||
>
|
||||
{{ menu.label }}
|
||||
</app-button>
|
||||
}
|
||||
<div class="col-12 px-0 d-md-none order-3">
|
||||
<hr class="store-layout__mobile-separator">
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md order-4 order-md-2">
|
||||
<div class="store-layout__search-wrapper ms-md-auto">
|
||||
<div class="input-group store-layout__search" role="search">
|
||||
<label class="visually-hidden" for="store-search-input">Buscar productos</label>
|
||||
<input
|
||||
id="store-search-input"
|
||||
type="search"
|
||||
class="form-control store-layout__search-input border-end-0 rounded-start"
|
||||
placeholder="Buscar productos"
|
||||
autocomplete="off"
|
||||
[attr.aria-describedby]="showSearchError() ? 'store-search-error' : null"
|
||||
[attr.aria-invalid]="showSearchError()"
|
||||
[minlength]="minSearchLength"
|
||||
[formControl]="searchControl"
|
||||
(input)="clearSearchError()"
|
||||
(keydown.enter)="submitSearch($event)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn store-layout__search-button border-start-0 rounded-end px-3"
|
||||
aria-label="Buscar"
|
||||
(click)="submitSearch()"
|
||||
>
|
||||
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (showSearchError()) {
|
||||
<p id="store-search-error" class="store-layout__search-error mb-0" role="alert">
|
||||
Ingresá al menos {{ minSearchLength }} caracteres para buscar.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="store-layout__actions-column col-auto col-md-auto order-2 order-md-3 ms-auto d-flex flex-row align-items-center justify-content-end gap-2 gap-md-3">
|
||||
@if (ticketsMenu(); as menu) {
|
||||
<app-button
|
||||
variant="primary"
|
||||
hostClass="store-layout__tickets-btn-host"
|
||||
data-testid="store-header-tickets"
|
||||
(click)="ticketsClick.emit()"
|
||||
>
|
||||
{{ menu.label }}
|
||||
</app-button>
|
||||
}
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center justify-content-end gap-1"
|
||||
aria-label="Acciones de usuario"
|
||||
>
|
||||
<app-cart-icon
|
||||
[quantity]="cartQuantity()"
|
||||
ariaLabel="Carrito de compras"
|
||||
title="Carrito"
|
||||
(click)="cartClick.emit()"
|
||||
/>
|
||||
<div class="store-layout__user-menu">
|
||||
|
||||
<div class="store-layout__user-menu d-none d-md-inline-flex position-relative">
|
||||
<app-icon-button
|
||||
variant="user"
|
||||
ariaLabel="Mi cuenta"
|
||||
@@ -94,12 +96,46 @@
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="store-layout__mobile-menu d-inline-flex d-md-none position-relative">
|
||||
<app-icon-button
|
||||
variant="bars"
|
||||
ariaLabel="Menu"
|
||||
ariaControls="store-mobile-menu"
|
||||
[ariaExpanded]="isMobileMenuOpen()"
|
||||
title="Menu"
|
||||
(clicked)="toggleMobileMenu()"
|
||||
/>
|
||||
|
||||
@if (isMobileMenuOpen()) {
|
||||
<div id="store-mobile-menu" class="position-absolute end-0 bg-white rounded shadow mt-2 py-2 store-layout__mobile-dropdown-menu">
|
||||
<a
|
||||
class="dropdown-item d-flex align-items-center gap-2 px-3 py-2 text-decoration-none store-layout__mobile-dropdown-item"
|
||||
(click)="onMobileAccountClick()"
|
||||
>
|
||||
<i class="fa-regular fa-circle-user fa-lg"></i>
|
||||
<span class="fw-medium">Mi cuenta</span>
|
||||
</a>
|
||||
|
||||
<hr class="my-1 border-secondary opacity-25">
|
||||
|
||||
@for (category of categories(); track category.id) {
|
||||
<a
|
||||
class="dropdown-item px-3 py-2 text-decoration-none store-layout__mobile-dropdown-item"
|
||||
(click)="onCategorySelect(category)"
|
||||
>
|
||||
<span class="fw-medium">{{ category.nombre }}</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="store-layout__categories">
|
||||
<div class="store-layout__categories d-none d-md-block">
|
||||
<div class="container-xl px-3 px-md-4">
|
||||
<div class="store-layout__category-menu">
|
||||
<button
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
|
||||
.store-layout__brand-slot {
|
||||
min-width: 150px;
|
||||
max-width:40px;
|
||||
}
|
||||
|
||||
.store-layout__brand-column {
|
||||
max-width: 12rem;
|
||||
}
|
||||
|
||||
.store-layout__brand-logo-shell {
|
||||
@@ -42,10 +45,25 @@
|
||||
}
|
||||
|
||||
.store-layout__search-wrapper {
|
||||
width: min(100%, 260px);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.store-layout__search-wrapper {
|
||||
width: min(100%, 260px);
|
||||
}
|
||||
|
||||
.store-layout__actions-column {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
::ng-deep .store-layout__tickets-btn-host {
|
||||
flex: 0 0 clamp(8rem, 15vw, 13.5rem);
|
||||
width: clamp(8rem, 15vw, 13.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.store-layout__search {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -137,5 +155,56 @@
|
||||
@media (max-width: 767.98px) {
|
||||
.store-layout__brand-slot {
|
||||
min-width: 0;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.store-layout__mobile-separator {
|
||||
border-color: #DDDDDD;
|
||||
opacity: 1;
|
||||
margin: 0 -1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 399.98px) {
|
||||
.store-layout__brand-column {
|
||||
flex: 1 1 0;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.store-layout__brand-slot {
|
||||
width: 100%;
|
||||
max-width: 6.5rem;
|
||||
}
|
||||
|
||||
.store-layout__actions-column {
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
margin-left: 0 !important;
|
||||
padding-left: 0.25rem;
|
||||
gap: 0.25rem !important;
|
||||
}
|
||||
|
||||
::ng-deep .store-layout__tickets-btn-host .btn {
|
||||
padding-inline: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.store-layout__mobile-dropdown-menu {
|
||||
top: 100%;
|
||||
min-width: 220px;
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
.store-layout__mobile-dropdown-item {
|
||||
cursor: pointer;
|
||||
color: #8a8a8a;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, ElementRef, HostListener, inject, input, output, signal } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { AuthUser } from '../../../services/auth/auth.interfaces';
|
||||
import { CartIconComponent } from '../../../../shared/components/cart-icon/cart-icon.component';
|
||||
import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component';
|
||||
@@ -26,6 +26,7 @@ import { CategoryDropdownComponent } from './category-dropdown/category-dropdown
|
||||
})
|
||||
export class StoreHeaderComponent {
|
||||
private readonly elementRef = inject(ElementRef<HTMLElement>);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
readonly logoUrl = input<string | null>(null);
|
||||
readonly cartQuantity = input<number>(0);
|
||||
@@ -43,13 +44,14 @@ export class StoreHeaderComponent {
|
||||
|
||||
protected readonly isUserDropdownOpen = signal(false);
|
||||
protected readonly isCategoryDropdownOpen = signal(false);
|
||||
protected readonly isMobileMenuOpen = signal(false);
|
||||
protected readonly minSearchLength = 3;
|
||||
protected readonly showSearchError = signal(false);
|
||||
protected readonly searchControl = new FormControl('', { nonNullable: true });
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
protected onDocumentClick(event: MouseEvent): void {
|
||||
if (!this.isUserDropdownOpen() && !this.isCategoryDropdownOpen()) {
|
||||
if (!this.isUserDropdownOpen() && !this.isCategoryDropdownOpen() && !this.isMobileMenuOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,6 +59,7 @@ export class StoreHeaderComponent {
|
||||
if (target instanceof Node && !this.elementRef.nativeElement.contains(target)) {
|
||||
this.isUserDropdownOpen.set(false);
|
||||
this.isCategoryDropdownOpen.set(false);
|
||||
this.isMobileMenuOpen.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +67,7 @@ export class StoreHeaderComponent {
|
||||
protected onEscapeKey(): void {
|
||||
this.isUserDropdownOpen.set(false);
|
||||
this.isCategoryDropdownOpen.set(false);
|
||||
this.isMobileMenuOpen.set(false);
|
||||
}
|
||||
|
||||
protected toggleUserDropdown(): void {
|
||||
@@ -109,6 +113,25 @@ export class StoreHeaderComponent {
|
||||
|
||||
protected onCategorySelect(category: Category): void {
|
||||
this.isCategoryDropdownOpen.set(false);
|
||||
this.isMobileMenuOpen.set(false);
|
||||
this.categorySelect.emit(category);
|
||||
}
|
||||
|
||||
protected toggleMobileMenu(): void {
|
||||
this.isMobileMenuOpen.update((isOpen) => !isOpen);
|
||||
}
|
||||
|
||||
protected onMobileAccountClick(): void {
|
||||
this.isMobileMenuOpen.set(false);
|
||||
if (!this.isAuthenticated()) {
|
||||
this.loginClick.emit();
|
||||
} else {
|
||||
const menus = this.accountNavigationMenus();
|
||||
if (menus.length > 0) {
|
||||
this.router.navigate([menus[0].route]);
|
||||
} else {
|
||||
this.router.navigate(['/cuenta']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
[footerSections]="footerSections()"
|
||||
[socialMedia]="tenant()?.social_media ?? []"
|
||||
[logoUrl]="tenant()?.footer_logo ?? null"
|
||||
[storeName]="tenant()?.nombre ?? null"
|
||||
(logoutClick)="onLogoutClick()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user