]> _ Git - pmi.git/commitdiff
WIP #2962 @6
authorStephen Cameron <stephen@cubedesigners.com>
Tue, 3 Sep 2019 14:53:46 +0000 (16:53 +0200)
committerStephen Cameron <stephen@cubedesigners.com>
Tue, 3 Sep 2019 14:53:46 +0000 (16:53 +0200)
package.json
resources/js/components/ProductsFilters.vue
resources/styles/components/vue-slider.styl [new file with mode: 0644]
resources/views/pages/products.blade.php
yarn.lock

index 5b7780f69e3085a58ca0a35a64a93d82a0b5a75f..20b88e8f0a267c7adf5a0beba809ec0897d44e03 100644 (file)
@@ -16,6 +16,7 @@
         "browser-sync": "^2.26.5",
         "browser-sync-webpack-plugin": "2.0.1",
         "cross-env": "^5.2.0",
+        "element-closest": "^3.0.1",
         "gsap": "^2.1.3",
         "jquery": "^3.2",
         "laravel-mix": "^4.0.7",
         "tailwindcss": "^1.0.4",
         "vue": "^2.6.10",
         "vue-slide-up-down": "^1.7.2",
-        "vue-template-compiler": "^2.6.10",
-        "element-closest": "^3.0.1"
+        "vue-slider-component": "^3.0.40",
+        "vue-template-compiler": "^2.6.10"
     },
-    "dependencies": {
-
-    }
+    "dependencies": {}
 }
index d14fe7517946582ec9ba6557bcae24142f3ff178..a9ed958207d93bb65999661ba4c197c4d6873a0a 100644 (file)
@@ -1,35 +1,49 @@
 <template>
 
-    <div class="container flex relative items-start">
+    <div class="flex relative items-start">
 
         <!-- Filters column -->
         <div class="products-filters-wrapper sticky top-0 mr-1v pt-4 whitespace-no-wrap">
 
             <!-- Filters panel -->
-            <div class="products-filters bg-white p-4" v-for="filter in filterData" :key="filter.id">
+            <div class="bg-white p-4">
+                <div class="products-filters" v-for="(filter, key, index) in filterData" :key="key">
 
-                <h3 class="text-base mb-2">{{ filter.label }}</h3>
+                    <hr class="h-px bg-grey-250 my-4" v-if="index !== 0" />
 
-                <ul v-if="filter.type === 'list'">
-                    <li v-for="(option, option_index) in filter.options" :key="option_index" class="flex justify-between py-1 text-sm">
+                    <h3 class="text-base mb-2">{{ filter.label }}</h3>
 
-                        <label>
-                            <input type="checkbox" v-model="filters[filter.id]" :value="option.value" @change="updateFilters">
-                            {{ option.label }}
-                        </label>
+                    <ul v-if="filter.type === 'list'">
+                        <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'">
-                    <!-- Todo: handle range using Vue slider component: https://github.com/NightCatSama/vue-slider-component -->
-                    (RANGE SLIDER GOES HERE)
-                </div>
+                        </li>
+                    </ul>
 
-                <hr class="h-px bg-grey-200 my-4" />
+                    <div v-if="filter.type === 'range'">
+                        <vue-slider class="mt-3"
+                                    v-model="filters[filter.id]"
+                                    :min="filter.min"
+                                    :max="filter.max"
+                                    :enable-cross="false"
+                                    :contained="true"
+                                    :lazy="true"
+                                    @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>
             </div>
 
         </div>
 
             <!-- Product Grid -->
             <slot></slot>
+
+            <!-- No results -->
+            <div class="text-navy font-display text-center my-8" v-if="Object.keys(this.matches.hits).length === 0">
+                {{ translations.no_results }}
+            </div>
+
         </div>
 
     </div>
 </template>
 
 <script>
