refactor: extract shared app-breadcrumb component (Z14)
Only breadcrumb logic anywhere in the storefront was a local signal + inline markup inside catalog-container. Extracted a generic shared/ui/breadcrumb component (rootLabel/items/ariaLabel inputs, rootClick/itemClick outputs) and repointed catalog-container onto it, removing the now-dead inline SCSS block. Future breadcrumb usages (product detail, admin) have something to reuse instead of duplicating. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,13 +3,12 @@
|
||||
<button type="button" class="catalog-root-link catalog-root-link-btn" (click)="browseCategories()">{{ 'catalog.title' | translate }}</button>
|
||||
|
||||
@if (catalogConfig().showBreadcrumbs && breadcrumb().length > 0) {
|
||||
<nav class="catalog-breadcrumb" [attr.aria-label]="'catalog.breadcrumb' | translate">
|
||||
<button type="button" (click)="browseCategories()">{{ 'catalog.allCategories' | translate }}</button>
|
||||
@for (category of breadcrumb(); track category.id) {
|
||||
<span aria-hidden="true">/</span>
|
||||
<button type="button" (click)="selectCategory(category)">{{ category.title }}</button>
|
||||
}
|
||||
</nav>
|
||||
<app-breadcrumb
|
||||
[rootLabel]="'catalog.allCategories' | translate"
|
||||
[ariaLabel]="'catalog.breadcrumb' | translate"
|
||||
[items]="breadcrumbItems()"
|
||||
(rootClick)="browseCategories()"
|
||||
(itemClick)="onBreadcrumbItemClick($event)" />
|
||||
}
|
||||
|
||||
<app-catalog-search-box
|
||||
|
||||
@@ -57,27 +57,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.catalog-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-md, 0.9375rem);
|
||||
|
||||
a,
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
font: inherit;
|
||||
font-weight: var(--font-weight-bold, 700);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.compo
|
||||
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
|
||||
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
|
||||
import { QuickViewDialogComponent } from '../../product/components/quick-view-dialog/quick-view-dialog.component';
|
||||
import { BreadcrumbComponent, BreadcrumbItem } from '../../../../shared/ui/breadcrumb/breadcrumb.component';
|
||||
import { CatalogState, createInitialCatalogState } from '../models/catalog-state.model';
|
||||
import { ProductShareService } from '../../user-experience/services/product-share.service';
|
||||
import { UserNotificationService } from '../../user-experience/services/user-notification.service';
|
||||
@@ -59,7 +60,8 @@ type CatalogLoadingStrategy = 'pagination' | 'loadMore' | 'infiniteScroll';
|
||||
SkeletonComponent,
|
||||
ButtonComponent,
|
||||
IconComponent,
|
||||
QuickViewDialogComponent
|
||||
QuickViewDialogComponent,
|
||||
BreadcrumbComponent
|
||||
],
|
||||
templateUrl: './catalog-container.component.html',
|
||||
styleUrls: ['./catalog-container.component.scss'],
|
||||
@@ -273,6 +275,17 @@ export class CatalogContainerComponent {
|
||||
});
|
||||
}
|
||||
|
||||
readonly breadcrumbItems = computed<BreadcrumbItem[]>(() =>
|
||||
this.breadcrumb().map(category => ({ id: category.id, label: category.title }))
|
||||
);
|
||||
|
||||
onBreadcrumbItemClick(item: BreadcrumbItem): void {
|
||||
const category = this.breadcrumb().find(c => c.id === item.id);
|
||||
if (category) {
|
||||
this.selectCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
selectCategory(category: Category): void {
|
||||
this.router.navigate([`/${this.languageService.currentLanguage()}/catalog`, category.id]);
|
||||
}
|
||||
|
||||
7
src/app/shared/ui/breadcrumb/breadcrumb.component.html
Normal file
7
src/app/shared/ui/breadcrumb/breadcrumb.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<nav class="app-breadcrumb" [attr.aria-label]="ariaLabel() || null">
|
||||
<button type="button" (click)="rootClick.emit()">{{ rootLabel() }}</button>
|
||||
@for (item of items(); track item.id) {
|
||||
<span aria-hidden="true">/</span>
|
||||
<button type="button" (click)="itemClick.emit(item)">{{ item.label }}</button>
|
||||
}
|
||||
</nav>
|
||||
19
src/app/shared/ui/breadcrumb/breadcrumb.component.scss
Normal file
19
src/app/shared/ui/breadcrumb/breadcrumb.component.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
.app-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-size-md, 0.9375rem);
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
font: inherit;
|
||||
font-weight: var(--font-weight-bold, 700);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
26
src/app/shared/ui/breadcrumb/breadcrumb.component.ts
Normal file
26
src/app/shared/ui/breadcrumb/breadcrumb.component.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
id: string | number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic breadcrumb trail. Consumer owns navigation - this component only
|
||||
* renders the trail and emits which item was clicked (root or a trail item).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-breadcrumb',
|
||||
standalone: true,
|
||||
templateUrl: './breadcrumb.component.html',
|
||||
styleUrl: './breadcrumb.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BreadcrumbComponent {
|
||||
readonly rootLabel = input.required<string>();
|
||||
readonly items = input<BreadcrumbItem[]>([]);
|
||||
readonly ariaLabel = input<string>('');
|
||||
|
||||
readonly rootClick = output<void>();
|
||||
readonly itemClick = output<BreadcrumbItem>();
|
||||
}
|
||||
Reference in New Issue
Block a user