<div x-data="initCarouselcarousel1475983948()" x-init="init()" class="relative w-full h-full mx-auto">
<div class="flex justify-between items-center mb-8 ">
<h2 class="text-3xl font-semibold ">En ce moment dans votre magasin</h2>
<div class="flex items-center justify-center space-x-4 ">
<div class=" hidden sm:flex flex space-x-2">
<button type="button" class="carousel-button-prev-carousel-1475983948 before:border-none btn btn-dark-subtle btn-only-icon">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.46967 12.5303C9.17678 12.2374 9.17678 11.7626 9.46967 11.4697L13.4697 7.46967C13.7626 7.17678 14.2374 7.17678 14.5303 7.46967C14.8232 7.76256 14.8232 8.23744 14.5303 8.53033L11.0607 12L14.5303 15.4697C14.8232 15.7626 14.8232 16.2374 14.5303 16.5303C14.2374 16.8232 13.7626 16.8232 13.4697 16.5303L9.46967 12.5303Z" fill="currentColor" />
</svg>
</button>
<button type="button" class="carousel-button-next-carousel-1475983948 before:border-none btn btn-dark-subtle btn-only-icon">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5303 11.4697C14.8232 11.7626 14.8232 12.2374 14.5303 12.5303L10.5303 16.5303C10.2374 16.8232 9.76256 16.8232 9.46967 16.5303C9.17678 16.2374 9.17678 15.7626 9.46967 15.4697L12.9393 12L9.46967 8.53033C9.17678 8.23744 9.17678 7.76256 9.46967 7.46967C9.76256 7.17678 10.2374 7.17678 10.5303 7.46967L14.5303 11.4697Z" fill="currentColor" />
</svg>
</button>
</div>
</div>
</div>
<div x-ref="swiperContainer" id="carousel-1475983948" class="swiper md:h-full">
<div class="swiper-wrapper h-auto">
<div class="swiper-slide !transform-none h-auto" data-theme="light" data-hash="index-carousel-1475983948-0">
<div x-ref="contentBlock" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 h-full md:gap-8 bg-dark-5 rounded-lg overflow-hidden">
<div class="relative aspect-square md:aspect-auto ">
<img src="/img/store/newsStore1.png" alt="" class="w-full h-full object-cover" />
</div>
<div class="flex flex-col justify-center p-8 md:p-12">
<h2 class="text-3xl font-medium text-neutral-900 mb-4">Needle yet value-added first product strategic loss</h2>
<p class="text-neutral-600 text-lg mb-8">Launch optimize charts post bells algorithm low-hanging hear.</p>
<button type="button" class="w-fit btn btn-dark-ghost btn-icons">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.25C8.41421 1.25 8.75 1.58579 8.75 2V3.25H15.25V2C15.25 1.58579 15.5858 1.25 16 1.25C16.4142 1.25 16.75 1.58579 16.75 2V3.25H20C21.5188 3.25 22.75 4.48122 22.75 6V20C22.75 21.5188 21.5188 22.75 20 22.75H4C2.48122 22.75 1.25 21.5188 1.25 20V6C1.25 4.48122 2.48122 3.25 4 3.25H7.25V2C7.25 1.58579 7.58579 1.25 8 1.25ZM7.25 4.75H4C3.30964 4.75 2.75 5.30964 2.75 6V8.25H21.25V6C21.25 5.30964 20.6904 4.75 20 4.75H16.75V6C16.75 6.41421 16.4142 6.75 16 6.75C15.5858 6.75 15.25 6.41421 15.25 6V4.75H8.75V6C8.75 6.41421 8.41421 6.75 8 6.75C7.58579 6.75 7.25 6.41421 7.25 6V4.75ZM21.25 9.75H2.75V20C2.75 20.6904 3.30964 21.25 4 21.25H20C20.6904 21.25 21.25 20.6904 21.25 20V9.75ZM12 14.5C12.5523 14.5 13 14.0523 13 13.5C13 12.9477 12.5523 12.5 12 12.5C11.4477 12.5 11 12.9477 11 13.5C11 14.0523 11.4477 14.5 12 14.5ZM8 17.5C8 18.0523 7.55228 18.5 7 18.5C6.44772 18.5 6 18.0523 6 17.5C6 16.9477 6.44772 16.5 7 16.5C7.55228 16.5 8 16.9477 8 17.5ZM12 18.5C12.5523 18.5 13 18.0523 13 17.5C13 16.9477 12.5523 16.5 12 16.5C11.4477 16.5 11 16.9477 11 17.5C11 18.0523 11.4477 18.5 12 18.5ZM17 14.5C17.5523 14.5 18 14.0523 18 13.5C18 12.9477 17.5523 12.5 17 12.5C16.4477 12.5 16 12.9477 16 13.5C16 14.0523 16.4477 14.5 17 14.5ZM17 18.5C17.5523 18.5 18 18.0523 18 17.5C18 16.9477 17.5523 16.5 17 16.5C16.4477 16.5 16 16.9477 16 17.5C16 18.0523 16.4477 18.5 17 18.5Z" fill="currentColor" />
</svg>
Réserver un essayage
</button>
</div>
</div>
</div>
<div class="swiper-slide !transform-none h-auto" data-theme="light" data-hash="index-carousel-1475983948-1">
<div x-ref="contentBlock" class="grid h-full md:gap-8 bg-dark-5 rounded-lg overflow-hidden">
<div class="relative grid ">
<img src="/img/store/newsStore2.png" alt="" class="w-full h-full object-cover" />
</div>
</div>
</div>
<div class="swiper-slide !transform-none h-auto" data-theme="light" data-hash="index-carousel-1475983948-2">
<div x-ref="contentBlock" class="grid h-full md:gap-8 bg-dark-5 rounded-lg overflow-hidden">
<div class="flex flex-col justify-center p-8 md:p-12">
<h2 class="text-3xl font-medium text-neutral-900 mb-4">Needle yet value-added first product strategic loss</h2>
<p class="text-neutral-600 text-lg mb-8">Launch optimize charts post bells algorithm low-hanging hear. Lunch place building email 2 search. Initiative anyway sandwich globalize right. Economy work run or weeks see without bells inclusion beforehand.</p>
<button type="button" class="w-fit btn btn-dark-ghost btn-icons">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.25C8.41421 1.25 8.75 1.58579 8.75 2V3.25H15.25V2C15.25 1.58579 15.5858 1.25 16 1.25C16.4142 1.25 16.75 1.58579 16.75 2V3.25H20C21.5188 3.25 22.75 4.48122 22.75 6V20C22.75 21.5188 21.5188 22.75 20 22.75H4C2.48122 22.75 1.25 21.5188 1.25 20V6C1.25 4.48122 2.48122 3.25 4 3.25H7.25V2C7.25 1.58579 7.58579 1.25 8 1.25ZM7.25 4.75H4C3.30964 4.75 2.75 5.30964 2.75 6V8.25H21.25V6C21.25 5.30964 20.6904 4.75 20 4.75H16.75V6C16.75 6.41421 16.4142 6.75 16 6.75C15.5858 6.75 15.25 6.41421 15.25 6V4.75H8.75V6C8.75 6.41421 8.41421 6.75 8 6.75C7.58579 6.75 7.25 6.41421 7.25 6V4.75ZM21.25 9.75H2.75V20C2.75 20.6904 3.30964 21.25 4 21.25H20C20.6904 21.25 21.25 20.6904 21.25 20V9.75ZM12 14.5C12.5523 14.5 13 14.0523 13 13.5C13 12.9477 12.5523 12.5 12 12.5C11.4477 12.5 11 12.9477 11 13.5C11 14.0523 11.4477 14.5 12 14.5ZM8 17.5C8 18.0523 7.55228 18.5 7 18.5C6.44772 18.5 6 18.0523 6 17.5C6 16.9477 6.44772 16.5 7 16.5C7.55228 16.5 8 16.9477 8 17.5ZM12 18.5C12.5523 18.5 13 18.0523 13 17.5C13 16.9477 12.5523 16.5 12 16.5C11.4477 16.5 11 16.9477 11 17.5C11 18.0523 11.4477 18.5 12 18.5ZM17 14.5C17.5523 14.5 18 14.0523 18 13.5C18 12.9477 17.5523 12.5 17 12.5C16.4477 12.5 16 12.9477 16 13.5C16 14.0523 16.4477 14.5 17 14.5ZM17 18.5C17.5523 18.5 18 18.0523 18 17.5C18 16.9477 17.5523 16.5 17 16.5C16.4477 16.5 16 16.9477 16 17.5C16 18.0523 16.4477 18.5 17 18.5Z" fill="currentColor" />
</svg>
Réserver un essayage
</button>
</div>
</div>
</div>
<div class="swiper-slide !transform-none h-auto" data-theme="light" data-hash="index-carousel-1475983948-3">
<div x-ref="contentBlock" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 h-full md:gap-8 bg-dark-5 rounded-lg overflow-hidden">
<div class="relative aspect-square md:aspect-auto ">
<img src="/img/store/newsStore3.png" alt="" class="w-full h-full object-cover" />
</div>
<div class="flex flex-col justify-center p-8 md:p-12">
<h2 class="text-3xl font-medium text-neutral-900 mb-4">Needle yet value-added first product strategic loss</h2>
<p class="text-neutral-600 text-lg mb-8">Launch optimize charts post bells algorithm low-hanging hear.</p>
<button type="button" class="w-fit btn btn-dark-ghost btn-icons">
<svg class=" shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.25C8.41421 1.25 8.75 1.58579 8.75 2V3.25H15.25V2C15.25 1.58579 15.5858 1.25 16 1.25C16.4142 1.25 16.75 1.58579 16.75 2V3.25H20C21.5188 3.25 22.75 4.48122 22.75 6V20C22.75 21.5188 21.5188 22.75 20 22.75H4C2.48122 22.75 1.25 21.5188 1.25 20V6C1.25 4.48122 2.48122 3.25 4 3.25H7.25V2C7.25 1.58579 7.58579 1.25 8 1.25ZM7.25 4.75H4C3.30964 4.75 2.75 5.30964 2.75 6V8.25H21.25V6C21.25 5.30964 20.6904 4.75 20 4.75H16.75V6C16.75 6.41421 16.4142 6.75 16 6.75C15.5858 6.75 15.25 6.41421 15.25 6V4.75H8.75V6C8.75 6.41421 8.41421 6.75 8 6.75C7.58579 6.75 7.25 6.41421 7.25 6V4.75ZM21.25 9.75H2.75V20C2.75 20.6904 3.30964 21.25 4 21.25H20C20.6904 21.25 21.25 20.6904 21.25 20V9.75ZM12 14.5C12.5523 14.5 13 14.0523 13 13.5C13 12.9477 12.5523 12.5 12 12.5C11.4477 12.5 11 12.9477 11 13.5C11 14.0523 11.4477 14.5 12 14.5ZM8 17.5C8 18.0523 7.55228 18.5 7 18.5C6.44772 18.5 6 18.0523 6 17.5C6 16.9477 6.44772 16.5 7 16.5C7.55228 16.5 8 16.9477 8 17.5ZM12 18.5C12.5523 18.5 13 18.0523 13 17.5C13 16.9477 12.5523 16.5 12 16.5C11.4477 16.5 11 16.9477 11 17.5C11 18.0523 11.4477 18.5 12 18.5ZM17 14.5C17.5523 14.5 18 14.0523 18 13.5C18 12.9477 17.5523 12.5 17 12.5C16.4477 12.5 16 12.9477 16 13.5C16 14.0523 16.4477 14.5 17 14.5ZM17 18.5C17.5523 18.5 18 18.0523 18 17.5C18 16.9477 17.5523 16.5 17 16.5C16.4477 16.5 16 16.9477 16 17.5C16 18.0523 16.4477 18.5 17 18.5Z" fill="currentColor" />
</svg>
Réserver un essayage
</button>
</div>
</div>
</div>
</div>
<div class="flex sm:hidden mt-6">
<a href="https://www.afflelou.com/" target="_blank" rel="noopenner noreferer" class="mx-auto btn btn-dark-subtle ">
Voir toute la collection
</a>
</div>
</div>
</div>
<script>
function initCarouselcarousel1475983948() {
return {
swiper: null,
options: {
"color": "dark",
"slidesPerView": {
"mobile": 1,
"tablet": 1,
"desktop": 1
},
"spaceBetween": 30,
"showPagination": false,
"showNavigation": true,
"showNavigationMobile": false
},
componentId: 'carousel-1475983948',
init() {
this.initSwiper();
if (typeof this.customFunction === 'function') {
this.customFunction();
}
},
initSwiper() {
this.swiper = new Swiper(this.$refs.swiperContainer, {
slidesPerView: this.options.slidesPerView.mobile,
spaceBetween: this.options.spaceBetween,
pagination: this.options.showPagination ? {
el: '.swiper-pagination-' + this.componentId,
clickable: true,
bulletClass: 'bullet',
bulletActiveClass: 'active',
} : false,
navigation: this.options.showNavigation ? {
nextEl: '.carousel-button-next-' + this.componentId,
prevEl: '.carousel-button-prev-' + this.componentId
} : false,
hashNavigation: {
watchState: true,
},
breakpoints: {
640: {
slidesPerView: this.options.slidesPerView.tablet
},
1024: {
slidesPerView: this.options.slidesPerView.desktop
}
},
on: {
slideChange: () => this.updateSliderTheme(),
},
});
this.updateSliderTheme();
},
updateSliderTheme() {
const activeSlide = this.swiper.slides[this.swiper.activeIndex];
const theme = activeSlide.getAttribute('data-slider-theme');
this.sliderTheme = theme || 'light';
},
customFunction() {
// Custom function can be defined here and overridden outside.
this.$nextTick(() => {
const updateContentHeight = () => {
const contentBlocks = document.querySelectorAll('[x-ref="contentBlock"]');
const maxHeight = Array.from(contentBlocks).reduce((max, block) =>
Math.max(max, block.offsetHeight), 400);
document.documentElement.style.setProperty('--content-height', `${maxHeight}px`);
};
// Initial update
updateContentHeight();
// Resize listener avec debounce
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateContentHeight, 250);
});
// Écouter les événements de breakpoint Swiper
this.swiper.on('breakpoint', updateContentHeight);
// Écouter quand le DOM est modifié
const observer = new MutationObserver(updateContentHeight);
observer.observe(this.$refs.swiperContainer, {
childList: true,
subtree: true,
attributes: true
});
});
}
};
}
</script>
{# components/carousel/carousel.twig #}
{% set uniqueId = "carousel-" ~ random() %}
{% set carouselFunctionName = "initCarousel" ~ uniqueId|replace({'-': ''}) %}
<div
x-data="{{ carouselFunctionName }}()"
x-init="init()"
class="relative w-full h-full mx-auto"
>
{# Header Section #}
<div class="flex justify-between items-center mb-8 {% if variant is defined and variant == 'small' %} absolute top-6 md:top-10 left-6 md:left-10 right-6 md:right-10 z-10 {% endif %}">
{% if title is defined and title %}
<h2 class="text-3xl font-semibold {{ titleClass }}">{{ title }}</h2>
{% endif %}
<div class="flex items-center justify-center space-x-4 {% if variant is defined and variant == 'small' %} justify-between w-full {% endif %}">
{% if variant is defined and variant == 'small' %}
{% if carousel.showPagination %}
<div x-ref="pagination"
class="swiper-pagination-{{ uniqueId }} swiper-pagination mt-0 !w-auto {{ paginationClass }}" {{ swiper_pagination_attribute }}></div>
{% endif %}
{% else %}
{% if showCTA %}
{% render "@template-button" with {
href: CTA.href,
external: CTA.external,
color: CTA.color ~ "-subtle",
label: CTA.label,
button_class:"hidden sm:inline-flex mx-auto",
button_attribute: cta_button_attribute,
} %}
{% endif %}
{% endif %}
{% if carousel.showNavigation %}
<div class="{% if carousel.showNavigationMobile == false %} hidden sm:flex {% endif %} flex space-x-2">
{% render "@template-button" with {
color: carousel.color ~ "-subtle",
type: "only-icon",
icon: {
name: "library--chevron-left"
},
button_class:"carousel-button-prev-" ~ uniqueId ~ " before:border-none",
button_attribute: swiper_navigation_attribute
} %}
{% render "@template-button" with {
color: carousel.color ~ "-subtle",
type: "only-icon",
icon: {
name: "library--chevron-right"
},
button_class:"carousel-button-next-" ~ uniqueId ~ " before:border-none",
button_attribute: swiper_navigation_attribute
} %}
</div>
{% endif %}
</div>
</div>
<div x-ref="swiperContainer" id="{{ uniqueId }}" class="swiper md:h-full">
<div class="swiper-wrapper {{ wrapperClass }}">
{% for index, slide in slides %}
<div
class="swiper-slide !transform-none {{ slideClass }}"
data-theme="{{ slide.theme|default('light') }}"
data-hash="index-{{ uniqueId }}-{{ index }}"
>
{% if slide.template %}
{% render "@" ~ slide.template with {
slide: slide
} %}
{% else %}
{{ slide.content|raw }}
{% endif %}
</div>
{% endfor %}
</div>
{% if variant is defined and variant != 'small' and carousel.showPagination %}
<div x-ref="pagination"
class="swiper-pagination-{{ uniqueId }} swiper-pagination {{ paginationClass }}" {{ swiper_pagination_attribute }}></div>
{% endif %}
{% if CTA %}
<div class="flex sm:hidden mt-6">
{% render "@template-button" with {
href: CTA.href,
external: CTA.external,
color: CTA.color ~ "-subtle",
label: CTA.label,
button_class:"mx-auto",
button_attribute: cta_button_attribute
} %}
</div>
{% endif %}
</div>
</div>
<script>
function {{ carouselFunctionName }}() {
return {
swiper: null,
options: {{ carousel|json_encode|raw }},
componentId: '{{ uniqueId }}',
init() {
this.initSwiper();
if (typeof this.customFunction === 'function') {
this.customFunction();
}
},
initSwiper() {
this.swiper = new Swiper(this.$refs.swiperContainer, {
slidesPerView: this.options.slidesPerView.mobile,
spaceBetween: this.options.spaceBetween,
pagination: this.options.showPagination ? {
el: '.swiper-pagination-' + this.componentId,
clickable: true,
bulletClass: 'bullet',
bulletActiveClass: 'active',
} : false,
navigation: this.options.showNavigation ? {
nextEl: '.carousel-button-next-' + this.componentId,
prevEl: '.carousel-button-prev-' + this.componentId
} : false,
hashNavigation: {
watchState: true,
},
breakpoints: {
640: {
slidesPerView: this.options.slidesPerView.tablet
},
1024: {
slidesPerView: this.options.slidesPerView.desktop
}
},
on: {
slideChange: () => this.updateSliderTheme(),
},
});
this.updateSliderTheme();
},
updateSliderTheme() {
const activeSlide = this.swiper.slides[this.swiper.activeIndex];
const theme = activeSlide.getAttribute('data-slider-theme');
this.sliderTheme = theme || 'light';
},
customFunction() {
// Custom function can be defined here and overridden outside.
{% if customFunction is defined and customFunction %}
{{ customFunction }}
{% endif %}
}
};
}
</script>
{
"title": "En ce moment dans votre magasin",
"variant": "default",
"showCTA": false,
"CTA": {
"label": "Voir toute la collection",
"href": "https://www.afflelou.com/",
"external": true,
"color": "dark"
},
"paginationClass": "swiper-light",
"carousel": {
"color": "dark",
"slidesPerView": {
"mobile": 1,
"tablet": 1,
"desktop": 1
},
"spaceBetween": 30,
"showPagination": false,
"showNavigation": true,
"showNavigationMobile": false
},
"slides": [
{
"template": "storeinfocard",
"variant": "default",
"image": "/img/store/newsStore1.png",
"title": "Needle yet value-added first product strategic loss",
"description": "Launch optimize charts post bells algorithm low-hanging hear.",
"cta": {
"label": "Réserver un essayage"
}
},
{
"template": "storeinfocard",
"variant": "image-only",
"image": "/img/store/newsStore2.png"
},
{
"template": "storeinfocard",
"variant": "text-only",
"title": "Needle yet value-added first product strategic loss",
"description": "Launch optimize charts post bells algorithm low-hanging hear. Lunch place building email 2 search. Initiative anyway sandwich globalize right. Economy work run or weeks see without bells inclusion beforehand.",
"cta": {
"label": "Réserver un essayage"
}
},
{
"template": "storeinfocard",
"variant": "default",
"image": "/img/store/newsStore3.png",
"title": "Needle yet value-added first product strategic loss",
"description": "Launch optimize charts post bells algorithm low-hanging hear.",
"cta": {
"label": "Réserver un essayage"
}
}
],
"wrapperClass": "h-auto",
"slideClass": "h-auto",
"customFunction": "this.$nextTick(() => {\n\t\t\t\t\t\t\t\t\t\t const updateContentHeight = () => {\n\t\t\t\t\t\t\t\t\t\t const contentBlocks = document.querySelectorAll('[x-ref=\"contentBlock\"]');\n\t\t\t\t\t\t\t\t\t\t const maxHeight = Array.from(contentBlocks).reduce((max, block) => \n\t\t\t\t\t\t\t\t\t\t Math.max(max, block.offsetHeight), 400);\n\t\t\t\t\t\t\t\t\t\t document.documentElement.style.setProperty('--content-height', `${maxHeight}px`);\n\t\t\t\t\t\t\t\t\t\t };\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t // Initial update\n\t\t\t\t\t\t\t\t\t\t updateContentHeight();\n\t\t\t\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t\t\t // Resize listener avec debounce\n\t\t\t\t\t\t\t\t\t\t let resizeTimer;\n\t\t\t\t\t\t\t\t\t\t window.addEventListener('resize', () => {\n\t\t\t\t\t\t\t\t\t\t clearTimeout(resizeTimer);\n\t\t\t\t\t\t\t\t\t\t resizeTimer = setTimeout(updateContentHeight, 250);\n\t\t\t\t\t\t\t\t\t\t });\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t // Écouter les événements de breakpoint Swiper\n\t\t\t\t\t\t\t\t\t\t this.swiper.on('breakpoint', updateContentHeight);\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t // Écouter quand le DOM est modifié\n\t\t\t\t\t\t\t\t\t\t const observer = new MutationObserver(updateContentHeight);\n\t\t\t\t\t\t\t\t\t\t observer.observe(this.$refs.swiperContainer, {\n\t\t\t\t\t\t\t\t\t\t childList: true,\n\t\t\t\t\t\t\t\t\t\t subtree: true,\n\t\t\t\t\t\t\t\t\t\t attributes: true\n\t\t\t\t\t\t\t\t\t\t });\n\t\t\t\t\t\t\t\t\t\t });\n\t\t\t\t\t\t\t\t\t\t"
}
A reusable component for displaying product cards, compatible with both carousels and grids.
The component can be configured via the productCard.config.js
file. Here are the available options:
Object containing product information:
url
: Product page URLopenInNewTab
: Boolean to open link in new tabimage
: Path to main product imagehoverImage
: Path to image displayed on hovername
: Product namebrand
: Product branddescription
: Product descriptioncolors
: Text indicating available colorsprice
: Product priceoldPrice
: Original price (for promotions)discountText
: Promotion textwebLabel
: Web price labelinlinePrice
: Boolean to display price inline with titlebuttonColor
: Action buttons colorbuttonColorHover
: Action buttons hover colorAction buttons configuration:
showActionButtons
: Boolean to show/hide action buttonsshowFavoriteButton
: Boolean to show/hide favorite buttonshowQuickViewButton
: Boolean to show/hide quick view buttonAlpine.js attributes configuration for reactivity:
alpineAttribute: {
productInfo: {
brand: 'x-text="product.brand"',
name: 'x-text="product.name"',
description: 'x-text="product.description"',
colors: 'x-text="product.colors"',
price: 'x-text="product.price"',
webLabel: 'x-text="product.webLabel"'
}
}
default
: Standard display with image, title, and pricewith-hover
: Variant with image hover effectwith-discount
: Variant with original price and promotion textinline-price
: Variant with price inline with title{# Basic usage #}
{% include '@components/product/productCard.twig' with {
product: {
url: '/product/1',
image: '/path/to/image.jpg',
name: 'Product name',
price: '199.00'
}
} %}
{# Usage with all options #}
{% include '@components/product/productCard.twig' with {
product: {
url: '/product/1',
openInNewTab: false,
image: '/path/to/image.jpg',
hoverImage: '/path/to/hover-image.jpg',
name: 'Product name',
brand: 'Brand',
description: 'Product description',
colors: '3 colors available',
price: '199.00',
oldPrice: '249.00',
discountText: '-20%',
webLabel: 'WEB PRICE',
inlinePrice: true,
buttonColor: 'light',
buttonColorHover: 'dark',
showActionButtons: true,
showFavoriteButton: true,
showQuickViewButton: true,
addCart: true,
alpineAttribute: {
productInfo: {
brand: 'x-text="product.brand"'
// ... other Alpine.js attributes
}
}
},
class: 'custom-class'
} %}
hoverImage
is definedinlinePrice
template-button
component for actionsThe component uses Tailwind CSS classes for styling:
The component integrates with the template-button component for: