feat: add menu content section component for improved layout and responsiveness across help and FAQ pages

This commit is contained in:
2026-08-06 09:15:34 -03:00
parent a012f94198
commit 393aeec7a5
11 changed files with 140 additions and 83 deletions

View File

@@ -0,0 +1,7 @@
<section class="menu-content-section" [attr.aria-labelledby]="titleId()">
<h2 class="menu-content-section__title" [id]="titleId()">{{ title() }}</h2>
<div class="menu-content-section__body">
<ng-content />
</div>
</section>

View File

@@ -0,0 +1,31 @@
:host {
display: block;
width: 100%;
min-width: 0;
}
.menu-content-section {
width: 100%;
}
.menu-content-section__title {
margin: 0 0 1.25rem;
color: #888888;
font-size: 1rem;
font-weight: 600;
line-height: 1.3;
}
.menu-content-section__body {
min-width: 0;
}
@media (max-width: 767.98px) {
.menu-content-section {
margin-inline: auto;
}
.menu-content-section__title {
text-align: center;
}
}

View File

@@ -0,0 +1,33 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { describe, expect, it } from 'vitest';
import { MenuContentSectionComponent } from './menu-content-section.component';
@Component({
imports: [MenuContentSectionComponent],
template: `
<app-menu-content-section title="Contacto" titleId="contact-title">
<p>Contenido proyectado</p>
</app-menu-content-section>
`,
})
class TestHostComponent {}
describe('MenuContentSectionComponent', () => {
it('renders an accessible title and projects its content', async () => {
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const section = element.querySelector('section');
expect(section?.getAttribute('aria-labelledby')).toBe('contact-title');
expect(element.querySelector('#contact-title')?.textContent?.trim()).toBe('Contacto');
expect(element.querySelector('.menu-content-section__body')?.textContent).toContain(
'Contenido proyectado',
);
});
});

View File

@@ -0,0 +1,11 @@
import { Component, input } from '@angular/core';
@Component({
selector: 'app-menu-content-section',
templateUrl: './menu-content-section.component.html',
styleUrl: './menu-content-section.component.scss',
})
export class MenuContentSectionComponent {
readonly title = input.required<string>();
readonly titleId = input.required<string>();
}

View File

@@ -80,5 +80,9 @@
justify-content: center; justify-content: center;
gap: 0.75rem 1.25rem; gap: 0.75rem 1.25rem;
} }
&__content {
width: 100%;
}
} }
} }

View File

@@ -1,53 +1,53 @@
<section class="contact-page" aria-labelledby="contact-page-title"> <app-menu-content-section title="CONTACTO" titleId="contact-page-title">
<div class="contact-page__details"> <div class="contact-page">
<h2 id="contact-page-title" class="contact-page__title">CONTACTO</h2> <div class="contact-page__details">
@if (contact().whatsappUrl && contact().whatsappLabel) {
<a
class="contact-page__contact-link"
[href]="contact().whatsappUrl"
target="_blank"
rel="noopener noreferrer"
>
<i class="fa-brands fa-whatsapp" aria-hidden="true"></i>
<span>{{ contact().whatsappLabel }}</span>
</a>
}
@if (contact().whatsappUrl && contact().whatsappLabel) { @if (contact().phone; as phone) {
<a <a class="contact-page__contact-link" [href]="phoneUrl(phone)">
class="contact-page__contact-link" <i class="fa-solid fa-phone" aria-hidden="true"></i>
[href]="contact().whatsappUrl" <span>{{ phone }}</span>
target="_blank" </a>
rel="noopener noreferrer" }
>
<i class="fa-brands fa-whatsapp" aria-hidden="true"></i>
<span>{{ contact().whatsappLabel }}</span>
</a>
}
@if (contact().phone; as phone) { @for (location of contact().locations; track location.id) {
<a class="contact-page__contact-link" [href]="phoneUrl(phone)"> <article class="contact-page__location">
<i class="fa-solid fa-phone" aria-hidden="true"></i> <h3 class="contact-page__location-title">
<span>{{ phone }}</span> <i class="fa-solid fa-location-dot" aria-hidden="true"></i>
</a> <span>{{ location.label }}</span>
} </h3>
@for (location of contact().locations; track location.id) { <ul class="contact-page__addresses">
<article class="contact-page__location"> @for (address of location.addresses; track address.id) {
<h3 class="contact-page__location-title"> <li>{{ address.address }}</li>
<i class="fa-solid fa-location-dot" aria-hidden="true"></i> }
<span>{{ location.label }}</span> </ul>
</h3> </article>
}
<ul class="contact-page__addresses"> @if (!contact().whatsappUrl && !contact().phone && contact().locations.length === 0) {
@for (address of location.addresses; track address.id) { <p class="contact-page__empty">No hay datos de contacto disponibles.</p>
<li>{{ address.address }}</li> }
} </div>
</ul>
</article>
}
@if (!contact().whatsappUrl && !contact().phone && contact().locations.length === 0) { @if (contact().mapLocations.length > 0) {
<p class="contact-page__empty">No hay datos de contacto disponibles.</p> <app-location-map
class="contact-page__map"
[locations]="contact().mapLocations"
[markerColor]="markerColor()"
mapStyle="minimal-light"
ariaLabel="Mapa de sucursales y puntos de atención"
/>
} }
</div> </div>
</app-menu-content-section>
@if (contact().mapLocations.length > 0) {
<app-location-map
class="contact-page__map"
[locations]="contact().mapLocations"
[markerColor]="markerColor()"
mapStyle="minimal-light"
ariaLabel="Mapa de sucursales y puntos de atención"
/>
}
</section>

