@shopware/cms-base-layer 2.1.0 → 3.0.1

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.
Files changed (109) hide show
  1. package/README.md +93 -135
  2. package/app/app.config.ts +1 -0
  3. package/app/components/SwCategoryNavigation.vue +1 -0
  4. package/app/components/SwCategoryNavigationLink.vue +16 -8
  5. package/app/components/SwContactForm.vue +1 -0
  6. package/app/components/SwFilterChips.vue +1 -0
  7. package/app/components/SwListingProductPrice.vue +16 -4
  8. package/app/components/SwMedia3D.vue +13 -9
  9. package/app/components/SwNewsletterForm.vue +4 -3
  10. package/app/components/SwProductAddToCart.vue +24 -7
  11. package/app/components/SwProductCard.vue +5 -0
  12. package/app/components/SwProductCardDetails.vue +27 -9
  13. package/app/components/SwProductCardImage.vue +52 -35
  14. package/app/components/SwProductCardSkeleton.vue +37 -14
  15. package/app/components/SwProductGallery.vue +1 -0
  16. package/app/components/SwProductListingFilter.vue +5 -1
  17. package/app/components/SwProductListingFilters.vue +23 -8
  18. package/app/components/SwProductListingFiltersHorizontal.vue +6 -3
  19. package/app/components/SwProductPrice.vue +1 -0
  20. package/app/components/SwProductReviews.vue +14 -6
  21. package/app/components/SwProductReviewsForm.vue +15 -3
  22. package/app/components/SwProductUnits.vue +1 -0
  23. package/app/components/SwQuantitySelect.vue +10 -3
  24. package/app/components/SwSharedPrice.vue +1 -0
  25. package/app/components/SwSlider.vue +1 -0
  26. package/app/components/SwStockInfo.vue +12 -6
  27. package/app/components/SwVariantConfigurator.vue +5 -3
  28. package/app/components/listing-filters/SwFilterPrice.vue +79 -30
  29. package/app/components/listing-filters/SwFilterProperties.vue +40 -25
  30. package/app/components/listing-filters/SwFilterRating.vue +21 -9
  31. package/app/components/listing-filters/SwFilterShippingFree.vue +16 -8
  32. package/app/components/public/cms/CmsBlockSpatialViewer.vue +1 -0
  33. package/app/components/public/cms/CmsGenericBlock.vue +11 -4
  34. package/app/components/public/cms/CmsGenericElement.vue +8 -2
  35. package/app/components/public/cms/CmsNoComponent.vue +88 -8
  36. package/app/components/public/cms/CmsPage.md +1 -1
  37. package/app/components/public/cms/CmsPage.vue +5 -6
  38. package/app/components/public/cms/FrontendAccountCustomerGroupRegistrationPage.vue +52 -0
  39. package/app/components/public/cms/block/CmsBlockCategoryNavigation.vue +1 -0
  40. package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -0
  41. package/app/components/public/cms/block/CmsBlockCrossSelling.vue +1 -0
  42. package/app/components/public/cms/block/CmsBlockCustomForm.vue +1 -0
  43. package/app/components/public/cms/block/CmsBlockForm.vue +1 -0
  44. package/app/components/public/cms/block/CmsBlockGalleryBuybox.vue +4 -1
  45. package/app/components/public/cms/block/CmsBlockImage.vue +1 -0
  46. package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +4 -1
  47. package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
  48. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +4 -1
  49. package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
  50. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +1 -0
  51. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +4 -1
  52. package/app/components/public/cms/block/CmsBlockImageSimpleGrid.vue +1 -0
  53. package/app/components/public/cms/block/CmsBlockImageSlider.vue +1 -0
  54. package/app/components/public/cms/block/CmsBlockImageText.vue +4 -1
  55. package/app/components/public/cms/block/CmsBlockImageTextBubble.vue +4 -1
  56. package/app/components/public/cms/block/CmsBlockImageTextCover.vue +4 -1
  57. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +4 -12
  58. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +4 -1
  59. package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
  60. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +4 -1
  61. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +4 -1
  62. package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
  63. package/app/components/public/cms/block/CmsBlockProductHeading.vue +1 -0
  64. package/app/components/public/cms/block/CmsBlockProductListing.vue +1 -0
  65. package/app/components/public/cms/block/CmsBlockProductThreeColumn.vue +1 -0
  66. package/app/components/public/cms/block/CmsBlockSidebarFilter.vue +1 -0
  67. package/app/components/public/cms/block/CmsBlockText.vue +1 -0
  68. package/app/components/public/cms/block/CmsBlockTextHero.vue +1 -0
  69. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +2 -4
  70. package/app/components/public/cms/block/CmsBlockTextThreeColumn.vue +1 -0
  71. package/app/components/public/cms/block/CmsBlockTextTwoColumn.vue +4 -1
  72. package/app/components/public/cms/block/CmsBlockVimeoVideo.vue +1 -0
  73. package/app/components/public/cms/block/CmsBlockYoutubeVideo.vue +1 -0
  74. package/app/components/public/cms/element/CmsElementBuyBox.vue +90 -72
  75. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +14 -3
  76. package/app/components/public/cms/element/CmsElementCrossSelling.vue +3 -1
  77. package/app/components/public/cms/element/CmsElementCustomForm.vue +1 -0
  78. package/app/components/public/cms/element/CmsElementForm.vue +1 -0
  79. package/app/components/public/cms/element/CmsElementHtml.vue +0 -1
  80. package/app/components/public/cms/element/CmsElementImage.vue +15 -34
  81. package/app/components/public/cms/element/CmsElementImageGallery.vue +6 -2
  82. package/app/components/public/cms/element/CmsElementManufacturerLogo.vue +1 -0
  83. package/app/components/public/cms/element/CmsElementProductBox.vue +1 -0
  84. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +77 -29
  85. package/app/components/public/cms/element/CmsElementProductListing.vue +22 -7
  86. package/app/components/public/cms/element/CmsElementProductSlider.vue +6 -4
  87. package/app/components/public/cms/element/CmsElementText.vue +2 -0
  88. package/app/components/public/cms/element/CmsElementVimeoVideo.vue +1 -0
  89. package/app/components/public/cms/element/CmsElementYoutubeVideo.vue +1 -0
  90. package/app/components/public/cms/element/SwProductListingPagination.vue +8 -2
  91. package/app/components/public/cms/section/CmsSectionSidebar.vue +13 -8
  92. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +16 -5
  93. package/app/components/ui/SwitchButton.vue +53 -14
  94. package/app/components/ui/WishlistIcon.vue +8 -2
  95. package/app/composables/useImagePlaceholder.ts +2 -2
  96. package/app/composables/useLcpImagePreload.test.ts +1 -0
  97. package/app/composables/useLcpImagePreload.ts +12 -6
  98. package/app/composables/useTypedAppConfig.ts +16 -0
  99. package/app/helpers/cms/getImageSizes.test.ts +1 -0
  100. package/app/helpers/html-to-vue/ast.ts +1 -0
  101. package/app/helpers/html-to-vue/getOptionsFromNode.test.ts +1 -0
  102. package/app/helpers/html-to-vue/renderToHtml.ts +1 -0
  103. package/app/helpers/html-to-vue/renderer.ts +1 -0
  104. package/app/providers/shopware.test.ts +1 -0
  105. package/index.d.ts +37 -29
  106. package/nuxt.config.ts +5 -0
  107. package/package.json +22 -25
  108. package/app/plugins/unocss-runtime.client.ts +0 -23
  109. package/uno.config.ts +0 -94
