fix(storefront): use shared app-skeleton in category/search loading state

Category and Search pages still hand-rolled their infinite-scroll
loading skeleton (.skeleton-card/.skeleton-image/.skeleton-line
divs with their own hardcoded-hex shimmer animation) instead of the
shared app-skeleton primitive already used by catalog-container and
product-details-container. Swapped both to app-skeleton (shape=rect
for image/button, shape=text for lines), removed the now-dead
per-page shimmer CSS/hex colors. Closes KNOWN-ISSUES.md item 9.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-23 23:50:59 +04:00
parent b672cd90d5
commit 2e854954ac
6 changed files with 34 additions and 96 deletions

View File

@@ -20,17 +20,17 @@
@if (loading() && items().length > 0) { @if (loading() && items().length > 0) {
@for (i of skeletonSlots; track i) { @for (i of skeletonSlots; track i) {
<div class="item-card skeleton-card"> <div class="item-card skeleton-card" aria-hidden="true">
<div class="item-link"> <div class="item-link">
<div class="item-image skeleton-image"></div> <app-skeleton shape="rect" class="item-image" height="auto" />
<div class="item-details"> <div class="item-details">
<div class="skeleton-line skeleton-title"></div> <app-skeleton shape="text" width="80%" height="16px" />
<div class="skeleton-line skeleton-rating"></div> <app-skeleton shape="text" width="50%" height="12px" />
<div class="skeleton-line skeleton-price"></div> <app-skeleton shape="text" width="40%" height="18px" />
<div class="skeleton-line skeleton-stock"></div> <app-skeleton shape="text" width="60px" height="6px" />
</div> </div>
</div> </div>
<div class="skeleton-btn"></div> <app-skeleton shape="rect" class="skeleton-btn" height="42px" />
</div> </div>
} }
} }

View File

