feat: implement product carousel component with thumbnail gallery and navigation controls

This commit is contained in:
2026-06-30 10:11:16 -03:00
parent cc16a53fac
commit ec261caab3
2 changed files with 38 additions and 49 deletions

View File

@@ -1,13 +1,10 @@
<div class="product-carousel d-flex flex-column gap-3">
<!-- Active/Main Image Area -->
<div class="product-carousel__main position-relative bg-light rounded overflow-hidden">
<div class="product-carousel__main position-relative bg-light overflow-hidden">
<!-- Main Image -->
@if (images().length > 0) {
<img
[src]="images()[activeIndex()]"
alt="Product active image"
class="product-carousel__main-image w-100 h-100 object-fit-cover"
/>
<img [src]="images()[activeIndex()]" alt="Product active image"
class="product-carousel__main-image w-100 h-100 object-fit-cover" />
} @else {
<!-- Placeholder -->
<div class="product-carousel__placeholder w-100 h-100 d-flex align-items-center justify-content-center text-muted">
@@ -17,28 +14,23 @@
<!-- Discount Badge -->
@if (discount() && discount()! > 0) {
<span class="product-carousel__discount-badge position-absolute top-0 end-0 bg-primary text-white px-3 py-2 fw-semibold">
<span
class="product-carousel__discount-badge position-absolute top-0 end-0 bg-primary text-white px-3 py-2 fw-semibold">
-{{ discount() }}%
</span>
}
<!-- Navigation Arrows -->
@if (images().length > 1) {
<button
type="button"
<button type="button"
class="product-carousel__nav-btn product-carousel__nav-btn--prev position-absolute top-50 translate-middle-y border-0 p-0 d-flex align-items-center justify-content-center"
(click)="prevImage()"
aria-label="Previous image"
>
(click)="prevImage()" aria-label="Previous image">
<i class="fa-solid fa-chevron-left"></i>
</button>
<button
type="button"
<button type="button"
class="product-carousel__nav-btn product-carousel__nav-btn--next position-absolute top-50 translate-middle-y border-0 p-0 d-flex align-items-center justify-content-center"
(click)="nextImage()"
aria-label="Next image"
>
(click)="nextImage()" aria-label="Next image">
<i class="fa-solid fa-chevron-right"></i>
</button>
}
@@ -48,13 +40,9 @@
@if (images().length > 1) {
<div class="product-carousel__thumbnails d-flex gap-3">
@for (image of images(); track image; let idx = $index) {
<button
type="button"
class="product-carousel__thumbnail border-0 p-0 rounded overflow-hidden bg-light"
[class.product-carousel__thumbnail--active]="idx === activeIndex()"
(click)="selectImage(idx)"
[attr.aria-label]="'Select image ' + (idx + 1)"
>
<button type="button" class="product-carousel__thumbnail border-0 p-0 overflow-hidden bg-light"
[class.product-carousel__thumbnail--active]="idx === activeIndex()" (click)="selectImage(idx)"
[attr.aria-label]="'Select image ' + (idx + 1)">
<img [src]="image" alt="Product thumbnail" class="w-100 h-100 object-fit-cover" />
</button>
}

View File

@@ -2,10 +2,11 @@
width: 100%;
&__main {
aspect-ratio: 1 / 1;
aspect-ratio: 463 / 489;
width: 100%;
background-color: #e0e0e0 !important; /* Matches grey placeholder in mock */
border-radius: 8px;
background-color: #e0e0e0 !important;
/* Matches grey placeholder in mock */
display: flex;
align-items: center;
justify-content: center;
@@ -63,8 +64,8 @@
width: 80px;
height: 80px;
aspect-ratio: 1 / 1;
background-color: #e0e0e0 !important; /* Matches mock layout */
border-radius: 4px;
background-color: #e0e0e0 !important;
/* Matches mock layout */
padding: 0;
transition: opacity 0.2s ease, border-color 0.2s ease;
border: 3px solid transparent !important;