@shopware/cms-base-layer 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +328 -13
- package/app/app.config.ts +7 -0
- package/app/assets/icons/check-circle.svg +3 -0
- package/app/assets/icons/checkmark.svg +3 -0
- package/app/assets/icons/chevron.svg +3 -0
- package/app/assets/icons/exclamation-circle.svg +3 -0
- package/app/assets/icons/star-empty.svg +3 -0
- package/app/assets/icons/star-filled.svg +3 -0
- package/app/assets/icons/user.svg +1 -0
- package/app/components/SwCategoryNavigation.vue +76 -0
- package/app/components/SwCategoryNavigationLink.vue +128 -0
- package/{components → app/components}/SwContactForm.vue +27 -27
- package/app/components/SwFilterChips.vue +144 -0
- package/app/components/SwListingProductPrice.vue +89 -0
- package/{components → app/components}/SwNewsletterForm.vue +45 -34
- package/{components → app/components}/SwPagination.vue +3 -5
- package/{components → app/components}/SwProductAddToCart.vue +22 -27
- package/app/components/SwProductCard.vue +170 -0
- package/app/components/SwProductCardDetails.vue +57 -0
- package/app/components/SwProductCardImage.vue +87 -0
- package/app/components/SwProductCardSkeleton.vue +33 -0
- package/app/components/SwProductListingFilter.vue +64 -0
- package/app/components/SwProductListingFilters.vue +308 -0
- package/{components → app/components}/SwProductReviews.vue +28 -13
- package/app/components/SwProductReviewsForm.vue +292 -0
- package/app/components/SwQuantitySelect.vue +106 -0
- package/{components → app/components}/SwSlider.vue +4 -4
- package/app/components/SwSortDropdown.vue +83 -0
- package/app/components/SwStockInfo.vue +44 -0
- package/{components → app/components}/SwVariantConfigurator.vue +1 -1
- package/app/components/listing-filters/SwFilterPrice.vue +214 -0
- package/app/components/listing-filters/SwFilterProperties.vue +113 -0
- package/app/components/listing-filters/SwFilterRating.vue +90 -0
- package/app/components/listing-filters/SwFilterShippingFree.vue +107 -0
- package/{components → app/components}/public/cms/CmsPage.vue +19 -4
- package/{components → app/components}/public/cms/block/CmsBlockGalleryBuybox.vue +5 -5
- package/{components → app/components}/public/cms/block/CmsBlockImageBubbleRow.vue +5 -5
- package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +41 -0
- package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +42 -0
- package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +37 -0
- package/{components → app/components}/public/cms/block/CmsBlockImageSimpleGrid.vue +11 -5
- package/{components → app/components}/public/cms/block/CmsBlockImageText.vue +7 -3
- package/{components → app/components}/public/cms/block/CmsBlockImageTextBubble.vue +13 -16
- package/{components → app/components}/public/cms/block/CmsBlockImageTextCover.vue +7 -9
- package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +88 -0
- package/app/components/public/cms/block/CmsBlockImageTextRow.vue +53 -0
- package/{components → app/components}/public/cms/block/CmsBlockImageThreeColumn.vue +10 -4
- package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +37 -0
- package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +37 -0
- package/{components → app/components}/public/cms/block/CmsBlockProductHeading.vue +1 -1
- package/{components → app/components}/public/cms/block/CmsBlockProductThreeColumn.vue +10 -4
- package/{components → app/components}/public/cms/block/CmsBlockSidebarFilter.vue +3 -1
- package/app/components/public/cms/block/CmsBlockTextOnImage.vue +30 -0
- package/{components → app/components}/public/cms/block/CmsBlockTextTeaserSection.vue +4 -4
- package/{components → app/components}/public/cms/block/CmsBlockTextTwoColumn.vue +3 -5
- package/app/components/public/cms/element/CmsElementBuyBox.vue +145 -0
- package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +53 -0
- package/{components → app/components}/public/cms/element/CmsElementCrossSelling.vue +3 -3
- package/{components → app/components}/public/cms/element/CmsElementImage.vue +52 -13
- package/app/components/public/cms/element/CmsElementImageGallery.vue +158 -0
- package/{components → app/components}/public/cms/element/CmsElementImageSlider.vue +2 -2
- package/{components → app/components}/public/cms/element/CmsElementProductBox.vue +2 -1
- package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +217 -0
- package/{components → app/components}/public/cms/element/CmsElementProductListing.vue +23 -94
- package/app/components/public/cms/element/CmsElementProductName.vue +11 -0
- package/{components → app/components}/public/cms/element/CmsElementProductSlider.vue +4 -4
- package/{components → app/components}/public/cms/element/CmsElementText.vue +8 -2
- package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.vue +8 -2
- package/app/components/public/cms/element/SwProductListingPagination.vue +70 -0
- package/{components → app/components}/public/cms/section/CmsSectionDefault.vue +1 -1
- package/app/components/public/cms/section/CmsSectionSidebar.vue +36 -0
- package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +28 -0
- package/app/components/ui/BaseButton.vue +99 -0
- package/app/components/ui/BaseIcon.vue +15 -0
- package/app/components/ui/Checkbox.vue +49 -0
- package/app/components/ui/CheckmarkIcon.vue +23 -0
- package/app/components/ui/ChevronIcon.vue +37 -0
- package/app/components/ui/ExclamationIcon.vue +11 -0
- package/app/components/ui/IconButton.vue +32 -0
- package/app/components/ui/RadioButton.vue +26 -0
- package/app/components/ui/StarIcon.vue +18 -0
- package/app/components/ui/SwitchButton.vue +100 -0
- package/app/components/ui/UserIcon.vue +11 -0
- package/app/components/ui/WishlistIcon.vue +20 -0
- package/app/composables/useImagePlaceholder.ts +27 -0
- package/{helpers → app/helpers}/clientOnly.ts +5 -0
- package/app/providers/shopware.test.ts +213 -0
- package/app/providers/shopware.ts +107 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +2 -2
- package/index.d.ts +12 -0
- package/nuxt.config.ts +80 -6
- package/package.json +29 -21
- package/uno.config.ts +83 -0
- package/components/SwCategoryNavigation.vue +0 -44
- package/components/SwCategoryNavigationLink.vue +0 -57
- package/components/SwListingProductPrice.vue +0 -89
- package/components/SwProductCard.vue +0 -286
- package/components/SwProductListingFilter.vue +0 -42
- package/components/SwProductListingFilters.vue +0 -292
- package/components/listing-filters/SwFilterPrice.vue +0 -160
- package/components/listing-filters/SwFilterProperties.vue +0 -123
- package/components/listing-filters/SwFilterRating.vue +0 -101
- package/components/listing-filters/SwFilterShippingFree.vue +0 -104
- package/components/public/cms/block/CmsBlockImageFourColumn.vue +0 -29
- package/components/public/cms/block/CmsBlockImageHighlightRow.vue +0 -27
- package/components/public/cms/block/CmsBlockImageTextGallery.vue +0 -85
- package/components/public/cms/block/CmsBlockImageTextRow.vue +0 -43
- package/components/public/cms/block/CmsBlockImageThreeCover.vue +0 -27
- package/components/public/cms/block/CmsBlockImageTwoColumn.vue +0 -25
- package/components/public/cms/block/CmsBlockTextOnImage.vue +0 -20
- package/components/public/cms/element/CmsBlockHtml.md +0 -1
- package/components/public/cms/element/CmsElementBuyBox.vue +0 -190
- package/components/public/cms/element/CmsElementCategoryNavigation.vue +0 -167
- package/components/public/cms/element/CmsElementImageGallery.vue +0 -249
- package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +0 -123
- package/components/public/cms/element/CmsElementProductName.vue +0 -10
- package/components/public/cms/section/CmsSectionSidebar.vue +0 -49
- package/components/public/cms/skeleton/ProductCardSkeleton.vue +0 -44
- /package/{components → app/components}/SwMedia3D.vue +0 -0
- /package/{components → app/components}/SwProductGallery.vue +0 -0
- /package/{components → app/components}/SwProductPrice.vue +0 -0
- /package/{components → app/components}/SwProductUnits.vue +0 -0
- /package/{components → app/components}/SwSharedPrice.vue +0 -0
- /package/{components → app/components}/public/cms/CmsGenericBlock.md +0 -0
- /package/{components → app/components}/public/cms/CmsGenericBlock.vue +0 -0
- /package/{components → app/components}/public/cms/CmsGenericElement.md +0 -0
- /package/{components → app/components}/public/cms/CmsGenericElement.vue +0 -0
- /package/{components → app/components}/public/cms/CmsNoComponent.vue +0 -0
- /package/{components → app/components}/public/cms/CmsPage.md +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockCategoryNavigation.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockCenterText.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockCrossSelling.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockCustomForm.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockDefault.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockForm.vue +0 -0
- /package/{components/public/cms/element → app/components/public/cms/block}/CmsBlockHtml.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockImage.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockImageCover.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockImageGallery.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockImageSlider.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockProductDescriptionReviews.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockProductListing.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockProductSlider.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockText.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockTextHero.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockTextTeaser.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockTextThreeColumn.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockVimeoVideo.vue +0 -0
- /package/{components → app/components}/public/cms/block/CmsBlockYoutubeVideo.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementBuyBox.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementCategoryNavigation.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementCrossSelling.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementCustomForm.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementCustomForm.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementForm.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementForm.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementHtml.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementImage.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementImageGallery.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementImageSlider.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementProductBox.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementProductDescriptionReviews.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementProductListing.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementProductName.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementProductSlider.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementText.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.md +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.vue +0 -0
- /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.md +0 -0
- /package/{components → app/components}/public/cms/section/CmsSectionDefault.md +0 -0
- /package/{components → app/components}/public/cms/section/CmsSectionSidebar.md +0 -0
- /package/{helpers → app/helpers}/html-to-vue/ast.ts +0 -0
- /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.test.ts +0 -0
- /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.ts +0 -0
- /package/{helpers → app/helpers}/html-to-vue/renderToHtml.ts +0 -0
- /package/{helpers → app/helpers}/html-to-vue/renderer.ts +0 -0
- /package/{helpers → app/helpers}/media/isSpatial.ts +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsBlockImageTwoColumn } from "@shopware/composables";
|
|
3
|
+
import { useCmsBlock } from "#imports";
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
content: CmsBlockImageTwoColumn;
|
|
7
|
+
}>();
|
|
8
|
+
|
|
9
|
+
const { getSlotContent } = useCmsBlock(props.content);
|
|
10
|
+
|
|
11
|
+
const leftContent = getSlotContent("left");
|
|
12
|
+
const rightContent = getSlotContent("right");
|
|
13
|
+
</script>
|
|
14
|
+
<template>
|
|
15
|
+
<div class="cms-block-image-two-column flex flex-col md:flex-row justify-start items-start gap-6 w-full">
|
|
16
|
+
<div class="w-full md:flex-1 md:min-w-0">
|
|
17
|
+
<CmsGenericElement :content="leftContent" />
|
|
18
|
+
</div>
|
|
19
|
+
<div class="w-full md:flex-1 md:min-w-0">
|
|
20
|
+
<CmsGenericElement :content="rightContent" />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.cms-block-image-two-column :deep(.cms-element-image) {
|
|
27
|
+
@apply relative h-full w-full;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.cms-block-image-two-column :deep(.cms-element-image img) {
|
|
31
|
+
@apply w-full h-full object-cover;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.cms-block-image-two-column :deep(.cms-element-image-slider) {
|
|
35
|
+
@apply w-full;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -13,7 +13,7 @@ const rightContent = getSlotContent("right");
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<div class="cms-block-product-heading flex justify-between">
|
|
16
|
+
<div class="cms-block-product-heading flex justify-between pt-4">
|
|
17
17
|
<CmsGenericElement :content="leftContent" />
|
|
18
18
|
<CmsGenericElement :content="rightContent" />
|
|
19
19
|
</div>
|
|
@@ -14,9 +14,15 @@ const centerContent = getSlotContent("center");
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
-
<div class="
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<div class="flex flex-col sm:flex-row justify-start items-start gap-6 w-full">
|
|
18
|
+
<div class="w-full sm:flex-1">
|
|
19
|
+
<CmsGenericElement :content="leftContent" class="w-full" />
|
|
20
|
+
</div>
|
|
21
|
+
<div class="w-full sm:flex-1">
|
|
22
|
+
<CmsGenericElement :content="centerContent" class="w-full" />
|
|
23
|
+
</div>
|
|
24
|
+
<div class="w-full sm:flex-1">
|
|
25
|
+
<CmsGenericElement :content="rightContent" class="w-full" />
|
|
26
|
+
</div>
|
|
21
27
|
</div>
|
|
22
28
|
</template>
|
|
@@ -10,7 +10,9 @@ const props = defineProps<{
|
|
|
10
10
|
}>();
|
|
11
11
|
|
|
12
12
|
const { getSlotContent } = useCmsBlock(props.content);
|
|
13
|
-
const slotContent = getSlotContent(
|
|
13
|
+
const slotContent = getSlotContent(
|
|
14
|
+
"content",
|
|
15
|
+
) as unknown as CmsElementSidebarFilter;
|
|
14
16
|
</script>
|
|
15
17
|
<template>
|
|
16
18
|
<CmsElementSidebarFilter :content="slotContent" />
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsBlockTextOnImage } from "@shopware/composables";
|
|
3
|
+
import { getCmsLayoutConfiguration } from "@shopware/helpers";
|
|
4
|
+
import { useCmsBlock } from "#imports";
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
content: CmsBlockTextOnImage;
|
|
8
|
+
}>();
|
|
9
|
+
|
|
10
|
+
const { getSlotContent } = useCmsBlock(props.content);
|
|
11
|
+
const { cssClasses, layoutStyles } = getCmsLayoutConfiguration(props.content);
|
|
12
|
+
|
|
13
|
+
const slotContent = getSlotContent("content");
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div
|
|
18
|
+
class="cms-block-text-on-image min-h-[500px] flex items-center justify-center py-20 px-4 bg-cover bg-center bg-no-repeat relative"
|
|
19
|
+
:class="cssClasses"
|
|
20
|
+
:style="layoutStyles as any"
|
|
21
|
+
>
|
|
22
|
+
<div class="relative z-10 text-center max-w-6xl">
|
|
23
|
+
<CmsGenericElement
|
|
24
|
+
v-if="slotContent"
|
|
25
|
+
:content="slotContent"
|
|
26
|
+
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
@@ -6,15 +6,15 @@ defineProps<{
|
|
|
6
6
|
}>();
|
|
7
7
|
</script>
|
|
8
8
|
<template>
|
|
9
|
-
<div class="
|
|
9
|
+
<div class="mx-auto grid md:grid-cols-3 gap-4 py-6">
|
|
10
10
|
<CmsGenericElement
|
|
11
11
|
v-for="(slot, i) in content.slots"
|
|
12
12
|
:key="slot.id"
|
|
13
13
|
:content="slot"
|
|
14
|
-
class="cms-block-text-teaser-section
|
|
14
|
+
class="cms-block-text-teaser-section"
|
|
15
15
|
:class="{
|
|
16
|
-
'
|
|
17
|
-
'
|
|
16
|
+
'md:col-span-1': i === 0,
|
|
17
|
+
'md:col-span-2': i === 1,
|
|
18
18
|
}"
|
|
19
19
|
/>
|
|
20
20
|
</div>
|
|
@@ -13,16 +13,14 @@ const rightContent = getSlotContent("right");
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<article
|
|
17
|
-
class="cms-block-text-two-column flex justify-center gap-5 md:gap-20"
|
|
18
|
-
>
|
|
16
|
+
<article class="cms-block-text-two-column grid md:grid-cols-2 gap-5 md:gap-20">
|
|
19
17
|
<CmsGenericElement
|
|
20
18
|
:content="leftContent"
|
|
21
|
-
class="
|
|
19
|
+
class="cms-block-text-two-column__text"
|
|
22
20
|
/>
|
|
23
21
|
<CmsGenericElement
|
|
24
22
|
:content="rightContent"
|
|
25
|
-
class="
|
|
23
|
+
class="cms-block-text-two-column__text"
|
|
26
24
|
/>
|
|
27
25
|
</article>
|
|
28
26
|
</template>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsElementBuyBox } from "@shopware/composables";
|
|
3
|
+
import { useCmsTranslations } from "@shopware/composables";
|
|
4
|
+
import { defu } from "defu";
|
|
5
|
+
import { computed } from "vue";
|
|
6
|
+
import {
|
|
7
|
+
useCmsElementConfig,
|
|
8
|
+
usePrice,
|
|
9
|
+
useProduct,
|
|
10
|
+
useProductPrice,
|
|
11
|
+
useSessionContext,
|
|
12
|
+
} from "#imports";
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
content: CmsElementBuyBox;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
type Translations = {
|
|
19
|
+
product: {
|
|
20
|
+
previously: string;
|
|
21
|
+
amount: string;
|
|
22
|
+
price: {
|
|
23
|
+
[key: string]: string;
|
|
24
|
+
};
|
|
25
|
+
to: string;
|
|
26
|
+
from: string;
|
|
27
|
+
content: string;
|
|
28
|
+
pricesIncl: string;
|
|
29
|
+
pricesExcl: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let translations: Translations = {
|
|
34
|
+
product: {
|
|
35
|
+
previously: "Previously",
|
|
36
|
+
amount: "Amount",
|
|
37
|
+
price: {
|
|
38
|
+
label: "Price",
|
|
39
|
+
to: "To",
|
|
40
|
+
from: "From",
|
|
41
|
+
},
|
|
42
|
+
to: "To",
|
|
43
|
+
from: "From",
|
|
44
|
+
content: "Content",
|
|
45
|
+
pricesIncl: "Prices incl. VAT plus shipping costs",
|
|
46
|
+
pricesExcl: "Prices excl. VAT plus shipping costs",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
translations = defu(useCmsTranslations(), translations) as Translations;
|
|
51
|
+
|
|
52
|
+
const { getConfigValue } = useCmsElementConfig(props.content);
|
|
53
|
+
const alignment = computed(() => getConfigValue("alignment"));
|
|
54
|
+
const { taxState, currency } = useSessionContext();
|
|
55
|
+
const { product, changeVariant } = useProduct(
|
|
56
|
+
props.content.data.product,
|
|
57
|
+
props.content.data.configuratorSettings || [],
|
|
58
|
+
);
|
|
59
|
+
const { unitPrice, price, tierPrices, isListPrice } = useProductPrice(product);
|
|
60
|
+
const regulationPrice = computed(() => price.value?.regulationPrice?.price);
|
|
61
|
+
const { getFormattedPrice } = usePrice();
|
|
62
|
+
const referencePrice = computed(
|
|
63
|
+
() => product.value?.calculatedPrice?.referencePrice,
|
|
64
|
+
);
|
|
65
|
+
const purchaseUnit = computed(() => product.value?.purchaseUnit);
|
|
66
|
+
const unitName = computed(() => product.value?.unit?.name);
|
|
67
|
+
const productName = computed(() => product.value?.translated?.name || "");
|
|
68
|
+
</script>
|
|
69
|
+
<template>
|
|
70
|
+
<div v-if="product" :class="{
|
|
71
|
+
'h-full w-full flex flex-col': true,
|
|
72
|
+
'justify-start': alignment === 'flex-start',
|
|
73
|
+
'justify-end': alignment === 'flex-end',
|
|
74
|
+
'justify-center': alignment === 'center',
|
|
75
|
+
}">
|
|
76
|
+
<div class="self-stretch inline-flex flex-col justify-start items-start gap-8 mt-4 min-w-0">
|
|
77
|
+
<div
|
|
78
|
+
class="md:hidden self-stretch text-surface-on-surface text-4xl font-normal font-serif leading-[60px]">
|
|
79
|
+
{{ productName }}</div>
|
|
80
|
+
|
|
81
|
+
<div v-if="tierPrices.length <= 1">
|
|
82
|
+
<SwSharedPrice v-if="isListPrice"
|
|
83
|
+
class="text-1xl text-secondary-900 basis-2/6 justify-start line-through"
|
|
84
|
+
:value="price?.listPrice?.price" />
|
|
85
|
+
<SwSharedPrice v-if="unitPrice"
|
|
86
|
+
class="text-surface-on-surface text-base font-bold leading-normal"
|
|
87
|
+
:class="{
|
|
88
|
+
'text-red': isListPrice,
|
|
89
|
+
}" :value="unitPrice" />
|
|
90
|
+
<div v-if="regulationPrice" class="text-xs flex text-secondary-500">
|
|
91
|
+
{{ translations.product.previously }}
|
|
92
|
+
<SwSharedPrice class="ml-1" :value="regulationPrice" />
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div v-else>
|
|
96
|
+
<table class="border-collapse table-auto w-full text-sm mb-8">
|
|
97
|
+
<thead>
|
|
98
|
+
<tr>
|
|
99
|
+
<th
|
|
100
|
+
class="border-b dark:border-secondary-600 font-medium p-4 pl-8 pt-0 pb-3 text-secondary-600 dark:text-secondary-200 text-left">
|
|
101
|
+
{{ translations.product.amount }}
|
|
102
|
+
</th>
|
|
103
|
+
|
|
104
|
+
<th
|
|
105
|
+
class="border-b dark:border-secondary-600 font-medium p-4 pr-8 pt-0 pb-3 text-secondary-600 dark:text-secondary-200 text-left">
|
|
106
|
+
{{ translations.product.price.label }}
|
|
107
|
+
</th>
|
|
108
|
+
</tr>
|
|
109
|
+
</thead>
|
|
110
|
+
<tbody class="bg-white dark:bg-secondary-800">
|
|
111
|
+
<tr v-for="(tierPrice, index) in tierPrices" :key="tierPrice.label">
|
|
112
|
+
<td
|
|
113
|
+
class="border-b border-secondary-100 dark:border-secondary-700 p-4 pl-8 font-medium text-secondary-500 dark:text-secondary-400">
|
|
114
|
+
<span v-if="index < tierPrices.length - 1">{{
|
|
115
|
+
translations.product.to
|
|
116
|
+
}}</span><span v-else>{{ translations.product.from }}</span>
|
|
117
|
+
{{ tierPrice.quantity }}
|
|
118
|
+
</td>
|
|
119
|
+
<td
|
|
120
|
+
class="border-b border-secondary-100 dark:border-secondary-700 p-4 pr-8 font-medium text-current-500 dark:text-secondary-400">
|
|
121
|
+
{{ getFormattedPrice(tierPrice.unitPrice) }}
|
|
122
|
+
</td>
|
|
123
|
+
</tr>
|
|
124
|
+
</tbody>
|
|
125
|
+
</table>
|
|
126
|
+
</div>
|
|
127
|
+
<div v-if="purchaseUnit && unitName" class="mt-1">
|
|
128
|
+
<span class="font-light"> {{ translations.product.content }}: </span>
|
|
129
|
+
<span class="font-light"> {{ purchaseUnit }} {{ unitName }} </span>
|
|
130
|
+
<span v-if="referencePrice" class="font-light">
|
|
131
|
+
{{ currency?.symbol }} {{ referencePrice?.price }} / /
|
|
132
|
+
{{ referencePrice?.referenceUnit }} {{ referencePrice?.unitName }}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
<span class="text-brand-primary">
|
|
136
|
+
<template v-if="taxState === 'gross'">
|
|
137
|
+
{{ translations.product.pricesIncl }}
|
|
138
|
+
</template>
|
|
139
|
+
<template v-else> {{ translations.product.pricesExcl }} </template>
|
|
140
|
+
</span>
|
|
141
|
+
<SwVariantConfigurator @change="changeVariant" />
|
|
142
|
+
<SwProductAddToCart :product="product" />
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { Ref } from "vue";
|
|
3
|
+
import { onMounted, ref } from "vue";
|
|
4
|
+
import { useCategory, useNavigation } from "#imports";
|
|
5
|
+
import type { Schemas } from "#shopware";
|
|
6
|
+
|
|
7
|
+
const { category: activeCategory } = useCategory();
|
|
8
|
+
const loading: Ref<boolean> = ref(true);
|
|
9
|
+
const flagAllowSubcategories: boolean = true; // could be passed maybe as a prop in the future
|
|
10
|
+
const categoryNavigation: Ref<Schemas["Category"][]> = ref([]);
|
|
11
|
+
|
|
12
|
+
const currentCategoryId = activeCategory.value?.id ?? "main-navigation";
|
|
13
|
+
const type = flagAllowSubcategories ? currentCategoryId : "main-navigation";
|
|
14
|
+
const { loadNavigationElements } = useNavigation({
|
|
15
|
+
type: type,
|
|
16
|
+
});
|
|
17
|
+
const removeChildrenIfNotActiveCategory = () => {
|
|
18
|
+
const navigation: Schemas["Category"][] = JSON.parse(
|
|
19
|
+
JSON.stringify(categoryNavigation.value),
|
|
20
|
+
);
|
|
21
|
+
return navigation?.map((navigationElement) => {
|
|
22
|
+
navigationElement.children =
|
|
23
|
+
activeCategory.value?.id === navigationElement.id
|
|
24
|
+
? navigationElement.children
|
|
25
|
+
: [];
|
|
26
|
+
return navigationElement;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
onMounted(async () => {
|
|
31
|
+
// depth 0 means, we load only first level of categories, depth 1 means we load first and second level of categories ...
|
|
32
|
+
const depth = flagAllowSubcategories ? 2 : 1;
|
|
33
|
+
categoryNavigation.value = await loadNavigationElements({ depth });
|
|
34
|
+
if (!flagAllowSubcategories) {
|
|
35
|
+
categoryNavigation.value = removeChildrenIfNotActiveCategory();
|
|
36
|
+
}
|
|
37
|
+
loading.value = false;
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
<template>
|
|
41
|
+
<ClientOnly>
|
|
42
|
+
<div v-if="!loading && categoryNavigation && categoryNavigation.length" class="self-stretch inline-flex flex-col justify-start items-start gap-3">
|
|
43
|
+
<SwCategoryNavigation
|
|
44
|
+
:level="0"
|
|
45
|
+
:elements="categoryNavigation"
|
|
46
|
+
:active-category="activeCategory"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div v-else-if="loading" class="self-stretch flex flex-col justify-start items-start gap-4 animate-pulse">
|
|
50
|
+
<div v-for="i in 3" :key="i" class="w-full h-12 bg-surface-surface-container rounded"></div>
|
|
51
|
+
</div>
|
|
52
|
+
</ClientOnly>
|
|
53
|
+
</template>
|
|
@@ -13,7 +13,7 @@ const props = defineProps<{
|
|
|
13
13
|
|
|
14
14
|
const { getConfigValue } = useCmsElementConfig(props.content);
|
|
15
15
|
const currentTabIndex = ref<number>(0);
|
|
16
|
-
const crossSellContainer = useTemplateRef("crossSellContainer");
|
|
16
|
+
const crossSellContainer = useTemplateRef<HTMLDivElement>("crossSellContainer");
|
|
17
17
|
const config = computed<SliderElementConfig>(() => ({
|
|
18
18
|
minHeight: {
|
|
19
19
|
value: "300px",
|
|
@@ -63,9 +63,9 @@ const toggleTab = (index: number) => {
|
|
|
63
63
|
<a
|
|
64
64
|
v-for="(collection, index) of crossSellCollections"
|
|
65
65
|
:key="index"
|
|
66
|
-
class="transition text-lg font-
|
|
66
|
+
class="transition text-lg font-semibold text-surface-on-surface-variant cursor-pointer"
|
|
67
67
|
:class="{
|
|
68
|
-
'border-b-3 border-primary text-primary': currentTabIndex === index,
|
|
68
|
+
'border-b-3 border-brand-primary text-brand-primary': currentTabIndex === index,
|
|
69
69
|
}"
|
|
70
70
|
@click="toggleTab(index)"
|
|
71
71
|
>
|
|
@@ -3,11 +3,10 @@ import type {
|
|
|
3
3
|
CmsElementImage,
|
|
4
4
|
CmsElementManufacturerLogo,
|
|
5
5
|
} from "@shopware/composables";
|
|
6
|
-
import { buildUrlPrefix } from "@shopware/helpers";
|
|
6
|
+
import { buildUrlPrefix, encodeUrlPath } from "@shopware/helpers";
|
|
7
7
|
import { useElementSize } from "@vueuse/core";
|
|
8
8
|
import { computed, defineAsyncComponent, useTemplateRef } from "vue";
|
|
9
9
|
import { useCmsElementImage, useUrlResolver } from "#imports";
|
|
10
|
-
import { ClientOnly } from "../../../../helpers/clientOnly";
|
|
11
10
|
import { isSpatial } from "../../../../helpers/media/isSpatial";
|
|
12
11
|
|
|
13
12
|
const props = defineProps<{
|
|
@@ -27,7 +26,7 @@ const {
|
|
|
27
26
|
} = useCmsElementImage(props.content);
|
|
28
27
|
|
|
29
28
|
const DEFAULT_THUMBNAIL_SIZE = 10;
|
|
30
|
-
const imageElement = useTemplateRef("imageElement");
|
|
29
|
+
const imageElement = useTemplateRef<HTMLImageElement>("imageElement");
|
|
31
30
|
const { width, height } = useElementSize(imageElement);
|
|
32
31
|
|
|
33
32
|
function roundUp(num: number) {
|
|
@@ -35,11 +34,34 @@ function roundUp(num: number) {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
const srcPath = computed(() => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
if (!imageAttrs.value.src) return "";
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Encode the URL first to handle special characters
|
|
41
|
+
const encodedUrl = encodeUrlPath(imageAttrs.value.src);
|
|
42
|
+
const url = new URL(encodedUrl);
|
|
43
|
+
|
|
44
|
+
// Only add size parameters if dimensions are available (after mount)
|
|
45
|
+
// This prevents hydration mismatch
|
|
46
|
+
const w = roundUp(width.value);
|
|
47
|
+
const h = roundUp(height.value);
|
|
48
|
+
|
|
49
|
+
if (w > DEFAULT_THUMBNAIL_SIZE || h > DEFAULT_THUMBNAIL_SIZE) {
|
|
50
|
+
if (width.value > height.value) {
|
|
51
|
+
url.searchParams.set("width", String(w));
|
|
52
|
+
} else {
|
|
53
|
+
url.searchParams.set("height", String(h));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add fit parameter
|
|
58
|
+
url.searchParams.set("fit", "crop,smart");
|
|
59
|
+
|
|
60
|
+
return url.toString();
|
|
61
|
+
} catch {
|
|
62
|
+
// Fallback if URL parsing fails
|
|
63
|
+
return imageAttrs.value.src;
|
|
64
|
+
}
|
|
43
65
|
});
|
|
44
66
|
const imageComputedContainerAttrs = computed(() => {
|
|
45
67
|
const imageAttrsCopy = Object.assign({}, imageContainerAttrs.value);
|
|
@@ -64,7 +86,7 @@ const SwMedia3D = computed(() => {
|
|
|
64
86
|
<component
|
|
65
87
|
:is="imageLink.url ? 'a' : 'div'"
|
|
66
88
|
v-if="imageAttrs.src"
|
|
67
|
-
class="cms-element-image
|
|
89
|
+
class="cms-element-image self-stretch relative"
|
|
68
90
|
:class="{
|
|
69
91
|
'flex justify-center items-center': imageGallery,
|
|
70
92
|
}"
|
|
@@ -75,9 +97,10 @@ const SwMedia3D = computed(() => {
|
|
|
75
97
|
v-if="isVideoElement"
|
|
76
98
|
controls
|
|
77
99
|
:class="{
|
|
78
|
-
'
|
|
100
|
+
'w-full h-full': true,
|
|
79
101
|
'absolute inset-0': ['cover', 'stretch'].includes(displayMode),
|
|
80
102
|
'object-cover': displayMode === 'cover',
|
|
103
|
+
'object-contain': displayMode !== 'cover',
|
|
81
104
|
}"
|
|
82
105
|
>
|
|
83
106
|
<source :src="imageAttrs.src" :type="mimeType" />
|
|
@@ -86,16 +109,17 @@ const SwMedia3D = computed(() => {
|
|
|
86
109
|
<ClientOnly v-else-if="isSpatial(props.content.data.media)">
|
|
87
110
|
<component :is="SwMedia3D" :src="props.content.data.media.url" />
|
|
88
111
|
</ClientOnly>
|
|
89
|
-
<
|
|
112
|
+
<NuxtImg
|
|
90
113
|
v-else
|
|
91
114
|
ref="imageElement"
|
|
115
|
+
preset="productDetail"
|
|
92
116
|
loading="lazy"
|
|
93
117
|
:class="{
|
|
94
118
|
'w-full h-full': !imageGallery,
|
|
95
119
|
'w-4/5': imageGallery,
|
|
96
|
-
'absolute
|
|
120
|
+
'absolute left-0 top-0': ['cover', 'stretch'].includes(displayMode),
|
|
97
121
|
'object-cover': displayMode === 'cover',
|
|
98
|
-
'object-contain': imageGallery,
|
|
122
|
+
'object-contain': imageGallery || displayMode !== 'cover',
|
|
99
123
|
}"
|
|
100
124
|
:alt="imageAttrs.alt"
|
|
101
125
|
:src="srcPath"
|
|
@@ -103,3 +127,18 @@ const SwMedia3D = computed(() => {
|
|
|
103
127
|
/>
|
|
104
128
|
</component>
|
|
105
129
|
</template>
|
|
130
|
+
|
|
131
|
+
<style scoped>
|
|
132
|
+
.cms-element-image {
|
|
133
|
+
display: block;
|
|
134
|
+
width: 100%;
|
|
135
|
+
height: 100%;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Ensure anchor tags within the element fill the container */
|
|
139
|
+
.cms-element-image a {
|
|
140
|
+
display: block;
|
|
141
|
+
width: 100%;
|
|
142
|
+
height: 100%;
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsElementImageGallery } from "@shopware/composables";
|
|
3
|
+
import { computed, ref } from "vue";
|
|
4
|
+
import { useCmsElementConfig } from "#imports";
|
|
5
|
+
import { isSpatial } from "../../../../helpers/media/isSpatial";
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(
|
|
8
|
+
defineProps<{
|
|
9
|
+
content: CmsElementImageGallery;
|
|
10
|
+
slidesToShow?: number;
|
|
11
|
+
slidesToScroll?: number;
|
|
12
|
+
}>(),
|
|
13
|
+
{
|
|
14
|
+
slidesToShow: 5,
|
|
15
|
+
slidesToScroll: 4,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const { getConfigValue } = useCmsElementConfig(props.content);
|
|
20
|
+
|
|
21
|
+
const currentIndex = ref(0);
|
|
22
|
+
const mediaGallery = computed(() => props.content.data?.sliderItems ?? []);
|
|
23
|
+
|
|
24
|
+
function goToSlide(index: number) {
|
|
25
|
+
if (index >= 0 && index < mediaGallery.value.length) {
|
|
26
|
+
currentIndex.value = index;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function previous() {
|
|
31
|
+
if (currentIndex.value > 0) {
|
|
32
|
+
currentIndex.value--;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function next() {
|
|
37
|
+
if (currentIndex.value < mediaGallery.value.length - 1) {
|
|
38
|
+
currentIndex.value++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const currentImage = computed(() => {
|
|
43
|
+
return mediaGallery.value[currentIndex.value]?.media;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Touch event handling for mobile swipe gestures - mobile
|
|
47
|
+
const touchStartX = ref(0);
|
|
48
|
+
const touchEndX = ref(0);
|
|
49
|
+
|
|
50
|
+
function onTouchStart(event: TouchEvent) {
|
|
51
|
+
touchStartX.value = event.touches?.[0]?.clientX || 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function onTouchMove(event: TouchEvent) {
|
|
55
|
+
touchEndX.value = event?.touches?.[0]?.clientX || 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function onTouchEnd() {
|
|
59
|
+
const deltaX = touchEndX.value - touchStartX.value;
|
|
60
|
+
|
|
61
|
+
// Define a threshold for swipe detection
|
|
62
|
+
const threshold = 50; // pixels
|
|
63
|
+
|
|
64
|
+
if (Math.abs(deltaX) > threshold) {
|
|
65
|
+
if (deltaX < 0) {
|
|
66
|
+
// Swipe Left
|
|
67
|
+
next();
|
|
68
|
+
} else {
|
|
69
|
+
// Swipe Right
|
|
70
|
+
previous();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Reset values
|
|
75
|
+
touchStartX.value = 0;
|
|
76
|
+
touchEndX.value = 0;
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<div class="w-full max-w-full relative inline-flex flex-col justify-center items-center gap-2 mx-auto">
|
|
82
|
+
<div class="w-full">
|
|
83
|
+
<!-- Main Image Display -->
|
|
84
|
+
|
|
85
|
+
<div class="w-full h-[400px] sm:h-[500px] lg:h-[600px] xl:h-[700px] relative overflow-hidden rounded-lg"
|
|
86
|
+
@touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
|
|
87
|
+
<Transition name="gallery-fade" mode="out-in">
|
|
88
|
+
<div v-if="currentImage && isSpatial(currentImage)" class="w-full h-full relative">
|
|
89
|
+
<CmsElementImageGallery3dPlaceholder class="w-full h-full absolute inset-0 object-cover" />
|
|
90
|
+
<span class="absolute bottom-4 right-4 text-sm bg-gray-800 rounded px-2 py-1 text-white">
|
|
91
|
+
3D
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
<NuxtImg v-else-if="currentImage" preset="hero" loading="lazy"
|
|
95
|
+
class="w-full h-full absolute inset-0 object-cover" :src="currentImage.url"
|
|
96
|
+
:key="currentImage.url" :alt="currentImage.alt || 'Product image'" />
|
|
97
|
+
<NuxtImg v-else preset="hero" class="w-full h-full absolute inset-0 object-cover"
|
|
98
|
+
src="https://placehold.co/600x500" alt="Placeholder image" />
|
|
99
|
+
</Transition>
|
|
100
|
+
|
|
101
|
+
</div>
|
|
102
|
+
<!-- Navigation Arrows -->
|
|
103
|
+
<div v-if="mediaGallery.length > 1"
|
|
104
|
+
class="absolute inset-0 flex items-center justify-between px-2 sm:px-4 pointer-events-none">
|
|
105
|
+
<!-- Previous Button -->
|
|
106
|
+
<button
|
|
107
|
+
class="w-10 h-10 bg-brand-tertiary rounded-full hover:bg-brand-tertiary-hover transition-colors disabled:opacity-50 pointer-events-auto shadow-lg"
|
|
108
|
+
:disabled="currentIndex === 0" @click="previous" aria-label="Previous image">
|
|
109
|
+
<div class="flex items-center justify-center w-full h-full">
|
|
110
|
+
<div class="i-carbon-chevron-left w-5 h-5 text-brand-on-tertiary"></div>
|
|
111
|
+
</div>
|
|
112
|
+
</button>
|
|
113
|
+
|
|
114
|
+
<!-- Next Button -->
|
|
115
|
+
<button
|
|
116
|
+
class="w-10 h-10 bg-brand-tertiary rounded-full hover:bg-brand-tertiary-hover transition-colors disabled:opacity-50 pointer-events-auto shadow-lg"
|
|
117
|
+
:disabled="currentIndex === mediaGallery.length - 1" @click="next" aria-label="Next image">
|
|
118
|
+
<div class="flex items-center justify-center w-full h-full">
|
|
119
|
+
<div class="i-carbon-chevron-right w-5 h-5 text-brand-on-tertiary"></div>
|
|
120
|
+
</div>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<!-- Dot Indicators -->
|
|
125
|
+
<div v-if="mediaGallery.length > 1" class="flex justify-center items-center gap-2 mt-2">
|
|
126
|
+
<button v-for="(image, index) in mediaGallery" :key="image.media.url"
|
|
127
|
+
class="relative rounded-full transition-all duration-200 hover:scale-110" :class="{
|
|
128
|
+
'w-6 h-2 bg-surface-on-surface-variant': index === currentIndex,
|
|
129
|
+
'w-2 h-2 bg-surface-surface-container-highest': index !== currentIndex
|
|
130
|
+
}" @click="goToSlide(index)" :aria-label="`Go to image ${index + 1}`" />
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</template>
|
|
135
|
+
|
|
136
|
+
<style scoped>
|
|
137
|
+
/* Gallery fade transition */
|
|
138
|
+
.gallery-fade-enter-active,
|
|
139
|
+
.gallery-fade-leave-active {
|
|
140
|
+
transition: all 0.3s ease-in-out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.gallery-fade-enter-from {
|
|
144
|
+
opacity: 0;
|
|
145
|
+
transform: scale(1.05) translateY(10px);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.gallery-fade-leave-to {
|
|
149
|
+
opacity: 0;
|
|
150
|
+
transform: scale(0.95) translateY(-10px);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.gallery-fade-enter-to,
|
|
154
|
+
.gallery-fade-leave-from {
|
|
155
|
+
opacity: 1;
|
|
156
|
+
transform: scale(1) translateY(0);
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -11,8 +11,8 @@ const props = defineProps<{
|
|
|
11
11
|
const items = computed(() => props.content.data.sliderItems);
|
|
12
12
|
</script>
|
|
13
13
|
<template>
|
|
14
|
-
<!-- need some
|
|
15
|
-
<div class="cms-element-image-slider w-[92vw] sm:w-[94vw] md:w-
|
|
14
|
+
<!-- need some width here for small views that the slider can calculate the correct items with -->
|
|
15
|
+
<div class="cms-element-image-slider w-[92vw] sm:w-[94vw] md:w-full">
|
|
16
16
|
<SwSlider :config="props.content.config">
|
|
17
17
|
<CmsElementImage
|
|
18
18
|
v-for="image of items"
|