feat: render product teasers in landscape mode when less than 3

addresses #86965w98p
This commit is contained in:
Björn Fromme
2026-06-16 12:11:48 +02:00
parent 9933a0d3f7
commit fbe5a882c7
5 changed files with 378 additions and 234 deletions
@@ -23,56 +23,86 @@
<div data-controller="teasergrid" <div data-controller="teasergrid"
data-teasergrid-grid-class="grid-cols-2" data-teasergrid-grid-class="grid-cols-2"
data-teasergrid-disabled-value="false"> data-teasergrid-disabled-value="false">
<div class="flex items-center space-x-1 mb-2 sm:hidden"> <f:if condition="{teasers -> f:count()} <= 2">
<button type="button" <f:then>
class="opacity-50" <div class="grid gap-4 md:gap-8">
aria-label="kompakte Ansicht" <f:for each="{teasers}" as="teaser" iteration="iteration">
data-action="teasergrid#grid" <f:render partial="Product/Teaser" section="Teaser" arguments="{
data-teasergrid-target="button"> landscape: 1,
<svg class="w-5 h-5 text-ep-primary"> concept: teaser.concept,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use> hotel: teaser.hotel,
</svg> product: teaser.product,
</button> country: teaser.country,
<button type="button" region: teaser.region,
aria-label="vergrößerte Ansicht" productImage: teaser.product.images.original.0,
data-action="teasergrid#block" nights: teaser.nights,
data-teasergrid-target="button"> board: teaser.board,
<svg class="w-5 h-5 text-ep-primary"> minPrice: teaser.minPrice,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use> daytrip: teaser.daytrip,
</svg> season: teaser.season,
</button> forceHotelUid: teaser.forceHotelUid,
</div> dateFrom: teaser.minDateStart,
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8" dateTo: teaser.maxDateEnd,
data-teasergrid-target="container"> index: iteration.index
<f:for each="{teasers}" as="teaser" iteration="iteration"> }"/>
<f:render partial="Product/Teaser" section="Teaser" arguments="{ </f:for>
concept: teaser.concept, </div>
hotel: teaser.hotel, </f:then>
product: teaser.product, <f:else>
country: teaser.country, <div class="flex items-center space-x-1 mb-2 sm:hidden">
region: teaser.region, <button type="button"
productImage: teaser.product.images.original.0, class="opacity-50"
nights: teaser.nights, aria-label="kompakte Ansicht"
board: teaser.board, data-action="teasergrid#grid"
minPrice: teaser.minPrice, data-teasergrid-target="button">
daytrip: teaser.daytrip, <svg class="w-5 h-5 text-ep-primary">
season: teaser.season, <use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use>
forceHotelUid: teaser.forceHotelUid, </svg>
dateFrom: teaser.minDateStart, </button>
dateTo: teaser.maxDateEnd, <button type="button"
index: iteration.index aria-label="vergrößerte Ansicht"
}"/> data-action="teasergrid#block"
</f:for> data-teasergrid-target="button">
</div> <svg class="w-5 h-5 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use>
</svg>
</button>
</div>
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
data-teasergrid-target="container">
<f:for each="{teasers}" as="teaser" iteration="iteration">
<f:render partial="Product/Teaser" section="Teaser" arguments="{
concept: teaser.concept,
hotel: teaser.hotel,
product: teaser.product,
country: teaser.country,
region: teaser.region,
productImage: teaser.product.images.original.0,
nights: teaser.nights,
board: teaser.board,
minPrice: teaser.minPrice,
daytrip: teaser.daytrip,
season: teaser.season,
forceHotelUid: teaser.forceHotelUid,
dateFrom: teaser.minDateStart,
dateTo: teaser.maxDateEnd,
index: iteration.index
}"/>
</f:for>
</div>
</f:else>
</f:if>
</div> </div>
<f:if condition="{settings.hideMoreLink}"> <f:if condition="{settings.hideMoreLink}">
<f:else> <f:else>
<f:link.typolink class="button bg-button ml-4" <div class="text-right mt-4">
parameter="{settings.defaultSearchPageUid}" <f:link.typolink class="button bg-button"
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}" parameter="{settings.defaultSearchPageUid}"
additionalAttributes="{rel: 'nofollow'}"> additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}"
Alle anzeigen additionalAttributes="{rel: 'nofollow'}">
</f:link.typolink> Alle anzeigen
</f:link.typolink>
</div>
</f:else> </f:else>
</f:if> </f:if>
</div> </div>
@@ -36,55 +36,81 @@
data-controller="teasergrid" data-controller="teasergrid"
data-teasergrid-grid-class="grid-cols-2" data-teasergrid-grid-class="grid-cols-2"
data-teasergrid-disabled-value="false"> data-teasergrid-disabled-value="false">
<f:if condition="{teasers}"> <f:if condition="{teasers -> f:count() <= 2}">
<div class="flex items-center space-x-1 mb-2 sm:hidden"> <f:then>
<button type="button" <div class="grid gap-4 md:gap-8">
class="opacity-50" <f:for each="{teasers}" as="teaser" iteration="iteration">
aria-label="kompakte Ansicht" <f:render partial="Product/Teaser" section="Teaser" arguments="{
data-action="teasergrid#grid" landscape: 1,
data-teasergrid-target="button"> concept: teaser.concept,
{ep:icon(icon: 'teaser-grid', class: 'w-5 h-5 text-ep-primary')} hotel: teaser.hotel,
</button> product: teaser.product,
<button type="button" country: teaser.country,
aria-label="vergrößerte Ansicht" region: teaser.region,
data-action="teasergrid#block" productImage: teaser.product.images.original.0,
data-teasergrid-target="button"> nights: teaser.nights,
{ep:icon(icon: 'teaser-single', class: 'w-5 h-5 text-ep-primary')} board: teaser.board,
</button> minPrice: teaser.minPrice,
</div> daytrip: teaser.daytrip,
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8" season: teaser.season,
data-teasergrid-target="container"> forceHotelUid: teaser.forceHotelUid,
<f:for each="{teasers}" as="teaser" iteration="iteration"> dateFrom: teaser.minDateStart,
<f:render partial="Product/Teaser" section="Teaser" arguments="{ dateTo: teaser.maxDateEnd,
concept: teaser.concept, index: iteration.index
hotel: teaser.hotel, }"/>
product: teaser.product, </f:for>
country: teaser.country, </div>
region: teaser.region, </f:then>
productImage: teaser.product.images.original.0, <f:else>
nights: teaser.nights, <div class="flex items-center space-x-1 mb-2 sm:hidden">
board: teaser.board, <button type="button"
minPrice: teaser.minPrice, class="opacity-50"
daytrip: teaser.daytrip, aria-label="kompakte Ansicht"
season: teaser.season, data-action="teasergrid#grid"
forceHotelUid: teaser.forceHotelUid, data-teasergrid-target="button">
dateFrom: teaser.minDateStart, {ep:icon(icon: 'teaser-grid', class: 'w-5 h-5 text-ep-primary')}
dateTo: teaser.maxDateEnd, </button>
index: iteration.index <button type="button"
}"/> aria-label="vergrößerte Ansicht"
</f:for> data-action="teasergrid#block"
</div> data-teasergrid-target="button">
<f:if condition="{settings.hideMoreLink}"> {ep:icon(icon: 'teaser-single', class: 'w-5 h-5 text-ep-primary')}
<f:else> </button>
<div class="text-right mt-4"> </div>
<f:link.typolink class="button bg-button" <div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
parameter="{settings.defaultSearchPageUid}" data-teasergrid-target="container">
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}"> <f:for each="{teasers}" as="teaser" iteration="iteration">
Alle anzeigen <f:render partial="Product/Teaser" section="Teaser" arguments="{
</f:link.typolink> concept: teaser.concept,
</div> hotel: teaser.hotel,
</f:else> product: teaser.product,
</f:if> country: teaser.country,
region: teaser.region,
productImage: teaser.product.images.original.0,
nights: teaser.nights,
board: teaser.board,
minPrice: teaser.minPrice,
daytrip: teaser.daytrip,
season: teaser.season,
forceHotelUid: teaser.forceHotelUid,
dateFrom: teaser.minDateStart,
dateTo: teaser.maxDateEnd,
index: iteration.index
}"/>
</f:for>
</div>
</f:else>
</f:if>
<f:if condition="{settings.hideMoreLink}">
<f:else>
<div class="text-right mt-4">
<f:link.typolink class="button bg-button"
parameter="{settings.defaultSearchPageUid}"
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}">
Alle anzeigen
</f:link.typolink>
</div>
</f:else>
</f:if> </f:if>
</div> </div>
</f:then> </f:then>
@@ -53,60 +53,87 @@
<f:if condition="{teasers}"> <f:if condition="{teasers}">
<f:then> <f:then>
<a id="teasers" class="anchor"></a> <a id="teasers" class="anchor"></a>
<div class="bg-zinc-50 p-4 mb-4" <div class="even:bg-zinc-50 even:p-4 mb-4"
data-controller="teasergrid" data-controller="teasergrid"
data-teasergrid-grid-class="grid-cols-2" data-teasergrid-grid-class="grid-cols-2"
data-teasergrid-disabled-value="false"> data-teasergrid-disabled-value="false">
<f:if condition="{teasers}"> <f:if condition="{teasers -> f:count()} <= 2">
<div class="flex items-center space-x-1 mb-2 sm:hidden"> <f:then>
<button type="button" <div class="grid gap-4 md:gap-8">
class="opacity-50" <f:for each="{teasers}" as="teaser" iteration="iteration">
aria-label="kompakte Ansicht" <f:render partial="Product/Teaser" section="Teaser" arguments="{
data-action="teasergrid#grid" landscape: 1,
data-teasergrid-target="button"> concept: teaser.concept,
{ep:icon(icon: 'teaser-grid', class: 'w-5 h-5 text-ep-primary')} hotel: teaser.hotel,
</button> product: teaser.product,
<button type="button" country: teaser.country,
aria-label="vergrößerte Ansicht" region: teaser.region,
data-action="teasergrid#block" productImage: teaser.product.images.original.0,
data-teasergrid-target="button"> nights: teaser.nights,
{ep:icon(icon: 'teaser-single', class: 'w-5 h-5 text-ep-primary')} board: teaser.board,
</button> minPrice: teaser.minPrice,
</div> daytrip: teaser.daytrip,
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8" season: teaser.season,
data-teasergrid-target="container"> forceHotelUid: teaser.forceHotelUid,
<f:for each="{teasers}" as="teaser" iteration="iteration"> dateFrom: teaser.minDateStart,
<f:render partial="Product/Teaser" section="Teaser" arguments="{ dateTo: teaser.maxDateEnd,
concept: teaser.concept, index: iteration.index,
hotel: teaser.hotel, skiPassIncluded: teaser.skiPassIncluded
product: teaser.product, }"/>
country: teaser.country, </f:for>
region: teaser.region, </div>
productImage: teaser.product.images.original.0, </f:then>
nights: teaser.nights, <f:else>
board: teaser.board, <div class="flex items-center space-x-1 mb-2 sm:hidden">
minPrice: teaser.minPrice, <button type="button"
daytrip: teaser.daytrip, class="opacity-50"
season: teaser.season, aria-label="kompakte Ansicht"
forceHotelUid: teaser.forceHotelUid, data-action="teasergrid#grid"
dateFrom: teaser.minDateStart, data-teasergrid-target="button">
dateTo: teaser.maxDateEnd, {ep:icon(icon: 'teaser-grid', class: 'w-5 h-5 text-ep-primary')}
index: iteration.index, </button>
skiPassIncluded: teaser.skiPassIncluded <button type="button"
}"/> aria-label="vergrößerte Ansicht"
</f:for> data-action="teasergrid#block"
</div> data-teasergrid-target="button">
<f:if condition="{settings.hideMoreLink}"> {ep:icon(icon: 'teaser-single', class: 'w-5 h-5 text-ep-primary')}
<f:else> </button>
<div class="text-right mt-4"> </div>
<f:link.typolink class="button bg-button" <div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
parameter="{settings.defaultSearchPageUid}" data-teasergrid-target="container">
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}"> <f:for each="{teasers}" as="teaser" iteration="iteration">
Alle anzeigen <f:render partial="Product/Teaser" section="Teaser" arguments="{
</f:link.typolink> concept: teaser.concept,
</div> hotel: teaser.hotel,
</f:else> product: teaser.product,
</f:if> country: teaser.country,
region: teaser.region,
productImage: teaser.product.images.original.0,
nights: teaser.nights,
board: teaser.board,
minPrice: teaser.minPrice,
daytrip: teaser.daytrip,
season: teaser.season,
forceHotelUid: teaser.forceHotelUid,
dateFrom: teaser.minDateStart,
dateTo: teaser.maxDateEnd,
index: iteration.index,
skiPassIncluded: teaser.skiPassIncluded
}"/>
</f:for>
</div>
</f:else>
</f:if>
<f:if condition="{settings.hideMoreLink}">
<f:else>
<div class="text-right mt-4">
<f:link.typolink class="button bg-button"
parameter="{settings.defaultSearchPageUid}"
additionalParams="f={filterSettings -> ep:filterSettingsEncoder()}">
Alle anzeigen
</f:link.typolink>
</div>
</f:else>
</f:if> </f:if>
</div> </div>
</f:then> </f:then>
@@ -34,49 +34,79 @@
data-controller="teasergrid" data-controller="teasergrid"
data-teasergrid-grid-class="grid-cols-2" data-teasergrid-grid-class="grid-cols-2"
data-teasergrid-disabled-value="false"> data-teasergrid-disabled-value="false">
<div class="flex items-center space-x-1 mb-2 sm:hidden"> <f:if condition="{teasers -> f:count()} <= 2">
<button type="button" <f:then>
class="opacity-50" <div class="grid gap-4 md:gap-8">
aria-label="kompakte Ansicht" <f:for each="{teasers}" as="teaser" iteration="iteration">
data-action="teasergrid#grid" <f:render partial="Product/Teaser" section="Teaser" arguments="{
data-teasergrid-target="button"> landscape: 1,
<svg class="w-12 h-12 text-ep-primary"> concept: teaser.concept,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use> hotel: teaser.hotel,
</svg> product: teaser.product,
</button> country: teaser.country,
<button type="button" region: teaser.region,
aria-label="vergrößerte Ansicht" productImage: teaser.product.images.original.0,
data-action="teasergrid#block" nights: teaser.nights,
data-teasergrid-target="button"> board: teaser.board,
<svg class="w-12 h-12 text-ep-primary"> minPrice: teaser.minPrice,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use> daytrip: teaser.daytrip,
</svg> season: teaser.season,
</button> forceHotelUid: teaser.forceHotelUid,
</div> dateFrom: dateFrom,
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8" dateTo: dateTo,
data-teasergrid-target="container"> index: iteration.index,
<f:for each="{teasers}" as="teaser" iteration="iteration"> globalConceptCode: settings.globalConceptCode,
<f:render partial="Product/Teaser" section="Teaser" arguments="{ skiPassIncluded: teaser.skiPassIncluded
concept: teaser.concept, }"/>
hotel: teaser.hotel, </f:for>
product: teaser.product, </div>
country: teaser.country, </f:then>
region: teaser.region, <f:else>
productImage: teaser.product.images.original.0, <div class="flex items-center space-x-1 mb-2 sm:hidden">
nights: teaser.nights, <button type="button"
board: teaser.board, class="opacity-50"
minPrice: teaser.minPrice, aria-label="kompakte Ansicht"
daytrip: teaser.daytrip, data-action="teasergrid#grid"
season: teaser.season, data-teasergrid-target="button">
forceHotelUid: teaser.forceHotelUid, <svg class="w-12 h-12 text-ep-primary">
dateFrom: dateFrom, <use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use>
dateTo: dateTo, </svg>
index: iteration.index, </button>
globalConceptCode: settings.globalConceptCode, <button type="button"
skiPassIncluded: teaser.skiPassIncluded aria-label="vergrößerte Ansicht"
}"/> data-action="teasergrid#block"
</f:for> data-teasergrid-target="button">
</div> <svg class="w-12 h-12 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use>
</svg>
</button>
</div>
<div class="grid grid-cols-2 xl:grid-cols-3 gap-4 md:gap-8"
data-teasergrid-target="container">
<f:for each="{teasers}" as="teaser" iteration="iteration">
<f:render partial="Product/Teaser" section="Teaser" arguments="{
concept: teaser.concept,
hotel: teaser.hotel,
product: teaser.product,
country: teaser.country,
region: teaser.region,
productImage: teaser.product.images.original.0,
nights: teaser.nights,
board: teaser.board,
minPrice: teaser.minPrice,
daytrip: teaser.daytrip,
season: teaser.season,
forceHotelUid: teaser.forceHotelUid,
dateFrom: dateFrom,
dateTo: dateTo,
index: iteration.index,
globalConceptCode: settings.globalConceptCode,
skiPassIncluded: teaser.skiPassIncluded
}"/>
</f:for>
</div>
</f:else>
</f:if>
<f:if condition="{settings.hideMoreLink}"> <f:if condition="{settings.hideMoreLink}">
<f:else> <f:else>
<div class="text-right mt-4"> <div class="text-right mt-4">
@@ -14,50 +14,81 @@
arguments="{header: data.header, subheader: data.subheader, layout: data.header_layout, link: data.header_link, default: settings.defaultHeaderType}" /> arguments="{header: data.header, subheader: data.subheader, layout: data.header_layout, link: data.header_link, default: settings.defaultHeaderType}" />
<f:if condition="{teasers}"> <f:if condition="{teasers}">
<f:then> <f:then>
<div class="flex items-center space-x-1 mb-2 sm:hidden"> <f:if condition="{teasers -> f:count()} <= 2">
<button type="button" <f:then>
class="opacity-50" <f:for each="{teasers}" as="teaser" iteration="iteration">
aria-label="kompakte Ansicht" <div class="grid gap-y-4 md:gap-y-8">
data-action="teasergrid#grid" <f:render partial="Product/Teaser" section="Teaser" arguments="{
data-teasergrid-target="button"> landscape: 1,
<svg class="w-12 h-12 text-ep-primary"> concept: teaser.concept,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use> hotel: teaser.hotel,
</svg> product: teaser.product,
</button> country: teaser.country,
<button type="button" region: teaser.region,
aria-label="vergrößerte Ansicht" productImage: teaser.product.images.original.0,
data-action="teasergrid#block" nights: teaser.nights,
data-teasergrid-target="button"> board: teaser.board,
<svg class="w-12 h-12 text-ep-primary"> minPrice: teaser.minPrice,
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use> daytrip: teaser.daytrip,
</svg> season: teaser.season,
</button> forceHotelUid: teaser.forceHotelUid,
</div> dateFrom: dateFrom,
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8" dateTo: dateTo,
data-teasergrid-target="container"> new: teaser.new,
<f:for each="{teasers}" as="teaser" iteration="iteration"> index: iteration.index,
<f:render partial="Product/Teaser" section="Teaser" arguments="{ globalConceptCode: settings.globalConceptCode,
concept: teaser.concept, skiPassIncluded: teaser.skiPassIncluded
hotel: teaser.hotel, }"/>
product: teaser.product, </div>
country: teaser.country, </f:for>
region: teaser.region, </f:then>
productImage: teaser.product.images.original.0, <f:else>
nights: teaser.nights, <div class="flex items-center space-x-1 mb-2 sm:hidden">
board: teaser.board, <button type="button"
minPrice: teaser.minPrice, class="opacity-50"
daytrip: teaser.daytrip, aria-label="kompakte Ansicht"
season: teaser.season, data-action="teasergrid#grid"
forceHotelUid: teaser.forceHotelUid, data-teasergrid-target="button">
dateFrom: dateFrom, <svg class="w-12 h-12 text-ep-primary">
dateTo: dateTo, <use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-grid"></use>
new: teaser.new, </svg>
index: iteration.index, </button>
globalConceptCode: settings.globalConceptCode, <button type="button"
skiPassIncluded: teaser.skiPassIncluded aria-label="vergrößerte Ansicht"
}"/> data-action="teasergrid#block"
</f:for> data-teasergrid-target="button">
</div> <svg class="w-12 h-12 text-ep-primary">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-teaser-single"></use>
</svg>
</button>
</div>
<div class="grid grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8"
data-teasergrid-target="container">
<f:for each="{teasers}" as="teaser" iteration="iteration">
<f:render partial="Product/Teaser" section="Teaser" arguments="{
concept: teaser.concept,
hotel: teaser.hotel,
product: teaser.product,
country: teaser.country,
region: teaser.region,
productImage: teaser.product.images.original.0,
nights: teaser.nights,
board: teaser.board,
minPrice: teaser.minPrice,
daytrip: teaser.daytrip,
season: teaser.season,
forceHotelUid: teaser.forceHotelUid,
dateFrom: dateFrom,
dateTo: dateTo,
new: teaser.new,
index: iteration.index,
globalConceptCode: settings.globalConceptCode,
skiPassIncluded: teaser.skiPassIncluded
}"/>
</f:for>
</div>
</f:else>
</f:if>
<f:if condition="{settings.hideMoreLink}"> <f:if condition="{settings.hideMoreLink}">
<f:else> <f:else>
<div class="text-right mt-4"> <div class="text-right mt-4">