@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
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { useCmsTranslations } from "@shopware/composables";
|
|
3
|
-
import { getTranslatedProperty } from "@shopware/helpers";
|
|
4
|
-
import { defu } from "defu";
|
|
5
|
-
import type { Ref } from "vue";
|
|
6
|
-
import { onMounted, ref } from "vue";
|
|
7
|
-
import { useCategory, useNavigation } from "#imports";
|
|
8
|
-
import type { Schemas } from "#shopware";
|
|
9
|
-
|
|
10
|
-
type Translations = {
|
|
11
|
-
listing: {
|
|
12
|
-
category: string;
|
|
13
|
-
subCategory: string;
|
|
14
|
-
subCategoryOf: string;
|
|
15
|
-
loading: string;
|
|
16
|
-
categories: string;
|
|
17
|
-
subCategories: string;
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
let translations: Translations = {
|
|
22
|
-
listing: {
|
|
23
|
-
category: "Category",
|
|
24
|
-
subCategory: "Sub-category",
|
|
25
|
-
subCategoryOf: "of",
|
|
26
|
-
loading: "Loading ...",
|
|
27
|
-
categories: "Categories",
|
|
28
|
-
subCategories: "Sub-categories",
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
translations = defu(useCmsTranslations(), translations) as Translations;
|
|
33
|
-
|
|
34
|
-
const { category: activeCategory } = useCategory();
|
|
35
|
-
const loading: Ref<boolean> = ref(true);
|
|
36
|
-
const flagAllowSubcategories: boolean = true; // could be passed maybe as a prop in the future
|
|
37
|
-
const categoryNavigation: Ref<Schemas["Category"][]> = ref([]);
|
|
38
|
-
|
|
39
|
-
const currentCategoryId = activeCategory.value?.id ?? "main-navigation";
|
|
40
|
-
const type = flagAllowSubcategories ? currentCategoryId : "main-navigation";
|
|
41
|
-
const { loadNavigationElements } = useNavigation({
|
|
42
|
-
type: type,
|
|
43
|
-
});
|
|
44
|
-
const removeChildrenIfNotActiveCategory = () => {
|
|
45
|
-
const navigation: Schemas["Category"][] = JSON.parse(
|
|
46
|
-
JSON.stringify(categoryNavigation.value),
|
|
47
|
-
);
|
|
48
|
-
return navigation?.map((navigationElement) => {
|
|
49
|
-
navigationElement.children =
|
|
50
|
-
activeCategory.value?.id === navigationElement.id
|
|
51
|
-
? navigationElement.children
|
|
52
|
-
: [];
|
|
53
|
-
return navigationElement;
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
onMounted(async () => {
|
|
58
|
-
// depth 0 means, we load only first level of categories, depth 1 means we load first and second level of categories ...
|
|
59
|
-
const depth = flagAllowSubcategories ? 0 : 1;
|
|
60
|
-
categoryNavigation.value = await loadNavigationElements({ depth });
|
|
61
|
-
if (!flagAllowSubcategories) {
|
|
62
|
-
categoryNavigation.value = removeChildrenIfNotActiveCategory();
|
|
63
|
-
}
|
|
64
|
-
loading.value = false;
|
|
65
|
-
});
|
|
66
|
-
</script>
|
|
67
|
-
<template>
|
|
68
|
-
<div>
|
|
69
|
-
<div
|
|
70
|
-
v-if="categoryNavigation && categoryNavigation.length"
|
|
71
|
-
class="cms-element-category-navigation max-w-screen-xl mx-auto"
|
|
72
|
-
>
|
|
73
|
-
<h2
|
|
74
|
-
v-if="categoryNavigation.length > 0 && !flagAllowSubcategories"
|
|
75
|
-
class="text-3xl tracking-tight text-secondary-900 m-0 px-5"
|
|
76
|
-
>
|
|
77
|
-
{{
|
|
78
|
-
categoryNavigation.length > 1
|
|
79
|
-
? translations.listing.categories
|
|
80
|
-
: translations.listing.category
|
|
81
|
-
}}
|
|
82
|
-
</h2>
|
|
83
|
-
<h2
|
|
84
|
-
v-if="categoryNavigation.length > 0 && flagAllowSubcategories"
|
|
85
|
-
class="text-3xl tracking-tight text-secondary-900 m-0 px-5"
|
|
86
|
-
>
|
|
87
|
-
{{
|
|
88
|
-
categoryNavigation.length > 1
|
|
89
|
-
? translations.listing.subCategories
|
|
90
|
-
: translations.listing.subCategory
|
|
91
|
-
}}
|
|
92
|
-
{{ translations.listing.subCategoryOf }}
|
|
93
|
-
{{ getTranslatedProperty(activeCategory, "name") }}
|
|
94
|
-
</h2>
|
|
95
|
-
<SwCategoryNavigation
|
|
96
|
-
:level="0"
|
|
97
|
-
:elements="categoryNavigation"
|
|
98
|
-
:active-category="activeCategory"
|
|
99
|
-
/>
|
|
100
|
-
</div>
|
|
101
|
-
<div v-if="loading">
|
|
102
|
-
<div class="px-5">
|
|
103
|
-
<h2 class="text-3xl tracking-tight text-secondary-900 m-0 px-5 pl-0">
|
|
104
|
-
{{ translations.listing.loading }}
|
|
105
|
-
</h2>
|
|
106
|
-
<div
|
|
107
|
-
class="border border-secondary-200 shadow rounded-md p-4 max-w-screen-xl mx-auto"
|
|
108
|
-
>
|
|
109
|
-
<div class="animate-pulse flex space-x-4">
|
|
110
|
-
<div class="flex-1 space-y-6 py-1">
|
|
111
|
-
<div class="space-y-3">
|
|
112
|
-
<div class="grid grid-cols-3 gap-4">
|
|
113
|
-
<div class="h-2 bg-light-200 rounded col-span-2"></div>
|
|
114
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
115
|
-
</div>
|
|
116
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
117
|
-
</div>
|
|
118
|
-
<div class="space-y-3">
|
|
119
|
-
<div class="grid grid-cols-3 gap-4">
|
|
120
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
121
|
-
<div class="h-2 bg-light-200 rounded col-span-2"></div>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
<div class="animate-pulse flex space-x-4">
|
|
128
|
-
<div class="flex-1 space-y-6 py-4">
|
|
129
|
-
<div class="space-y-3">
|
|
130
|
-
<div class="grid grid-cols-3 gap-4">
|
|
131
|
-
<div class="h-2 bg-light-200 rounded col-span-2"></div>
|
|
132
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
133
|
-
</div>
|
|
134
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
135
|
-
</div>
|
|
136
|
-
<div class="space-y-3">
|
|
137
|
-
<div class="grid grid-cols-3 gap-4">
|
|
138
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
139
|
-
<div class="h-2 bg-light-200 rounded col-span-2"></div>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
<div class="animate-pulse flex space-x-4">
|
|
146
|
-
<div class="flex-1 space-y-6 pt-4">
|
|
147
|
-
<div class="space-y-3">
|
|
148
|
-
<div class="grid grid-cols-6 gap-4">
|
|
149
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
150
|
-
<div class="h-2 bg-light-200 rounded col-span-5"></div>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="space-y-3">
|
|
155
|
-
<div class="grid grid-cols-3 gap-4">
|
|
156
|
-
<div class="h-2 bg-light-200 rounded col-span-2"></div>
|
|
157
|
-
<div class="h-2 bg-light-200 rounded col-span-1"></div>
|
|
158
|
-
</div>
|
|
159
|
-
<div class="h-2 bg-secondary-200 rounded"></div>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
</template>
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { CmsElementImageGallery } from "@shopware/composables";
|
|
3
|
-
import { computed, onMounted, ref, useTemplateRef } 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 speed = ref<number>(300);
|
|
22
|
-
const currentIndex = ref(0);
|
|
23
|
-
const currentThumb = ref(0);
|
|
24
|
-
const imageSlider = useTemplateRef("imageSlider");
|
|
25
|
-
const imageThumbsTrack = useTemplateRef("imageThumbsTrack");
|
|
26
|
-
const isLoading = ref(true);
|
|
27
|
-
const imageThumbsTrackStyle = ref({});
|
|
28
|
-
const imageThumbs = useTemplateRef("imageThumbs");
|
|
29
|
-
const imageThumbsStyle = ref({});
|
|
30
|
-
const mediaGallery = computed(() => props.content.data?.sliderItems ?? []);
|
|
31
|
-
const galleryPosition = computed<string>(
|
|
32
|
-
() => getConfigValue("galleryPosition") ?? "left",
|
|
33
|
-
);
|
|
34
|
-
const scrollPx = ref(0);
|
|
35
|
-
|
|
36
|
-
onMounted(() => {
|
|
37
|
-
initThumbs();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
function initThumbs() {
|
|
41
|
-
if (imageThumbsTrack.value) {
|
|
42
|
-
setTimeout(() => {
|
|
43
|
-
if (galleryPosition.value === "left") {
|
|
44
|
-
const clientHeight = imageThumbsTrack.value?.clientHeight ?? 0;
|
|
45
|
-
scrollPx.value = clientHeight / mediaGallery.value.length;
|
|
46
|
-
imageThumbsStyle.value = {
|
|
47
|
-
height: `${scrollPx.value * +props.slidesToShow}px`,
|
|
48
|
-
};
|
|
49
|
-
} else {
|
|
50
|
-
const clientWidth = imageThumbs.value?.clientWidth ?? 0;
|
|
51
|
-
scrollPx.value = clientWidth / props.slidesToShow;
|
|
52
|
-
imageThumbsTrackStyle.value = {
|
|
53
|
-
width: `${scrollPx.value * mediaGallery.value.length}px`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
isLoading.value = false;
|
|
57
|
-
}, 100);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function changeCover(i: number) {
|
|
62
|
-
if (i === currentIndex.value) return;
|
|
63
|
-
imageSlider.value?.goToSlide(i);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function handleChangeSlide(e: number) {
|
|
67
|
-
currentIndex.value = e;
|
|
68
|
-
if (currentIndex.value > currentThumb.value + props.slidesToShow - 1) {
|
|
69
|
-
move("next", currentIndex.value);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (currentIndex.value < currentThumb.value) {
|
|
73
|
-
move("previous", currentIndex.value);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function move(type: "next" | "previous", specificIndex?: number | string) {
|
|
79
|
-
let step: number;
|
|
80
|
-
const index =
|
|
81
|
-
typeof specificIndex !== "number"
|
|
82
|
-
? Number.parseInt(specificIndex as string)
|
|
83
|
-
: specificIndex;
|
|
84
|
-
if (index >= 0) {
|
|
85
|
-
if (type === "next") {
|
|
86
|
-
step =
|
|
87
|
-
index + props.slidesToScroll < mediaGallery.value.length
|
|
88
|
-
? index
|
|
89
|
-
: mediaGallery.value.length - props.slidesToShow;
|
|
90
|
-
} else {
|
|
91
|
-
step =
|
|
92
|
-
index - props.slidesToScroll > 0 ? index - props.slidesToScroll : 0;
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
if (type === "next") {
|
|
96
|
-
step =
|
|
97
|
-
currentThumb.value + props.slidesToShow - 1 + props.slidesToScroll <
|
|
98
|
-
mediaGallery.value.length
|
|
99
|
-
? currentThumb.value + props.slidesToScroll
|
|
100
|
-
: mediaGallery.value.length - props.slidesToShow;
|
|
101
|
-
} else {
|
|
102
|
-
step =
|
|
103
|
-
currentThumb.value - props.slidesToScroll > 0
|
|
104
|
-
? currentThumb.value - props.slidesToScroll
|
|
105
|
-
: 0;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
currentThumb.value = step;
|
|
109
|
-
let xAxis = 0;
|
|
110
|
-
let yAxis = 0;
|
|
111
|
-
if (galleryPosition.value === "left") {
|
|
112
|
-
yAxis = scrollPx.value * currentThumb.value;
|
|
113
|
-
} else {
|
|
114
|
-
xAxis = scrollPx.value * currentThumb.value;
|
|
115
|
-
}
|
|
116
|
-
imageThumbsTrackStyle.value = {
|
|
117
|
-
...imageThumbsTrackStyle.value,
|
|
118
|
-
transform: `translate3d(-${xAxis}px, -${yAxis}px, 0px)`,
|
|
119
|
-
transition: `transform ${speed.value}ms ease 0s`,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function previous() {
|
|
124
|
-
if (currentThumb.value <= 0) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
move("previous");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function next() {
|
|
131
|
-
if (currentThumb.value + props.slidesToShow >= mediaGallery.value.length) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
move("next");
|
|
135
|
-
}
|
|
136
|
-
</script>
|
|
137
|
-
|
|
138
|
-
<template>
|
|
139
|
-
<div
|
|
140
|
-
:class="{
|
|
141
|
-
'opacity-0': isLoading,
|
|
142
|
-
'flex gap-10': true,
|
|
143
|
-
'flex-col-reverse': galleryPosition === 'underneath',
|
|
144
|
-
}"
|
|
145
|
-
>
|
|
146
|
-
<div
|
|
147
|
-
:class="{
|
|
148
|
-
'hidden lg:flex basis-20 relative flex-col items-center':
|
|
149
|
-
galleryPosition === 'left',
|
|
150
|
-
'flex relative w-full': galleryPosition === 'underneath',
|
|
151
|
-
}"
|
|
152
|
-
>
|
|
153
|
-
<button
|
|
154
|
-
v-if="mediaGallery.length > slidesToShow"
|
|
155
|
-
class="disabled:opacity-10 p-1"
|
|
156
|
-
aria-label="Previous image"
|
|
157
|
-
@click="previous"
|
|
158
|
-
>
|
|
159
|
-
<div
|
|
160
|
-
class="h-7 w-7"
|
|
161
|
-
:class="{
|
|
162
|
-
'i-carbon-chevron-up': galleryPosition === 'left',
|
|
163
|
-
'i-carbon-chevron-left': galleryPosition !== 'left',
|
|
164
|
-
}"
|
|
165
|
-
/>
|
|
166
|
-
</button>
|
|
167
|
-
<span class="sr-only">Previous image</span>
|
|
168
|
-
<div
|
|
169
|
-
ref="imageThumbs"
|
|
170
|
-
class="overflow-hidden -my-2.5"
|
|
171
|
-
:style="imageThumbsStyle"
|
|
172
|
-
>
|
|
173
|
-
<div
|
|
174
|
-
ref="imageThumbsTrack"
|
|
175
|
-
:class="{
|
|
176
|
-
flex: true,
|
|
177
|
-
'flex-col': galleryPosition === 'left',
|
|
178
|
-
}"
|
|
179
|
-
:style="imageThumbsTrackStyle"
|
|
180
|
-
>
|
|
181
|
-
<div
|
|
182
|
-
v-for="(image, i) in mediaGallery"
|
|
183
|
-
:key="image.media.url"
|
|
184
|
-
:class="{
|
|
185
|
-
'py-2.5': galleryPosition === 'left',
|
|
186
|
-
'flex-1 px-2.5': galleryPosition === 'underneath',
|
|
187
|
-
}"
|
|
188
|
-
>
|
|
189
|
-
<div
|
|
190
|
-
class="w-20 h-20 overflow-hidden cursor-pointer p-1 border-secondary-200 rounded transition duration-150 ease-in-out"
|
|
191
|
-
:class="{
|
|
192
|
-
border: i !== currentIndex,
|
|
193
|
-
'border-indigo-500 border-3': i === currentIndex,
|
|
194
|
-
}"
|
|
195
|
-
@click="() => changeCover(i)"
|
|
196
|
-
>
|
|
197
|
-
<div v-if="isSpatial(image.media)" class="h-full relative">
|
|
198
|
-
<CmsElementImageGallery3dPlaceholder
|
|
199
|
-
class="w-full h-full object-center"
|
|
200
|
-
/>
|
|
201
|
-
<span
|
|
202
|
-
class="absolute bottom-0 text-sm bg-gray rounded px-1 text-white"
|
|
203
|
-
>
|
|
204
|
-
3D</span
|
|
205
|
-
>
|
|
206
|
-
</div>
|
|
207
|
-
<img
|
|
208
|
-
v-else
|
|
209
|
-
loading="lazy"
|
|
210
|
-
:src="image.media.url"
|
|
211
|
-
class="w-full h-full object-center object-cover"
|
|
212
|
-
alt="Product image"
|
|
213
|
-
/>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
</div>
|
|
218
|
-
<button
|
|
219
|
-
v-if="mediaGallery.length > slidesToShow"
|
|
220
|
-
class="disabled:opacity-10 p-1"
|
|
221
|
-
aria-label="Next image"
|
|
222
|
-
@click="next"
|
|
223
|
-
>
|
|
224
|
-
<span class="sr-only">Next image</span>
|
|
225
|
-
<div
|
|
226
|
-
class="h-7 w-7"
|
|
227
|
-
:class="{
|
|
228
|
-
'i-carbon-chevron-down': galleryPosition === 'left',
|
|
229
|
-
'i-carbon-chevron-right': galleryPosition !== 'left',
|
|
230
|
-
}"
|
|
231
|
-
/>
|
|
232
|
-
</button>
|
|
233
|
-
</div>
|
|
234
|
-
<div class="flex-1 overflow-hidden">
|
|
235
|
-
<SwSlider
|
|
236
|
-
ref="imageSlider"
|
|
237
|
-
:config="props.content.config"
|
|
238
|
-
@change-slide="handleChangeSlide"
|
|
239
|
-
>
|
|
240
|
-
<CmsElementImage
|
|
241
|
-
v-for="image of mediaGallery"
|
|
242
|
-
:key="image.media.url"
|
|
243
|
-
:image-gallery="true"
|
|
244
|
-
:content="{ data: image, config: props.content.config } as any"
|
|
245
|
-
/>
|
|
246
|
-
</SwSlider>
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</template>
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { CmsElementProductDescriptionReviews } from "@shopware/composables";
|
|
3
|
-
import { useCmsTranslations } from "@shopware/composables";
|
|
4
|
-
import { getProductName, getTranslatedProperty } from "@shopware/helpers";
|
|
5
|
-
import { defu } from "defu";
|
|
6
|
-
import { type Ref, computed, onMounted, ref } from "vue";
|
|
7
|
-
import xss from "xss";
|
|
8
|
-
import { useProduct } from "#imports";
|
|
9
|
-
import type { Schemas } from "#shopware";
|
|
10
|
-
|
|
11
|
-
const props = defineProps<{
|
|
12
|
-
content: CmsElementProductDescriptionReviews;
|
|
13
|
-
}>();
|
|
14
|
-
|
|
15
|
-
type Translations = {
|
|
16
|
-
product: {
|
|
17
|
-
description: string;
|
|
18
|
-
reviews: string;
|
|
19
|
-
messages: {
|
|
20
|
-
reviewAdded: string;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
let translations: Translations = {
|
|
26
|
-
product: {
|
|
27
|
-
description: "Description",
|
|
28
|
-
reviews: "Reviews",
|
|
29
|
-
messages: {
|
|
30
|
-
reviewAdded: "Thank you for submitting your review",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
translations = defu(useCmsTranslations(), translations) as Translations;
|
|
35
|
-
|
|
36
|
-
const currentTab = ref<number>(1);
|
|
37
|
-
const { product } = useProduct(props.content.data?.product);
|
|
38
|
-
|
|
39
|
-
const description = computed(() =>
|
|
40
|
-
xss(getTranslatedProperty(product.value, "description")),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const toggleTabs = (tabNumber: number) => {
|
|
44
|
-
currentTab.value = tabNumber;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const reviews: Ref<Schemas["ProductReview"][]> = ref([]);
|
|
48
|
-
|
|
49
|
-
onMounted(async () => {
|
|
50
|
-
if (props.content.data?.reviews?.elements) {
|
|
51
|
-
reviews.value = props.content.data.reviews.elements;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
<template>
|
|
57
|
-
<div
|
|
58
|
-
v-if="product"
|
|
59
|
-
class="cms-block-product-description-reviews flex flex-wrap"
|
|
60
|
-
>
|
|
61
|
-
<div class="w-full">
|
|
62
|
-
<ul
|
|
63
|
-
class="flex flex-wrap text-sm font-medium list-none text-center text-secondary-500 border-b border-secondary-200 dark:border-secondary-500 dark:text-secondary-400"
|
|
64
|
-
>
|
|
65
|
-
<li class="mr-2 text-center">
|
|
66
|
-
<a
|
|
67
|
-
class="font-bold uppercase px-5 py-3 block leading-normal cursor-pointer"
|
|
68
|
-
:class="[
|
|
69
|
-
currentTab !== 1
|
|
70
|
-
? 'text-secondary-500 bg-white'
|
|
71
|
-
: 'text-white bg-secondary-500',
|
|
72
|
-
]"
|
|
73
|
-
@click="() => toggleTabs(1)"
|
|
74
|
-
>
|
|
75
|
-
<i class="fas fa-space-shuttle text-base mr-1" />
|
|
76
|
-
{{ translations.product.description }}
|
|
77
|
-
</a>
|
|
78
|
-
</li>
|
|
79
|
-
<li class="mr-2 text-center">
|
|
80
|
-
<a
|
|
81
|
-
class="font-bold uppercase px-5 py-3 block leading-normal cursor-pointer"
|
|
82
|
-
:class="[
|
|
83
|
-
currentTab !== 2
|
|
84
|
-
? 'text-secondary-500 bg-white'
|
|
85
|
-
: 'text-white bg-secondary-500',
|
|
86
|
-
]"
|
|
87
|
-
data-testid="product-reviews-tab"
|
|
88
|
-
@click="() => toggleTabs(2)"
|
|
89
|
-
>
|
|
90
|
-
<i class="fas fa-cog text-base mr-1" />
|
|
91
|
-
{{ translations.product.reviews }} ({{ reviews.length }})
|
|
92
|
-
</a>
|
|
93
|
-
</li>
|
|
94
|
-
</ul>
|
|
95
|
-
<div class="relative flex flex-col min-w-0 break-words w-full mb-6">
|
|
96
|
-
<div class="px-4 py-5 flex-auto">
|
|
97
|
-
<div class="tab-content tab-space">
|
|
98
|
-
<div
|
|
99
|
-
:class="[
|
|
100
|
-
'cms-block-product-description-reviews__description',
|
|
101
|
-
currentTab !== 1 ? 'hidden' : 'block',
|
|
102
|
-
]"
|
|
103
|
-
>
|
|
104
|
-
<p class="text-xl font-bold mt-3">
|
|
105
|
-
{{ getProductName({ product }) }}
|
|
106
|
-
</p>
|
|
107
|
-
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
108
|
-
<div class="mt-2" v-html="description"></div>
|
|
109
|
-
</div>
|
|
110
|
-
<div
|
|
111
|
-
:class="[
|
|
112
|
-
'cms-block-product-description-reviews__reviews',
|
|
113
|
-
currentTab !== 2 ? 'hidden' : 'block',
|
|
114
|
-
]"
|
|
115
|
-
>
|
|
116
|
-
<SwProductReviews :product="product" :reviews="reviews" />
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
</template>
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { useCmsSection } from "@shopware/composables";
|
|
3
|
-
import type { CmsSectionSidebar } from "@shopware/composables";
|
|
4
|
-
import { getTranslatedProperty } from "@shopware/helpers";
|
|
5
|
-
import { computed } from "vue";
|
|
6
|
-
import { useCategory } from "#imports";
|
|
7
|
-
|
|
8
|
-
const props = defineProps<{
|
|
9
|
-
content: CmsSectionSidebar;
|
|
10
|
-
}>();
|
|
11
|
-
const { getPositionContent } = useCmsSection(props.content);
|
|
12
|
-
|
|
13
|
-
const sidebarBlocks = getPositionContent("sidebar");
|
|
14
|
-
const mainBlocks = getPositionContent("main");
|
|
15
|
-
const mobileBehavior = computed(() => props.content.mobileBehavior);
|
|
16
|
-
const { category } = useCategory();
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<template>
|
|
20
|
-
<div class="cms-section-sidebar grid grid-cols-12 md:grid">
|
|
21
|
-
<div class="col-span-12 mx-8 md:mx-5 mt-8">
|
|
22
|
-
<h1 class="text-4xl font-extrabold tracking-tight text-gray-900">
|
|
23
|
-
{{ getTranslatedProperty(category, "name") }}
|
|
24
|
-
</h1>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="col-span-12 md:col-span-7 lg:col-span-9 order-1 md:order-2">
|
|
27
|
-
<CmsGenericBlock
|
|
28
|
-
v-for="cmsBlock in mainBlocks"
|
|
29
|
-
:key="cmsBlock.id"
|
|
30
|
-
class="overflow-auto"
|
|
31
|
-
:content="cmsBlock"
|
|
32
|
-
/>
|
|
33
|
-
</div>
|
|
34
|
-
<div
|
|
35
|
-
:class="{
|
|
36
|
-
'align-top col-span-12 md:col-span-5 lg:col-span-3 order-2 md:order-1':
|
|
37
|
-
mobileBehavior !== 'hidden',
|
|
38
|
-
'hidden md:block': mobileBehavior === 'hidden',
|
|
39
|
-
}"
|
|
40
|
-
>
|
|
41
|
-
<CmsGenericBlock
|
|
42
|
-
v-for="cmsBlock in sidebarBlocks"
|
|
43
|
-
:key="cmsBlock.id"
|
|
44
|
-
class="overflow-auto"
|
|
45
|
-
:content="cmsBlock"
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</template>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
role="status"
|
|
4
|
-
class="max-w-sm p-4 border border-gray-200 rounded shadow animate-pulse md:p-6 dark:border-gray-700"
|
|
5
|
-
>
|
|
6
|
-
<div
|
|
7
|
-
class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700"
|
|
8
|
-
>
|
|
9
|
-
<svg
|
|
10
|
-
class="w-10 h-10 text-gray-200 dark:text-gray-600"
|
|
11
|
-
aria-hidden="true"
|
|
12
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
-
fill="currentColor"
|
|
14
|
-
viewBox="0 0 16 20"
|
|
15
|
-
>
|
|
16
|
-
<path
|
|
17
|
-
d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"
|
|
18
|
-
/>
|
|
19
|
-
<path
|
|
20
|
-
d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"
|
|
21
|
-
/>
|
|
22
|
-
</svg>
|
|
23
|
-
</div>
|
|
24
|
-
<div
|
|
25
|
-
class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"
|
|
26
|
-
></div>
|
|
27
|
-
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
|
|
28
|
-
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
|
|
29
|
-
<div class="mt-4 text-right">
|
|
30
|
-
<div
|
|
31
|
-
class="mt-8 h-2 bg-gray-200 rounded-full dark:bg-gray-700 w-24"
|
|
32
|
-
></div>
|
|
33
|
-
<button
|
|
34
|
-
disabled
|
|
35
|
-
tabindex="-1"
|
|
36
|
-
class="select-none font-sans font-bold text-center uppercase transition-all disabled:opacity-50 disabled:shadow-none disabled:pointer-events-none text-xs py-3 px-6 rounded-lg text-white shadow-gray-900/10 hover:shadow-gray-900/20 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none h-8 w-20 bg-gray-300 shadow-none hover:shadow-none"
|
|
37
|
-
type="button"
|
|
38
|
-
>
|
|
39
|
-
|
|
40
|
-
</button>
|
|
41
|
-
</div>
|
|
42
|
-
<span class="sr-only">Loading...</span>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|