@@ -5,6 +5,7 @@ import type {
5
5
  } from "@shopware/composables";
6
6
  import { useElementSize } from "@vueuse/core";
7
7
  import { computed, inject, ref, useTemplateRef } from "vue";
8
+
8
9
  import { useCmsElementConfig } from "#imports";
9
10
 
10
11
  const props = defineProps<{
@@ -80,7 +81,8 @@ const toggleTab = (index: number) => {
80
81
  :key="index"
81
82
  class="transition text-lg font-semibold text-surface-on-surface-variant cursor-pointer"
82
83
  :class="{
83
- 'border-b-3 border-brand-primary text-brand-primary': currentTabIndex === index,
84
+ 'border-b-3 border-brand-primary text-brand-primary':
85
+ currentTabIndex === index,
84
86
  }"
85
87
  @click="toggleTab(index)"
86
88
  >
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementForm } from "@shopware/composables";
3
3
  import { computed, defineAsyncComponent } from "vue";
4
+
4
5
  import { useCmsElementConfig } from "#imports";
5
6
 
6
7
  const props = defineProps<{
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementForm } from "@shopware/composables";
3
3
  import { computed, defineAsyncComponent } from "vue";
4
+
4
5
  import { useCmsElementConfig } from "#imports";
5
6
 
