Skip to content

Commit 1f53881

Browse files
committed
ui/ux: add space - between producers, catalog and pagination; price inputs One row -> two rows; add transition to show/hide categories
1 parent b2470cc commit 1f53881

3 files changed

Lines changed: 124 additions & 12 deletions

File tree

app/components/catalog/CatalogFiltersPanel.vue

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const priceToValue = computed({
7474
})
7575
const isInStock = ref(true)
7676
const categoriesExpanded = ref(false)
77+
const isPriceRangeDragging = ref(false)
7778
7879
watch(priceFromValue, (newValue) => {
7980
const clampedValue = clampPriceValue(newValue)
@@ -101,14 +102,21 @@ watch(priceToValue, (newValue) => {
101102
}
102103
})
103104
104-
const visibleCategories = computed(() => {
105-
return categoriesExpanded.value ? categories.value : categories.value.slice(0, 5)
106-
})
105+
const primaryCategories = computed(() => categories.value.slice(0, 5))
106+
const additionalCategories = computed(() => categories.value.slice(5))
107107
108108
const hasMoreCategories = computed(() => {
109109
return categories.value.length > 5
110110
})
111111
112+
const startPriceRangeDrag = () => {
113+
isPriceRangeDragging.value = true
114+
}
115+
116+
const stopPriceRangeDrag = () => {
117+
isPriceRangeDragging.value = false
118+
}
119+
112120
const pricePresets = [50000, 100000, 200000, 300000, 500000, 800000, 1000000, 1500000]
113121
114122
const getIcon = (category: CategoryOption) => {
@@ -123,20 +131,20 @@ const classes = {
123131
resetButton: 'filter-panel-reset',
124132
section: 'space-y-4',
125133
sectionTitle: 'text-sm font-semibold tracking-tight filter-panel-heading',
126-
sectionList: 'space-y-3',
134+
sectionList: 'space-y-2',
127135
categoryList: 'space-y-2',
128136
categoryRow: 'flex items-center gap-2 px-2 py-2 cursor-pointer transition filter-panel-category-row w-full',
129137
categoryRowActive: 'filter-panel-category-row-active',
130138
categoryIcon: 'text-base shrink-0',
131139
categoryLabel: 'text-sm filter-panel-heading',
132140
categoryCount: 'text-xs filter-panel-muted ml-auto',
133141
expandButton: 'w-full mt-2 px-3 py-2 text-sm font-semibold text-left transition filter-panel-expand-btn',
134-
brandRow: 'flex items-center justify-between gap-3 px-2 py-3 cursor-pointer transition filter-panel-brand-row',
142+
brandRow: 'flex items-center justify-between gap-3 px-2.5 py-2.5 cursor-pointer transition filter-panel-brand-row',
135143
brandRowActive: 'filter-panel-brand-row-active',
136144
brandLabel: 'text-sm font-semibold filter-panel-heading',
137145
brandAction: 'hidden text-xs filter-panel-muted sm:inline',
138146
priceSection: 'space-y-3',
139-
priceInputs: 'grid grid-cols-2 gap-3',
147+
priceInputs: 'grid grid-cols-1 gap-2.5',
140148
stockRow: 'flex items-center justify-between',
141149
stockCheckbox: 'flex items-center gap-3',
142150
stockCount: 'text-sm font-semibold filter-panel-heading',
@@ -189,7 +197,7 @@ const classes = {
189197
<h3 id="catalog-category-title" :class="classes.sectionTitle">Категории</h3>
190198
<div :class="classes.categoryList">
191199
<button
192-
v-for="category in visibleCategories"
200+
v-for="category in primaryCategories"
193201
:key="category.slug"
194202
type="button"
195203
:class="[
@@ -203,6 +211,28 @@ const classes = {
203211
<span :class="classes.categoryLabel">{{ category.label }}</span>
204212
<span :class="classes.categoryCount">{{ category.count ?? 0 }}</span>
205213
</button>
214+
<TransitionGroup
215+
v-if="hasMoreCategories"
216+
name="filter-category-reveal"
217+
tag="div"
218+
class="filter-panel-category-extra"
219+
>
220+
<button
221+
v-for="category in categoriesExpanded ? additionalCategories : []"
222+
:key="category.slug"
223+
type="button"
224+
:class="[
225+
classes.categoryRow,
226+
selectedCategorySlugs.includes(category.slug) && classes.categoryRowActive
227+
]"
228+
:aria-pressed="selectedCategorySlugs.includes(category.slug)"
229+
@click="$emit('toggleCategory', category.slug)"
230+
>
231+
<span :class="classes.categoryIcon">{{ getIcon(category) }}</span>
232+
<span :class="classes.categoryLabel">{{ category.label }}</span>
233+
<span :class="classes.categoryCount">{{ category.count ?? 0 }}</span>
234+
</button>
235+
</TransitionGroup>
206236
</div>
207237
<button
208238
v-if="hasMoreCategories"
@@ -217,7 +247,10 @@ const classes = {
217247
<section aria-labelledby="catalog-price-title" :class="classes.section">
218248
<h3 id="catalog-price-title" :class="classes.sectionTitle">Цена</h3>
219249
<div :class="classes.priceSection">
220-
<div class="range-slider-container">
250+
<div
251+
class="range-slider-container"
252+
:class="{ 'is-dragging': isPriceRangeDragging }"
253+
>
221254
<input
222255
v-model.number="priceFromValue"
223256
type="range"
@@ -226,6 +259,10 @@ const classes = {
226259
step="1000"
227260
class="range-slider range-slider-from"
228261
aria-label="Минимальная цена"
262+
@pointerdown="startPriceRangeDrag"
263+
@pointerup="stopPriceRangeDrag"
264+
@pointercancel="stopPriceRangeDrag"
265+
@blur="stopPriceRangeDrag"
229266
>
230267
<input
231268
v-model.number="priceToValue"
@@ -235,6 +272,10 @@ const classes = {
235272
step="1000"
236273
class="range-slider range-slider-to"
237274
aria-label="Максимальная цена"
275+
@pointerdown="startPriceRangeDrag"
276+
@pointerup="stopPriceRangeDrag"
277+
@pointercancel="stopPriceRangeDrag"
278+
@blur="stopPriceRangeDrag"
238279
>
239280
<div class="range-track">
240281
<div
@@ -357,18 +398,49 @@ const classes = {
357398
color: rgba(var(--color-text-rgb), 0.55);
358399
}
359400
360-
.filter-panel-brand-row,
401+
.filter-panel-brand-row {
402+
border: 1px solid transparent;
403+
border-radius: 1rem;
404+
background: transparent;
405+
min-height: 3.45rem;
406+
outline: none;
407+
transition:
408+
background-color 190ms ease-out,
409+
border-color 190ms ease-out,
410+
box-shadow 190ms ease-out,
411+
color 190ms ease-out;
412+
}
413+
361414
.filter-panel-category-row {
362415
background: transparent;
363416
border: none;
364417
}
365418
366419
.filter-panel-brand-row:hover,
420+
.filter-panel-brand-row:focus-visible {
421+
border-color: rgba(var(--color-border-rgb), 0.18);
422+
background: rgba(var(--color-text-rgb), 0.055);
423+
box-shadow:
424+
0 10px 24px rgba(0, 0, 0, 0.08),
425+
inset 0 1px 0 rgba(255, 255, 255, 0.08);
426+
}
427+
428+
.filter-panel-brand-row:focus-visible {
429+
box-shadow:
430+
0 10px 24px rgba(0, 0, 0, 0.08),
431+
0 0 0 3px rgba(var(--color-primary-rgb), 0.2);
432+
}
433+
367434
.filter-panel-category-row:hover {
368435
background: transparent;
369436
}
370437
371-
.filter-panel-brand-row-active,
438+
.filter-panel-brand-row-active {
439+
border-color: rgba(var(--color-primary-rgb), 0.2);
440+
background: rgba(var(--color-primary-rgb), 0.075);
441+
color: var(--color-primary);
442+
}
443+
372444
.filter-panel-category-row-active {
373445
background: transparent;
374446
color: var(--color-primary);
@@ -388,6 +460,36 @@ const classes = {
388460
background: rgba(var(--color-text-rgb), 0.04);
389461
}
390462
463+
.filter-panel-category-extra {
464+
display: grid;
465+
gap: 0.5rem;
466+
overflow: hidden;
467+
}
468+
469+
.filter-category-reveal-enter-active,
470+
.filter-category-reveal-leave-active {
471+
transition:
472+
opacity 190ms ease-out,
473+
transform 190ms ease-out,
474+
max-height 220ms ease-out,
475+
margin 220ms ease-out;
476+
}
477+
478+
.filter-category-reveal-enter-from,
479+
.filter-category-reveal-leave-to {
480+
max-height: 0;
481+
margin-top: -0.35rem;
482+
opacity: 0;
483+
transform: translateY(-0.25rem);
484+
}
485+
486+
.filter-category-reveal-enter-to,
487+
.filter-category-reveal-leave-from {
488+
max-height: 3rem;
489+
opacity: 1;
490+
transform: translateY(0);
491+
}
492+
391493
/* Dual range slider */
392494
.range-slider-container {
393495
position: relative;
@@ -418,6 +520,10 @@ const classes = {
418520
transition: left 180ms ease-out, right 180ms ease-out;
419521
}
420522
523+
.range-slider-container.is-dragging .range-fill {
524+
transition: none;
525+
}
526+
421527
.range-slider {
422528
position: absolute;
423529
width: 100%;
@@ -545,11 +651,12 @@ const classes = {
545651
546652
.filter-panel-price-input-left {
547653
padding-left: 1.7rem;
654+
padding-right: 0.9rem;
548655
}
549656
550657
.filter-panel-price-input-right {
551658
padding-left: 1.5rem;
552-
padding-right: 2.65rem;
659+
padding-right: 3rem;
553660
}
554661
555662
.filter-panel-price-input:focus {

app/pages/products/[id].vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ useSeoMeta({
355355
<div class="flex min-w-0 flex-col gap-6 lg:gap-8">
356356
<div class="flex flex-col gap-2">
357357
<div class="text-xs font-bold uppercase tracking-widest text-white/40">{{ getCategoryLabel(product.category) }}</div>
358-
<h1 class="text-3xl font-heading font-bold sm:text-4xl lg:text-5xl">{{ product.title }}</h1>
358+
<h1 class="text-3xl font-heading font-bold sm:text-4xl lg:text-3xl">{{ product.title }}</h1>
359359
<div class="mt-2 flex flex-wrap items-center gap-3 sm:gap-4">
360360
<span class="text-2xl font-bold text-secondary sm:text-3xl">{{ formatPriceRub(product.price) }}</span>
361361
</div>

app/pages/products/index.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ useSeoMeta({
733733

734734
<AppPagination
735735
v-if="totalPages > 1 && products.length"
736+
class="catalog-pagination"
736737
:page="currentPage"
737738
:total-pages="totalPages"
738739
@change="handlePageChange"
@@ -853,6 +854,10 @@ useSeoMeta({
853854
min-height: clamp(24rem, 54vw, 48rem);
854855
}
855856
857+
.catalog-pagination {
858+
margin-top: clamp(1.75rem, 4vw, 3.5rem);
859+
}
860+
856861
.catalog-results-overlay {
857862
position: absolute;
858863
inset: -0.75rem;

0 commit comments

Comments
 (0)