Enhanced store filters with a refined price range slider, accommodating category-specific min/max prices and dynamic updates. Optimized reactivity for product filtering by consolidating watchers into a unified approach. Adjusted UI elements for consistent spacing and modern icon usage in filters. - Added `minMaxPrices` and debounce logic to improve price filtering performance. - Updated filter UI with collapsible headers and better styling for usability. - Refactored multiple watchers into a single handler for better efficiency. - Introduced global constants for currency symbol usage. Breaking Changes: Components relying on price filters must adapt to new props and event names (`filterMinPrice`, `filterMaxPrice`). Styles may require alignment with refined SCSS rules for filters.
136 lines
No EOL
2.7 KiB
Vue
136 lines
No EOL
2.7 KiB
Vue
<template>
|
||
<div class="top" :class="[{ filters: isFilters }]">
|
||
<div class="top__sorting">
|
||
<p>{{ t('store.sorting') }}</p>
|
||
<client-only>
|
||
<el-select
|
||
v-model="select"
|
||
size="large"
|
||
style="width: 240px"
|
||
>
|
||
<el-option
|
||
v-for="item in options"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</client-only>
|
||
</div>
|
||
<div class="top__view">
|
||
<button
|
||
class="top__view-button"
|
||
:class="{ active: productView === 'list' }"
|
||
@click="setView('list')"
|
||
>
|
||
<icon name="material-symbols:view-list-sharp" size="16" />
|
||
</button>
|
||
<button
|
||
class="top__view-button"
|
||
:class="{ active: productView === 'grid' }"
|
||
@click="setView('grid')"
|
||
>
|
||
<icon name="material-symbols:grid-view" size="16" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const {t} = useI18n();
|
||
const { $appHelpers } = useNuxtApp();
|
||
|
||
const props = defineProps<{
|
||
modelValue: string;
|
||
isFilters: boolean;
|
||
}>();
|
||
const emit = defineEmits<{
|
||
(e: 'update:modelValue', value: string): void;
|
||
(e: 'toggle-filter'): void;
|
||
}>();
|
||
|
||
const productView = useCookie($appHelpers.COOKIES_PRODUCT_VIEW_KEY as string);
|
||
function setView(view: 'list' | 'grid') {
|
||
productView.value = view;
|
||
}
|
||
|
||
const select = ref(props.modelValue || 'created');
|
||
const options = [
|
||
{
|
||
value: 'created',
|
||
label: 'New',
|
||
},
|
||
{
|
||
value: 'rating',
|
||
label: 'Rating',
|
||
},
|
||
{
|
||
value: 'price',
|
||
label: 'Сheap first',
|
||
},
|
||
{
|
||
value: '-price',
|
||
label: 'Expensive first',
|
||
}
|
||
];
|
||
|
||
watch(select, value => {
|
||
emit('update:modelValue', value);
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.top {
|
||
width: 100%;
|
||
position: relative;
|
||
z-index: 2;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 40px;
|
||
border-radius: $default_border_radius;
|
||
border: 1px solid $border;
|
||
padding: 16px;
|
||
|
||
&.filters {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
&__sorting {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
|
||
& p {
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
letter-spacing: -0.5px;
|
||
color: $text;
|
||
}
|
||
}
|
||
|
||
&__view {
|
||
display: flex;
|
||
align-items: center;
|
||
border-radius: $less_border_radius;
|
||
border: 1px solid $border;
|
||
|
||
&-button {
|
||
cursor: pointer;
|
||
background-color: $main;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 10px;
|
||
color: $primary_dark;
|
||
|
||
@include hover {
|
||
background-color: $main_hover;
|
||
}
|
||
|
||
&.active {
|
||
background-color: $link_secondary;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |