@shopware/cms-base-layer 1.5.1 → 2.1.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.
Files changed (198) hide show
  1. package/README.md +398 -12
  2. package/app/app.config.ts +18 -0
  3. package/app/assets/icons/check-circle.svg +3 -0
  4. package/app/assets/icons/checkmark.svg +3 -0
  5. package/app/assets/icons/chevron.svg +3 -0
  6. package/app/assets/icons/exclamation-circle.svg +3 -0
  7. package/app/assets/icons/star-empty.svg +3 -0
  8. package/app/assets/icons/star-filled.svg +3 -0
  9. package/app/assets/icons/user.svg +1 -0
  10. package/app/components/SwCategoryNavigation.vue +83 -0
  11. package/app/components/SwCategoryNavigationLink.vue +128 -0
  12. package/{components → app/components}/SwContactForm.vue +27 -27
  13. package/app/components/SwFilterChips.vue +144 -0
  14. package/app/components/SwFilterDropdown.vue +54 -0
  15. package/app/components/SwListingProductPrice.vue +89 -0
  16. package/{components → app/components}/SwMedia3D.vue +4 -2
  17. package/{components → app/components}/SwNewsletterForm.vue +45 -34
  18. package/{components → app/components}/SwPagination.vue +3 -5
  19. package/{components → app/components}/SwProductAddToCart.vue +22 -27
  20. package/app/components/SwProductCard.vue +169 -0
  21. package/app/components/SwProductCardDetails.vue +74 -0
  22. package/app/components/SwProductCardImage.vue +90 -0
  23. package/app/components/SwProductCardSkeleton.vue +33 -0
  24. package/app/components/SwProductGallery.vue +43 -0
  25. package/app/components/SwProductListingFilter.vue +75 -0
  26. package/app/components/SwProductListingFilters.vue +304 -0
  27. package/app/components/SwProductListingFiltersHorizontal.vue +306 -0
  28. package/{components → app/components}/SwProductPrice.vue +3 -3
  29. package/app/components/SwProductRating.vue +40 -0
  30. package/{components → app/components}/SwProductReviews.vue +25 -23
  31. package/app/components/SwProductReviewsForm.vue +292 -0
  32. package/{components → app/components}/SwProductUnits.vue +10 -15
  33. package/app/components/SwQuantitySelect.vue +103 -0
  34. package/{components → app/components}/SwSlider.vue +154 -55
  35. package/app/components/SwSortDropdown.vue +87 -0
  36. package/app/components/SwStockInfo.vue +44 -0
  37. package/{components → app/components}/SwVariantConfigurator.vue +13 -12
  38. package/app/components/listing-filters/SwFilterPrice.vue +219 -0
  39. package/app/components/listing-filters/SwFilterProperties.vue +120 -0
  40. package/app/components/listing-filters/SwFilterRating.vue +99 -0
  41. package/app/components/listing-filters/SwFilterShippingFree.vue +114 -0
  42. package/app/components/public/cms/CmsBlockSpatialViewer.vue +94 -0
  43. package/app/components/public/cms/CmsGenericBlock.md +42 -0
  44. package/{components → app/components}/public/cms/CmsGenericBlock.vue +15 -1
  45. package/{components → app/components}/public/cms/CmsPage.md +19 -2
  46. package/{components → app/components}/public/cms/CmsPage.vue +30 -5
  47. package/{components → app/components}/public/cms/block/CmsBlockCenterText.vue +1 -1
  48. package/{components → app/components}/public/cms/block/CmsBlockGalleryBuybox.vue +5 -5
  49. package/{components → app/components}/public/cms/block/CmsBlockImageBubbleRow.vue +5 -5
  50. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +41 -0
  51. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +42 -0
  52. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +37 -0
  53. package/{components → app/components}/public/cms/block/CmsBlockImageSimpleGrid.vue +11 -5
  54. package/{components → app/components}/public/cms/block/CmsBlockImageText.vue +7 -3
  55. package/{components → app/components}/public/cms/block/CmsBlockImageTextBubble.vue +13 -16
  56. package/{components → app/components}/public/cms/block/CmsBlockImageTextCover.vue +7 -9
  57. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +88 -0
  58. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +53 -0
  59. package/{components → app/components}/public/cms/block/CmsBlockImageThreeColumn.vue +10 -4
  60. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +37 -0
  61. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +37 -0
  62. package/{components → app/components}/public/cms/block/CmsBlockProductHeading.vue +1 -1
  63. package/{components → app/components}/public/cms/block/CmsBlockProductThreeColumn.vue +10 -4
  64. package/{components → app/components}/public/cms/block/CmsBlockSidebarFilter.vue +3 -1
  65. package/{components → app/components}/public/cms/block/CmsBlockTextOnImage.vue +8 -5
  66. package/app/components/public/cms/element/CmsElementBuyBox.vue +145 -0
  67. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +53 -0
  68. package/{components → app/components}/public/cms/element/CmsElementCrossSelling.vue +22 -6
  69. package/{components → app/components}/public/cms/element/CmsElementImage.vue +58 -21
  70. package/app/components/public/cms/element/CmsElementImageGallery.vue +225 -0
  71. package/{components → app/components}/public/cms/element/CmsElementImageSlider.vue +2 -2
  72. package/{components → app/components}/public/cms/element/CmsElementProductBox.vue +8 -1
  73. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +217 -0
  74. package/{components → app/components}/public/cms/element/CmsElementProductListing.vue +31 -95
  75. package/app/components/public/cms/element/CmsElementProductName.vue +16 -0
  76. package/app/components/public/cms/element/CmsElementProductSlider.vue +101 -0
  77. package/app/components/public/cms/element/CmsElementSidebarFilter.vue +20 -0
  78. package/{components → app/components}/public/cms/element/CmsElementText.vue +17 -12
  79. package/app/components/public/cms/element/SwProductListingPagination.vue +70 -0
  80. package/{components → app/components}/public/cms/section/CmsSectionDefault.vue +2 -2
  81. package/app/components/public/cms/section/CmsSectionSidebar.vue +39 -0
  82. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +28 -0
  83. package/app/components/ui/BaseButton.vue +102 -0
  84. package/app/components/ui/BaseIcon.vue +15 -0
  85. package/app/components/ui/Checkbox.vue +49 -0
  86. package/app/components/ui/CheckmarkIcon.vue +23 -0
  87. package/app/components/ui/ChevronIcon.vue +34 -0
  88. package/app/components/ui/ExclamationIcon.vue +11 -0
  89. package/app/components/ui/IconButton.vue +32 -0
  90. package/app/components/ui/RadioButton.vue +26 -0
  91. package/app/components/ui/StarIcon.vue +18 -0
  92. package/app/components/ui/SwitchButton.vue +100 -0
  93. package/app/components/ui/UserIcon.vue +11 -0
  94. package/app/components/ui/WishlistIcon.vue +15 -0
  95. package/app/composables/useImagePlaceholder.ts +27 -0
  96. package/app/composables/useLcpImagePreload.test.ts +229 -0
  97. package/app/composables/useLcpImagePreload.ts +39 -0
  98. package/{helpers → app/helpers}/clientOnly.ts +5 -0
  99. package/app/helpers/cms/findFirstCmsImageUrl.ts +86 -0
  100. package/app/helpers/cms/getImageSizes.test.ts +50 -0
  101. package/app/helpers/cms/getImageSizes.ts +36 -0
  102. package/app/helpers/html-to-vue/ast.ts +106 -0
  103. package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.ts +1 -1
  104. package/{helpers → app/helpers}/html-to-vue/renderToHtml.ts +7 -11
  105. package/app/helpers/html-to-vue/renderer.ts +116 -0
  106. package/app/plugins/unocss-runtime.client.ts +23 -0
  107. package/app/providers/shopware.test.ts +213 -0
  108. package/app/providers/shopware.ts +107 -0
  109. package/dist/index.d.mts +3 -3
  110. package/dist/index.d.ts +3 -3
  111. package/dist/index.mjs +2 -2
  112. package/index.d.ts +36 -0
  113. package/nuxt.config.ts +100 -6
  114. package/package.json +33 -23
  115. package/uno.config.ts +94 -0
  116. package/components/SwCategoryNavigation.vue +0 -44
  117. package/components/SwCategoryNavigationLink.vue +0 -57
  118. package/components/SwListingProductPrice.vue +0 -89
  119. package/components/SwProductCard.vue +0 -286
  120. package/components/SwProductGallery.vue +0 -39
  121. package/components/SwProductListingFilter.vue +0 -42
  122. package/components/SwProductListingFilters.vue +0 -292
  123. package/components/listing-filters/SwFilterPrice.vue +0 -160
  124. package/components/listing-filters/SwFilterProperties.vue +0 -123
  125. package/components/listing-filters/SwFilterRating.vue +0 -101
  126. package/components/listing-filters/SwFilterShippingFree.vue +0 -104
  127. package/components/public/cms/CmsGenericBlock.md +0 -27
  128. package/components/public/cms/block/CmsBlockImageFourColumn.vue +0 -29
  129. package/components/public/cms/block/CmsBlockImageHighlightRow.vue +0 -27
  130. package/components/public/cms/block/CmsBlockImageTextGallery.vue +0 -85
  131. package/components/public/cms/block/CmsBlockImageTextRow.vue +0 -43
  132. package/components/public/cms/block/CmsBlockImageThreeCover.vue +0 -27
  133. package/components/public/cms/block/CmsBlockImageTwoColumn.vue +0 -25
  134. package/components/public/cms/element/CmsElementBuyBox.vue +0 -190
  135. package/components/public/cms/element/CmsElementCategoryNavigation.vue +0 -167
  136. package/components/public/cms/element/CmsElementImageGallery.vue +0 -249
  137. package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +0 -123
  138. package/components/public/cms/element/CmsElementProductName.vue +0 -10
  139. package/components/public/cms/element/CmsElementProductSlider.vue +0 -80
  140. package/components/public/cms/element/CmsElementSidebarFilter.vue +0 -12
  141. package/components/public/cms/section/CmsSectionSidebar.vue +0 -41
  142. package/components/public/cms/skeleton/ProductCardSkeleton.vue +0 -44
  143. package/helpers/html-to-vue/ast.ts +0 -72
  144. package/helpers/html-to-vue/renderer.ts +0 -56
  145. /package/{components → app/components}/SwSharedPrice.vue +0 -0
  146. /package/{components → app/components}/public/cms/CmsGenericElement.md +0 -0
  147. /package/{components → app/components}/public/cms/CmsGenericElement.vue +0 -0
  148. /package/{components → app/components}/public/cms/CmsNoComponent.vue +0 -0
  149. /package/{components → app/components}/public/cms/block/CmsBlockCategoryNavigation.vue +0 -0
  150. /package/{components → app/components}/public/cms/block/CmsBlockCrossSelling.vue +0 -0
  151. /package/{components → app/components}/public/cms/block/CmsBlockCustomForm.vue +0 -0
  152. /package/{components → app/components}/public/cms/block/CmsBlockDefault.vue +0 -0
  153. /package/{components → app/components}/public/cms/block/CmsBlockForm.vue +0 -0
  154. /package/{components → app/components}/public/cms/block/CmsBlockHtml.vue +0 -0
  155. /package/{components → app/components}/public/cms/block/CmsBlockImage.vue +0 -0
  156. /package/{components → app/components}/public/cms/block/CmsBlockImageCover.vue +0 -0
  157. /package/{components → app/components}/public/cms/block/CmsBlockImageGallery.vue +0 -0
  158. /package/{components → app/components}/public/cms/block/CmsBlockImageSlider.vue +0 -0
  159. /package/{components → app/components}/public/cms/block/CmsBlockProductDescriptionReviews.vue +0 -0
  160. /package/{components → app/components}/public/cms/block/CmsBlockProductListing.vue +0 -0
  161. /package/{components → app/components}/public/cms/block/CmsBlockProductSlider.vue +0 -0
  162. /package/{components → app/components}/public/cms/block/CmsBlockText.vue +0 -0
  163. /package/{components → app/components}/public/cms/block/CmsBlockTextHero.vue +0 -0
  164. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaser.vue +0 -0
  165. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaserSection.vue +0 -0
  166. /package/{components → app/components}/public/cms/block/CmsBlockTextThreeColumn.vue +0 -0
  167. /package/{components → app/components}/public/cms/block/CmsBlockTextTwoColumn.vue +0 -0
  168. /package/{components → app/components}/public/cms/block/CmsBlockVimeoVideo.vue +0 -0
  169. /package/{components → app/components}/public/cms/block/CmsBlockYoutubeVideo.vue +0 -0
  170. /package/{components → app/components}/public/cms/element/CmsElementBuyBox.md +0 -0
  171. /package/{components → app/components}/public/cms/element/CmsElementCategoryNavigation.md +0 -0
  172. /package/{components → app/components}/public/cms/element/CmsElementCrossSelling.md +0 -0
  173. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.md +0 -0
  174. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.vue +0 -0
  175. /package/{components → app/components}/public/cms/element/CmsElementForm.md +0 -0
  176. /package/{components → app/components}/public/cms/element/CmsElementForm.vue +0 -0
  177. /package/{components → app/components}/public/cms/element/CmsElementHtml.vue +0 -0
  178. /package/{components → app/components}/public/cms/element/CmsElementImage.md +0 -0
  179. /package/{components → app/components}/public/cms/element/CmsElementImageGallery.md +0 -0
  180. /package/{components → app/components}/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +0 -0
  181. /package/{components → app/components}/public/cms/element/CmsElementImageSlider.md +0 -0
  182. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.md +0 -0
  183. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.vue +0 -0
  184. /package/{components → app/components}/public/cms/element/CmsElementProductBox.md +0 -0
  185. /package/{components → app/components}/public/cms/element/CmsElementProductDescriptionReviews.md +0 -0
  186. /package/{components → app/components}/public/cms/element/CmsElementProductListing.md +0 -0
  187. /package/{components → app/components}/public/cms/element/CmsElementProductName.md +0 -0
  188. /package/{components → app/components}/public/cms/element/CmsElementProductSlider.md +0 -0
  189. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.md +0 -0
  190. /package/{components → app/components}/public/cms/element/CmsElementText.md +0 -0
  191. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.md +0 -0
  192. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.vue +0 -0
  193. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.md +0 -0
  194. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.vue +0 -0
  195. /package/{components → app/components}/public/cms/section/CmsSectionDefault.md +0 -0
  196. /package/{components → app/components}/public/cms/section/CmsSectionSidebar.md +0 -0
  197. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.test.ts +0 -0
  198. /package/{helpers → app/helpers}/media/isSpatial.ts +0 -0
