From 8268e58547f72ceb55fdfdeac40b5de464071808 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Thu, 23 Jul 2026 11:49:23 -0300 Subject: [PATCH] feat(carousel): enhance carousel component with gap support and layout adjustments --- .../reutilizables-test-page.component.html | 1 + .../reutilizables-test-page.component.scss | 1 - .../carousel/carousel.component.html | 75 ++++++++++--------- .../carousel/carousel.component.scss | 7 +- .../components/carousel/carousel.component.ts | 47 ++++++++++-- 5 files changed, 88 insertions(+), 43 deletions(-) diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html index 3aadc6c..267fd82 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html @@ -601,6 +601,7 @@ [items]="carouselProducts" [itemTemplate]="productCarouselItem" [trackBy]="trackCarouselProduct" + [gap]="16" ariaLabel="Productos con imágenes de prueba" /> diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.scss b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.scss index 088cf5e..87d2f54 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.scss +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.scss @@ -22,7 +22,6 @@ .carousel-product { width: clamp(15rem, 30vw, 18rem); height: 100%; - margin-right: 1rem; app-product-column-with-image { display: block; diff --git a/src/app/shared/components/carousel/carousel.component.html b/src/app/shared/components/carousel/carousel.component.html index a506f35..8767158 100644 --- a/src/app/shared/components/carousel/carousel.component.html +++ b/src/app/shared/components/carousel/carousel.component.html @@ -1,40 +1,43 @@ diff --git a/src/app/shared/components/carousel/carousel.component.scss b/src/app/shared/components/carousel/carousel.component.scss index d69a64e..be6e564 100644 --- a/src/app/shared/components/carousel/carousel.component.scss +++ b/src/app/shared/components/carousel/carousel.component.scss @@ -4,9 +4,14 @@ } .carousel { - position: relative; width: 100%; + &__frame { + position: relative; + width: 100%; + margin-inline: auto; + } + &__viewport { display: flex; width: 100%; diff --git a/src/app/shared/components/carousel/carousel.component.ts b/src/app/shared/components/carousel/carousel.component.ts index 4ce7e2a..4c7e7dd 100644 --- a/src/app/shared/components/carousel/carousel.component.ts +++ b/src/app/shared/components/carousel/carousel.component.ts @@ -31,19 +31,23 @@ export type CarouselTrackBy = (index: number, item: T) => unknown; export class CarouselComponent implements AfterViewInit { private readonly destroyRef = inject(DestroyRef); private readonly platformId = inject(PLATFORM_ID); + private readonly host = inject>(ElementRef); private readonly viewport = viewChild.required>('viewport'); readonly items = input.required(); readonly itemTemplate = input.required>>(); readonly trackBy = input>((index) => index); readonly scrollStep = input(null); + readonly gap = input(0); readonly ariaLabel = input('Carrusel'); readonly previousLabel = input('Mostrar elementos anteriores'); readonly nextLabel = input('Mostrar elementos siguientes'); protected readonly canScrollPrevious = signal(false); protected readonly canScrollNext = signal(false); + protected readonly viewportWidth = signal(null); protected readonly hasMultipleItems = computed(() => this.items().length > 1); + protected readonly resolvedGap = computed(() => Math.max(this.gap(), 0)); ngAfterViewInit(): void { if (!isPlatformBrowser(this.platformId)) { @@ -51,18 +55,18 @@ export class CarouselComponent implements AfterViewInit { } const viewport = this.viewport().nativeElement; - this.refreshNavigation(); + this.refreshLayout(); const resizeObserver = typeof ResizeObserver === 'undefined' ? null - : new ResizeObserver(() => this.refreshNavigation()); - resizeObserver?.observe(viewport); + : new ResizeObserver(() => this.refreshLayout()); + resizeObserver?.observe(this.host.nativeElement); const mutationObserver = typeof MutationObserver === 'undefined' ? null - : new MutationObserver(() => this.refreshNavigation()); + : new MutationObserver(() => this.refreshLayout()); mutationObserver?.observe(viewport, { childList: true }); this.destroyRef.onDestroy(() => { @@ -109,7 +113,9 @@ export class CarouselComponent implements AfterViewInit { const viewport = this.viewport().nativeElement; const configuredStep = this.scrollStep(); const step = - configuredStep !== null && configuredStep > 0 ? configuredStep : viewport.clientWidth; + configuredStep !== null && configuredStep > 0 + ? configuredStep + : viewport.clientWidth + this.resolvedGap(); viewport.scrollBy({ left: direction * step, @@ -125,4 +131,35 @@ export class CarouselComponent implements AfterViewInit { this.canScrollPrevious.set(viewport.scrollLeft > tolerance); this.canScrollNext.set(viewport.scrollLeft < maxScrollLeft - tolerance); } + + private refreshLayout(): void { + const viewport = this.viewport().nativeElement; + const firstItem = viewport.firstElementChild as HTMLElement | null; + const availableWidth = this.host.nativeElement.clientWidth; + + if (!firstItem || availableWidth <= 0) { + this.viewportWidth.set(null); + this.refreshNavigation(); + return; + } + + const itemWidth = firstItem.getBoundingClientRect().width; + + if (itemWidth <= 0) { + this.viewportWidth.set(null); + this.refreshNavigation(); + return; + } + + const gap = this.resolvedGap(); + const fittingItems = Math.max( + 1, + Math.floor((availableWidth + gap) / (itemWidth + gap)), + ); + const visibleItems = Math.min(fittingItems, viewport.children.length); + const fittedWidth = visibleItems * itemWidth + Math.max(visibleItems - 1, 0) * gap; + + this.viewportWidth.set(Math.min(Math.ceil(fittedWidth), availableWidth)); + this.refreshNavigation(); + } }