fix(storefront): premium UX polish for product, compare, wishlist
- delivery-information, product-actions, product-description, product-gallery, product-information, related-products, variant-selector: normalize hardcoded hex colors to design tokens (--text-primary, --text-secondary, --border-color, --bg-primary/ --bg-secondary, --primary-color/--primary-hover); stock status and discount badge now use semantic --success-color/--warning-color/ --error-color instead of near-duplicate literal hex - product-actions: add aria-pressed to wishlist/compare toggle buttons; add hover/active/disabled states to action buttons - product-gallery: add aria-current + aria-label to active thumbnail button; add focus-visible ring and hover state on thumbnails and toolbar buttons - variant-selector: add aria-pressed to colour/size option buttons; add a visible checkmark glyph on the selected colour swatch so selection isn't color-only; add hover states - product-tabs: add role="tab"/aria-selected to tab buttons - star-selector: add per-star aria-label (new starsLabel i18n key added to en/hy/ru + translations.ts interface) - question-list: add aria-expanded to the ask-question disclosure toggle; swap plain empty-state <p> for app-empty-state; add hover/disabled states to pager buttons - review-list: swap plain empty-state <p> for app-empty-state; add hover/disabled states to pager and load-more buttons - question-card, question-form, review-form: normalize accepted/ success/error colors to semantic tokens; add focus-visible and hover/disabled states to inputs and submit buttons - compare-table: add scope="col"/scope="row" to table headers; make header row and attribute column sticky for easier comparison on long tables - compare-page: add hover/focus states to the remove-from-compare chip button Build verified green via `npm run build`. Out of scope / skipped: - src/app/pages/item-detail/* is dead code (not referenced by any route or component) - left untouched - wishlist page and product-details-container were already fully composed with shared skeleton/empty-state/button components from the RC-Visual-02 pass - no changes needed - stars.component display-only rating glyphs use a light gray not an exact token match - left as-is to avoid an unintended visual shift Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,20 @@
|
|||||||
.delivery-information {
|
.delivery-information {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: var(--space-sm);
|
||||||
padding: 16px;
|
padding: var(--space-md);
|
||||||
border: 1px solid #d3dad9;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius-lg, 13px);
|
border-radius: var(--radius-lg, 13px);
|
||||||
background: #f8faf9;
|
background: var(--bg-secondary);
|
||||||
|
transition: box-shadow var(--transition-normal);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-size: var(--font-size-lg, 1rem);
|
font-size: var(--font-size-lg, 1rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +24,7 @@
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: var(--radius-full, 999px);
|
border-radius: var(--radius-full, 999px);
|
||||||
background: rgba(73, 118, 113, 0.12);
|
background: rgba(73, 118, 113, 0.12);
|
||||||
color: #3d635f;
|
color: var(--primary-hover);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +38,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
color: #697777;
|
color: var(--text-secondary);
|
||||||
|
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -42,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if (config.wishlist) {
|
@if (config.wishlist) {
|
||||||
<button type="button" class="secondary" [class.active]="inWishlist" (click)="wishlist.emit()">{{ 'productDetails.wishlist' | translate }}</button>
|
<button type="button" class="secondary" [class.active]="inWishlist" [attr.aria-pressed]="inWishlist" (click)="wishlist.emit()">{{ 'productDetails.wishlist' | translate }}</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (config.compare) {
|
@if (config.compare) {
|
||||||
<button type="button" class="secondary" [class.active]="inCompare" (click)="compare.emit()">{{ 'productDetails.compare' | translate }}</button>
|
<button type="button" class="secondary" [class.active]="inCompare" [attr.aria-pressed]="inCompare" (click)="compare.emit()">{{ 'productDetails.compare' | translate }}</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (config.share) {
|
@if (config.share) {
|
||||||
|
|||||||
@@ -7,32 +7,60 @@
|
|||||||
button {
|
button {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #d3dad9;
|
border: 1px solid var(--border-color);
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary {
|
button.primary {
|
||||||
border-color: #497671;
|
border-color: var(--primary-color);
|
||||||
background: #497671;
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
border-color: var(--primary-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary.ghost {
|
button.primary.ghost {
|
||||||
background: #eef4f3;
|
background: var(--accent-color);
|
||||||
color: #2e5e59;
|
color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary.ghost:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.secondary.active {
|
button.secondary.active {
|
||||||
border-color: #497671;
|
border-color: var(--primary-color);
|
||||||
|
color: var(--primary-hover);
|
||||||
box-shadow: 0 0 0 2px rgba(73, 118, 113, 0.18);
|
box-shadow: 0 0 0 2px rgba(73, 118, 113, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 2px solid #497671;
|
outline: 2px solid var(--primary-color);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-size: 1.45rem;
|
font-size: var(--font-size-3xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #697777;
|
color: var(--text-secondary);
|
||||||
line-height: 1.65;
|
line-height: var(--line-height-relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
@if (media.length > 1) {
|
@if (media.length > 1) {
|
||||||
<div class="product-gallery-thumbs">
|
<div class="product-gallery-thumbs">
|
||||||
@for (item of media; track $index) {
|
@for (item of media; track $index) {
|
||||||
<button type="button" class="product-gallery-thumb" [class.active]="selectedIndex === $index" (click)="select($index)">
|
<button type="button" class="product-gallery-thumb" [class.active]="selectedIndex === $index" [attr.aria-current]="selectedIndex === $index ? 'true' : null" [attr.aria-label]="('productDetails.photo' | translate) + ' ' + ($index + 1)" (click)="select($index)">
|
||||||
@if (item.type === 'video') {
|
@if (item.type === 'video') {
|
||||||
<span class="product-gallery-video">▶</span>
|
<span class="product-gallery-video">▶</span>
|
||||||
} @else if (item.type !== 'image') {
|
} @else if (item.type !== 'image') {
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
.product-gallery-main {
|
.product-gallery-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border: 1px solid #d3dad9;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius-lg, 13px);
|
border-radius: var(--radius-lg, 13px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: box-shadow 0.24s ease, border-color 0.24s ease;
|
transition: box-shadow var(--transition-slow), border-color var(--transition-slow);
|
||||||
|
|
||||||
img,
|
img,
|
||||||
video,
|
video,
|
||||||
@@ -39,11 +39,22 @@
|
|||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
border: 1px solid rgba(30, 60, 56, 0.25);
|
border: 1px solid rgba(30, 60, 56, 0.25);
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
border-radius: var(--radius-full, 999px);
|
border-radius: var(--radius-full, 999px);
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:focus-visible {
|
||||||
|
outline: 2px solid var(--primary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-viewer {
|
.document-viewer {
|
||||||
@@ -55,7 +66,7 @@
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,21 +92,27 @@
|
|||||||
.product-gallery-thumb {
|
.product-gallery-thumb {
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
border: 1px solid #d3dad9;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
padding: 0;
|
||||||
|
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: #497671;
|
border-color: var(--primary-color);
|
||||||
box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.18);
|
box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
border-color: #497671;
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--primary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
font-size: clamp(1.8rem, 4vw, 2.8rem);
|
||||||
line-height: 1.08;
|
line-height: 1.08;
|
||||||
}
|
}
|
||||||
@@ -28,19 +28,19 @@
|
|||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
padding: 3px 9px;
|
padding: 3px 9px;
|
||||||
border-radius: var(--radius-full, 999px);
|
border-radius: var(--radius-full, 999px);
|
||||||
background: #497671;
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
font-size: var(--font-size-xs, 0.75rem);
|
font-size: var(--font-size-xs, 0.75rem);
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discount-badge {
|
.discount-badge {
|
||||||
background: #dc2626;
|
background: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-rating {
|
.product-rating {
|
||||||
color: #697777;
|
color: var(--text-secondary);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,35 +50,35 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-size: var(--font-size-4xl, 2rem);
|
font-size: var(--font-size-4xl, 2rem);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.old-price {
|
.old-price {
|
||||||
color: #a1b4b5;
|
color: var(--secondary-color);
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-stock {
|
.product-stock {
|
||||||
color: #697777;
|
color: var(--text-secondary);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
|
|
||||||
&.out {
|
&.out {
|
||||||
color: #b91c1c;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.low {
|
&.low {
|
||||||
color: #b45309;
|
color: var(--warning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.medium {
|
&.medium {
|
||||||
color: #497671;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.high {
|
&.high {
|
||||||
color: #15803d;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,16 +94,28 @@
|
|||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-lg, 13px);
|
border-radius: var(--radius-lg, 13px);
|
||||||
background: #497671;
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
font-size: var(--font-size-lg, 1rem);
|
font-size: var(--font-size-lg, 1rem);
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s ease, background-color 0.2s ease;
|
transition: transform var(--transition-normal), background-color var(--transition-normal), box-shadow var(--transition-normal);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #3d635f;
|
background: var(--primary-hover);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-size: 1.45rem;
|
font-size: var(--font-size-3xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,13 @@
|
|||||||
<div class="variant-options">
|
<div class="variant-options">
|
||||||
@for (option of group.options; track option.value) {
|
@for (option of group.options; track option.value) {
|
||||||
@if (group.key === 'colour') {
|
@if (group.key === 'colour') {
|
||||||
<button type="button" class="colour-swatch" [class.active]="isActive(group.key, option.value)" [style.background-color]="option.value" [attr.aria-label]="option.label || option.value" (click)="onSelect(group.key, option.value)"></button>
|
<button type="button" class="colour-swatch" [class.active]="isActive(group.key, option.value)" [attr.aria-pressed]="isActive(group.key, option.value)" [style.background-color]="option.value" [attr.aria-label]="option.label || option.value" (click)="onSelect(group.key, option.value)">
|
||||||
|
@if (isActive(group.key, option.value)) {
|
||||||
|
<app-icon name="check" [size]="14" />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
<button type="button" class="size-chip" [class.active]="isActive(group.key, option.value)" (click)="onSelect(group.key, option.value)">{{ option.label || option.value }}</button>
|
<button type="button" class="size-chip" [class.active]="isActive(group.key, option.value)" [attr.aria-pressed]="isActive(group.key, option.value)" (click)="onSelect(group.key, option.value)">{{ option.label || option.value }}</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.variant-label {
|
.variant-label {
|
||||||
color: #697777;
|
color: var(--text-secondary);
|
||||||
font-size: var(--font-size-base, 0.875rem);
|
font-size: var(--font-size-base, 0.875rem);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
@@ -25,12 +25,22 @@
|
|||||||
.colour-swatch {
|
.colour-swatch {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
border: 2px solid #d3dad9;
|
border: 2px solid var(--border-color);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--bg-primary);
|
||||||
|
transition: transform var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: #1e3c38;
|
border-color: var(--text-primary);
|
||||||
box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.2);
|
box-shadow: 0 0 0 3px rgba(73, 118, 113, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: 2px solid #1e3c38;
|
outline: 2px solid var(--text-primary);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,17 +58,23 @@
|
|||||||
.size-chip {
|
.size-chip {
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
border: 1px solid #d3dad9;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--radius-full, 999px);
|
border-radius: var(--radius-full, 999px);
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
color: #1e3c38;
|
color: var(--text-primary);
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-color: #497671;
|
border-color: var(--primary-color);
|
||||||
background: #497671;
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.readonly {
|
&.readonly {
|
||||||
@@ -68,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: 2px solid #497671;
|
outline: 2px solid var(--primary-color);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
||||||
|
import { IconComponent } from '../../../../../shared/ui/icon/icon.component';
|
||||||
|
|
||||||
export interface VariantOption {
|
export interface VariantOption {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -15,7 +16,7 @@ export interface VariantOptionGroup {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-product-variant-selector',
|
selector: 'app-product-variant-selector',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe],
|
imports: [TranslatePipe, IconComponent],
|
||||||
templateUrl: './variant-selector.component.html',
|
templateUrl: './variant-selector.component.html',
|
||||||
styleUrls: ['./variant-selector.component.scss'],
|
styleUrls: ['./variant-selector.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="tab-btn"
|
class="tab-btn"
|
||||||
|
role="tab"
|
||||||
[class.active]="activeKey === tab.key"
|
[class.active]="activeKey === tab.key"
|
||||||
|
[attr.aria-selected]="activeKey === tab.key"
|
||||||
[disabled]="tab.enabled === false"
|
[disabled]="tab.enabled === false"
|
||||||
(click)="selectTab(tab)">
|
(click)="selectTab(tab)">
|
||||||
<span>{{ tab.label }}</span>
|
<span>{{ tab.label }}</span>
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.accepted-pill {
|
.accepted-pill {
|
||||||
background: #e9f9ec;
|
background: color-mix(in srgb, var(--success-color) 16%, white);
|
||||||
color: #18723a;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ textarea {
|
|||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus-visible {
|
||||||
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.check {
|
.check {
|
||||||
@@ -30,11 +35,22 @@ button {
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:not(:disabled) {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active:not(:disabled) {
|
||||||
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<section class="question-list section">
|
<section class="question-list section">
|
||||||
@if (allowSubmission) {
|
@if (allowSubmission) {
|
||||||
<button type="button" class="ask-btn" (click)="askFormVisible.set(!askFormVisible())">
|
<button type="button" class="ask-btn" [attr.aria-expanded]="askFormVisible()" (click)="askFormVisible.set(!askFormVisible())">
|
||||||
@if (askFormVisible()) { {{ 'productDetails.hideQuestionForm' | translate }} } @else { {{ 'productDetails.askQuestion' | translate }} }
|
@if (askFormVisible()) { {{ 'productDetails.hideQuestionForm' | translate }} } @else { {{ 'productDetails.askQuestion' | translate }} }
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="empty">{{ 'productDetails.questionsEmpty' | translate }}</p>
|
<app-empty-state class="empty" [title]="'productDetails.questionsEmpty' | translate" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (result && totalPages > 1) {
|
@if (result && totalPages > 1) {
|
||||||
|
|||||||
@@ -51,6 +51,17 @@
|
|||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: border-color var(--transition-fast), transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager button:hover:not(:disabled) {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pager button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-list {
|
.skeleton-list {
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { EngagementListResult, Question, SubmitQuestionInput } from '../../../..
|
|||||||
import { TranslatePipe } from '../../../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../../../i18n/translate.pipe';
|
||||||
import { QuestionCardComponent } from '../question-card/question-card.component';
|
import { QuestionCardComponent } from '../question-card/question-card.component';
|
||||||
import { QuestionFormComponent } from '../question-form/question-form.component';
|
import { QuestionFormComponent } from '../question-form/question-form.component';
|
||||||
|
import { EmptyStateComponent } from '../../../../../../shared/ui/empty-state/empty-state.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-question-list',
|
selector: 'app-question-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [QuestionCardComponent, QuestionFormComponent, TranslatePipe],
|
imports: [QuestionCardComponent, QuestionFormComponent, TranslatePipe, EmptyStateComponent],
|
||||||
templateUrl: './question-list.component.html',
|
templateUrl: './question-list.component.html',
|
||||||
styleUrls: ['./question-list.component.scss'],
|
styleUrls: ['./question-list.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ textarea {
|
|||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus-visible,
|
||||||
|
textarea:focus-visible {
|
||||||
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -48,12 +54,12 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
.success {
|
||||||
color: #166534;
|
color: var(--success-color);
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: #991b1b;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -62,10 +68,19 @@ button {
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--bg-primary);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), transform var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:not(:disabled) {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active:not(:disabled) {
|
||||||
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="empty">{{ 'productDetails.reviewsEmpty' | translate }}</p>
|
<app-empty-state class="empty" [title]="'productDetails.reviewsEmpty' | translate" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (result && totalPages > 1 && mode === 'pages') {
|
@if (result && totalPages > 1 && mode === 'pages') {
|
||||||
|
|||||||
@@ -35,6 +35,11 @@
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pager button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.load-more-wrap {
|
.load-more-wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
@@ -50,6 +55,17 @@
|
|||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more:hover:not(:disabled) {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-more-wrap small {
|
.load-more-wrap small {
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import { TranslatePipe } from '../../../../../../i18n/translate.pipe';
|
|||||||
import { RatingSummaryComponent } from '../rating-summary/rating-summary.component';
|
import { RatingSummaryComponent } from '../rating-summary/rating-summary.component';
|
||||||
import { ReviewCardComponent } from '../review-card/review-card.component';
|
import { ReviewCardComponent } from '../review-card/review-card.component';
|
||||||
import { ReviewFormComponent } from '../review-form/review-form.component';
|
import { ReviewFormComponent } from '../review-form/review-form.component';
|
||||||
|
import { EmptyStateComponent } from '../../../../../../shared/ui/empty-state/empty-state.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-review-list',
|
selector: 'app-review-list',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RatingSummaryComponent, ReviewCardComponent, ReviewFormComponent, TranslatePipe],
|
imports: [RatingSummaryComponent, ReviewCardComponent, ReviewFormComponent, TranslatePipe, EmptyStateComponent],
|
||||||
templateUrl: './review-list.component.html',
|
templateUrl: './review-list.component.html',
|
||||||
styleUrls: ['./review-list.component.scss'],
|
styleUrls: ['./review-list.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
class="star-btn"
|
class="star-btn"
|
||||||
[class.selected]="star <= rating"
|
[class.selected]="star <= rating"
|
||||||
[attr.aria-checked]="star === rating"
|
[attr.aria-checked]="star === rating"
|
||||||
|
[attr.aria-label]="star + ' ' + ('productDetails.starsLabel' | translate)"
|
||||||
role="radio"
|
role="radio"
|
||||||
(click)="ratingChange.emit(star)">
|
(click)="ratingChange.emit(star)">
|
||||||
★
|
★
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<table class="compare-table">
|
<table class="compare-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'ux.compareAttribute' | translate }}</th>
|
<th scope="col">{{ 'ux.compareAttribute' | translate }}</th>
|
||||||
@for (product of products; track product.itemID) {
|
@for (product of products; track product.itemID) {
|
||||||
<th>
|
<th scope="col">
|
||||||
<div class="compare-product-title">{{ product.name }}</div>
|
<div class="compare-product-title">{{ product.name }}</div>
|
||||||
</th>
|
</th>
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@for (row of rows(); track row.key) {
|
@for (row of rows(); track row.key) {
|
||||||
<tr [class.compare-different]="isDifferentRow(row)">
|
<tr [class.compare-different]="isDifferentRow(row)">
|
||||||
<th>{{ row.label }}</th>
|
<th scope="row">{{ row.label }}</th>
|
||||||
@for (value of row.values; track $index) {
|
@for (value of row.values; track $index) {
|
||||||
<td>{{ value }}</td>
|
<td>{{ value }}</td>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,18 @@
|
|||||||
background: color-mix(in srgb, var(--bg-secondary) 60%, white);
|
background: color-mix(in srgb, var(--bg-secondary) 60%, white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compare-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compare-table tbody th {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.compare-product-title {
|
.compare-product-title {
|
||||||
font-size: var(--font-size-md, 0.9375rem);
|
font-size: var(--font-size-md, 0.9375rem);
|
||||||
font-weight: var(--font-weight-bold, 700);
|
font-weight: var(--font-weight-bold, 700);
|
||||||
|
|||||||
@@ -50,6 +50,12 @@
|
|||||||
border-radius: var(--radius-full, 999px);
|
border-radius: var(--radius-full, 999px);
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
|
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compare-product-chip:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.compare-product-chip a {
|
.compare-product-chip a {
|
||||||
@@ -58,11 +64,33 @@
|
|||||||
font-weight: var(--font-weight-semibold, 600);
|
font-weight: var(--font-weight-semibold, 600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compare-product-chip a:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.compare-product-chip button {
|
.compare-product-chip button {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background var(--transition-fast), color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compare-product-chip button:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compare-product-chip button:focus-visible {
|
||||||
|
outline: 2px solid var(--primary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compare-empty {
|
.compare-empty {
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ export const en: Translations = {
|
|||||||
mediaAria: 'Product media',
|
mediaAria: 'Product media',
|
||||||
ratingStarsAria: 'Rating stars',
|
ratingStarsAria: 'Rating stars',
|
||||||
selectRatingAria: 'Select rating',
|
selectRatingAria: 'Select rating',
|
||||||
|
starsLabel: 'stars',
|
||||||
reviewsEmpty: 'No reviews yet.',
|
reviewsEmpty: 'No reviews yet.',
|
||||||
questionsEmpty: 'No questions yet.',
|
questionsEmpty: 'No questions yet.',
|
||||||
askQuestion: 'Ask question',
|
askQuestion: 'Ask question',
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ export const hy: Translations = {
|
|||||||
mediaAria: 'Ապրանքի մեդիա',
|
mediaAria: 'Ապրանքի մեդիա',
|
||||||
ratingStarsAria: 'Աստղային վարկանիշ',
|
ratingStarsAria: 'Աստղային վարկանիշ',
|
||||||
selectRatingAria: 'Ընտրեք գնահատականը',
|
selectRatingAria: 'Ընտրեք գնահատականը',
|
||||||
|
starsLabel: 'աստղ',
|
||||||
reviewsEmpty: 'Կարծիքներ դեռ չկան։',
|
reviewsEmpty: 'Կարծիքներ դեռ չկան։',
|
||||||
questionsEmpty: 'Հարցեր դեռ չկան։',
|
questionsEmpty: 'Հարցեր դեռ չկան։',
|
||||||
askQuestion: 'Տալ հարց',
|
askQuestion: 'Տալ հարց',
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ export const ru: Translations = {
|
|||||||
mediaAria: 'Медиа товара',
|
mediaAria: 'Медиа товара',
|
||||||
ratingStarsAria: 'Рейтинг в звездах',
|
ratingStarsAria: 'Рейтинг в звездах',
|
||||||
selectRatingAria: 'Выберите оценку',
|
selectRatingAria: 'Выберите оценку',
|
||||||
|
starsLabel: 'звёзд',
|
||||||
reviewsEmpty: 'Пока нет отзывов.',
|
reviewsEmpty: 'Пока нет отзывов.',
|
||||||
questionsEmpty: 'Пока нет вопросов.',
|
questionsEmpty: 'Пока нет вопросов.',
|
||||||
askQuestion: 'Задать вопрос',
|
askQuestion: 'Задать вопрос',
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ export interface Translations {
|
|||||||
mediaAria: string;
|
mediaAria: string;
|
||||||
ratingStarsAria: string;
|
ratingStarsAria: string;
|
||||||
selectRatingAria: string;
|
selectRatingAria: string;
|
||||||
|
starsLabel: string;
|
||||||
reviewsEmpty: string;
|
reviewsEmpty: string;
|
||||||
questionsEmpty: string;
|
questionsEmpty: string;
|
||||||
askQuestion: string;
|
askQuestion: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user