ldc-tregalette/resources/ts/fo/components/RangeSliderComponent.vue
2023-12-21 15:37:01 +01:00

393 lines
11 KiB
Vue

<template>
<div
class="ranges-slider position-relative"
ref="rangeSlider"
>
<!-- RANGE MAIN CONTENT -->
<div class="row w-100 mx-auto">
<div class="d-none d-lg-block col-3">
<!-- SUZETTE -->
<img
src="@/images/homepage/suzette-empty-hand.png"
alt="Suzette tenant un rozell dans sa main"
title="Suzette tenant un rozell"
class="suzette-picture z-1 w-100 position-relative"
>
</div>
<!-- RANGES -->
<template
v-for="(range, rangeIndex) in rangeModels"
:key="rangeIndex"
>
<div :class="`${rangeIndex === 0 ? 'col-12 col-lg-5 active-range position-relative text-center' : 'col-2 d-none d-lg-flex justify-content-center'}`">
<button
v-if="rangeIndex === 0"
class="bootstrap-btn-play btn btn-primary position-absolute bottom-0 start-0 z-1"
:title="'Modifier le statut de défilement du carousel'"
type="button"
data-bs-toggle="tooltip"
>
<FontAwesomeIcon
icon="fa-solid fa-play"
:class="(sliderPlay) ? 'd-none' : ''"
/>
<FontAwesomeIcon
icon="fa-solid fa-pause"
:class="(!sliderPlay) ? 'd-none' : ''"
/>
</button>
<img
:role="`${rangeIndex !== 0 ? 'button': ''}`"
:class="`btn btn-link rounded-circle range-picture range-picture-${rangeIndex +1} ${rangeIndex === 0 ? 'position-relative z-2' : ''}`"
:src="String(range.picture)"
:alt="String(range.picture_alt)"
:title="String(range.picture_title)"
@click.prevent="setSliderRotate(Number.parseInt(String(range.id)))"
:tabindex="`${rangeIndex !== 0 ? '0': '-1'}`"
ref="rangePicture"
>
</div>
</template>
</div>
<!-- RANGES PICTOGRAMS -->
<div class="f-intro-head-b-base lh-1 pictos">
<div
class="d-flex rotate7 mb-sm-1 mb-lg-3"
v-if="currentRange.picto1_picture"
>
<img
class="picto-picture my-auto me-2"
:src="String(currentRange.picto1_picture)"
:title="String(currentRange.picto1_picture_title)"
:alt="String(currentRange.picto1_picture_alt)"
>
<div
class="d-flex flex-column align-items-center justify-content-center picto-text text-center"
>
<p class="text-uppercase mb-0">
{{ currentRange.picto1_line1 }}
</p>
<p class="text-uppercase fw-bold mb-0">
{{ currentRange.picto1_line2 }}
</p>
</div>
</div>
<div
class="d-flex rotate7-lg-reverse second-picto"
v-if="currentRange.picto2_picture"
>
<img
class="picto-picture my-auto me-2"
:src="String(currentRange.picto2_picture)"
:title="String(currentRange.picto2_picture_title)"
:alt="String(currentRange.picto2_picture_alt)"
>
<div
class="d-flex flex-column align-items-center justify-content-center picto-text text-center"
>
<p class="text-uppercase mb-0">
{{ currentRange.picto2_line1 }}
</p>
<p class="text-uppercase fw-bold mb-0">
{{ currentRange.picto2_line2 }}
</p>
</div>
</div>
</div>
<!-- PAGINATION -->
<div class="row">
<div
class="pagination d-flex justify-content-center align-items-center position-relative mt-4 p-0"
ref="pagination"
>
<template
v-for="(range, rangeIndex) in staticRangeModels"
:key="rangeIndex"
>
<div
:class="`btn btn-link range-pagination m-0 mx-lg-2 ${range.id === currentRange.id ? 'range-pagination-active' : ''}`"
role="button"
tabindex="0"
@click.prevent="setSliderRotate(Number.parseInt(String(range.id)))"
/>
</template>
</div>
</div>
<!-- RANGE LABEL -->
<div class="row">
<a
class="btn btn-tertiary btn-rotate-left mx-auto mt-4 mb-2 w-fit fs-2 px-4 range-label z-2"
:href="getRangeShowRoute(currentRange.slug as string)"
>
{{ currentRange.label }}
</a>
</div>
</div>
</template>
<script lang="ts">
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { defineComponent } from "vue";
import route from "../../modules/route";
import bsTooltips from "./../../modules/bsTooltips";
export default defineComponent({
name: "RangeSliderComponent",
mixins: [route, bsTooltips],
components: {
FontAwesomeIcon,
},
data(): {
rangeModels: LaravelModelList;
staticRangeModels: LaravelModelList;
currentRange: LaravelModel;
slider: HTMLElement;
sliderPlay: boolean;
sliderInterval: number;
sliderIntervalFunction: ReturnType<typeof setInterval>;
} {
return {
rangeModels: [],
staticRangeModels: [],
currentRange: {
id: 0
},
slider: document.createElement("div"),
sliderPlay: true,
sliderInterval: 3000,
sliderIntervalFunction: 0,
};
},
computed: {},
mounted() {
const json = String(this.$attrs.json ?? "{}"),
data = JSON.parse(json);
this.rangeModels = data.rangeModels;
this.staticRangeModels = [...this.rangeModels.values()];
this.currentRange = this.rangeModels[0];
this.slider = this.$refs.rangeSlider as HTMLElement;
this.$nextTick(() => {
this.initSlider();
this.initTooltips();
});
},
methods: {
/** Init pagination for Desktop */
initSlider() {
// Pagination dots
const paginationElements = (this.$refs.pagination as HTMLElement).children as HTMLCollectionOf<HTMLElement>;
for (const paginationElement of paginationElements) {
paginationElement.setAttribute("tabindex", "0");
paginationElement.setAttribute("role", "button");
paginationElement.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.code === "Space") {
e.preventDefault();
e.stopPropagation();
paginationElement.click();
}
});
}
// Range pictures
const rangePictureElements = this.$refs.rangePicture as Array<HTMLElement>;
for (const rangePictureElement of rangePictureElements) {
if (rangePictureElements.indexOf(rangePictureElement) !== 0) {
rangePictureElement.setAttribute("tabindex", "0");
rangePictureElement.setAttribute("role", "button");
rangePictureElement.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.code === "Space") {
e.preventDefault();
e.stopPropagation();
rangePictureElement.click();
}
});
}
}
/** Set autoplay functionnality */
document.querySelector(".bootstrap-btn-play")?.addEventListener("click", () => {
this.sliderPlay = (this.sliderPlay) ? false : true;
this.updateSliderInterval();
});
if (this.sliderPlay) {
this.setSliderInterval();
}
},
/** Rotate ranges */
setSliderRotate(toId: number):void {
if(!this.rangeModels.length) {
return;
}
while (this.rangeModels.at(0)?.id !== toId) {
const tmp = this.rangeModels.pop();
if (!tmp) {
throw new Error("Cannot rotate products with empty array");
}
this.rangeModels.unshift(tmp);
}
this.currentRange = this.rangeModels.at(0) as LaravelModel;
if (!this.sliderPlay) this.sliderPlay = true;
this.updateSliderInterval();
},
/** Get fo.ranges.show route */
getRangeShowRoute(slug: string): string {
const route = this.route("fo.ranges.show", {
SLUG: slug,
});
if (!route) {
throw new Error("Undefined route fo.ranges.show");
}
return route;
},
/** Update slider interval */
updateSliderInterval() {
clearInterval(this.sliderIntervalFunction);
this.setSliderInterval();
},
/** Set new slider interval */
setSliderInterval() {
this.sliderIntervalFunction = setInterval(() => {
if (this.sliderPlay) this.setSliderRotate(Number(this.rangeModels.at(1)?.id));
}, this.sliderInterval);
}
},
});
</script>
<style lang="scss">
// Bootstrap reimport to use breakpoints
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/mixins";
.ranges-slider {
.active-range {
background-image: url("@/images/global/plate.png");
background-position-x: center;
background-repeat: no-repeat;
background-size: contain;
padding: 1.5rem;
}
.range-picture {
width: 75%;
}
.pagination {
.range-pagination {
background-image: url("@/images/global/slider-nav.png");
background-position-x: center;
background-repeat: no-repeat;
background-size: contain;
width: 1rem;
height: 1rem;
&:hover {
cursor: pointer;
}
}
.range-pagination-active {
background-image: url("@/images/global/slider-nav-active.png");
width: 1.3rem;
height: 1.3rem;
}
}
.pictos {
position: relative;
.d-flex {
margin-top: 1rem;
justify-content: center;
}
@media screen and (min-width: 380px) {
position: absolute;
right: -2rem;
bottom: 5.5rem;
.d-flex {
margin-top: 0;
justify-content: left;
}
}
.picto-picture {
max-width: 25%;
}
.picto-text {
font-size: 0.9rem;
}
}
}
@include media-breakpoint-up(sm) {
.ranges-slider .pictos {
bottom: 5.5rem;
}
}
@include media-breakpoint-up(lg) {
.ranges-slider {
.range-picture {
width: 100%;
&-1 {
transition: transform 330ms ease-in-out;
&:hover {
transform: scale(1.1);
}
}
&-2, &-3 {
&:hover {
cursor: pointer;
}
}
&-2 {
position: relative;
top: -20%;
height: fit-content;
}
&-3 {
height: fit-content;
}
}
.pagination {
left: -2.5rem;
}
.suzette-picture {
left: 35%;
}
.range-label {
position: absolute;
top: 60%;
left: 54%;
transform: rotate(2.159deg)
}
.pictos {
right: 15% !important;
bottom: 50% !important;
.picto-picture {
max-width: 30% !important;
}
.picto-text {
font-size: 1.3rem !important;
}
}
.second-picto {
transform: rotate(2.159deg);
}
}
}
</style>