diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
index a29ca38..fd1e2b1 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
@@ -11,10 +11,145 @@
{{ error() }}
} @else if (product(); as prod) {
-
-
-
-
+
+
+
+
+
+
+ {{ prod.nombre }}
+
+
+
+ {{ getFormattedPrice(prod.precio) }}
+
+
+ @if (oldPrice(); as oldPrice) {
+ {{ oldPrice }}
+ }
+
+
+
+
+
+ @if (colorAttribute() || sizeAttribute()) {
+
+ @if (colorAttribute(); as colorAttr) {
+
+
Color:
+
+
+ @for (option of colorAttr.options; track option.id) {
+
+ }
+
+
+ }
+
+ @if (sizeAttribute(); as sizeAttr) {
+
+
Talle:
+
+
+ @for (option of sizeAttr.options; track option.id) {
+
+ }
+
+
+ }
+
+ }
+
+
+
+
+
+
+
+
+ {{ quantity() }}
+
+
+
+
+
+
+ Agregar al carrito
+
+
+
+ Comprar
+
+
+
+
+
+
+
+
+ DESCRIPCIÓN
+
+
+ {{ prod.descripcion }}
+
+
+ @if (showDescriptionToggle()) {
+
+ }
+
+
} @else {
@@ -24,5 +159,3 @@
}
-
-
diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.scss b/src/app/features/store/pages/product-detail-page/product-detail-page.component.scss
index ef37f26..4dc0a3a 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.scss
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.scss
@@ -1,2 +1,221 @@
-/* empty */
+.product-detail {
+ color: #000000;
+ &__panel {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ }
+
+ &__section {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ &__divider {
+ border-top: 1px solid #e6e6e6;
+ }
+
+ &__title {
+ font-size: 28px;
+ font-weight: 325;
+ line-height: 1.35;
+ color: #000000;
+ }
+
+ &__price-group {
+ display: flex;
+ align-items: baseline;
+ gap: 0.875rem;
+ flex-wrap: wrap;
+ }
+
+ &__price-current {
+ font-size: 42px;
+ font-weight: 425;
+ line-height: 1.1;
+ color: var(--tenant-primary);
+ }
+
+ &__price-previous {
+ font-size: 19px;
+ font-weight: 325;
+ line-height: 1.2;
+ color: #a0a0a0;
+ text-decoration: line-through;
+ }
+
+ &__section--attributes {
+ gap: 1.25rem;
+ }
+
+ &__attribute-row {
+ display: flex;
+ align-items: center;
+ gap: 0.875rem;
+ flex-wrap: wrap;
+ }
+
+ &__attribute-label {
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 1.2;
+ color: #666666;
+ min-width: 44px;
+ }
+
+ &__color-options,
+ &__size-options,
+ &__actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ }
+
+ &__color-swatch {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 1px solid transparent;
+ padding: 0;
+ cursor: pointer;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+
+ &--selected {
+ border-color: var(--tenant-primary);
+ box-shadow:
+ 0 0 0 2px rgba(255, 255, 255, 1),
+ 0 0 0 3px var(--tenant-primary);
+ }
+ }
+
+ &__size-option,
+ &__quantity-button {
+ border: 1px solid #dcdcdc;
+ background: #ffffff;
+ color: #666666;
+ transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease;
+ }
+
+ &__size-option {
+ min-width: 34px;
+ min-height: 34px;
+ border-radius: 4px;
+ padding: 0.35rem 0.6rem;
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 1;
+
+ &--selected {
+ background: var(--tenant-primary);
+ border-color: var(--tenant-primary);
+ color: #ffffff;
+ }
+ }
+
+ &__purchase {
+ display: flex;
+ align-items: stretch;
+ gap: 0.875rem;
+ flex-wrap: wrap;
+ }
+
+ &__quantity {
+ display: inline-flex;
+ align-items: center;
+ border: 1px solid #dcdcdc;
+ border-radius: 4px;
+ overflow: hidden;
+ min-height: 42px;
+ }
+
+ &__quantity-button {
+ width: 34px;
+ height: 42px;
+ padding: 0;
+ font-size: 18px;
+ font-weight: 400;
+ line-height: 1;
+ }
+
+ &__quantity-value {
+ min-width: 40px;
+ text-align: center;
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 1;
+ color: #666666;
+ }
+
+ &__actions {
+ flex: 1 1 0;
+ }
+
+ &__description-heading {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 400;
+ line-height: 1.2;
+ letter-spacing: 0.04em;
+ color: #666666;
+ }
+
+ &__description-body {
+ font-size: 14px;
+ font-weight: 325;
+ line-height: 1.45;
+ color: #666666;
+ white-space: pre-line;
+ display: -webkit-box;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+
+ &--expanded {
+ display: block;
+ -webkit-line-clamp: unset;
+ max-height: none;
+ overflow-y: auto;
+ padding-right: 0.35rem;
+ }
+ }
+
+ &__description-toggle {
+ align-self: center;
+ border: 0;
+ background: transparent;
+ padding: 0;
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--tenant-primary);
+ }
+}
+
+app-button.product-detail__cta {
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+@media (max-width: 991.98px) {
+ .product-detail {
+ &__panel {
+ gap: 1.25rem;
+ }
+
+ &__purchase {
+ flex-direction: column;
+ }
+
+ &__actions {
+ width: 100%;
+ flex-direction: column;
+ }
+ }
+}
diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts b/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts
index 0e05a83..2677943 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.spec.ts
@@ -1,9 +1,13 @@
-import { TestBed } from '@angular/core/testing';
+import { TestBed, getTestBed } from '@angular/core/testing';
+import {
+ BrowserTestingModule,
+ platformBrowserTesting
+} from '@angular/platform-browser/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { By } from '@angular/platform-browser';
import { BehaviorSubject, of, throwError } from 'rxjs';
import { convertToParamMap } from '@angular/router';
-import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { ProductDetail } from '../../../../core/services/catalog/catalog.interface';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
@@ -30,6 +34,17 @@ describe('ProductDetailPageComponent', () => {
let catalogServiceStub: any;
let routerStub: any;
+ beforeAll(() => {
+ try {
+ getTestBed().initTestEnvironment(
+ BrowserTestingModule,
+ platformBrowserTesting()
+ );
+ } catch {
+ // Test environment may already be initialized by another setup entrypoint.
+ }
+ });
+
beforeEach(() => {
vi.restoreAllMocks();
paramMapSubject = new BehaviorSubject(convertToParamMap({ id: '1' }));
@@ -69,9 +84,12 @@ describe('ProductDetailPageComponent', () => {
const element = fixture.nativeElement as HTMLElement;
expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1);
-
- // Check that ProductCarouselComponent is embedded
+
expect(element.querySelector('app-product-carousel')).not.toBeNull();
+ expect(element.querySelector('.product-detail__title')?.textContent).toContain('Auriculares Bluetooth');
+ expect(element.querySelector('.product-detail__price-current')?.textContent).toContain('$24.999');
+ expect(element.querySelector('.product-detail__price-previous')).toBeNull();
+ expect(element.querySelector('.product-carousel__discount-badge')).toBeNull();
});
it('shows error message if load fails', async () => {
@@ -136,5 +154,154 @@ describe('ProductDetailPageComponent', () => {
const carousel = fixture.debugElement.query(By.css('app-product-carousel')).componentInstance;
expect(carousel.images()).toEqual([]);
});
-});
+ it('renders color and size selectors and preselects default variant options', async () => {
+ const detailProduct: ProductDetail = {
+ ...mockProduct,
+ attributes: [
+ {
+ id: 1,
+ codigo: 'color',
+ nombre: 'Color',
+ is_required: true,
+ metadata_schema: null,
+ type: 'select',
+ options: [
+ {
+ id: 10,
+ value: 'beige',
+ label: 'Beige',
+ sort_order: 1,
+ metadata: { hex: '#D8D1C7' }
+ },
+ {
+ id: 11,
+ value: 'brown',
+ label: 'Marrón',
+ sort_order: 2,
+ metadata: { palette: { primary: '#7E6460' } }
+ }
+ ]
+ },
+ {
+ id: 2,
+ codigo: 'talle',
+ nombre: 'Talle',
+ is_required: true,
+ metadata_schema: null,
+ type: 'select',
+ options: [
+ {
+ id: 20,
+ value: 'S',
+ label: 'S',
+ sort_order: 1,
+ metadata: null
+ },
+ {
+ id: 21,
+ value: 'M',
+ label: 'M',
+ sort_order: 2,
+ metadata: null
+ }
+ ]
+ }
+ ],
+ default_variant: {
+ variant_id: 123,
+ images: ['https://example.com/variant1.png'],
+ attributes: {
+ color: 'beige',
+ talle: 'M'
+ }
+ }
+ };
+ catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));
+
+ await configureTestingModule();
+ const fixture = TestBed.createComponent(ProductDetailPageComponent);
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement as HTMLElement;
+ const colorOptions = element.querySelectorAll('.product-detail__color-swatch');
+ const sizeOptions = element.querySelectorAll('.product-detail__size-option');
+
+ expect(colorOptions).toHaveLength(2);
+ expect(sizeOptions).toHaveLength(2);
+ expect(colorOptions[0].classList.contains('product-detail__color-swatch--selected')).toBe(true);
+ expect(sizeOptions[1].classList.contains('product-detail__size-option--selected')).toBe(true);
+ expect((colorOptions[0] as HTMLElement).style.backgroundColor).not.toBe('');
+ });
+
+ it('keeps quantity at a minimum of one and increments locally', async () => {
+ await configureTestingModule();
+ const fixture = TestBed.createComponent(ProductDetailPageComponent);
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement as HTMLElement;
+ const decreaseButton = element.querySelector(
+ '[aria-label="Disminuir cantidad"]'
+ ) as HTMLButtonElement;
+ const increaseButton = element.querySelector(
+ '[aria-label="Aumentar cantidad"]'
+ ) as HTMLButtonElement;
+
+ decreaseButton.click();
+ fixture.detectChanges();
+
+ expect(element.querySelector('.product-detail__quantity-value')?.textContent?.trim()).toBe('1');
+
+ increaseButton.click();
+ increaseButton.click();
+ fixture.detectChanges();
+
+ expect(element.querySelector('.product-detail__quantity-value')?.textContent?.trim()).toBe('3');
+ });
+
+ it('toggles the full description state when overflow is available', async () => {
+ await configureTestingModule();
+ const fixture = TestBed.createComponent(ProductDetailPageComponent);
+ fixture.detectChanges();
+
+ (fixture.componentInstance as any).descriptionHasOverflow.set(true);
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement as HTMLElement;
+ const toggleButton = element.querySelector(
+ '.product-detail__description-toggle'
+ ) as HTMLButtonElement;
+
+ expect(toggleButton.textContent?.trim()).toBe('Mostrar más');
+
+ toggleButton.click();
+ fixture.detectChanges();
+
+ expect(
+ element
+ .querySelector('.product-detail__description-body')
+ ?.classList.contains('product-detail__description-body--expanded')
+ ).toBe(true);
+ expect(toggleButton.textContent?.trim()).toBe('Mostrar menos');
+ });
+
+ it('renders purchase actions without side effects', async () => {
+ await configureTestingModule();
+ const fixture = TestBed.createComponent(ProductDetailPageComponent);
+ fixture.detectChanges();
+
+ const buttons = Array.from(
+ fixture.nativeElement.querySelectorAll('app-button button')
+ ) as HTMLButtonElement[];
+
+ expect(buttons.map((button) => button.textContent?.trim())).toEqual([
+ 'Agregar al carrito',
+ 'Comprar'
+ ]);
+
+ buttons[0].click();
+ buttons[1].click();
+
+ expect(routerStub.navigate).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
index ae51147..b32c6a0 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
@@ -1,16 +1,33 @@
-import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ OnDestroy,
+ OnInit,
+ computed,
+ effect,
+ inject,
+ signal,
+ viewChild
+} from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { Subscription } from 'rxjs';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
-import { ProductDetail } from '../../../../core/services/catalog/catalog.interface';
+import {
+ ProductAttribute,
+ ProductAttributeOption,
+ ProductDetail,
+ ProductVariant
+} from '../../../../core/services/catalog/catalog.interface';
import { ProductCarouselComponent } from '../../components/product-carousel/product-carousel.component';
+import { ButtonComponent } from '../../../../shared/components/button/button.component';
@Component({
selector: 'app-product-detail-page',
standalone: true,
- imports: [CommonModule, RouterModule, ProductCarouselComponent],
+ imports: [CommonModule, RouterModule, ProductCarouselComponent, ButtonComponent],
templateUrl: './product-detail-page.component.html',
styleUrl: './product-detail-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
@@ -19,9 +36,14 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly catalogService = inject(CatalogService);
+ private readonly carouselHost = viewChild
>('carouselHost');
+ private readonly descriptionBody = viewChild>('descriptionBody');
private routeSub: Subscription | null = null;
private productSub: Subscription | null = null;
+ private carouselResizeObserver: ResizeObserver | null = null;
+ private observedCarouselPreview: HTMLElement | null = null;
+ private measurementTimer: ReturnType | null = null;
protected readonly product = signal(null);
protected readonly carouselImages = computed(() => {
@@ -37,6 +59,36 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
});
protected readonly loading = signal(false);
protected readonly error = signal(null);
+ protected readonly selectedColor = signal(null);
+ protected readonly selectedSize = signal(null);
+ protected readonly quantity = signal(1);
+ protected readonly descriptionExpanded = signal(false);
+ protected readonly descriptionMaxHeight = signal(0);
+ protected readonly descriptionHasOverflow = signal(false);
+ protected readonly colorAttribute = computed(() =>
+ this.findAttribute(this.product()?.attributes ?? [], ['color', 'colour'])
+ );
+ protected readonly sizeAttribute = computed(() =>
+ this.findAttribute(this.product()?.attributes ?? [], ['talle', 'talla', 'size'])
+ );
+ protected readonly oldPrice = computed(() => null);
+ protected readonly showDescriptionToggle = computed(
+ () => this.descriptionExpanded() || this.descriptionHasOverflow()
+ );
+
+ constructor() {
+ effect(() => {
+ this.carouselHost();
+ this.descriptionBody();
+ this.product();
+ this.descriptionExpanded();
+
+ queueMicrotask(() => {
+ this.bindCarouselResizeObserver();
+ this.refreshMeasurements();
+ });
+ });
+ }
ngOnInit(): void {
this.routeSub = this.route.paramMap.subscribe((params) => {
@@ -55,6 +107,8 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.routeSub?.unsubscribe();
this.productSub?.unsubscribe();
+ this.carouselResizeObserver?.disconnect();
+ this.clearMeasurementTimer();
}
private loadProduct(id: number): void {
@@ -66,6 +120,10 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
this.productSub = this.catalogService.getProducto(id).subscribe({
next: (prod) => {
this.product.set(prod);
+ this.initializeSelections(prod);
+ this.quantity.set(1);
+ this.descriptionExpanded.set(false);
+ this.descriptionHasOverflow.set(false);
this.loading.set(false);
},
error: () => {
@@ -88,4 +146,217 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '.');
return `$${parts.join(',')}`;
}
+
+ protected hasColorSelection(optionId: number): boolean {
+ return this.selectedColor() === optionId;
+ }
+
+ protected hasSizeSelection(optionId: number): boolean {
+ return this.selectedSize() === optionId;
+ }
+
+ protected selectColor(optionId: number): void {
+ this.selectedColor.set(optionId);
+ }
+
+ protected selectSize(optionId: number): void {
+ this.selectedSize.set(optionId);
+ }
+
+ protected increaseQuantity(): void {
+ this.quantity.update((current) => current + 1);
+ }
+
+ protected decreaseQuantity(): void {
+ this.quantity.update((current) => Math.max(1, current - 1));
+ }
+
+ protected toggleDescription(): void {
+ this.descriptionExpanded.update((current) => !current);
+ }
+
+ protected getOptionSwatchColor(option: ProductAttributeOption): string {
+ return this.findFirstHexValue(option.metadata) ?? '#D9D9D9';
+ }
+
+ private initializeSelections(product: ProductDetail): void {
+ const colorAttribute = this.findAttribute(product.attributes, ['color', 'colour']);
+ const sizeAttribute = this.findAttribute(product.attributes, ['talle', 'talla', 'size']);
+ const defaultVariant = product.default_variant;
+
+ this.selectedColor.set(this.findDefaultOptionId(colorAttribute, defaultVariant));
+ this.selectedSize.set(this.findDefaultOptionId(sizeAttribute, defaultVariant));
+ }
+
+ private findDefaultOptionId(
+ attribute: ProductAttribute | undefined,
+ variant: ProductVariant | null
+ ): number | null {
+ if (!attribute || !variant) {
+ return null;
+ }
+
+ const defaultValue = this.getDefaultVariantAttributeValue(attribute, variant.attributes);
+ if (!defaultValue) {
+ return null;
+ }
+
+ const normalizedValue = this.normalizeText(defaultValue);
+ const matchByValue = attribute.options.find(
+ (option) => this.normalizeText(option.value) === normalizedValue
+ );
+
+ if (matchByValue) {
+ return matchByValue.id;
+ }
+
+ const matchByLabel = attribute.options.find(
+ (option) => this.normalizeText(option.label) === normalizedValue
+ );
+
+ return matchByLabel?.id ?? null;
+ }
+
+ private getDefaultVariantAttributeValue(
+ attribute: ProductAttribute,
+ variantAttributes: Record
+ ): string | null {
+ const normalizedCodigo = this.normalizeText(attribute.codigo);
+ const normalizedNombre = this.normalizeText(attribute.nombre);
+
+ for (const [key, value] of Object.entries(variantAttributes)) {
+ const normalizedKey = this.normalizeText(key);
+
+ if (normalizedKey === normalizedCodigo || normalizedKey === normalizedNombre) {
+ return value;
+ }
+ }
+
+ return null;
+ }
+
+ private findAttribute(
+ attributes: ProductAttribute[],
+ aliases: string[]
+ ): ProductAttribute | undefined {
+ const normalizedAliases = aliases.map((alias) => this.normalizeText(alias));
+
+ const codeMatch = attributes.find((attribute) =>
+ normalizedAliases.includes(this.normalizeText(attribute.codigo))
+ );
+
+ if (codeMatch) {
+ return codeMatch;
+ }
+
+ return attributes.find((attribute) =>
+ normalizedAliases.includes(this.normalizeText(attribute.nombre))
+ );
+ }
+
+ private normalizeText(value: string | null | undefined): string {
+ return (value ?? '')
+ .normalize('NFD')
+ .replace(/[\u0300-\u036f]/g, '')
+ .trim()
+ .toLowerCase();
+ }
+
+ private findFirstHexValue(value: unknown): string | null {
+ if (typeof value === 'string') {
+ const match = value.match(/#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/);
+ return match ? match[0] : null;
+ }
+
+ if (Array.isArray(value)) {
+ for (const item of value) {
+ const found = this.findFirstHexValue(item);
+ if (found) {
+ return found;
+ }
+ }
+ return null;
+ }
+
+ if (value && typeof value === 'object') {
+ for (const item of Object.values(value)) {
+ const found = this.findFirstHexValue(item);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private bindCarouselResizeObserver(): void {
+ const previewElement = this.getCarouselPreviewElement();
+
+ if (!previewElement || previewElement === this.observedCarouselPreview) {
+ return;
+ }
+
+ this.carouselResizeObserver?.disconnect();
+ this.observedCarouselPreview = previewElement;
+
+ if (typeof ResizeObserver === 'undefined') {
+ this.updateDescriptionMaxHeight();
+ return;
+ }
+
+ this.carouselResizeObserver = new ResizeObserver(() => {
+ this.scheduleMeasurement();
+ });
+ this.carouselResizeObserver.observe(previewElement);
+ this.updateDescriptionMaxHeight();
+ }
+
+ private scheduleMeasurement(): void {
+ this.clearMeasurementTimer();
+ this.measurementTimer = setTimeout(() => {
+ this.measurementTimer = null;
+ this.refreshMeasurements();
+ }, 0);
+ }
+
+ private clearMeasurementTimer(): void {
+ if (this.measurementTimer) {
+ clearTimeout(this.measurementTimer);
+ this.measurementTimer = null;
+ }
+ }
+
+ private refreshMeasurements(): void {
+ this.updateDescriptionMaxHeight();
+ this.updateDescriptionOverflow();
+ }
+
+ private updateDescriptionMaxHeight(): void {
+ const previewElement = this.getCarouselPreviewElement();
+ const nextHeight = previewElement?.getBoundingClientRect().height ?? 0;
+
+ if (nextHeight > 0) {
+ this.descriptionMaxHeight.set(nextHeight);
+ }
+ }
+
+ private updateDescriptionOverflow(): void {
+ const descriptionElement = this.descriptionBody()?.nativeElement;
+
+ if (!descriptionElement || this.descriptionExpanded()) {
+ return;
+ }
+
+ this.descriptionHasOverflow.set(
+ descriptionElement.scrollHeight - descriptionElement.clientHeight > 1
+ );
+ }
+
+ private getCarouselPreviewElement(): HTMLElement | null {
+ return (
+ this.carouselHost()?.nativeElement.querySelector('.product-carousel__main') ??
+ null
+ );
+ }
}
diff --git a/src/app/shared/components/button/button.component.scss b/src/app/shared/components/button/button.component.scss
index b226c71..5019740 100644
--- a/src/app/shared/components/button/button.component.scss
+++ b/src/app/shared/components/button/button.component.scss
@@ -2,6 +2,11 @@
display: inline-block;
}
+:host(.product-detail__cta) {
+ display: block;
+ width: 100%;
+}
+
.btn {
min-height: 40px;
font-weight: 700;
@@ -9,6 +14,10 @@
min-width: 216px;
}
+:host(.product-detail__cta) .btn {
+ width: 100%;
+}
+
.btn-primary,
.btn-secondary,
.btn-danger {