]> _ Git - pmi.git/commitdiff
Done #4988 @5
authorStephen Cameron <stephen@cubedesigners.com>
Mon, 7 Feb 2022 18:33:58 +0000 (19:33 +0100)
committerStephen Cameron <stephen@cubedesigners.com>
Mon, 7 Feb 2022 18:33:58 +0000 (19:33 +0100)
resources/js/components/ProductsFilters.vue
resources/views/pages/category.blade.php
resources/views/pages/category_listing.blade.php

index caba3bfea363a285d8fbb141ad27367003c5d601..7853294f4554b384aac69a6bf75543d71bf6ebf5 100644 (file)
@@ -3,48 +3,59 @@
     <div class="relative" :class="{ 'overflow-hidden': noResults }">
 
         <!-- Filters column -->
-        <div
+        <div ref="filters"
             class="products-filters-wrapper sticky sm:static top-60 mr-1v sm:mr-0 pt-4 whitespace-no-wrap float-left sm:float-none">
 
             <!-- Filters panel -->
             <div class="bg-white p-4">
-                <div class="products-filters" v-for="(filter, index) in filterData" :key="index">
+                <div class="products-filters" :class="{ 'folded': currentFilterIndex !== index }" v-for="(filter, index) in filterData" :key="index">
 
                     <hr class="h-px bg-grey-250 my-4" v-if="index !== 0"/>
 
-                    <h3 class="text-base mb-2 whitespace-normal">{{ filter.label }}</h3>
+                    <h3 class="flex items-center justify-between text-base whitespace-normal mb-0" @click="expandFilters(index)">
+                        <span class="pr-4">{{ filter.label }}</span>
+                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 8" width="13" height="8" xml:space="preserve"
+                             :class="{ 'rotate-180': currentFilterIndex !== index }"
+                             class="transition transform origin-center duration-300">
+                            <path d="m.8 7.2 5.7-5.7 5.7 5.7" fill="none" stroke="#6b7287" stroke-width="2"/>
+                        </svg>
+                    </h3>
 
-                    <ul v-if="filter.type === 'list' || filter.type === 'mlist'">
-                        <li v-for="(option, option_index) in filter.options" :key="option_index"
-                            class="flex justify-between py-1 text-sm">
+                    <!-- To allow accordion transition to be animated, this element will have
+                         its max-height set based on the child elements - see mounted() function -->
+                    <div class="filters-wrapper">
 
-                            <label>
-                                <input type="checkbox" v-model="filters[filter.id]" :value="option.value"
-                                       @change="updateFilters">
-                                {{ option.label }}
-                            </label>
+                        <ul v-if="filter.type === 'list' || filter.type === 'mlist'" class="pt-3">
+                            <li v-for="(option, option_index) in filter.options" :key="option_index"
+                                class="flex justify-between py-1 text-sm">
 
-                            <div class="products-filters-count pl-8">({{ option.nb_products }})</div>
+                                <label>
+                                    <input type="checkbox" v-model="filters[filter.id]" :value="option.value"
+                                           @change="updateFilters">
+                                    {{ option.label }}
+                                </label>
 
-                        </li>
-                    </ul>
+                                <div class="products-filters-count pl-8">({{ option.nb_products }})</div>
 
-                    <div v-if="filter.type === 'range'">
-                        <vue-slider class="mt-3"
-                                    v-model="filters[filter.id]"
-                                    :min="parseFloat(filter.minscale)"
-                                    :max="parseFloat(filter.maxscale)"
-                                    :enable-cross="false"
-                                    :contained="true"
-                                    :lazy="true"
-                                    :interval="(filter.maxscale - filter.minscale) / 100"
-                                    :tooltip-formatter="filter.scale === 'log' ? log : linear"
-                                    @change="updateFilters">
-                        </vue-slider>
-
-                        <div class="flex justify-between mt-2">
-                            <span class="text-xs">{{ filter.prefix }} {{ filter.min }} {{ filter.unit }}</span>
-                            <span class="text-xs">{{ filter.prefix }} {{ filter.max }} {{ filter.unit }}</span>
+                            </li>
+                        </ul>
+
+                        <div v-if="filter.type === 'range'" class="pt-3">
+                            <vue-slider v-model="filters[filter.id]"
+                                        :min="parseFloat(filter.minscale)"
+                                        :max="parseFloat(filter.maxscale)"
+                                        :enable-cross="false"
+                                        :contained="true"
+                                        :lazy="true"
+                                        :interval="(filter.maxscale - filter.minscale) / 100"
+                                        :tooltip-formatter="filter.scale === 'log' ? log : linear"
+                                        @change="updateFilters">
+                            </vue-slider>
+
+                            <div class="flex justify-between mt-2">
+                                <span class="text-xs">{{ filter.prefix }} {{ filter.min }} {{ filter.unit }}</span>
+                                <span class="text-xs">{{ filter.prefix }} {{ filter.max }} {{ filter.unit }}</span>
+                            </div>
                         </div>
                     </div>
 
             productType: {
                 required: true,
             },