@@ -310,59 +310,27 @@
padding: 40px 20px; padding: 40px 20px;
} }
// Skeleton loading cards // Skeleton loading cards (shimmer/animation owned by shared app-skeleton)
.skeleton-card { .skeleton-card {
pointer-events: none; pointer-events: none;
.skeleton-image { .item-image {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); display: block;
background-size: 200% 100%; aspect-ratio: 1 / 1;
animation: shimmer 1.5s infinite;
} }
.skeleton-line { .item-details app-skeleton {
border-radius: 6px; display: block;
background: linear-gradient(90deg, #e8e8e8 25%, #d8d8d8 50%, #e8e8e8 75%); margin-bottom: var(--space-sm, 8px);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-title {
height: 16px;
width: 80%;
}
.skeleton-rating {
height: 12px;
width: 50%;
}
.skeleton-price {
height: 18px;
width: 40%;
margin-top: auto;
}
.skeleton-stock {
height: 6px;
width: 60px;
} }
.skeleton-btn { .skeleton-btn {
height: 42px; display: block;
background: linear-gradient(90deg, #5a8a85 25%, #497671 50%, #5a8a85 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 0 0 13px 13px; border-radius: 0 0 13px 13px;
margin-top: -1px; margin-top: -1px;
} }
} }
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
// Responsive // Responsive
@media (max-width: 1200px) { @media (max-width: 1200px) {
.items-grid { .items-grid {

View File

@@ -12,10 +12,11 @@ import { SCROLL_THRESHOLD_PX, SCROLL_DEBOUNCE_MS, ITEMS_PER_PAGE } from '../../c
import { ProductFacade } from '../../facades/platform/product.facade'; import { ProductFacade } from '../../facades/platform/product.facade';
import { ProductCardComponent } from '../../components/product-card/product-card.component'; import { ProductCardComponent } from '../../components/product-card/product-card.component';
import { IconComponent } from '../../shared/ui/icon/icon.component'; import { IconComponent } from '../../shared/ui/icon/icon.component';
import { SkeletonComponent } from '../../shared/ui/skeleton/skeleton.component';
@Component({ @Component({
selector: 'app-category', selector: 'app-category',
imports: [RouterLink, LangRoutePipe, TranslatePipe, ProductCardComponent, IconComponent], imports: [RouterLink, LangRoutePipe, TranslatePipe, ProductCardComponent, IconComponent, SkeletonComponent],
templateUrl: './category.component.html', templateUrl: './category.component.html',
styleUrls: ['./category.component.scss'], styleUrls: ['./category.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -72,17 +72,17 @@
@if (loading() && items().length > 0) { @if (loading() && items().length > 0) {
@for (i of skeletonSlots; track i) { @for (i of skeletonSlots; track i) {
<div class="item-card skeleton-card"> <div class="item-card skeleton-card" aria-hidden="true">
<div class="item-link"> <div class="item-link">
<div class="item-image skeleton-image"></div> <app-skeleton shape="rect" class="item-image" height="auto" />
<div class="item-details"> <div class="item-details">
<div class="skeleton-line skeleton-title"></div> <app-skeleton shape="text" width="80%" height="16px" />
<div class="skeleton-line skeleton-rating"></div> <app-skeleton shape="text" width="50%" height="12px" />
<div class="skeleton-line skeleton-price"></div> <app-skeleton shape="text" width="40%" height="18px" />
<div class="skeleton-line skeleton-stock"></div> <app-skeleton shape="text" width="60px" height="6px" />
</div> </div>
</div> </div>
<div class="skeleton-btn"></div> <app-skeleton shape="rect" class="skeleton-btn" height="42px" />
</div> </div>
} }
} }

View File

@@ -352,59 +352,27 @@
text-align: center; text-align: center;
} }
// Skeleton loading cards // Skeleton loading cards (shimmer/animation owned by shared app-skeleton)
.skeleton-card { .skeleton-card {
pointer-events: none; pointer-events: none;
.skeleton-image { .item-image {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); display: block;
background-size: 200% 100%; aspect-ratio: 1 / 1;
animation: shimmer 1.5s infinite;
} }
.skeleton-line { .item-details app-skeleton {
border-radius: 6px; display: block;
background: linear-gradient(90deg, #e8e8e8 25%, #d8d8d8 50%, #e8e8e8 75%); margin-bottom: var(--space-sm, 8px);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skeleton-title {
height: 16px;
width: 80%;
}
.skeleton-rating {
height: 12px;
width: 50%;
}
.skeleton-price {
height: 18px;
width: 40%;
margin-top: auto;
}
.skeleton-stock {
height: 6px;
width: 60px;
} }
.skeleton-btn { .skeleton-btn {
height: 42px; display: block;
background: linear-gradient(90deg, #5a8a85 25%, #497671 50%, #5a8a85 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 0 0 13px 13px; border-radius: 0 0 13px 13px;
margin-top: -1px; margin-top: -1px;
} }
} }
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@media (max-width: 768px) { @media (max-width: 768px) {
.search-header h1 { .search-header h1 {
font-size: var(--font-size-3xl, 1.5rem); font-size: var(--font-size-3xl, 1.5rem);

View File

@@ -13,10 +13,11 @@ import { SEARCH_DEBOUNCE_MS, ITEMS_PER_PAGE, SCROLL_THRESHOLD_PX, SCROLL_DEBOUNC
import { ProductFacade } from '../../facades/platform/product.facade'; import { ProductFacade } from '../../facades/platform/product.facade';
import { ProductCardComponent } from '../../components/product-card/product-card.component'; import { ProductCardComponent } from '../../components/product-card/product-card.component';
import { IconComponent } from '../../shared/ui/icon/icon.component'; import { IconComponent } from '../../shared/ui/icon/icon.component';
import { SkeletonComponent } from '../../shared/ui/skeleton/skeleton.component';
@Component({ @Component({
selector: 'app-search', selector: 'app-search',
imports: [FormsModule, TranslatePipe, ProductCardComponent, IconComponent], imports: [FormsModule, TranslatePipe, ProductCardComponent, IconComponent, SkeletonComponent],
templateUrl: './search.component.html', templateUrl: './search.component.html',
styleUrls: ['./search.component.scss'], styleUrls: ['./search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush