feat(product-list): enhance layout handling with responsive design and computed properties
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<div
|
||||
class="product-list"
|
||||
[class.product-list--row]="layout() === 'row'"
|
||||
[class.product-list--column]="layout() !== 'row'"
|
||||
[class.product-list--row]="effectiveLayout() === 'row'"
|
||||
[class.product-list--column]="effectiveLayout() !== 'row'"
|
||||
>
|
||||
@for (item of items().data; track item.id; let index = $index) {
|
||||
<div class="product-list__item">
|
||||
@switch (layout()) {
|
||||
@switch (effectiveLayout()) {
|
||||
@case ('row') {
|
||||
<app-product-row-card
|
||||
[title]="item.nombre"
|
||||
|
||||
@@ -33,7 +33,10 @@ describe('ProductListComponent', () => {
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => TestBed.resetTestingModule());
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
async function render(layout: ProductListLayout) {
|
||||
await TestBed.configureTestingModule({ imports: [ProductListComponent] }).compileComponents();
|
||||
@@ -76,6 +79,25 @@ describe('ProductListComponent', () => {
|
||||
expect(element.querySelector('app-product-column-with-image')).toBeNull();
|
||||
});
|
||||
|
||||
it('replaces the row layout with column with cart on mobile', async () => {
|
||||
const matchMedia = vi.fn().mockReturnValue({
|
||||
matches: true,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
} as unknown as MediaQueryList);
|
||||
vi.stubGlobal('matchMedia', matchMedia);
|
||||
|
||||
const fixture = await render('row');
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(matchMedia).toHaveBeenCalledWith('(max-width: 767.98px)');
|
||||
expect(element.querySelector('.product-list--column')).not.toBeNull();
|
||||
expect(element.querySelectorAll('app-product-vertical-with-cart-card')).toHaveLength(2);
|
||||
expect(element.querySelector('app-product-row-card')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders image products next to each other in the column grid', async () => {
|
||||
const fixture = await render('column_with_image');
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
DestroyRef,
|
||||
inject,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
|
||||
import {
|
||||
@@ -37,6 +47,9 @@ export interface ProductListCartEvent {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ProductListComponent {
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly mobile = signal(false);
|
||||
|
||||
readonly layout = input.required<ProductListLayout>();
|
||||
readonly items = input.required<ApiPaginatedResponse<ProductListItem[]>>();
|
||||
readonly loading = input(false);
|
||||
@@ -45,6 +58,22 @@ export class ProductListComponent {
|
||||
readonly addToCart = output<ProductListCartEvent>();
|
||||
readonly pageChange = output<number>();
|
||||
|
||||
protected readonly effectiveLayout = computed<ProductListLayout>(() =>
|
||||
this.mobile() && this.layout() === 'row' ? 'column_with_cart' : this.layout(),
|
||||
);
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
const mobileQuery = window.matchMedia('(max-width: 767.98px)');
|
||||
const updateMobile = (event: MediaQueryList | MediaQueryListEvent): void =>
|
||||
this.mobile.set(event.matches);
|
||||
|
||||
updateMobile(mobileQuery);
|
||||
mobileQuery.addEventListener('change', updateMobile);
|
||||
this.destroyRef.onDestroy(() => mobileQuery.removeEventListener('change', updateMobile));
|
||||
});
|
||||
}
|
||||
|
||||
protected price(item: ProductListItem): number {
|
||||
const price = Number(item.precio);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user