Skip to content

Commit 19a3653

Browse files
authored
refactor: move desktop category filters from SfSidebar to the main layout column (#884)
refactor: change removable filters behavior - removable filters will be now updated only on the explict filter selection action - actions are now sticky so it will be easier to apply filters refactor: fix CR issues M2-425
1 parent 1657629 commit 19a3653

File tree

13 files changed

+446
-508
lines changed

13 files changed

+446
-508
lines changed

packages/theme/lang/de.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,5 @@ export default {
260260
"Show less": "Zeige weniger",
261261
"Yes": "Ja",
262262
"No": "Nein",
263+
"Apply filters": "Filter anwenden"
263264
};

packages/theme/lang/en.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,5 @@ export default {
258258
"Show less": "Show less",
259259
"Yes": "Yes",
260260
"No": "No",
261+
"Apply filters": "Apply filters"
261262
};

packages/theme/modules/catalog/category/components/filters/CategoryFilters.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.filters {
2+
position: relative;
23
&__title {
34
--heading-title-font-size: var(--font-size--xl);
45
margin: var(--spacer-xl) 0 var(--spacer-base) 0;
@@ -49,6 +50,11 @@
4950

5051
&__buttons {
5152
margin: var(--spacer-sm) 0;
53+
@include for-desktop {
54+
background-color: #fff;
55+
position: sticky;
56+
bottom: 0;
57+
}
5258
}
5359

5460
&__button-clear {
@@ -57,3 +63,10 @@
5763
margin: var(--spacer-xs) 0 0 0;
5864
}
5965
}
66+
.heading__title {
67+
border: 1px solid var(--c-light);
68+
border-width: 1px 0 0 0;
69+
padding: var(--spacer-xs) 0;
70+
font-weight: normal;
71+
margin-bottom: var(--spacer-sm);
72+
}

packages/theme/modules/catalog/category/components/filters/CategoryFilters.vue

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
<template>
2-
<SfSidebar
3-
:visible="isVisible"
4-
class="sidebar-filters"
5-
title="Filters"
6-
@close="$emit('close')"
7-
>
8-
<div class="filters desktop-only">
2+
<div>
3+
<h4 class="heading__title h4 desktop-only">
4+
{{ $t('Filters') }}
5+
</h4>
6+
<div v-if="isLoading">
7+
<div
8+
v-for="n in 3"
9+
:key="n"
10+
>
11+
<SkeletonLoader
12+
class="filters__title sf-heading--left sf-heading"
13+
height="20px"
14+
width="85%"
15+
/>
16+
<SkeletonLoader width="50%" />
17+
<SkeletonLoader
18+
width="62%"
19+
/>
20+
<SkeletonLoader
21+
width="40%"
22+
height="10px"
23+
/>
24+
</div>
25+
</div>
26+
<div
27+
v-else
28+
class="filters desktop-only"
29+
>
930
<SelectedFilters
31+
:removable-filters="removableFilters"
1032
@removeFilter="doRemoveFilter($event)"
1133
/>
1234
<hr class="sf-divider">
@@ -26,36 +48,12 @@
2648
@selectFilter="selectFilter(filter, $event)"
2749
/>
2850
</div>
29-
</div>
30-
<SfAccordion class="filters smartphone-only">
31-
<SelectedFilters
32-
@removeFilter="doRemoveFilter($event)"
33-
/>
34-
<hr class="sf-divider">
35-
<div
36-
v-for="(filter, i) in filters"
37-
:key="i"
38-
>
39-
<SfAccordionItem
40-
:key="`filter-title-${filter.attribute_code}`"
41-
:header="filter.label"
42-
class="filters__accordion-item"
43-
>
44-
<component
45-
:is="getFilterConfig(filter.attribute_code).component"
46-
:filter="filter"
47-
@selectFilter="selectFilter(filter, $event)"
48-
/>
49-
</SfAccordionItem>
50-
</div>
51-
</SfAccordion>
52-
<template #content-bottom>
5351
<div class="filters__buttons">
5452
<SfButton
5553
class="sf-button--full-width"
5654
@click="doApplyFilters"
5755
>
58-
{{ $t('Done') }}
56+
{{ $t('Apply filters') }}
5957
</SfButton>
6058
<SfButton
6159
class="sf-button--full-width filters__button-clear"
@@ -64,8 +62,53 @@
6462
{{ $t('Clear all') }}
6563
</SfButton>
6664
</div>
67-
</template>
68-
</SfSidebar>
65+
</div>
66+
<SfSidebar
67+
:visible="isVisible"
68+
class="sidebar-filters smartphone-only"
69+
title="Filters"
70+
@close="$emit('close')"
71+
>
72+
<SfAccordion class="filters smartphone-only">
73+
<SelectedFilters
74+
@removeFilter="doRemoveFilter($event)"
75+
/>
76+
<hr class="sf-divider">
77+
<div
78+
v-for="(filter, i) in filters"
79+
:key="i"
80+
>
81+
<SfAccordionItem
82+
:key="`filter-title-${filter.attribute_code}`"
83+
:header="filter.label"
84+
class="filters__accordion-item"
85+
>
86+
<component
87+
:is="getFilterConfig(filter.attribute_code).component"
88+
:filter="filter"
89+
@selectFilter="selectFilter(filter, $event)"
90+
/>
91+
</SfAccordionItem>
92+
</div>
93+
</SfAccordion>
94+
<template #content-bottom>
95+
<div class="filters__buttons">
96+
<SfButton
97+
class="sf-button--full-width"
98+
@click="doApplyFilters"
99+
>
100+
{{ $t('Apply filters') }}
101+
</SfButton>
102+
<SfButton
103+
class="sf-button--full-width filters__button-clear"
104+
@click="doClearFilters"
105+
>
106+
{{ $t('Clear all') }}
107+
</SfButton>
108+
</div>
109+
</template>
110+
</SfSidebar>
111+
</div>
69112
</template>
70113
<script lang="ts">
71114
import {
@@ -80,6 +123,7 @@ import {
80123
SfSidebar,
81124
} from '@storefront-ui/vue';
82125
126+
import SkeletonLoader from '~/components/SkeletonLoader/index.vue';
83127
import { useUiHelpers } from '~/composables';
84128
import { getFilterConfig, getDisabledFilters } from '~/modules/catalog/category/config/FiltersConfig';
85129
import SelectedFilters from '~/modules/catalog/category/components/filters/FiltersSidebar/SelectedFilters.vue';
@@ -98,6 +142,7 @@ export default defineComponent({
98142
name: 'CategoryFilters',
99143
components: {
100144
SelectedFilters,
145+
SkeletonLoader,
101146
CheckboxType: () => import('~/modules/catalog/category/components/filters/renderer/CheckboxType.vue'),
102147
SwatchColorType: () => import('~/modules/catalog/category/components/filters/renderer/SwatchColorType.vue'),
103148
RadioType: () => import('~/modules/catalog/category/components/filters/renderer/RadioType.vue'),
@@ -121,35 +166,49 @@ export default defineComponent({
121166
},
122167
setup(props, { emit }) {
123168
const { changeFilters, clearFilters } = useUiHelpers();
169+
const removableFilters = ref([]);
170+
const filters = ref<Aggregation[]>([]);
171+
const isLoading = ref(true);
172+
124173
const {
125-
selectedFilters, selectFilter, removeFilter, isFilterSelected,
174+
selectedFilters, selectFilter, removeFilter, isFilterSelected, getRemovableFilters,
126175
} = useFilters();
127176
177+
const updateRemovableFilters = () => {
178+
removableFilters.value = getRemovableFilters(filters.value, selectedFilters.value);
179+
};
180+
128181
const doApplyFilters = () => {
129182
changeFilters(selectedFilters.value, false);
183+
updateRemovableFilters();
184+
if (window?.scroll) {
185+
window.scroll(0, 0);
186+
}
130187
emit('reloadProducts');
131188
emit('close');
132189
};
133190
134191
const doRemoveFilter = ({ id, value }: { id: string, value: string }) => {
135192
removeFilter(id, value);
136193
changeFilters(selectedFilters.value, false);
194+
updateRemovableFilters();
137195
emit('reloadProducts');
138196
emit('close');
139197
};
140198
141199
const doClearFilters = () => {
142200
clearFilters(false);
143201
selectedFilters.value = {};
202+
updateRemovableFilters();
144203
emit('reloadProducts');
145204
emit('close');
146205
};
147206
148-
const filters = ref<Aggregation[]>([]);
149-
150207
onMounted(async () => {
151208
const loadedFilters = await getProductFilterByCategoryCommand.execute({ eq: props.catUid });
152209
filters.value = loadedFilters.filter((filter) => !getDisabledFilters().includes(filter.attribute_code));
210+
updateRemovableFilters();
211+
isLoading.value = false;
153212
});
154213
155214
provide('UseFiltersProvider', { isFilterSelected, selectedFilters, filters });
@@ -162,6 +221,8 @@ export default defineComponent({
162221
getFilterConfig,
163222
selectedFilters,
164223
filters,
224+
isLoading,
225+
removableFilters,
165226
};
166227
},
167228
});
Lines changed: 35 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,43 @@
11
<template>
2-
<div>
3-
<div class="sf-heading filters__title">
4-
<h2 class="sf-heading__title h2">
5-
{{ $t('Filters') }}
6-
</h2>
7-
</div>
8-
<div class="selected-filters-wrapper">
9-
<SfBadge
10-
v-for="(filter, key) in removableFilters"
11-
:key="key"
12-
class="selected-filter"
2+
<div class="selected-filters-wrapper">
3+
<SfBadge
4+
v-for="(filter, key) in removableFilters"
5+
:key="key"
6+
class="selected-filter"
7+
>
8+
<span class="selected-filter__label">
9+
{{ filter.label }}
10+
</span>
11+
<button
12+
type="button"
13+
class="selected-filter__remove"
14+
@click.prevent="$emit('removeFilter', {id: filter.id, value: filter.value})"
1315
>
14-
<a
15-
href="#"
16-
class="selected-filter__remove"
17-
@click.prevent="$emit('removeFilter', {id: filter.id, value: filter.value})"
18-
>
19-
<SfIcon
20-
class="sf-cross__icon"
21-
icon="cross"
22-
size="20px"
23-
color="white"
24-
viewBox="0 0 24 12"
25-
/>
26-
</a>
27-
<span class="selected-filter__label">
28-
{{ filter.label }}
29-
</span>
30-
</SfBadge>
31-
</div>
16+
<SfIcon
17+
class="sf-cross__icon"
18+
icon="cross"
19+
size="20px"
20+
color="white"
21+
viewBox="0 0 24 12"
22+
/>
23+
</button>
24+
</SfBadge>
3225
</div>
3326
</template>
3427
<script lang="ts">
35-
import { computed, defineComponent, inject } from '@nuxtjs/composition-api';
28+
import { defineComponent } from '@nuxtjs/composition-api';
3629
import { SfBadge, SfIcon } from '@storefront-ui/vue';
37-
import type { UseFiltersProviderInterface } from '~/modules/catalog/category/components/filters/CategoryFilters.vue';
3830
3931
export default defineComponent({
4032
components: {
4133
SfBadge,
4234
SfIcon,
4335
},
44-
setup() {
45-
const { selectedFilters, filters }: UseFiltersProviderInterface = inject('UseFiltersProvider');
46-
47-
const removableFilters = computed(() => {
48-
const result = [];
49-
50-
filters.value.forEach((filter) => {
51-
filter.options.forEach((option) => {
52-
if ((selectedFilters.value[filter.attribute_code] ?? []).includes(option.value)) {
53-
result.push({
54-
id: filter.attribute_code, name: filter.label, label: option.label, value: option.value,
55-
});
56-
}
57-
});
58-
});
59-
60-
return result;
61-
});
62-
return {
63-
removableFilters,
64-
};
36+
props: {
37+
removableFilters: {
38+
type: Array,
39+
default: () => [],
40+
},
6541
},
6642
});
6743
</script>
@@ -70,17 +46,23 @@ export default defineComponent({
7046
display: flex;
7147
flex-wrap: wrap;
7248
}
49+
7350
.selected-filter {
7451
display: flex;
7552
margin-right: var(--spacer-xs);
7653
margin-bottom: var(--spacer-xs);
54+
padding: var(--spacer-xs) var(--spacer-xs);
55+
7756
&__label {
7857
font-size: var(--font-size--base);
7958
}
59+
8060
&__remove {
8161
display: inline-flex;
8262
align-items: center;
83-
margin-right: 6px;
63+
background: none;
64+
border: none;
65+
cursor: pointer;
8466
}
8567
}
8668
</style>

0 commit comments

Comments
 (0)