6
7
  const props = defineProps<{
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementHtml } from "@shopware/composables";
3
-
4
3
  import { getCmsLayoutConfiguration } from "@shopware/helpers";
5
4
  import { h } from "vue";
6
5
 
@@ -3,14 +3,12 @@ import type {
3
3
  CmsElementImage,
4
4
  CmsElementManufacturerLogo,
5
5
  } from "@shopware/composables";
6
- import {
7
- buildCdnImageUrl,
8
- buildUrlPrefix,
9
- generateCdnSrcSet,
10
- } from "@shopware/helpers";
6
+ import { buildUrlPrefix } from "@shopware/helpers";
11
7
  import { useElementSize } from "@vueuse/core";
12
- import { computed, defineAsyncComponent, inject, useTemplateRef } from "vue";
13
- import { useAppConfig, useCmsElementImage, useUrlResolver } from "#imports";
8
+ import { computed, defineAsyncComponent, useTemplateRef } from "vue";
9
+
10
+ import { useCmsElementImage, useUrlResolver } from "#imports";
11
+
14
12
  import { isSpatial } from "../../../../helpers/media/isSpatial";
15
13
 
16
14
  const props = defineProps<{
@@ -29,34 +27,17 @@ const {
29
27
  mimeType,
30
28
  } = useCmsElementImage(props.content);
31
29
 
32
- const imageSizes = inject<string>("cms-image-sizes", "100vw");
33
- const appConfig = useAppConfig();
34
-
35
30
  const imageElement = useTemplateRef<HTMLImageElement>("imageElement");
36
31
  const { width, height } = useElementSize(imageElement);
37
32
 
38
- const cdnOptions = computed(() => ({
39
- format: appConfig.backgroundImage?.format,
40
- quality: appConfig.backgroundImage?.quality,
41
- }));
42
-
43
- const srcSet = computed(
44
- () =>
45
- imageAttrs.value.srcset ||
46
- generateCdnSrcSet(imageAttrs.value.src, undefined, cdnOptions.value),
47
- );
33
+ function roundUp(num: number) {
34
+ return Math.ceil(num / 100) * 100;
35
+ }
48
36
 
49
- const srcPath = computed(() => {
50
- // Only add dimension params after mount to avoid hydration mismatch
51
- // (useElementSize returns 0 during SSR). The srcset handles responsive loading.
52
- if (width.value || height.value) {
53
- return buildCdnImageUrl(
54
- imageAttrs.value.src,
55
- { width: width.value, height: height.value },
56
- cdnOptions.value,
57
- );
58
- }
59
- return imageAttrs.value.src || "";
37
+ const imageSize = computed(() => {
38
+ const containerSize = Math.max(width.value || 0, height.value || 0);
39
+ if (!containerSize) return undefined;
40
+ return roundUp(containerSize * 2);
60
41
  });
61
42
 
62
43
  const imageComputedContainerAttrs = computed(() => {
@@ -110,7 +91,8 @@ const SwMedia3D = computed(() => {
110
91
  ref="imageElement"
111
92
  preset="productDetail"
112
93
  loading="lazy"
113
- :sizes="imageSizes"
94
+ :width="imageSize"
95
+ :height="imageSize"
114
96
  :class="{
115
97
  'w-full': !imageGallery,
116
98
  'h-full': !imageGallery && ['cover', 'stretch'].includes(displayMode),
@@ -120,8 +102,7 @@ const SwMedia3D = computed(() => {
120
102
  'object-contain': imageGallery || displayMode !== 'cover',
121
103
  }"
122
104
  :alt="imageAttrs.alt"
123
- :src="srcPath"
124
- :srcset="srcSet"
105
+ :src="imageAttrs.src"
125
106
  />
126
107
  </component>
127
108
  </template>
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementImageGallery } from "@shopware/composables";
3
3
  import { computed, defineAsyncComponent, ref } from "vue";
4
+
4
5
  import { useCmsElementConfig, useImagePlaceholder } from "#imports";
6
+
5
7
  import { isSpatial } from "../../../../helpers/media/isSpatial";
6
8
 
7
9
  // Load SwMedia3D only on client-side to avoid SSR issues with three.js packages
@@ -102,7 +104,7 @@ function onTouchEnd() {
102
104
  v-if="currentImage && isSpatial(currentImage)"
103
105
  :key="currentImage.url + '-3d'"
104
106
  class="w-full h-full relative"
105
- :style="{ minHeight }"
107
+ :style="{ minHeight }"
106
108
  >
107
109
  <client-only>
108
110
  <SwMedia3DAsync :src="currentImage.url" />
@@ -180,7 +182,9 @@ function onTouchEnd() {
180
182
  v-if="mediaGallery.length > 1 && navigationDots !== 'none'"
181
183
  :class="[
182
184
  'flex justify-center items-center gap-2',
183
- navigationDots === 'outside' ? 'mt-4' : 'absolute bottom-4 left-1/2 transform -translate-x-1/2',
185
+ navigationDots === 'outside'
186
+ ? 'mt-4'
187
+ : 'absolute bottom-4 left-1/2 transform -translate-x-1/2',
184
188
  ]"
185
189
  >
186
190
  <button
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementManufacturerLogo } from "@shopware/composables";
3
+
3
4
  import CmsElementImage from "./CmsElementImage.vue";
4
5
 
5
6
  defineProps<{
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementProductBox } from "@shopware/composables";
3
3
  import { computed } from "vue";
4
+
4
5
  import { useCmsElementConfig } from "#imports";
5
6
 
6
7
  const props = defineProps<{
@@ -5,6 +5,7 @@ import { getTranslatedProperty } from "@shopware/helpers";
5
5
  import { defu } from "defu";
6
6
  import { type Ref, computed, onMounted, ref } from "vue";
7
7
  import xss from "xss";
8
+
8
9
  import { useProduct, useShopwareContext, useUser } from "#imports";
9
10
  import type { Schemas } from "#shopware";
10
11
 
@@ -89,28 +90,39 @@ onMounted(async () => {
89
90
  </script>
90
91
 
91
92
  <template>
92
- <div class="w-full self-stretch inline-flex flex-col justify-start items-start gap-4">
93
+ <div
94
+ class="w-full self-stretch inline-flex flex-col justify-start items-start gap-4"
95
+ >
93
96
  <div class="self-stretch flex flex-col justify-center items-center">
94
97
  <div
95
98
  class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1 cursor-pointer hover:bg-surface-surface-variant transition-colors"
96
- @click="toggleSection(1)">
99
+ @click="toggleSection(1)"
100
+ >
97
101
  <div class="flex-1 flex items-center gap-2.5">
98
- <div class="flex-1 text-surface-on-surface text-base font-bold leading-normal">
102
+ <div
103
+ class="flex-1 text-surface-on-surface text-base font-bold leading-normal"
104
+ >
99
105
  {{ translations.product.description }}
100
106
  </div>
101
107
  </div>
102
108
  <div class="w-6 h-6 relative">
103
109
  <div class="w-2.5 h-1.5 left-[7px] top-[9.50px] absolute">
104
- <div class="i-carbon-chevron-down transition-transform duration-200"
105
- :class="{ 'rotate-180': isSectionOpen(1) }"></div>
110
+ <div
111
+ class="i-carbon-chevron-down transition-transform duration-200"
112
+ :class="{ 'rotate-180': isSectionOpen(1) }"
113
+ ></div>
106
114
  </div>
107
115
  </div>
108
116
  </div>
109
117
  </div>
110
118
  <Transition name="accordion">
111
- <div v-if="isSectionOpen(1)" class="self-stretch flex flex-col justify-center items-center gap-2.5">
119
+ <div
120
+ v-if="isSectionOpen(1)"
121
+ class="self-stretch flex flex-col justify-center items-center gap-2.5"
122
+ >
112
123
  <div
113
- class="self-stretch text-surface-on-surface text-base font-normal leading-normal">
124
+ class="self-stretch text-surface-on-surface text-base font-normal leading-normal"
125
+ >
114
126
  <!-- eslint-disable-next-line vue/no-v-html -->
115
127
  <div v-html="description"></div>
116
128
  </div>
@@ -119,43 +131,68 @@ onMounted(async () => {
119
131
  <div class="self-stretch flex flex-col justify-center items-center">
120
132
  <div
121
133
  class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1 cursor-pointer hover:bg-surface-surface-variant transition-colors"
122
- @click="toggleSection(2)">
134
+ @click="toggleSection(2)"
135
+ >
123
136
  <div class="flex-1 flex items-center gap-2.5">
124
- <div class="flex-1 text-surface-on-surface text-base font-bold leading-normal">
137
+ <div
138
+ class="flex-1 text-surface-on-surface text-base font-bold leading-normal"
139
+ >
125
140
  {{ translations.product.reviews }} ({{ reviews.length }})
126
141
  </div>
127
142
  </div>
128
143
  <div class="w-6 h-6 relative">
129
144
  <div class="w-2.5 h-1.5 left-[7px] top-[9.50px] absolute">
130
- <div class="i-carbon-chevron-down transition-transform duration-200"
131
- :class="{ 'rotate-180': isSectionOpen(2) }"></div>
145
+ <div
146
+ class="i-carbon-chevron-down transition-transform duration-200"
147
+ :class="{ 'rotate-180': isSectionOpen(2) }"
148
+ ></div>
132
149
  </div>
133
150
  </div>
134
151
  </div>
135
152
  </div>
136
153
  <Transition name="accordion">
137
- <div v-if="isSectionOpen(2)" class="self-stretch flex flex-col justify-center items-center gap-2.5">
154
+ <div
155
+ v-if="isSectionOpen(2)"
156
+ class="self-stretch flex flex-col justify-center items-center gap-2.5"
157
+ >
138
158
  <div
139
- class="self-stretch text-surface-on-surface text-base font-normal leading-normal">
140
- <SwProductReviews v-if="product" :product="product" :reviews="reviews" />
159
+ class="self-stretch text-surface-on-surface text-base font-normal leading-normal"
160
+ >
161
+ <SwProductReviews
162
+ v-if="product"
163
+ :product="product"
164
+ :reviews="reviews"
165
+ />
141
166
  <ClientOnly>
142
167
  <SwProductReviewsForm
143
168
  v-if="isLoggedIn && !reviewAdded && product"
144
169
  :product-id="product.id"
145
170
  @success="handleReviewAdded"
146
171
  />
147
- <div v-else-if="!isLoggedIn && product" class="mt-4 p-3 bg-surface-surface-container border border-surface-on-surface-variant rounded-md flex gap-2 md:gap-3 items-center">
148
- <div class="w-5 h-5 text-surface-on-surface-variant flex-shrink-0">
172
+ <div
173
+ v-else-if="!isLoggedIn && product"
174
+ class="mt-4 p-3 bg-surface-surface-container border border-surface-on-surface-variant rounded-md flex gap-2 md:gap-3 items-center"
175
+ >
176
+ <div
177
+ class="w-5 h-5 text-surface-on-surface-variant flex-shrink-0"
178
+ >
149
179
  <SwUserIcon :size="20" />
150
180
  </div>
151
- <span class="text-sm text-surface-on-surface-variant">{{ translations.product.messages.loginToReview }}</span>
181
+ <span class="text-sm text-surface-on-surface-variant">{{
182
+ translations.product.messages.loginToReview
183
+ }}</span>
152
184
  </div>
153
185
  </ClientOnly>
154
- <div v-if="reviewAdded" class="mt-4 p-3 bg-surface-surface-container border border-states-success rounded-md flex gap-2 md:gap-3 items-center">
186
+ <div
187
+ v-if="reviewAdded"
188
+ class="mt-4 p-3 bg-surface-surface-container border border-states-success rounded-md flex gap-2 md:gap-3 items-center"
189
+ >
155
190
  <div class="w-5 h-5 text-states-success flex-shrink-0">
156
191
  <SwCheckmarkIcon :size="20" :filled="true" alt="Success" />
157
192
  </div>
158
- <span class="text-sm text-states-success">{{ translations.product.messages.reviewAdded }}</span>
193
+ <span class="text-sm text-states-success">{{
194
+ translations.product.messages.reviewAdded
195
+ }}</span>
159
196
  </div>
160
197
  </div>
161
198
  </div>
@@ -163,32 +200,43 @@ onMounted(async () => {
163
200
  <div class="self-stretch flex flex-col justify-center items-center">
164
201
  <div
165
202
  class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1 cursor-pointer hover:bg-surface-surface-variant transition-colors"
166
- @click="toggleSection(3)">
203
+ @click="toggleSection(3)"
204
+ >
167
205
  <div class="flex-1 flex items-center gap-2.5">
168
- <div class="flex-1 text-surface-on-surface text-base font-bold leading-normal">
206
+ <div
207
+ class="flex-1 text-surface-on-surface text-base font-bold leading-normal"
208
+ >
169
209
  Category
170
210
  </div>
171
211
  </div>
172
212
  <div class="w-6 h-6 relative">
173
213
  <div class="w-2.5 h-1.5 left-[7px] top-[9.50px] absolute">
174
- <div class="i-carbon-chevron-down transition-transform duration-200"
175
- :class="{ 'rotate-180': isSectionOpen(3) }"></div>
214
+ <div
215
+ class="i-carbon-chevron-down transition-transform duration-200"
216
+ :class="{ 'rotate-180': isSectionOpen(3) }"
217
+ ></div>
176
218
  </div>
177
219
  </div>
178
220
  </div>
179
221
  </div>
180
222
  <Transition name="accordion">
181
- <div v-if="isSectionOpen(3)" class="self-stretch flex flex-col justify-center items-center gap-2.5">
223
+ <div
224
+ v-if="isSectionOpen(3)"
225
+ class="self-stretch flex flex-col justify-center items-center gap-2.5"
226
+ >
182
227
  <div
183
- class="self-stretch text-surface-on-surface text-base font-normal leading-normal">
228
+ class="self-stretch text-surface-on-surface text-base font-normal leading-normal"
229
+ >
184
230
  <div v-if="product?.categories">
185
- <div v-for="category in product.categories" :key="category.id" class="mb-2">
231
+ <div
232
+ v-for="category in product.categories"
233
+ :key="category.id"
234
+ class="mb-2"
235
+ >
186
236
  {{ category.name }}
187
237
  </div>
188
238
  </div>
189
- <div v-else>
190
- No categories available
191
- </div>
239
+ <div v-else>No categories available</div>
192
240
  </div>
193
241
  </div>
194
242
  </Transition>
@@ -3,8 +3,13 @@ import type { CmsElementProductListing } from "@shopware/composables";
3
3
  import { useCmsTranslations } from "@shopware/composables";
4
4
  import { defu } from "defu";
5
5
  import { computed, ref, useTemplateRef, watch } from "vue";
6
- import { useRoute, useRouter } from "vue-router";
7
- import { useCategoryListing, useCmsElementConfig } from "#imports";
6
+
7
+ import {
8
+ useCategoryListing,
9
+ useCmsElementConfig,
10
+ useRoute,
11
+ useRouter,
12
+ } from "#imports";
8
13
  import type { Schemas, operations } from "#shopware";
9
14
 
10
15
  const props = defineProps<{
@@ -151,10 +156,17 @@ compareRouteQueryWithInitialListing();
151
156
 
152
157
  <template>
153
158
  <div class="max-w-2xl mx-auto lg:max-w-full">
154
- <div v-if="!loading && getElements.length < 1" class="text-center text-xl py-16 text-surface-on-surface-variant">
159
+ <div
160
+ v-if="!loading && getElements.length < 1"
161
+ class="text-center text-xl py-16 text-surface-on-surface-variant"
162
+ >
155
163
  {{ translations.listing.noProducts }}
156
164
  </div>
157
- <div v-if="!loading" ref="productListElement" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 auto-rows-fr gap-x-4 sm:gap-x-6 lg:gap-x-8 gap-y-8 sm:gap-y-12 lg:gap-y-16">
165
+ <div
166
+ v-if="!loading"
167
+ ref="productListElement"
168
+ class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 auto-rows-fr gap-x-4 sm:gap-x-6 lg:gap-x-8 gap-y-8 sm:gap-y-12 lg:gap-y-16"
169
+ >
158
170
  <SwProductCard
159
171
  v-for="product in getElements"
160
172
  :key="product.id"
@@ -164,9 +176,12 @@ compareRouteQueryWithInitialListing();
164
176
  class="w-full"
165
177
  />
166
178
  </div>
167
- <div v-if="loading" data-testid="loading" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 auto-rows-fr gap-x-4 sm:gap-x-6 lg:gap-x-8 gap-y-8 sm:gap-y-12 lg:gap-y-16">
168
- <ProductCardSkeleton v-for="index in limit" :key="index"
169
- class="w-full" />
179
+ <div
180
+ v-if="loading"
181
+ data-testid="loading"
182
+ class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 auto-rows-fr gap-x-4 sm:gap-x-6 lg:gap-x-8 gap-y-8 sm:gap-y-12 lg:gap-y-16"
183
+ >
184
+ <ProductCardSkeleton v-for="index in limit" :key="index" class="w-full" />
170
185
  </div>
171
186
  <SwProductListingPagination
172
187
  v-if="!loading"
@@ -6,6 +6,7 @@ import type {
6
6
  import { useElementSize } from "@vueuse/core";
7
7
  import { computed, inject, useTemplateRef } from "vue";
8
8
  import type { CSSProperties, ComputedRef } from "vue";
9
+
9
10
  import { useCmsElementConfig } from "#imports";
10
11
 
11
12
  const props = defineProps<{
@@ -70,11 +71,12 @@ const hasVerticalAlignment = computed(
70
71
  );
71
72
  </script>
72
73
  <template>
73
- <div
74
- :style="hasVerticalAlignment ? verticalAlignStyle : undefined"
75
- >
74
+ <div :style="hasVerticalAlignment ? verticalAlignStyle : undefined">
76
75
  <div ref="productSlider" class="cms-element-product-slider">
77
- <h3 v-if="title" class="pl-6 pb-6 text-center md:text-left text-surface-on-surface">
76
+ <h3
77
+ v-if="title"
78
+ class="pl-6 pb-6 text-center md:text-left text-surface-on-surface"
79
+ >
78
80
  {{ title }}
79
81
  </h3>
80
82
  <div :class="{ 'py-5 border border-outline-outline-variant': border }">
@@ -3,7 +3,9 @@ import type { CmsElementText } from "@shopware/composables";
3
3
  import { decodeHTML } from "entities";
4
4
  import { computed, defineComponent, getCurrentInstance, h } from "vue";
5
5
  import type { CSSProperties, VNode, VNodeArrayChildren } from "vue";
6
+
6
7
  import { useCmsElementConfig, useUrlResolver } from "#imports";
8
+
7
9
  import { getOptionsFromNode } from "../../../../helpers/html-to-vue/getOptionsFromNode";
8
10
  import type { NodeObject } from "../../../../helpers/html-to-vue/getOptionsFromNode";
9
11
  import { renderHtml } from "../../../../helpers/html-to-vue/renderToHtml";
@@ -2,6 +2,7 @@
2
2
  import type { CmsElementVimeoVideo } from "@shopware/composables";
3
3
  import { ref } from "vue";
4
4
  import type { Ref } from "vue";
5
+
5
6
  import { useCmsElementConfig } from "#imports";
6
7
 
7
8
  const props = defineProps<{
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { CmsElementYoutubeVideo } from "@shopware/composables";
3
3
  import { computed } from "vue";
4
+
4
5
  import { useCmsElementConfig } from "#imports";
5
6
 
6
7
  const props = defineProps<{
@@ -35,7 +35,11 @@ const handleLimitChange = (event: Event) => {
35
35
  <div v-if="total > 0" class="flex flex-col gap-6 sm:gap-8 mt-6 sm:mt-8">
36
36
  <!-- Pagination Controls -->
37
37
  <div class="flex justify-center w-full">
38
- <SwPagination :total="total" :current="current" @change-page="handlePageChange" />
38
+ <SwPagination
39
+ :total="total"
40
+ :current="current"
41
+ @change-page="handlePageChange"
42
+ />
39
43
  </div>
40
44
 
41
45
  <!-- Items per page selector -->
@@ -61,7 +65,9 @@ const handleLimitChange = (event: Event) => {
61
65
  <option :value="30">30 {{ translations.listing.products }}</option>
62
66
  <option :value="45">45 {{ translations.listing.products }}</option>
63
67
  </select>
64
- <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
68
+ <div
69
+ class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"
70
+ >
65
71
  <SwChevronIcon direction="down" :size="16" />
66
72
  </div>
67
73
  </div>
@@ -18,14 +18,19 @@ provide("cms-section-layout", "sidebar");
18
18
  </script>
19
19
 
20
20
  <template>
21
- <div class="self-stretch flex flex-col lg:flex-row items-stretch gap-16" :class="{
22
- 'px-6': fullWidth,
23
- }">
24
- <aside :class="{
25
- 'w-full lg:w-72 xl:w-80 flex-shrink-0 bg-surface-surface flex flex-col justify-start items-stretch gap-4 lg:sticky lg:top-20 px-4 lg:px-0':
26
- mobileBehavior !== 'hidden',
27
- 'hidden lg:block': mobileBehavior === 'hidden',
28
- }">
21
+ <div
22
+ class="self-stretch flex flex-col lg:flex-row items-stretch gap-16"
23
+ :class="{
24
+ 'px-6': fullWidth,
25
+ }"
26
+ >
27
+ <aside
28
+ :class="{
29
+ 'w-full lg:w-72 xl:w-80 flex-shrink-0 bg-surface-surface flex flex-col justify-start items-stretch gap-4 lg:sticky lg:top-20 px-4 lg:px-0':
30
+ mobileBehavior !== 'hidden',
31
+ 'hidden lg:block': mobileBehavior === 'hidden',
32
+ }"
33
+ >
29
34
  <div v-for="cmsBlock in sidebarBlocks" :key="cmsBlock.id" class="w-full">
30
35
  <CmsGenericBlock :content="cmsBlock" />
31
36
  </div>
@@ -5,9 +5,14 @@ const placeholderSvg = useImagePlaceholder();
5
5
  </script>
6
6
 
7
7
  <template>
8
- <div role="status" class="p-px flex flex-col justify-start items-start overflow-hidden">
8
+ <div
9
+ role="status"
10
+ class="p-px flex flex-col justify-start items-start overflow-hidden"
11
+ >
9
12
  <!-- Image skeleton -->
10
- <div class="self-stretch min-h-[350px] relative flex items-center justify-center overflow-hidden aspect-square animate-pulse">
13
+ <div
14
+ class="self-stretch min-h-[350px] relative flex items-center justify-center overflow-hidden aspect-square animate-pulse"
15
+ >
11
16
  <img
12
17
  :src="placeholderSvg"
13
18
  alt=""
@@ -18,9 +23,15 @@ const placeholderSvg = useImagePlaceholder();
18
23
 
19
24
  <!-- Details skeleton -->
20
25
  <div class="w-full pt-4 animate-pulse">
21
- <div class="h-4 bg-gray-200 rounded-full dark:bg-gray-700 w-3/4 mb-3"></div>
22
- <div class="h-3 bg-gray-200 rounded-full dark:bg-gray-700 w-1/2 mb-4"></div>
23
- <div class="h-5 bg-gray-200 rounded-full dark:bg-gray-700 w-20 mb-3"></div>
26
+ <div
27
+ class="h-4 bg-gray-200 rounded-full dark:bg-gray-700 w-3/4 mb-3"
28
+ ></div>
29
+ <div
30
+ class="h-3 bg-gray-200 rounded-full dark:bg-gray-700 w-1/2 mb-4"
31
+ ></div>
32
+ <div
33
+ class="h-5 bg-gray-200 rounded-full dark:bg-gray-700 w-20 mb-3"
34
+ ></div>
24
35
  <div class="h-10 bg-gray-200 rounded dark:bg-gray-700 w-full"></div>
25
36
  </div>
26
37
  <span class="sr-only">Loading...</span>