+            baseFilters: {
+                type: String,
+                default: '',
+            },
             filterData: {
                 required: true,
             },
         data: () => ({
             viewStyle: 'grid',
             filters: {},
+            filterWrappers: [], // All the sets of filters in the accordion
+            currentFilterIndex: 0, // Index of the filter group that is open
             matches: {},
             products: [],
             log: function (v) {
                 this.viewStyle = localStorage.viewStyle;
             }
 
+            // Gather all the filter sets for the accordion
+            this.filterWrappers = this.$refs.filters.querySelectorAll('.filters-wrapper');
+
+            // Calculate the max-height of each filter set (required so accordion height animation can work)
+            this.filterWrappers.forEach(function(el) {
+
+                let totalHeight = 0;
+                for (let i = 0; i < el.children.length; i++) {
+                    totalHeight += el.children[i].clientHeight;
+                }
+
+                el.style.maxHeight = totalHeight + 'px';
+            });
+
+            // Find the first filter set that has pre-selected filters and open it
+            let initialFilterIndex = 0;
+            for (let fi = 0; fi < this.filterData.length; fi++) {
+                let id = this.filterData[fi].id;
+                if (this.filters[id].length > 0 && this.filterData[fi].type !== 'range') {
+                    initialFilterIndex = fi;
+                    break;
+                }
+            }
+            this.expandFilters(initialFilterIndex);
+
         },
 
         methods: {
 
             parseQuerystring() {
                 const $this = this;
-                const querystring = window.baseFilters + '&' + location.search.substring(1); // Get querystring minus first character (?)
+                const querystring = this.baseFilters + '&' + location.search.substring(1); // Get querystring minus first character (?)
 
                 if (querystring.length > 0) {
                     querystring.split('&').forEach(function (pair) {
                 }
                 this.viewStyle = this.viewStyle === 'list' ? 'grid' : 'list';
             },
+
+            expandFilters(index) {
+                // For the accordion animation to work, overflow:hidden must be set on the wrapping element.
+                // However, some filter elements like sliders don't display properly if the overflow is hidden
+                // (due to tooltips etc), so we need to set the overflow to visible on the open set of filters
+                // immediately *after* the CSS transition (300ms) ends. When a new filter set is opened, the
+                // overflow:visible override needs to be removed immediately, before the transition accordion
+                // animation begins...
+                let $this = this;
+                let previousIndex = this.currentFilterIndex;
+                this.filterWrappers[previousIndex].style.removeProperty('overflow');
+                this.currentFilterIndex = index;
+                setTimeout(function() {
+                    $this.filterWrappers[index].style.overflow = 'visible';
+                }, 300); // Keep this value in sync with the CSS transition duration defined below!
+            },
         },
 
     }
 </script>
+
+<style lang="stylus" scoped>
+    .products-filters
+        &.folded
+            h3
+                cursor: pointer
+            .filters-wrapper
+                max-height: 0 !important
+
+        .filters-wrapper
+            overflow: hidden
+            transition: max-height 300ms ease-out
+</style>
index acd3dc92636e7ae08bf21552e999b57cb2fc96e2..aee44625b5eb330af76e4fcdabeddc9249d57456 100644 (file)
@@ -1,9 +1,7 @@
 @extends('layouts/app')
 
 @section('content')
-    <script>
-        window.baseFilters = "{{$page->get('filter')}}";
-    </script>
+
     @intro(['padding' => 'pb-1v'])
 
     <full-width class="bg-grey-100" padding="pt-1v pb-2v">
@@ -12,7 +10,8 @@
 
             @if ($filters && $filter_results)
                 <products-filters v-cloak
-                                  :product-type="{{ $product_type->id }}"
+                                  product-type="{{ $product_type->id }}"
+                                  base-filters="{{ $page->get('filter') }}"
                                   :filter-data='@json($filters)'
                                   :result-data='@json($filter_results)'
                                   :translations='@json([
index 5685e23c7d0335e52fe72fe0b934fdd7cbbd0d35..9984dac0f4ee230c956eec9b1320a7034deb9450 100644 (file)
@@ -1,9 +1,6 @@
 @extends('layouts/app')
 
 @section('content')
-    <script>
-        window.baseFilters = "{{$page->get('filter')}}";
-    </script>
     @intro(['padding' => 'pb-1v'])
 
     <full-width class="bg-grey-100" padding="pt-3v pb-3v">