package/README.md CHANGED
@@ -87,15 +87,377 @@ Since all CMS components are registered in your Nuxt application, you can now st
87
87
 
88
88
  See a [short guide](https://frontends.shopware.com/getting-started/cms/content-pages.html#use-the-cms-base-package) how to use `cms-base` package in your project based on Nuxt v3.
89
89
 
90
+ ## Default styling
91
+
92
+ The components are styled using [Tailwind CSS](https://tailwindcss.com/) utility classes, so you can use them in your project without any additional configuration if your project uses Tailwind CSS.
93
+
94
+ This layer provides a default Tailwind CSS configuration (see [uno.config.ts](./uno.config.ts) for details), which is used to style the components. If you want to customize the styling, you can do so by creating your own Tailwind CSS configuration file and extending the default one:
95
+
96
+ ```ts [nuxt.config.ts]
97
+ // nuxt.config.ts
98
+ export default defineNuxtConfig({
99
+ // ...
100
+ unocss: {
101
+ nuxtLayers: true, // enable Nuxt layers for UnoCSS
102
+ },
103
+ })
104
+ ```
105
+
106
+ ```ts [uno.config.ts]
107
+ // uno.config.ts
108
+ import config from './.nuxt/uno.config.mjs'
109
+
110
+ export default config
111
+ ```
112
+
113
+ Thanks to this, you can **use the default configuration** provided by this layer, or **extend/overwrite** it with your own customizations in your end-project:
114
+
115
+ ```ts [uno.config.ts]
116
+ // uno.config.ts
117
+ import { mergeConfigs } from '@unocss/core'
118
+ import config from './.nuxt/uno.config.mjs'
119
+
120
+ export default mergeConfigs([config, {
121
+ theme: {
122
+ colors: {
123
+ primary: '#ff3e00',
124
+ secondary: '#1c1c1c',
125
+ },
126
+ },
127
+ }])
128
+
129
+ ```
130
+
131
+ See the [UnoCSS reference](https://unocss.dev/integrations/nuxt#configuration) for more information on how to configure UnoCSS in Nuxt when work with layers.
132
+
133
+ ## 🖼️ Image Optimization
134
+
135
+ This layer includes [Nuxt Image](https://image.nuxt.com/) configuration optimized for Shopware 6 instances, with a custom provider that maps Nuxt Image modifiers to Shopware's query parameters (`width`, `height`, `quality`, `format`, `fit`).
136
+
137
+ > **Note for Cloud (SaaS) Users:** Image optimization and all modifiers used in the Nuxt Image module are handled automatically by Shopware Cloud infrastructure powered by [Fastly CDN](https://developer.shopware.com/docs/products/paas/shopware/cdn/). No additional configuration or plugins are required - simply use `<NuxtImg>` and all transformations (format conversion, quality adjustment, responsive sizing) work out of the box through Fastly's Image Optimizer.
138
+
139
+ ### Features
140
+
141
+ - ✅ Automatic WebP/AVIF format conversion
142
+ - ✅ Responsive image sizing based on viewport
143
+ - ✅ Lazy loading support
144
+ - ✅ Quality optimization
145
+ - ✅ Multiple image presets for common use cases
146
+ - ✅ Works with Shopware Cloud (SaaS) and self-hosted instances
147
+
148
+ ### Configuration
149
+
150
+ The layer comes pre-configured with optimized settings. No additional setup is required! The configuration includes:
151
+
152
+ **Available Presets:**
153
+ - `productCard` - Product listing images (WebP, quality 90, cover fit)
154
+ - `productDetail` - Product detail page images (WebP, quality 90, contain fit)
155
+ - `thumbnail` - Small thumbnails (150x150, WebP, quality 90)
156
+ - `hero` - Hero banners (WebP, quality 95, cover fit)
157
+
158
+ **Responsive Breakpoints:**
159
+ - `xs: 320px`, `sm: 640px`, `md: 768px`, `lg: 1024px`, `xl: 1280px`, `xxl: 1536px`
160
+
161
+ ### Usage in Components
162
+
163
+ Replace standard `<img>` tags with `<NuxtImg>` to enable automatic optimization:
164
+
165
+ ```vue
166
+ <!-- Using presets -->
167
+ <NuxtImg
168
+ src="https://cdn.shopware.store/media/path/to/image.jpg"
169
+ preset="productCard"
170
+ :width="400"
171
+ alt="Product"
172
+ loading="lazy"
173
+ />
174
+
175
+ <!-- Custom modifiers -->
176
+ <NuxtImg
177
+ src="https://cdn.shopware.store/media/path/to/image.jpg"
178
+ :width="800"
179
+ :height="600"
180
+ format="webp"
181
+ :quality="85"
182
+ fit="cover"
183
+ alt="Custom image"
184
+ />
185
+
186
+ <!-- Using with dynamic Shopware media URLs -->
187
+ <NuxtImg
188
+ :src="product.cover.media.url"
189
+ preset="productDetail"
190
+ :width="800"
191
+ :alt="product.cover.media.alt"
192
+ />
193
+ ```
194
+
195
+ ### Supported Modifiers
196
+
197
+ Shopware supports the following URL parameters for image transformation:
198
+
199
+ | Modifier | Description | Example | Support |
200
+ |----------|-------------|---------|---------|
201
+ | `width` | Image width in pixels | `400` | ✅ Always supported |
202
+ | `height` | Image height in pixels | `600` | ✅ Always supported |
203
+ | `quality` | Image quality (0-100) | `85` | ⚠️ Cloud/Plugin required* |
204
+ | `format` | Output format | `webp`, `avif`, `jpg`, `png` | ⚠️ Cloud/Plugin required* |
205
+ | `fit` | Resize behavior | `cover`, `contain`, `fill` | ⚠️ Cloud/Plugin required* |
206
+
207
+ *Advanced transformations (quality, format, fit) are available in:
208
+ - **Shopware Cloud (SaaS)**: Built-in support via managed infrastructure. For a complete list of supported image transformation parameters, see [Fastly Image Optimizer Query Parameters](https://www.fastly.com/documentation/reference/io/#query-parameters).
209
+ - **Self-hosted instances**: Require thumbnail processor plugins like [FroshPlatformThumbnailProcessor](https://github.com/FriendsOfShopware/FroshPlatformThumbnailProcessor) or third-party CDN integration
210
+
211
+ ### How It Works
212
+
213
+ This layer includes a custom Shopware provider for Nuxt Image that maps modifiers to Shopware's query parameters:
214
+ - `width` modifier → `?width=400`
215
+ - `height` modifier → `?height=300`
216
+ - `quality` modifier → `?quality=85`
217
+ - `format` modifier → `?format=webp`
218
+ - `fit` modifier → `?fit=cover`
219
+
220
+ When you use `<NuxtImg>`, the custom provider automatically converts your component props into the correct URL format for Shopware. The images are then processed on-the-fly by Shopware Cloud (SaaS) infrastructure or your configured thumbnail processor.
221
+
222
+ #### 🔍 Understanding Image Processing in Shopware
223
+
224
+ **Built-in Thumbnail Generation:**
225
+ Shopware has native thumbnail generation (using GD2 or ImageMagick) that creates predefined sizes (400x400, 800x800, 1920x1920) during image upload. These thumbnails are generated once and stored on your server.
226
+
227
+ **Dynamic On-the-Fly Transformations:**
228
+ For dynamic image transformations via query parameters (like `?width=800&format=webp`), you need **remote thumbnail generation** configured:
229
+
230
+ - **Shopware Cloud (SaaS)**: ✅ Fully supported out-of-the-box via Fastly CDN - all query parameters work automatically
231
+ - **Self-hosted**: ⚠️ Requires additional setup:
232
+ - Install a plugin like [FroshPlatformThumbnailProcessor](https://github.com/FriendsOfShopware/FroshPlatformThumbnailProcessor) for on-the-fly processing, OR
233
+ - Configure external middleware (Thumbor, Sharp, imgproxy) via [remote thumbnail generation](https://developer.shopware.com/docs/guides/plugins/plugins/content/media/remote-thumbnail-generation.html)
234
+
235
+ **Without remote thumbnail generation configured**, query parameters will be ignored and only the predefined static thumbnails will be served.
236
+
237
+ > **💡 Recommendation**: If you're self-hosting Shopware and want to use dynamic image transformations with Nuxt Image modifiers, install the FroshPlatformThumbnailProcessor plugin first to enable on-the-fly processing.
238
+
239
+ ### Customizing Configuration
240
+
241
+ You can extend or override the default settings in your project's `nuxt.config.ts`:
242
+
243
+ ```ts
244
+ export default defineNuxtConfig({
245
+ extends: ["@shopware/cms-base-layer"],
246
+
247
+ image: {
248
+ // Change default quality
249
+ quality: 85,
250
+
251
+ // Add/change formats
252
+ formats: ['avif', 'webp', 'jpg'],
253
+
254
+ // Override or add presets
255
+ presets: {
256
+ // Override existing preset
257
+ productCard: {
258
+ modifiers: {
259
+ format: 'avif',
260
+ quality: 80,
261
+ fit: 'cover',
262
+ }
263
+ },
264
+ // Add custom preset
265
+ categoryBanner: {
266
+ modifiers: {
267
+ format: 'webp',
268
+ quality: 90,
269
+ width: 1200,
270
+ height: 400,
271
+ fit: 'cover',
272
+ }
273
+ }
274
+ }
275
+ }
276
+ })
277
+ ```
278
+
279
+ ## 🖼️ Image Placeholder
280
+
281
+ This layer provides a `useImagePlaceholder` composable that generates an SVG placeholder for images during loading. The placeholder features a centered icon with a subtle background.
282
+
283
+ ### Customizing Placeholder Color
284
+
285
+ You can customize the placeholder color globally in your project's `app.config.ts`:
286
+
287
+ ```ts
288
+ export default defineAppConfig({
289
+ imagePlaceholder: {
290
+ color: "#your-color-here", // Default: #543B95
291
+ },
292
+ });
293
+ ```
294
+
295
+ Or use a custom color for specific instances:
296
+
297
+ ```vue
298
+ <script setup>
299
+ const customPlaceholder = useImagePlaceholder("#FF0000");
300
+ </script>
301
+
302
+ <template>
303
+ <NuxtImg :placeholder="customPlaceholder" src="..." />
304
+ </template>
305
+ ```
306
+
307
+ ## 🖼️ Background Image Optimization
308
+
309
+ CMS sections and blocks can have background images set via the Shopware admin. This layer automatically optimizes those background image URLs by appending `format` and `quality` query parameters — bringing the same optimization applied to `<NuxtImg>` components to CSS background images.
310
+
311
+ Both `CmsPage` (for section backgrounds) and `CmsGenericBlock` (for block backgrounds) read the configuration from `app.config.ts` and pass it to the `getBackgroundImageUrl` helper from `@shopware/helpers`.
312
+
313
+ ### Configuration
314
+
315
+ Default values are set in `app.config.ts` and can be overridden in your project:
316
+
317
+ ```ts
318
+ export default defineAppConfig({
319
+ backgroundImage: {
320
+ format: "webp", // Default: "webp" — output format ("webp" | "avif" | "jpg" | "png")
321
+ quality: 90, // Default: 90 — image quality (0-100)
322
+ },
323
+ });
324
+ ```
325
+
326
+ Setting `format` or `quality` to `undefined` (or omitting the key) will skip that parameter in the generated URL.
327
+
328
+ ### How It Works
329
+
330
+ When a CMS section or block has a `backgroundMedia` set, the components call `getBackgroundImageUrl()` which:
331
+
332
+ 1. Extracts the raw image URL from the CSS `url()` value
333
+ 2. Appends `width` or `height` based on the image's original dimensions (capped at 1920px)
334
+ 3. Adds `fit=crop,smart` for intelligent cropping
335
+ 4. Appends `format` and `quality` from `app.config.ts` if provided
336
+
337
+ **Example generated URL:**
338
+ ```
339
+ url("https://cdn.shopware.store/.../image.jpg?width=1000&fit=crop,smart&format=webp&quality=85")
340
+ ```
341
+
342
+ > **Note:** Like other dynamic image transformations, background image optimization requires remote thumbnail generation support. See the [Image Optimization](#%EF%B8%8F-image-optimization) section above for Shopware Cloud vs. self-hosted requirements.
343
+
344
+ ## LCP Image Preload
345
+
346
+ This layer includes a `useLcpImagePreload` composable that automatically preloads the first image found in CMS page content. This targets the [Largest Contentful Paint (LCP)](https://web.dev/lcp/) element, which is often a hero background image or the first visible image element.
347
+
348
+ ### How it works
349
+
350
+ The composable scans CMS sections in document order, checking:
351
+ 1. Section background images (`section.backgroundMedia`)
352
+ 2. Block background images (`block.backgroundMedia`)
353
+ 3. Image element media (`slot.data.media`)
354
+
355
+ The first image found is injected as a `<link rel="preload" as="image" fetchpriority="high">` in the `<head>` during SSR. This allows the browser to start fetching the LCP image immediately, before parsing CSS or executing JavaScript. The `fetchpriority="high"` attribute ensures the preload is prioritized — this is especially useful for background images which don't natively support `fetchpriority`.
356
+
357
+ ### Usage
358
+
359
+ The composable is already called in `CmsPage.vue`. If you override `CmsPage`, you can use it in your custom component:
360
+
361
+ ```vue
362
+ <script setup>
363
+ import { useLcpImagePreload } from "@shopware/cms-base-layer/composables/useLcpImagePreload";
364
+
365
+ const props = defineProps<{ content: Schemas["CmsPage"] }>();
366
+
367
+ useLcpImagePreload(props.content?.sections || []);
368
+ </script>
369
+ ```
370
+
371
+ The preload URL includes the optimized `format` and `quality` parameters from `app.config.ts` for both background images and element images.
372
+
373
+ ## Responsive CMS Images
374
+
375
+ CMS image elements (`CmsElementImage`) automatically serve appropriately-sized images using responsive `srcset` and `sizes` attributes. This prevents the browser from downloading images larger than their displayed dimensions — a common Lighthouse performance issue.
376
+
377
+ ### How it works
378
+
379
+ 1. **`CmsGenericBlock`** counts the number of slots in each block, `provide`s a responsive `sizes` value (e.g., a 2-slot block means images are ~50% viewport width on desktop), and `provide`s the slot count via `cms-block-slot-count` for slider SSR breakpoint scaling.
380
+ 2. **`CmsElementImage`** `inject`s the sizes hint and applies it to `<NuxtImg>`.
381
+ 3. If the media has **pre-generated thumbnails** from Shopware, the existing `srcset` from thumbnails is used.
382
+ 4. If **no thumbnails** exist, a synthetic `srcset` is generated using CDN width-based resizing (`?width=400`, `?width=800`, etc.) via the `generateCdnSrcSet` helper from `@shopware/helpers`.
383
+ 5. **Slider components** (`CmsElementProductSlider`, `CmsElementCrossSelling`) `inject` the slot count to scale their SSR breakpoints — ensuring media queries account for the container being a fraction of the viewport (e.g., a 2-slot block doubles the breakpoint thresholds).
384
+
385
+ The browser combines `sizes` + `srcset` to download only the image size it actually needs — during HTML parsing, before any JavaScript runs.
386
+
387
+ ### Configuration
388
+
389
+ Default slot-count-to-sizes mappings are set in `app.config.ts` and can be overridden:
390
+
391
+ ```ts
392
+ export default defineAppConfig({
393
+ imageSizes: {
394
+ // slot count → sizes attribute value
395
+ 1: "(max-width: 768px) 100vw, 100vw", // full-width blocks
396
+ 2: "(max-width: 768px) 100vw, 50vw", // two-column blocks (e.g., image-text)
397
+ 3: "(max-width: 768px) 100vw, 33vw", // three-column blocks
398
+ default: "(max-width: 768px) 50vw, 25vw", // 4+ columns
399
+ },
400
+ });
401
+ ```
402
+
403
+ For example, to cap image sizes at a fixed pixel width for boxed layouts:
404
+
405
+ ```ts
406
+ export default defineAppConfig({
407
+ imageSizes: {
408
+ 1: "(max-width: 768px) 100vw, 1200px",
409
+ 2: "(max-width: 768px) 100vw, 600px",
410
+ 3: "(max-width: 768px) 100vw, 400px",
411
+ default: "(max-width: 768px) 50vw, 300px",
412
+ },
413
+ });
414
+ ```
415
+
416
+ ### Per-block override
417
+
418
+ Individual block components can override the sizes value by calling `provide("cms-image-sizes", "custom value")` — this takes precedence over the default from `CmsGenericBlock`.
419
+
420
+ ### Synthetic srcset fallback
421
+
422
+ When Shopware media has no thumbnails (common in Cloud/SaaS setups using CDN-based resizing), the layer generates a synthetic `srcset` using CDN query parameters:
423
+
424
+ ```html
425
+ <img srcset="
426
+ ...image.jpg?width=400&fit=crop,smart&format=webp&quality=90 400w,
427
+ ...image.jpg?width=800&fit=crop,smart&format=webp&quality=90 800w,
428
+ ...image.jpg?width=1200&fit=crop,smart&format=webp&quality=90 1200w,
429
+ ...image.jpg?width=1600&fit=crop,smart&format=webp&quality=90 1600w"
430
+ sizes="(max-width: 768px) 100vw, 50vw"
431
+ >
432
+ ```
433
+
434
+ The `format` and `quality` values are taken from the `backgroundImage` config in `app.config.ts`.
435
+
436
+ > **Note:** Synthetic srcset requires CDN-based image resizing support. See the [Image Optimization](#%EF%B8%8F-image-optimization) section for requirements.
437
+
438
+ ## 🔄 UnoCSS Runtime
439
+
440
+ This layer includes a client-side [UnoCSS runtime](https://unocss.dev/integrations/runtime) plugin that resolves utility classes dynamically at runtime using a DOM MutationObserver. This is useful when CMS content from Shopware contains utility classes that aren't known at build time (e.g., inline styles or dynamic class bindings from the admin panel).
441
+
442
+ The runtime is **enabled by default**. To disable it, set `unocssRuntime` to `false` in your project's `app.config.ts`:
443
+
444
+ ```ts
445
+ export default defineAppConfig({
446
+ unocssRuntime: false,
447
+ });
448
+ ```
449
+
450
+ > **When to disable**: If you don't use dynamic CMS utility classes, or if you experience performance issues caused by the MutationObserver in pages with frequent DOM mutations.
451
+
90
452
  ## 📘 Available components
91
453
 
92
- The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base.html#available-components).
454
+ The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base-layer.html#available-components).
93
455
 
94
456
  ## 🔄 Overwriting components
95
457
 
96
458
  The procedure is:
97
459
 
98
- - find a component in component's [list](https://frontends.shopware.com/packages/cms-base.html#available-components), using a [Vue devtools](https://devtools.vuejs.org/) or browsing the github [repository](https://github.com/shopware/frontends/tree/main/packages/cms-base-layer/components)
460
+ - find a component in component's [list](https://frontends.shopware.com/packages/cms-base.html#available-components), using a [Vue devtools](https://devtools.vuejs.org/) or browsing the github [repository](https://github.com/shopware/frontends/tree/main/packages/cms-base-layer/app/components)
99
461
  - take its name
100
462
  - create a file with the same name and place it into `~/components` dir in your nuxt project (or wherever according your nuxt config)
101
463
 
@@ -105,7 +467,7 @@ The procedure is:
105
467
 
106
468
  ❗**Internal components are not a part of public API. Once overwritten you need to track the changes on your own.**
107
469
 
108
- There is also a possibility to override the internal components, shared between public blocks and elements, the ones starting with `Sw` prefix, like [SwSlider.vue](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/components/SwSlider.vue) or [SwProductCard.vue](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/components/SwProductCard.vue).
470
+ There is also a possibility to override the internal components, shared between public blocks and elements, the ones starting with `Sw` prefix, like [SwSlider.vue](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/app/components/SwSlider.vue) or [SwProductCard.vue](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/app/components/SwProductCard.vue).
109
471
 
110
472
  An example: some components use `SwSharedPrice.vue` to show prices with corresponding currency for products in many places like product card, product details page and so on. In order to change the way how the price is displayed consistently - create a one component with a name `SwSharedPrice.vue` and that's it. The new component will be used everywhere where is "imported" (autoimported actually).
111
473
 
@@ -124,7 +486,7 @@ No additional packages needed to be installed.
124
486
 
125
487
  - [📘 Documentation](https://frontends.shopware.com)
126
488
 
127
- - [👥 Community](https://shopwarecommunity.slack.com) (`#composable-frontends`)
489
+ - [👥 Community](https://discord.com/channels/1308047705309708348/1405501315160739951) (`#composable-frontend`)
128
490
 
129
491
  <!-- AUTO GENERATED CHANGELOG -->
130
492
 
@@ -132,18 +494,42 @@ No additional packages needed to be installed.
132
494
 
133
495
  Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
134
496
 
135
- ### Latest changes: 1.5.1
497
+ ### Latest changes: 2.1.0
498
+
499
+ ### Minor Changes
500
+
501
+ - [#2275](https://github.com/shopware/frontends/pull/2275) [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c) Thanks [@mkucmus](https://github.com/mkucmus)! - - Add configurable UnoCSS runtime plugin for dynamic CMS class support
502
+ - Extend theme with overlay and fixed color tokens
503
+
504
+ - [#2223](https://github.com/shopware/frontends/pull/2223) [`1db8704`](https://github.com/shopware/frontends/commit/1db870413dcea13c690504ffcaee13526bc8035f) Thanks [@mkucmus](https://github.com/mkucmus)! - Add horizontal filter layout for product listings. When the sidebar filter element is placed outside a sidebar section, filters now display as horizontal dropdowns.
505
+
506
+ Includes new `SwFilterDropdown` and `SwProductListingFiltersHorizontal` components, with a `displayMode` prop added to all filter components to support both layouts.
507
+
508
+ - [#2287](https://github.com/shopware/frontends/pull/2287) [`c9bde38`](https://github.com/shopware/frontends/commit/c9bde38d497d5c6c2fbd97700a362eb44ce8881f) Thanks [@mkucmus](https://github.com/mkucmus)! - Add responsive image sizing (srcset/sizes) via provide/inject, LCP preload with fetchpriority, configurable imageSizes in app.config, ISR route rules, inline styles, and AppConfig type declarations
509
+
510
+ - [#2241](https://github.com/shopware/frontends/pull/2241) [`9ccbaa1`](https://github.com/shopware/frontends/commit/9ccbaa1fb6cc1f790d979c3dd3745c5402b6d8d1) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Replace `withDefaults` by props destructure
511
+
512
+ - [#2273](https://github.com/shopware/frontends/pull/2273) [`18455e7`](https://github.com/shopware/frontends/commit/18455e77221fcc77b119d0ba7eae89dfce0e2941) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Remove SwMedia3D.vue component from autoload
513
+
514
+ - [#2268](https://github.com/shopware/frontends/pull/2268) [`c3fff84`](https://github.com/shopware/frontends/commit/c3fff847e46a17c9c905bd893f1c1de287426c65) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Improve accessibility (A11y) of CMS base layer components.
515
+
516
+ - [#2214](https://github.com/shopware/frontends/pull/2214) [`ccb9384`](https://github.com/shopware/frontends/commit/ccb93849be07f1b6a4e192de02579a528b5b6ac4) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Add full TypeScript typing to html-to-vue helper functions
136
517
 
137
518
  ### Patch Changes
138
519
 
139
- - [#1879](https://github.com/shopware/frontends/pull/1879) [`eaf170b`](https://github.com/shopware/frontends/commit/eaf170bd037a278b3d2e155c4d69de8e5fd9516d) Thanks [@acuriouspotion](https://github.com/acuriouspotion)! - Fix youtube player control display and usage of advanced privacy mode setting.
520
+ - [#2226](https://github.com/shopware/frontends/pull/2226) [`d77eacc`](https://github.com/shopware/frontends/commit/d77eaccdec6c56a6f2d999048c751fb9f01177d4) Thanks [@mkucmus](https://github.com/mkucmus)! - Add config prop support to `SwProductGallery` and make `CmsElementImageGallery` respect `minHeight`, `navigationArrows`, and `navigationDots` config values.
140
521
 
141
- - [#1862](https://github.com/shopware/frontends/pull/1862) [`20fd2c6`](https://github.com/shopware/frontends/commit/20fd2c615738f93a3947c69f351fc5d37ea89ebf) Thanks [@aheartforspinach](https://github.com/aheartforspinach)! - Fix CmsSectionSidebar.vue when used on a landing page by removing useCategory and related code
522
+ - [#2275](https://github.com/shopware/frontends/pull/2275) [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c) Thanks [@mkucmus](https://github.com/mkucmus)! - - Fix CMS block layout: height propagation in CmsBlockImageText, conditional `h-full` in CmsElementImage, `backgroundSize` forwarding in CmsGenericBlock, `w-full` for full_width sizing mode, and exclude `sizingMode` from section inline styles
523
+ - Fix vertical alignment support in CmsElementText and CmsElementProductSlider using `align-content` CSS property
524
+ - Remove rounded corners from image placeholder SVG and simplify CmsBlockTextOnImage structure
142
525
 
143
- - [#1884](https://github.com/shopware/frontends/pull/1884) [`3004b97`](https://github.com/shopware/frontends/commit/3004b973913b90cdf4b255ffb3f9cee265241666) Thanks [@MorennMcFly](https://github.com/MorennMcFly)! - Fix cms blocks TextTeaserSection and TextTwoColumn responsivity so the elements stack from md breakpoint and under.
526
+ - [#2210](https://github.com/shopware/frontends/pull/2210) [`c6b88b7`](https://github.com/shopware/frontends/commit/c6b88b7d2c50054188356aeb0f83053554d442f5) Thanks [@mkucmus](https://github.com/mkucmus)! - Anchor tags with "btn btn-primary" classes from the API were not being
527
+ transformed to Tailwind utility classes due to condition matching issues
528
+ in the html-to-vue renderer.
144
529
 
145
- - [#1863](https://github.com/shopware/frontends/pull/1863) [`f8c5cd5`](https://github.com/shopware/frontends/commit/f8c5cd5c9aa8b65d394c831e3ac548b4743c53a6) Thanks [@aheartforspinach](https://github.com/aheartforspinach)! - move `CmsBlockHtml.vue` to block folder (instead of element), remove `CmsBlockHtml.md`
530
+ - [#2318](https://github.com/shopware/frontends/pull/2318) [`b40305f`](https://github.com/shopware/frontends/commit/b40305f9e2ec51f29c279650e411bb773438faed) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Components (CmsElementBuyBox, SwListingProductPrice, SwProductPrice) updated to use `hasListPrice` instead of deprecated `isListPrice`.
146
531
 
147
- - Updated dependencies [[`ab040bb`](https://github.com/shopware/frontends/commit/ab040bb6cc05541001a983c26d5cb6dbf3192394), [`c8fa438`](https://github.com/shopware/frontends/commit/c8fa438b38de6dbc43a2895f2d1906907447c384)]:
148
- - @shopware/composables@1.9.1
149
- - @shopware/helpers@1.5.0
532
+ - Updated dependencies [[`9604f22`](https://github.com/shopware/frontends/commit/9604f22678150d04c3c3156fd8ee2ce440c8c8bf), [`b40305f`](https://github.com/shopware/frontends/commit/b40305f9e2ec51f29c279650e411bb773438faed), [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c), [`b5f7e2a`](https://github.com/shopware/frontends/commit/b5f7e2a20c9dfdde1690e9006252d847f732bc0a), [`b5f7e2a`](https://github.com/shopware/frontends/commit/b5f7e2a20c9dfdde1690e9006252d847f732bc0a), [`9604f22`](https://github.com/shopware/frontends/commit/9604f22678150d04c3c3156fd8ee2ce440c8c8bf), [`a871c7b`](https://github.com/shopware/frontends/commit/a871c7b6256b75c2e40d93fc0354ba1971420062), [`c9bde38`](https://github.com/shopware/frontends/commit/c9bde38d497d5c6c2fbd97700a362eb44ce8881f)]:
533
+ - @shopware/api-client@1.5.0
534
+ - @shopware/composables@1.11.0
535
+ - @shopware/helpers@1.7.0
@@ -0,0 +1,18 @@
1
+ import { defineAppConfig } from "#imports";
2
+
3
+ export default defineAppConfig({
4
+ imagePlaceholder: {
5
+ color: "#543B95",
6
+ },
7
+ backgroundImage: {
8
+ format: "webp",
9
+ quality: 90,
10
+ },
11
+ imageSizes: {
12
+ 1: "(max-width: 768px) 100vw, 100vw",
13
+ 2: "(max-width: 768px) 100vw, 50vw",
14
+ 3: "(max-width: 768px) 100vw, 33vw",
15
+ default: "(max-width: 768px) 50vw, 25vw",
16
+ },
17
+ unocssRuntime: true,
18
+ });
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM7.56066 10.9393L10.5 13.8787L16.4393 7.93934C17.0251 7.35355 17.9749 7.35355 18.5607 7.93934C19.1464 8.52513 19.1464 9.47487 18.5607 10.0607L11.5607 17.0607C10.9749 17.6464 10.0251 17.6464 9.43934 17.0607L5.43934 13.0607C4.85355 12.4749 4.85355 11.5251 5.43934 10.9393C6.02513 10.3536 6.97487 10.3536 7.56066 10.9393Z" fill="#1E1E24"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M3.06066 10.4393C2.47487 9.85355 1.52513 9.85355 0.93934 10.4393C0.353553 11.0251 0.353553 11.9749 0.93934 12.5607L7.93934 19.5607C8.52513 20.1464 9.47487 20.1464 10.0607 19.5607L23.0607 6.56066C23.6464 5.97487 23.6464 5.02513 23.0607 4.43934C22.4749 3.85355 21.5251 3.85355 20.9393 4.43934L9 16.3787L3.06066 10.4393Z" fill="#1E1E24"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8.70711 8.79289C8.31658 8.40237 7.68342 8.40237 7.29289 8.79289C6.90237 9.18342 6.90237 9.81658 7.29289 10.2071L11.2929 14.2071C11.6834 14.5976 12.3166 14.5976 12.7071 14.2071L16.7071 10.2071C17.0976 9.81658 17.0976 9.18342 16.7071 8.79289C16.3166 8.40237 15.6834 8.40237 15.2929 8.79289L12 12.0858L8.70711 8.79289Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12ZM10.5 7.5V12C10.5 12.8284 11.1716 13.5 12 13.5C12.8284 13.5 13.5 12.8284 13.5 12V7.5C13.5 6.67157 12.8284 6 12 6C11.1716 6 10.5 6.67157 10.5 7.5ZM12 18C12.8284 18 13.5 17.3284 13.5 16.5C13.5 15.6716 12.8284 15 12 15C11.1716 15 10.5 15.6716 10.5 16.5C10.5 17.3284 11.1716 18 12 18Z" fill="#1E1E24"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8.00015 12.5599L12.0046 14.3245L11.5638 9.97075L14.4795 6.70761L10.2026 5.78147L8.00015 2.00012L5.79771 5.78147L1.52085 6.70761L4.43653 9.97075L3.99572 14.3245L8.00015 12.5599ZM4.53339 15.5446C3.85953 15.8416 3.07255 15.536 2.7756 14.8622C2.68261 14.6511 2.64594 14.4196 2.66917 14.1902L3.0508 10.421L0.526591 7.59599C0.0359476 7.04688 0.083346 6.20399 0.632458 5.71335C0.804415 5.5597 1.01328 5.45328 1.23866 5.40448L4.94128 4.60269L6.848 1.32905C7.21862 0.692736 8.0349 0.477347 8.67122 0.847967C8.87048 0.964028 9.03624 1.12979 9.1523 1.32905L11.059 4.60269L14.7616 5.40448C15.4813 5.56033 15.9384 6.2701 15.7826 6.9898C15.7338 7.21517 15.6274 7.42404 15.4737 7.59599L12.9495 10.421L13.3311 14.1902C13.4053 14.9228 12.8715 15.5769 12.1389 15.651C11.9095 15.6743 11.6779 15.6376 11.4669 15.5446L8.00015 14.0169L4.53339 15.5446Z" fill="#696470"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M4.53339 15.5446C3.85953 15.8416 3.07255 15.536 2.7756 14.8622C2.68261 14.6511 2.64594 14.4196 2.66917 14.1902L3.0508 10.421L0.526591 7.59599C0.0359476 7.04688 0.083346 6.20399 0.632458 5.71335C0.804415 5.5597 1.01328 5.45328 1.23866 5.40448L4.94128 4.60269L6.848 1.32905C7.21862 0.692736 8.0349 0.477347 8.67122 0.847967C8.87048 0.964028 9.03624 1.12979 9.1523 1.32905L11.059 4.60269L14.7616 5.40448C15.4813 5.56033 15.9384 6.2701 15.7826 6.9898C15.7338 7.21517 15.6274 7.42404 15.4737 7.59599L12.9495 10.421L13.3311 14.1902C13.4053 14.9228 12.8715 15.5769 12.1389 15.651C11.9095 15.6743 11.6779 15.6376 11.4669 15.5446L8.00015 14.0169L4.53339 15.5446Z" fill="#696470"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ <svg id="meteor-icon-kit__regular-user" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6C6 8.20914 7.79086 10 10 10C12.2091 10 14 8.20914 14 6C14 3.79086 12.2091 2 10 2zM10 0C13.3137 0 16 2.68629 16 6C16 9.3137 13.3137 12 10 12C6.68629 12 4 9.3137 4 6C4 2.68629 6.68629 0 10 0zM2 21.099C2 21.6513 1.55228 22.099 1 22.099C0.44772 22.099 0 21.6513 0 21.099V19C0 16.2386 2.23858 14 5 14H15.0007C17.7621 14 20.0007 16.2386 20.0007 19V21.099C20.0007 21.6513 19.553 22.099 19.0007 22.099C18.4484 22.099 18.0007 21.6513 18.0007 21.099V19C18.0007 17.3431 16.6576 16 15.0007 16H5C3.34315 16 2 17.3431 2 19V21.099z" fill="currentColor"/></svg>
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import type { Schemas } from "#shopware";
4
+
5
+ const {
6
+ activeCategory,
7
+ elements,
8
+ level = 0,
9
+ } = defineProps<{
10
+ activeCategory: Schemas["Category"];
11
+ elements: Schemas["Category"][];
12
+ level: number;
13
+ }>();
14
+
15
+ const expandedItems = ref<Set<string>>(new Set());
16
+
17
+ function isActive(navigationElement: Schemas["Category"]) {
18
+ return navigationElement.id === activeCategory?.id;
19
+ }
20
+
21
+ function toggleExpanded(id: string) {
22
+ if (expandedItems.value.has(id)) {
23
+ expandedItems.value.delete(id);
24
+ } else {
25
+ expandedItems.value.add(id);
26
+ }
27
+ }
28
+
29
+ function isExpanded(id: string) {
30
+ return expandedItems.value.has(id);
31
+ }
32
+ </script>
33
+ <template>
34
+ <div
35
+ v-if="elements?.length"
36
+ class="self-stretch flex flex-col justify-start items-start gap-4"
37
+ >
38
+ <div
39
+ v-for="(navigationElement, index) in elements"
40
+ :key="index"
41
+ class="w-full"
42
+ >
43
+ <SwCategoryNavigationLink
44
+ :navigation-element="navigationElement"
45
+ :is-active="isActive(navigationElement)"
46
+ :is-expanded="isExpanded(navigationElement.id)"
47
+ :level="level"
48
+ @toggle="toggleExpanded(navigationElement.id)"
49
+ />
50
+ <transition name="filter-collapse">
51
+ <div
52
+ v-if="navigationElement.children && isExpanded(navigationElement.id)"
53
+ class="self-stretch flex flex-col justify-start items-start"
54
+ >
55
+ <SwCategoryNavigation
56
+ :elements="navigationElement.children"
57
+ :active-category="activeCategory"
58
+ :level="level + 1"
59
+ />
60
+ </div>
61
+ </transition>
62
+ </div>
63
+ </div>
64
+ </template>
65
+ <style scoped>
66
+ .filter-collapse-enter-active,
67
+ .filter-collapse-leave-active {
68
+ transition:
69
+ max-height 240ms ease,
70
+ opacity 200ms ease;
71
+ overflow: hidden;
72
+ }
73
+ .filter-collapse-enter-from,
74
+ .filter-collapse-leave-to {
75
+ max-height: 0;
76
+ opacity: 0;
77
+ }
78
+ .filter-collapse-enter-to,
79
+ .filter-collapse-leave-from {
80
+ max-height: 800px;
81
+ opacity: 1;
82
+ }
83
+ </style>