+    import VueSlider from 'vue-slider-component' // See vue-slider.styl for CSS
 
     export default {
 
+        components: {
+            VueSlider
+        },
+
         props: {
             productType: {
                 required: true,
                 let filter_list = [];
 
                 Object.keys(this.filters).forEach(filterID => {
+
+                    // Don't include range values if they're unchanged (at min / max values)
+                    if (this.filterData[filterID].type === 'range'
+                        && this.filterData[filterID].min === this.filters[filterID][0]
+                        && this.filterData[filterID].max === this.filters[filterID][1]) {
+                        return;
+                    }
+
                     filter_list.push(`filter[${filterID}]=` + this.filters[filterID].join(';'));
                 });
 
             // Create dynamic data so we can bind the filter fields via v-model for each option
             let filters = {};
             Object.values(this.filterData).forEach(filter => {
-                filters[filter.id] = [];
+                if (filter.type === 'range') {
+                    filters[filter.id] = [filter.min, filter.max];
+                } else {
+                    filters[filter.id] = [];
+                }
             });
 
             this.filters = filters;
diff --git a/resources/styles/components/vue-slider.styl b/resources/styles/components/vue-slider.styl
new file mode 100644 (file)
index 0000000..db21b97
--- /dev/null
@@ -0,0 +1,147 @@
+// Theme for vue-slider component
+// Adapted from https://github.com/NightCatSama/vue-slider-component/blob/master/lib/theme/default.scss
+
+$themeColor = #6b7287
+$disabledOpacity = 0.5
+
+$bgColor = #ccc
+$railBorderRadius = 15px
+
+$dotShadow = 0.5px 0.5px 2px 1px rgba(0, 0, 0, 0.32)
+//$dotShadowFocus = 0px 0px 1px 2px rgba($themeColor, 0.36)
+$dotBgColor = #fff
+$dotBgColorDisable = #ccc
+$dotBorderRadius = 50%
+
+$tooltipBgColor = $themeColor
+$tooltipColor = #fff
+$tooltipBorderRadius = 5px
+$tooltipPadding = 2px 5px
+$tooltipMinWidth = 20px
+$tooltipArrow = 10px
+$tooltipFontSize = 12px
+
+$stepBorderRadius = 50%
+$stepBgColor = rgba(0, 0, 0, 0.16)
+
+$labelFontSize = 14px
+
+//=================================
+
+triangle($size, $color, $direction)
+  height: 0
+  width: 0
+  if $direction == top or $direction == bottom or $direction == right or $direction == left
+    border-color: transparent
+    border-style: solid
+    border-width: ($size / 2)
+    if $direction == top
+      border-bottom-color: $color
+    else if $direction == right
+      border-left-color: $color
+    else if $direction == bottom
+      border-top-color: $color
+    else if $direction == left
+      border-right-color: $color
+arrow($size, $color)
+  &::after
+    content: ''
+    position: absolute
+  &-top
+    &::after
+      top: 100%
+      left: 50%
+      transform: translate(-50%, 0)
+      triangle($size, $color, bottom)
+  &-bottom
+    &::after
+      bottom: 100%
+      left: 50%
+      transform: translate(-50%, 0)
+      triangle($size, $color, top)
+  &-left
+    &::after
+      left: 100%
+      top: 50%
+      transform: translate(0, -50%)
+      triangle($size, $color, right)
+  &-right
+    &::after
+      right: 100%
+      top: 50%
+      transform: translate(0, -50%)
+      triangle($size, $color, left)
+
+//=================================
+
+/* component style */
+.vue-slider-disabled
+  opacity: $disabledOpacity
+  cursor: not-allowed
+
+/* rail style */
+.vue-slider-rail
+  background-color: $bgColor
+  border-radius: $railBorderRadius
+
+/* process style */
+.vue-slider-process
+  background-color: $themeColor
+  border-radius: $railBorderRadius
+
+/* mark style */
+.vue-slider-mark
+  z-index: 4
+
+  &:first-child,
+  &:last-child
+    .vue-slider-mark-step
+      display: none
+
+  &-step
+    width: 100%
+    height: 100%
+    border-radius: $stepBorderRadius
+    background-color: $stepBgColor
+
+  &-label
+    font-size: $labelFontSize
+    white-space: nowrap
+
+
+/* dot style */
+.vue-slider-dot
+  &-handle
+    cursor: pointer
+    width: 100%
+    height: 100%
+    border-radius: $dotBorderRadius
+    background-color: $dotBgColor
+    box-sizing: border-box
+    box-shadow: $dotShadow
+
+  //&-focus
+  //  box-shadow: $dotShadowFocus
+  &-disabled
+    cursor: not-allowed
+    background-color: $dotBgColorDisable
+
+  &-tooltip
+    &-inner
+      font-size: $tooltipFontSize
+      white-space: nowrap
+      padding: $tooltipPadding
+      min-width: $tooltipMinWidth
+      text-align: center
+      color: $tooltipColor
+      border-radius: $tooltipBorderRadius
+      border-color: $tooltipBgColor
+      background-color: $tooltipBgColor
+      box-sizing: content-box
+      arrow($tooltipArrow, inherit)
+
+  &-tooltip-wrapper
+    opacity: 0
+    transition: all 0.3s
+    &-show
+      opacity: 1
index 610572273d6ccb79f8a332e91734140c7693f95b..e33aa0e01ef460206669118883453972a79e1ad9 100644 (file)
@@ -4,23 +4,34 @@
     @intro(['padding' => 'pb-1v'])
 
     <full-width class="bg-grey-100" padding="pt-1v pb-2v">
-        <products-filters class=""
-                          :product-type="{{ $product_type->id }}"
-                          :filter-data='@json($filters)'
-                          :result-data='@json($filter_results)'
-                          :translations='@json(['result' => __('résultat'), 'results' => __('résultats')])'>
 
-            {{-- Product Grid --}}
-            <grid cols="auto" class="products-grid mt-6">
-                @foreach($products as $id => $product)
+        <content>
 
-                {{-- TODO: find a way to include easily hide products that don't match the filter. Maybe wrap in a <template v-if="matches.hits.includes($id)"> or make a child component similar to the Tabs / Tab setup and then pass the ID of the product to the child component or just a v-if on it? --}}
+            @if ($filters && $filter_results)
+                <products-filters class=""
+                                  :product-type="{{ $product_type->id }}"
+                                  :filter-data='@json($filters)'
+                                  :result-data='@json($filter_results)'
+                                  :translations='@json([
+                                    'result' => __('résultat'),
+                                    'results' => __('résultats'),
+                                    'no_results' => __('Aucun produit ne correspond aux filtres sélectionnés')
+                                ])'>
+            @endif
 
-                    @include('partials.product-link', ['id' => $id, 'product' => $product])
-                @endforeach
-            </grid>
+                {{-- Product Grid --}}
+                <grid cols="auto" class="products-grid mt-6">
+                    @foreach($products as $id => $product)
+                        @include('partials.product-link', ['id' => $id, 'product' => $product])
+                    @endforeach
+                </grid>
+
+            @if ($filters && $filter_results)
+                </products-filters>
+            @endif
+
+        </content>
 
-        </products-filters>
     </full-width>
 
 @endsection
index 72184c998007dc860d4800b424cf12d2c1a3f898..ea382e5d3f416319696db7d682d25c7945dd5a80 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -2504,6 +2504,11 @@ electron-to-chromium@^1.3.150:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.155.tgz#ebf0cc8eeaffd6151d1efad60fd9e021fb45fd3a"
   integrity sha512-/ci/XgZG8jkLYOgOe3mpJY1onxPPTDY17y7scldhnSjjZqV6VvREG/LvwhRuV7BJbnENFfuDWZkSqlTh4x9ZjQ==
 
+element-closest@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/element-closest/-/element-closest-3.0.1.tgz#0b2000266ae43a800274401dc39486f5e4bfbce2"
+  integrity sha512-Wm8B0in+k6GsSCra8vLVnFIjIrff2T1s2b++jU6VL6mqIteP19THxDXwT5JDrmJPlqT3YifOK9cu28+uRGUdew==
+
 elliptic@^6.0.0:
   version "6.4.1"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a"
@@ -7245,6 +7250,11 @@ vm-browserify@0.0.4:
   dependencies:
     indexof "0.0.1"
 
+vue-class-component@^7.0.1:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.1.0.tgz#b33efcb10e17236d684f70b1e96f1946ec793e87"
+  integrity sha512-G9152NzUkz0i0xTfhk0Afc8vzdXxDR1pfN4dTwE72cskkgJtdXfrKBkMfGvDuxUh35U500g5Ve4xL8PEGdWeHg==
+
 vue-hot-reload-api@^2.3.0:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
@@ -7261,11 +7271,25 @@ vue-loader@^15.4.2:
     vue-hot-reload-api "^2.3.0"
     vue-style-loader "^4.1.0"
 
+vue-property-decorator@^8.0.0:
+  version "8.2.2"
+  resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-8.2.2.tgz#ac895e9508ee1bf86e3a28568d94d842c2c8e42f"
+  integrity sha512-3gRrIeoUtjXvkoMX2stJsVs7805Pa9MXEndnk21ej+sWO7AIc5HF1TKqK0Pox5TEjpO02UbadIF0QWNrx6ZwXQ==
+  dependencies:
+    vue-class-component "^7.0.1"
+
 vue-slide-up-down@^1.7.2:
   version "1.7.2"
   resolved "https://registry.yarnpkg.com/vue-slide-up-down/-/vue-slide-up-down-1.7.2.tgz#88bcea3203eb054a44f4eba1bc77062d555db3d6"
   integrity sha512-y7vpjKNfjQGdKiLTZyonNZbWjtzyEA9nGXIK8wojJvGQapHi7EsLcWAYOQHJ1dE70FrpbAyMP6OIV3xRJEwXkg==
 
+vue-slider-component@^3.0.40:
+  version "3.0.40"
+  resolved "https://registry.yarnpkg.com/vue-slider-component/-/vue-slider-component-3.0.40.tgz#f8eb0ae2d4d8c63f7b767e2ae7a316449a88958c"
+  integrity sha512-8obhszDPC34b43h/dd0Gh+oQmUD402X+yo129K3m1EiqAlQ5Imr12XAOQ+RcwCKSOLigLW/flZ0H3dvmhk2ZWQ==
+  dependencies:
+    vue-property-decorator "^8.0.0"
+
 vue-style-loader@^4.1.0:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"