ldc-showroom/components/ProductList.vue
Jérémy Munsch 7720230013
Tooltips fix
2024-02-02 08:47:23 +01:00

214 lines
6.4 KiB
Vue

<template>
<div id="product-list-component" class="container justify-content-center d-flex flex-column">
<div v-if="range.products" ref="rangeList" class="row justify-content-around">
<div
v-for="(productPair, productIndex) of chunkInefficient(range.products, 2)"
:key="productIndex"
:class="{
'row justify-content-center': true,
'shelf': displayShelves
}"
>
<div
v-for="(product, productPairIndex) of productPair"
:key="productPairIndex"
class="'col-12 col-md-6 product text-center"
>
<button
class="d-inline-block btn bg-body position-relative"
:title="t('more_about_this_product')"
data-bs-toggle="tooltip"
@click.stop="showProduct(product, $event)"
>
<div class="container-fluid">
<div class="row">
<div class="col">
<img
class="img-thumbnail"
:src="`/data/${product.picture}`"
:alt="t('products_logo', {'product_name': transField(product, 'name')})"
>
</div>
</div>
<div class="row">
<div class="col">
<span
:class="{
...{
'product-title': true,
'd-flex fw-bold text-capitalize justify-content-center align-items-center rounded mt-4 py-3 px-4': true
},
...rangeFont(range.font_id)
}"
:style="{
'color': getContrastYIQ(range.color),
'background-color': range.color
}"
>
{{ transField(product, 'name') }}
</span>
</div>
</div>
</div>
</button>
</div>
</div>
</div>
<div v-else class="row">
<div class="col">
<span>{{ t('no_products_available') }}</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import DOMPurify from "dompurify";
import type { DirectiveBinding } from "vue";
import { defineComponent } from "vue";
import { useRoute, type RouteLocationNormalizedLoaded } from "vue-router";
import ToolboxHelper from "~/classes/ToolboxHelper";
import { Tooltips } from "~/vueModules/bsTooltips";
const saneHtml = {
mounted: (el: HTMLElement, binding: DirectiveBinding) => {
el.innerHTML = DOMPurify.sanitize(binding.value);
},
};
export default defineComponent({
name: "ProductList",
directives: {
saneHtml,
},
props: {
range: {
type: Object as PropType<TypeRange>,
required: true,
},
},
emits: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
selectedProduct(payload: {product: TypeProduct}) {
return true;
}
},
data(): {
route: RouteLocationNormalizedLoaded;
displayShelves: boolean;
tooltips: Tooltips|null;
} {
return {
route: useRoute(),
displayShelves: this.range.shelf,
tooltips: null
};
},
watch: {
range() {
this.displayShelves = this.range.shelf;
}
},
mounted() {
this.$nextTick(() => {this.tooltips = Tooltips.make({type:"parent", parentSelector: "#product-list-component"});});
this.$nextTick(this.applySameHeight);
},
beforeUnmount() {
this.tooltips?.closeBootstrapTooltip();
},
methods: {
chunkInefficient(products: Array<TypeProduct>, chunkSize: number):Array<Array<TypeProduct>> {
const out:Array<Array<TypeProduct>> = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
products.forEach((elem: TypeProduct, i: number) => {
if (i % chunkSize) {
out.push(products.slice(i, i + chunkSize));
}
});
return out;
},
t(text: string, params: Record<string, string> = {}, localeToUse: string | null = null): string {
return ToolboxHelper.trans(text, params, localeToUse);
},
transField(model: Typei18nModel, field: Typei18nFieldList) {
return ToolboxHelper.getTranslatableField(model, field);
},
getContrastYIQ(hexcolor: string): string {
return ToolboxHelper.getContrastYIQ(hexcolor);
},
toRGBA(hexcolor: string, alpha: number = 1): string {
return ToolboxHelper.toRGBA(hexcolor, alpha);
},
rangeFont(fontId: number) {
const font = useAppStore().getFont(fontId);
const out: Map<string, boolean> = new Map<string, boolean>();
if(font) {
out.set(`f-${font.font_name}`, true);
}
return Object.fromEntries(out.entries());
},
showProduct(product: TypeProduct, $event: Event) {
this.tooltips?.refreshTooltips();
const el = $event.target as HTMLButtonElement;
el.scrollIntoView({
behavior: "smooth",
block: "center"
});
setTimeout(() => {
this.$emit("selectedProduct", {product});
}, 500);
},
/** Pour que ces putains de titres label aient la meme hauteur. */
applySameHeight() {
const spanList = (this.$refs.rangeList as HTMLDivElement).querySelectorAll(".product-title") as NodeListOf<HTMLSpanElement>;
let maxHeight = 0;
spanList.forEach((productTitle) => {
const spanHeight = productTitle.getBoundingClientRect().height;
if (spanHeight > maxHeight) {
maxHeight = spanHeight;
}
});
spanList.forEach(productTitle => {
productTitle.style.setProperty("min-height", `${maxHeight}px`);
});
}
}
});
</script>
<style lang="scss" scoped>
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/vendor/rfs";
.product {
transition: all 0.2s ease-out allow-discrete;
@include rfs(3.5rem, padding-top);
@include media-breakpoint-up(md) {
@include rfs(4.5rem, padding-top);
}
@include media-breakpoint-up(xxl) {
@include rfs(6rem, padding-top);
}
.product-title {
@include rfs(1.4rem, font-size);
@include rfs(1rem, padding-bottom);
@include media-breakpoint-up(md) {
@include rfs(1.8rem, font-size);
@include rfs(3rem, padding-bottom);
}
@include media-breakpoint-up(xxl) {
@include rfs(2.8rem, font-size);
@include rfs(6rem, padding-bottom);
}
}
}
</style>