"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": {}
}
<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;
--- /dev/null
+// 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
@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
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"
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"
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"