View File

@@ -20,14 +20,6 @@
align-items: flex-start; align-items: flex-start;
} }
.contact-page__title {
margin: 0 0 1.25rem;
color: inherit;
font-size: 1rem;
font-weight: 400;
line-height: 1.3;
}
.contact-page__contact-link { .contact-page__contact-link {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -128,10 +120,6 @@
text-align: center; text-align: center;
} }
.contact-page__title {
width: 100%;
}
.contact-page__location { .contact-page__location {
width: 100%; width: 100%;
} }

View File

@@ -2,6 +2,7 @@ import { Component, computed, inject } from '@angular/core';
import { findMenu } from '../../../../../../core/services/menu.utils'; import { findMenu } from '../../../../../../core/services/menu.utils';
import { TenantService } from '../../../../../../core/services/tenant.service'; import { TenantService } from '../../../../../../core/services/tenant.service';
import { MenuContentSectionComponent } from '../../../../components/menu-content-section/menu-content-section.component';
import { import {
LocationMapComponent, LocationMapComponent,
MapLocation, MapLocation,
@@ -30,7 +31,7 @@ type ContactContent = {
@Component({ @Component({
selector: 'app-contact-page', selector: 'app-contact-page',
imports: [LocationMapComponent], imports: [LocationMapComponent, MenuContentSectionComponent],
templateUrl: './contact-page.html', templateUrl: './contact-page.html',
styleUrl: './contact-page.scss', styleUrl: './contact-page.scss',
}) })

View File

@@ -1,14 +1,8 @@
<section class="faq-page" aria-labelledby="faq-page-title"> <app-menu-content-section class="faq-page" title="PREGUNTAS FRECUENTES" titleId="faq-page-title">
<h2 id="faq-page-title" class="faq-page__title">PREGUNTAS FRECUENTES</h2>
@if (faqs().length > 0) { @if (faqs().length > 0) {
<app-accordion [flush]="true"> <app-accordion [flush]="true">
@for (faq of faqs(); track $index; let last = $last) { @for (faq of faqs(); track $index; let last = $last) {
<app-accordion-item <app-accordion-item [title]="faq.question" [headingLevel]="3" [open]="last">
[title]="faq.question"
[headingLevel]="3"
[open]="last"
>
<p>{{ faq.answer }}</p> <p>{{ faq.answer }}</p>
</app-accordion-item> </app-accordion-item>
} }
@@ -16,4 +10,4 @@
} @else { } @else {
<p class="faq-page__empty">No hay preguntas frecuentes disponibles.</p> <p class="faq-page__empty">No hay preguntas frecuentes disponibles.</p>
} }
</section> </app-menu-content-section>

View File

@@ -4,18 +4,11 @@
} }
.faq-page { .faq-page {
display: block;
width: 100%; width: 100%;
max-width: 760px; max-width: 760px;
} }
.faq-page__title {
margin: 0 0 0.5rem;
color: #a0a0a0;
font-size: 0.9375rem;
font-weight: 700;
line-height: 1.3;
}
.faq-page p { .faq-page p {
margin: 0; margin: 0;
} }
@@ -26,9 +19,3 @@
color: #888888; color: #888888;
font-size: 0.8125rem; font-size: 0.8125rem;
} }
@media (max-width: 767.98px) {
.faq-page__title {
text-align: center;
}
}

View File

@@ -4,6 +4,7 @@ import { findMenu } from '../../../../../../core/services/menu.utils';
import { TenantService } from '../../../../../../core/services/tenant.service'; import { TenantService } from '../../../../../../core/services/tenant.service';
import { AccordionItemComponent } from '../../../../../../shared/components/accordion/accordion-item.component'; import { AccordionItemComponent } from '../../../../../../shared/components/accordion/accordion-item.component';
import { AccordionComponent } from '../../../../../../shared/components/accordion/accordion.component'; import { AccordionComponent } from '../../../../../../shared/components/accordion/accordion.component';
import { MenuContentSectionComponent } from '../../../../components/menu-content-section/menu-content-section.component';
type Faq = { type Faq = {
question: string; question: string;
@@ -12,7 +13,7 @@ type Faq = {
@Component({ @Component({
selector: 'app-faq-page', selector: 'app-faq-page',
imports: [AccordionComponent, AccordionItemComponent], imports: [AccordionComponent, AccordionItemComponent, MenuContentSectionComponent],
templateUrl: './faq-page.html', templateUrl: './faq-page.html',
styleUrl: './faq-page.scss', styleUrl: './faq-page.scss',
}) })