Files
marketplaces/src/app/components/header/header.component.html

166 lines
6.8 KiB
HTML
Raw Normal View History

<!-- platform VERSION - Redesigned 2026 -->
<header class="platform-header" [class.platform-header--static]="headerConfig().sticky === false" [class.platform-header--centered]="headerConfig().layout === 'centered'">
<div class="platform-header-container">
2026-02-14 00:45:17 +04:00
<!-- Logo -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showLogo) {
<a [attr.href]="homeUrl" class="platform-logo" (click)="navigateHome($event)">
<app-logo />
</a>
}
2026-01-18 18:57:06 +04:00
2026-02-14 02:59:26 +04:00
<!-- Navigation Buttons (desktop) -->
<nav class="platform-nav">
<div class="platform-nav-group">
<a [routerLink]="'/' | langRoute" routerLinkActive="platform-active" [routerLinkActiveOptions]="{exact: true}"
(click)="closeMenu()" class="platform-nav-btn">
2026-02-26 23:09:20 +04:00
{{ 'header.home' | translate }}
2026-02-14 00:45:17 +04:00
</a>
2026-07-10 13:43:53 +04:00
@if (headerConfig().showCategories) {
<button type="button" (click)="navigateToCatalog()" class="platform-nav-btn">
2026-07-10 13:43:53 +04:00
{{ 'header.catalog' | translate }}
</button>
}
2026-07-10 13:52:01 +04:00
@for (page of headerPages(); track page.id) {
<button type="button" (click)="navigateToStatic(page.route)" class="platform-nav-btn">
2026-07-10 13:52:01 +04:00
{{ page.title }}
</button>
}
2026-02-14 00:45:17 +04:00
</div>
</nav>
2026-02-14 02:34:11 +04:00
<!-- Search Box (desktop) -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showSearch) {
<div class="platform-search-wrapper">
<div class="platform-search-box">
<app-icon name="search" [size]="20" class="platform-search-icon" />
fix(storefront): WCAG 2.1 AA accessibility fixes RC A11Y-01 audit pass, storefront + shared app-shell chrome only. Builds on RC-Visual-02/RC-Premium-01/RC STORE-01 without redoing that work. - Skip link: added first-focusable "skip to main content" link (app.html, styles.scss .skip-link/.sr-only), targeting new #main-content landmark. New app.skipToContent i18n key in en/ru/hy. - Header: mobile menu items stayed keyboard-focusable and screen-reader reachable while visually collapsed (max-height:0 with no visibility toggle) - fixed with visibility:hidden + matched transition-delay. Desktop search input (readonly, click-to-navigate) had no keyboard activation - added aria-label + (keydown.enter). - Cart payment/bank-payment modals: custom (non-app-dialog) UI had no focus trap, no Escape handling, and never returned focus to the triggering element - ported app-dialog's confirmed-correct focus-trap/Escape/return-focus pattern directly onto cart.component.ts. Added role="dialog"/aria-modal/aria-label to both panels and role="status"|"alert"/aria-live to every payment-status screen so screen readers announce state changes (creating/waiting/success/ error/timeout). - Search combobox: suggestion listbox had no role="combobox" wiring on the input and suggestion buttons weren't role="option" - added aria-autocomplete, aria-controls, aria-activedescendant, aria-selected so the existing arrow-key navigation is announced to screen readers. - Product tabs: tablist/tab pattern was incomplete (no role="tablist", no tabpanel) - added role="tablist" + ids to product-tabs.component, role="tabpanel"/aria-labelledby to the content panel in product-details-container. - Review form: rating/text validation errors weren't associated with their controls (no aria-describedby, no role="alert") - fixed; added aria-required to the review textarea. - delivery-selector: added aria-required to the delivery <select> when a selection is mandatory. - Shared app-icon component: doc comment claimed "decorative by default (aria-hidden)" but no aria-hidden was ever applied - fixed to actually set aria-hidden="true" when undecorated, and role="img"/aria-label when ariaLabel is passed. Shared component, affects every icon-only usage app-wide, no visual change. - Color contrast: --text-light fails WCAG AA 4.5:1 for normal text in every theme (dexar 3.39:1, lavero/novo 2.54:1 against white). The two in-scope usages (company-details org-short/basis, review-form upload-placeholder) switched to --text-secondary (4.55:1-7.56:1, passes), same visual family, no layout change. Flagged, not fixed (design-system decisions, not polish): - --border-color fails WCAG 1.4.11 3:1 for UI-component boundaries in every theme (dexar 1.42:1, lavero/novo 1.24:1 vs white) - pervasive token used by hundreds of borders app-wide; needs theme-owner sign-off. - --success-color/--warning-color/--error-color/--info-color used as plain text-on-white in several places (product-information, question-card, review-form, compare-page) fail 4.5:1 (2.15-3.76:1) - genuine brand semantic colors, changing them to pass would visibly shift the palette; needs a deliberate token decision. - Header mobile-menu max-height/padding transition (pre-existing, unrelated to this fix) flagged by design lint as layout-thrashing; left as-is per the "no layout/business-logic changes" constraint. Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing bundle-budget warning, unrelated to this pass). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:46:18 +04:00
<input type="text" [placeholder]="'header.searchPlaceholder' | translate" class="platform-search-input" [attr.aria-label]="'header.search' | translate" (click)="navigateToSearch()" (keydown.enter)="navigateToSearch()" readonly />
2026-02-14 00:45:17 +04:00
</div>
</div>
2026-07-10 13:43:53 +04:00
}
2026-02-14 00:45:17 +04:00
2026-02-14 02:34:11 +04:00
<!-- Search Icon (mobile only) -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showSearch) {
<button class="platform-search-mobile" (click)="navigateToSearch()" [attr.aria-label]="'header.search' | translate">
<app-icon name="search" [size]="18" />
2026-02-14 02:34:11 +04:00
</button>
2026-07-10 13:43:53 +04:00
}
2026-02-14 02:34:11 +04:00
2026-02-14 00:45:17 +04:00
<!-- Right Actions -->
<div class="platform-actions">
2026-07-10 15:55:38 +04:00
@if (headerConfig().showWishlist && features().wishlist && userExperienceConfig().wishlist.enabled) {
<button type="button" class="platform-ux-btn" (click)="navigateToWishlist()" [attr.aria-label]="'header.wishlist' | translate">
<app-icon name="heart" [size]="16" class="platform-ux-icon" />
2026-07-10 13:43:53 +04:00
@if (userExperienceConfig().wishlist.headerBadgeEnabled && wishlistCount() > 0) {
<span class="platform-ux-badge">{{ wishlistCount() }}</span>
}
</button>
}
2026-07-10 15:55:38 +04:00
@if (headerConfig().showCompare && features().compare && userExperienceConfig().compare.enabled) {
<button type="button" class="platform-ux-btn" (click)="navigateToCompare()" [attr.aria-label]="'header.compare' | translate">
<app-icon name="scale" [size]="16" class="platform-ux-icon" />
@if (compareCount() > 0) {
<span class="platform-ux-badge">{{ compareCount() }}</span>
}
</button>
}
2026-02-14 00:45:17 +04:00
<!-- Cart Button -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showCart) {
<a [routerLink]="'/cart' | langRoute" routerLinkActive="platform-cart-active" class="platform-cart-btn" (click)="closeMenu()">
<span class="platform-cart-icon">
<app-icon name="cart" [size]="24" />
2026-06-20 14:00:28 +04:00
@if (cartItemCount() > 0) {
<span class="platform-cart-badge">{{ cartItemCount() }}</span>
2026-06-20 14:00:28 +04:00
}
</span>
2026-01-18 18:57:06 +04:00
@if (cartItemCount() > 0) {
<span class="platform-cart-total">{{ formatCartTotal(cartTotal()) }}</span>
2026-01-18 18:57:06 +04:00
}
</a>
2026-07-10 13:43:53 +04:00
}
2026-01-18 18:57:06 +04:00
<!-- Profile (login/logout only) -->
@if (headerConfig().showProfile) {
@if (isAuthenticated()) {
<button type="button" class="platform-ux-btn" (click)="logout()" [attr.aria-label]="'header.logout' | translate">
<app-icon name="logOut" [size]="16" class="platform-ux-icon" />
</button>
} @else {
<button type="button" class="platform-ux-btn" (click)="login()" [attr.aria-label]="'header.login' | translate">
<app-icon name="user" [size]="16" class="platform-ux-icon" />
</button>
}
}
2026-02-28 17:18:24 +04:00
<!-- Region Selector (desktop only) -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showRegion) {
<div class="platform-region-selector platform-lang-desktop">
2026-02-28 17:18:24 +04:00
<app-region-selector />
</div>
2026-07-10 13:43:53 +04:00
}
2026-02-28 17:18:24 +04:00
2026-02-14 02:59:26 +04:00
<!-- Language Selector (desktop only) -->
2026-07-10 13:43:53 +04:00
@if (headerConfig().showLanguages) {
<div class="platform-lang-selector platform-lang-desktop">
2026-02-14 00:45:17 +04:00
<app-language-selector />
</div>
2026-07-10 13:43:53 +04:00
}
2026-01-18 18:57:06 +04:00
2026-02-14 00:45:17 +04:00
<!-- Mobile Menu Toggle -->
<button class="platform-menu-toggle" (click)="toggleMenu()" [class.active]="menuOpen" [attr.aria-label]="menuOpen ? ('header.closeMenu' | translate) : ('header.openMenu' | translate)" [attr.aria-expanded]="menuOpen">
2026-02-14 00:45:17 +04:00
<span></span>
<span></span>
<span></span>
</button>
2026-01-18 18:57:06 +04:00
</div>
</div>
</header>
2026-02-14 02:59:26 +04:00
<!-- Mobile Menu Backdrop (outside header to blur page content) -->
@if (menuOpen) {
<div class="platform-menu-backdrop" (click)="closeMenu()"></div>
2026-02-14 02:59:26 +04:00
}
<!-- Mobile Menu Panel (outside header for correct stacking) -->
<div class="platform-mobile-menu" [class.platform-mobile-menu-open]="menuOpen">
<a [routerLink]="'/' | langRoute" routerLinkActive="platform-mobile-active" [routerLinkActiveOptions]="{exact: true}"
(click)="closeMenu()" class="platform-mobile-item">
<app-icon name="home" [size]="24" />
2026-02-26 23:09:20 +04:00
<span>{{ 'header.home' | translate }}</span>
<app-icon name="chevronRight" [size]="16" class="platform-mobile-chevron" />
2026-02-14 02:59:26 +04:00
</a>
2026-07-10 13:43:53 +04:00
@if (headerConfig().showCategories) {
<button type="button" (click)="navigateToCatalog()" class="platform-mobile-item">
<app-icon name="layoutGrid" [size]="24" />
2026-02-26 23:09:20 +04:00
<span>{{ 'header.catalog' | translate }}</span>
<app-icon name="chevronRight" [size]="16" class="platform-mobile-chevron" />
</button>
2026-07-10 13:43:53 +04:00
}
2026-02-14 02:59:26 +04:00
2026-07-10 13:52:01 +04:00
@for (page of headerPages(); track page.id) {
<button type="button" (click)="navigateToStatic(page.route)" class="platform-mobile-item">
2026-07-10 13:52:01 +04:00
<span>{{ page.title }}</span>
<app-icon name="chevronRight" [size]="16" class="platform-mobile-chevron" />
</button>
2026-07-10 13:52:01 +04:00
}
2026-02-14 02:59:26 +04:00
<div class="platform-mobile-controls">
2026-07-10 13:43:53 +04:00
@if (headerConfig().showRegion) {
<div class="platform-mobile-lang">
2026-06-10 17:49:22 +04:00
<app-region-selector />
</div>
2026-07-10 13:43:53 +04:00
}
@if (headerConfig().showLanguages) {
<div class="platform-mobile-lang">
2026-06-10 17:49:22 +04:00
<app-language-selector />
</div>
2026-07-10 13:43:53 +04:00
}
2026-02-14 02:59:26 +04:00
</div>
</div>
<app-telegram-login />