@shopware/cms-base-layer 1.5.1 → 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.
Files changed (183) hide show
  1. package/README.md +330 -12
  2. package/app/app.config.ts +7 -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 +76 -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/SwListingProductPrice.vue +89 -0
  15. package/{components → app/components}/SwNewsletterForm.vue +45 -34
  16. package/{components → app/components}/SwPagination.vue +3 -5
  17. package/{components → app/components}/SwProductAddToCart.vue +22 -27
  18. package/app/components/SwProductCard.vue +170 -0
  19. package/app/components/SwProductCardDetails.vue +57 -0
  20. package/app/components/SwProductCardImage.vue +87 -0
  21. package/app/components/SwProductCardSkeleton.vue +33 -0
  22. package/app/components/SwProductListingFilter.vue +64 -0
  23. package/app/components/SwProductListingFilters.vue +308 -0
  24. package/{components → app/components}/SwProductReviews.vue +28 -13
  25. package/app/components/SwProductReviewsForm.vue +292 -0
  26. package/app/components/SwQuantitySelect.vue +106 -0
  27. package/{components → app/components}/SwSlider.vue +4 -4
  28. package/app/components/SwSortDropdown.vue +83 -0
  29. package/app/components/SwStockInfo.vue +44 -0
  30. package/{components → app/components}/SwVariantConfigurator.vue +1 -1
  31. package/app/components/listing-filters/SwFilterPrice.vue +214 -0
  32. package/app/components/listing-filters/SwFilterProperties.vue +113 -0
  33. package/app/components/listing-filters/SwFilterRating.vue +90 -0
  34. package/app/components/listing-filters/SwFilterShippingFree.vue +107 -0
  35. package/{components → app/components}/public/cms/CmsPage.vue +19 -4
  36. package/{components → app/components}/public/cms/block/CmsBlockGalleryBuybox.vue +5 -5
  37. package/{components → app/components}/public/cms/block/CmsBlockImageBubbleRow.vue +5 -5
  38. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +41 -0
  39. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +42 -0
  40. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +37 -0
  41. package/{components → app/components}/public/cms/block/CmsBlockImageSimpleGrid.vue +11 -5
  42. package/{components → app/components}/public/cms/block/CmsBlockImageText.vue +7 -3
  43. package/{components → app/components}/public/cms/block/CmsBlockImageTextBubble.vue +13 -16
  44. package/{components → app/components}/public/cms/block/CmsBlockImageTextCover.vue +7 -9
  45. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +88 -0
  46. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +53 -0
  47. package/{components → app/components}/public/cms/block/CmsBlockImageThreeColumn.vue +10 -4
  48. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +37 -0
  49. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +37 -0
  50. package/{components → app/components}/public/cms/block/CmsBlockProductHeading.vue +1 -1
  51. package/{components → app/components}/public/cms/block/CmsBlockProductThreeColumn.vue +10 -4
  52. package/{components → app/components}/public/cms/block/CmsBlockSidebarFilter.vue +3 -1
  53. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +30 -0
  54. package/app/components/public/cms/element/CmsElementBuyBox.vue +145 -0
  55. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +53 -0
  56. package/{components → app/components}/public/cms/element/CmsElementCrossSelling.vue +3 -3
  57. package/{components → app/components}/public/cms/element/CmsElementImage.vue +52 -13
  58. package/app/components/public/cms/element/CmsElementImageGallery.vue +158 -0
  59. package/{components → app/components}/public/cms/element/CmsElementImageSlider.vue +2 -2
  60. package/{components → app/components}/public/cms/element/CmsElementProductBox.vue +2 -1
  61. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +217 -0
  62. package/{components → app/components}/public/cms/element/CmsElementProductListing.vue +23 -94
  63. package/app/components/public/cms/element/CmsElementProductName.vue +11 -0
  64. package/{components → app/components}/public/cms/element/CmsElementProductSlider.vue +4 -4
  65. package/{components → app/components}/public/cms/element/CmsElementText.vue +8 -2
  66. package/app/components/public/cms/element/SwProductListingPagination.vue +70 -0
  67. package/{components → app/components}/public/cms/section/CmsSectionDefault.vue +1 -1
  68. package/app/components/public/cms/section/CmsSectionSidebar.vue +36 -0
  69. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +28 -0
  70. package/app/components/ui/BaseButton.vue +99 -0
  71. package/app/components/ui/BaseIcon.vue +15 -0
  72. package/app/components/ui/Checkbox.vue +49 -0
  73. package/app/components/ui/CheckmarkIcon.vue +23 -0
  74. package/app/components/ui/ChevronIcon.vue +37 -0
  75. package/app/components/ui/ExclamationIcon.vue +11 -0
  76. package/app/components/ui/IconButton.vue +32 -0
  77. package/app/components/ui/RadioButton.vue +26 -0
  78. package/app/components/ui/StarIcon.vue +18 -0
  79. package/app/components/ui/SwitchButton.vue +100 -0
  80. package/app/components/ui/UserIcon.vue +11 -0
  81. package/app/components/ui/WishlistIcon.vue +20 -0
  82. package/app/composables/useImagePlaceholder.ts +27 -0
  83. package/{helpers → app/helpers}/clientOnly.ts +5 -0
  84. package/app/providers/shopware.test.ts +213 -0
  85. package/app/providers/shopware.ts +107 -0
  86. package/dist/index.d.mts +3 -3
  87. package/dist/index.d.ts +3 -3
  88. package/dist/index.mjs +2 -2
  89. package/index.d.ts +12 -0
  90. package/nuxt.config.ts +80 -6
  91. package/package.json +29 -21
  92. package/uno.config.ts +83 -0
  93. package/components/SwCategoryNavigation.vue +0 -44
  94. package/components/SwCategoryNavigationLink.vue +0 -57
  95. package/components/SwListingProductPrice.vue +0 -89
  96. package/components/SwProductCard.vue +0 -286
  97. package/components/SwProductListingFilter.vue +0 -42
  98. package/components/SwProductListingFilters.vue +0 -292
  99. package/components/listing-filters/SwFilterPrice.vue +0 -160
  100. package/components/listing-filters/SwFilterProperties.vue +0 -123
  101. package/components/listing-filters/SwFilterRating.vue +0 -101
  102. package/components/listing-filters/SwFilterShippingFree.vue +0 -104
  103. package/components/public/cms/block/CmsBlockImageFourColumn.vue +0 -29
  104. package/components/public/cms/block/CmsBlockImageHighlightRow.vue +0 -27
  105. package/components/public/cms/block/CmsBlockImageTextGallery.vue +0 -85
  106. package/components/public/cms/block/CmsBlockImageTextRow.vue +0 -43
  107. package/components/public/cms/block/CmsBlockImageThreeCover.vue +0 -27
  108. package/components/public/cms/block/CmsBlockImageTwoColumn.vue +0 -25
  109. package/components/public/cms/block/CmsBlockTextOnImage.vue +0 -20
  110. package/components/public/cms/element/CmsElementBuyBox.vue +0 -190
  111. package/components/public/cms/element/CmsElementCategoryNavigation.vue +0 -167
  112. package/components/public/cms/element/CmsElementImageGallery.vue +0 -249
  113. package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +0 -123
  114. package/components/public/cms/element/CmsElementProductName.vue +0 -10
  115. package/components/public/cms/section/CmsSectionSidebar.vue +0 -41
  116. package/components/public/cms/skeleton/ProductCardSkeleton.vue +0 -44
  117. /package/{components → app/components}/SwMedia3D.vue +0 -0
  118. /package/{components → app/components}/SwProductGallery.vue +0 -0
  119. /package/{components → app/components}/SwProductPrice.vue +0 -0
  120. /package/{components → app/components}/SwProductUnits.vue +0 -0
  121. /package/{components → app/components}/SwSharedPrice.vue +0 -0
  122. /package/{components → app/components}/public/cms/CmsGenericBlock.md +0 -0
  123. /package/{components → app/components}/public/cms/CmsGenericBlock.vue +0 -0
  124. /package/{components → app/components}/public/cms/CmsGenericElement.md +0 -0
  125. /package/{components → app/components}/public/cms/CmsGenericElement.vue +0 -0
  126. /package/{components → app/components}/public/cms/CmsNoComponent.vue +0 -0
  127. /package/{components → app/components}/public/cms/CmsPage.md +0 -0
  128. /package/{components → app/components}/public/cms/block/CmsBlockCategoryNavigation.vue +0 -0
  129. /package/{components → app/components}/public/cms/block/CmsBlockCenterText.vue +0 -0
  130. /package/{components → app/components}/public/cms/block/CmsBlockCrossSelling.vue +0 -0
  131. /package/{components → app/components}/public/cms/block/CmsBlockCustomForm.vue +0 -0
  132. /package/{components → app/components}/public/cms/block/CmsBlockDefault.vue +0 -0
  133. /package/{components → app/components}/public/cms/block/CmsBlockForm.vue +0 -0
  134. /package/{components → app/components}/public/cms/block/CmsBlockHtml.vue +0 -0
  135. /package/{components → app/components}/public/cms/block/CmsBlockImage.vue +0 -0
  136. /package/{components → app/components}/public/cms/block/CmsBlockImageCover.vue +0 -0
  137. /package/{components → app/components}/public/cms/block/CmsBlockImageGallery.vue +0 -0
  138. /package/{components → app/components}/public/cms/block/CmsBlockImageSlider.vue +0 -0
  139. /package/{components → app/components}/public/cms/block/CmsBlockProductDescriptionReviews.vue +0 -0
  140. /package/{components → app/components}/public/cms/block/CmsBlockProductListing.vue +0 -0
  141. /package/{components → app/components}/public/cms/block/CmsBlockProductSlider.vue +0 -0
  142. /package/{components → app/components}/public/cms/block/CmsBlockText.vue +0 -0
  143. /package/{components → app/components}/public/cms/block/CmsBlockTextHero.vue +0 -0
  144. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaser.vue +0 -0
  145. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaserSection.vue +0 -0
  146. /package/{components → app/components}/public/cms/block/CmsBlockTextThreeColumn.vue +0 -0
  147. /package/{components → app/components}/public/cms/block/CmsBlockTextTwoColumn.vue +0 -0
  148. /package/{components → app/components}/public/cms/block/CmsBlockVimeoVideo.vue +0 -0
  149. /package/{components → app/components}/public/cms/block/CmsBlockYoutubeVideo.vue +0 -0
  150. /package/{components → app/components}/public/cms/element/CmsElementBuyBox.md +0 -0
  151. /package/{components → app/components}/public/cms/element/CmsElementCategoryNavigation.md +0 -0
  152. /package/{components → app/components}/public/cms/element/CmsElementCrossSelling.md +0 -0
  153. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.md +0 -0
  154. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.vue +0 -0
  155. /package/{components → app/components}/public/cms/element/CmsElementForm.md +0 -0
  156. /package/{components → app/components}/public/cms/element/CmsElementForm.vue +0 -0
  157. /package/{components → app/components}/public/cms/element/CmsElementHtml.vue +0 -0
  158. /package/{components → app/components}/public/cms/element/CmsElementImage.md +0 -0
  159. /package/{components → app/components}/public/cms/element/CmsElementImageGallery.md +0 -0
  160. /package/{components → app/components}/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +0 -0
  161. /package/{components → app/components}/public/cms/element/CmsElementImageSlider.md +0 -0
  162. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.md +0 -0
  163. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.vue +0 -0
  164. /package/{components → app/components}/public/cms/element/CmsElementProductBox.md +0 -0
  165. /package/{components → app/components}/public/cms/element/CmsElementProductDescriptionReviews.md +0 -0
  166. /package/{components → app/components}/public/cms/element/CmsElementProductListing.md +0 -0
  167. /package/{components → app/components}/public/cms/element/CmsElementProductName.md +0 -0
  168. /package/{components → app/components}/public/cms/element/CmsElementProductSlider.md +0 -0
  169. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.md +0 -0
  170. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.vue +0 -0
  171. /package/{components → app/components}/public/cms/element/CmsElementText.md +0 -0
  172. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.md +0 -0
  173. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.vue +0 -0
  174. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.md +0 -0
  175. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.vue +0 -0
  176. /package/{components → app/components}/public/cms/section/CmsSectionDefault.md +0 -0
  177. /package/{components → app/components}/public/cms/section/CmsSectionSidebar.md +0 -0
  178. /package/{helpers → app/helpers}/html-to-vue/ast.ts +0 -0
  179. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.test.ts +0 -0
  180. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.ts +0 -0
  181. /package/{helpers → app/helpers}/html-to-vue/renderToHtml.ts +0 -0
  182. /package/{helpers → app/helpers}/html-to-vue/renderer.ts +0 -0
  183. /package/{helpers → app/helpers}/media/isSpatial.ts +0 -0
package/README.md CHANGED
@@ -87,15 +87,232 @@ 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
+
90
307
  ## 📘 Available components
91
308
 
92
- The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base.html#available-components).
309
+ The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base-layer.html#available-components).
93
310
 
94
311
  ## 🔄 Overwriting components
95
312
 
96
313
  The procedure is:
97
314
 
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)
315
+ - 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
316
  - take its name
100
317
  - create a file with the same name and place it into `~/components` dir in your nuxt project (or wherever according your nuxt config)
101
318
 
@@ -105,7 +322,7 @@ The procedure is:
105
322
 
106
323
  ❗**Internal components are not a part of public API. Once overwritten you need to track the changes on your own.**
107
324
 
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).
325
+ 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
326
 
110
327
  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
328
 
@@ -124,7 +341,7 @@ No additional packages needed to be installed.
124
341
 
125
342
  - [📘 Documentation](https://frontends.shopware.com)
126
343
 
127
- - [👥 Community](https://shopwarecommunity.slack.com) (`#composable-frontends`)
344
+ - [👥 Community](https://discord.com/channels/1308047705309708348/1405501315160739951) (`#composable-frontend`)
128
345
 
129
346
  <!-- AUTO GENERATED CHANGELOG -->
130
347
 
@@ -132,18 +349,119 @@ No additional packages needed to be installed.
132
349
 
133
350
  Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
134
351
 
135
- ### Latest changes: 1.5.1
352
+ ### Latest changes: 2.0.0
353
+
354
+ ### Major Changes
355
+
356
+ - [#1944](https://github.com/shopware/frontends/pull/1944) [`c41a839`](https://github.com/shopware/frontends/commit/c41a8397538e5b18475134635cc44295c34dde2d) Thanks [@mkucmus](https://github.com/mkucmus)! - Updates the `@shopware/cms-base-layer` package with the following changes:
357
+ - Adds support for the new `SwQuantitySelect` component
358
+ - Updates the `SwProductAddToCart` component to use the new `SwQuantitySelect` component
359
+ - Fixes the `Status` component to use the new state classes
360
+ - Updates the `uno.config.ts` file to include default styling that can be used and extended in the end-project:
361
+
362
+ ## Nuxt UnoCSS Configuration Example
363
+
364
+ ```ts
365
+ // nuxt.config.ts in your end-project
366
+ {
367
+ unocss: {
368
+ nuxtLayers: true; // enable Nuxt layers support in order to merge UnoCSS configurations
369
+ }
370
+ }
371
+ ```
372
+
373
+ ## UnoCSS Configuration Example
374
+
375
+ ```ts
376
+ // uno.config.ts in your end-project
377
+ import { mergeConfigs } from "@unocss/core";
378
+ import baseConfig from "./.nuxt/uno.config.mjs";
379
+
380
+ export default mergeConfigs(baseConfig, {
381
+ // will be merged with the base config - all optional
382
+ theme: {
383
+ colors: {
384
+ "brand-primary": "#ff3e00",
385
+ "brand-secondary": "#ff6a00",
386
+ },
387
+ },
388
+ safelist: ["states-success"],
389
+ preflights: [
390
+ {
391
+ getCSS: () => `
392
+ body {
393
+ font-family: 'Inter', sans-serif;
394
+ -moz-osx-font-smoothing: grayscale;
395
+ -webkit-font-smoothing: antialiased;
396
+ }
397
+ `,
398
+ },
399
+ ],
400
+ });
401
+ ```
402
+
403
+ ### Minor Changes
404
+
405
+ - [#2030](https://github.com/shopware/frontends/pull/2030) [`22ff62e`](https://github.com/shopware/frontends/commit/22ff62e354f024599d64ea8096af57695248851c) Thanks [@mkucmus](https://github.com/mkucmus)! - Introduce new UI components, refine listing filters (structure and UX), add global collapse animations, and improve type safety.
406
+
407
+ ### Features
408
+ - New UI components:
409
+ - SwFilterChips – shows active filters as removable chips
410
+ - SwSortDropdown – sorting dropdown
411
+ - SwProductListingPagination – listing pagination
412
+ - Checkbox – reusable checkbox
413
+ - ChevronIcon – configurable chevron (up/down/left/right)
414
+ - RadioButton – reusable radio button
415
+ - SwitchButton – toggle switch
416
+
417
+ ### Refactors
418
+ - SwFilterProperties:
419
+ - Replace computed factory with `isChecked()` and `selectValue()` helpers for better performance and readability.
420
+ - Filter collapse animation:
421
+ - Unified expand/collapse animations for SwFilterProperties, SwFilterRating, SwFilterShippingFree, and SwFilterPrice using UnoCSS preflights.
422
+
423
+ ### TypeScript fixes
424
+ - SwProductListingFilters:
425
+ - Provide fallbacks (`?? []`, `?? ''`) when passing `getSortingOrders` and `getCurrentSortingOrder`.
426
+ - SwFilterChips:
427
+ - Relax prop types to accept union types compatible with both full Shopware schemas and simplified helper types.
428
+
429
+ ### Code quality improvements
430
+ - SwFilterPrice:
431
+ - Remove unnecessary optional chaining on `props.selectedFilters` to prevent masking undefined errors
432
+ - Checkbox component:
433
+ - Replace `outline-blue-500` with `outline-brand-primary` for brand consistency
434
+ - Make `label` prop optional to support checkbox-only pattern
435
+ - SwFilterShippingFree:
436
+ - Add i18n support using `useCmsTranslations` instead of hardcoded "free delivery" text
437
+ - SwFilterProperties:
438
+ - Remove unnecessary empty label prop from Checkbox usage
439
+
440
+ **Note:** Transition classes are globally available via UnoCSS preflights.
441
+
442
+ - [#2139](https://github.com/shopware/frontends/pull/2139) [`20d1066`](https://github.com/shopware/frontends/commit/20d106638958170dd191ac3d95e3e536f3fcc787) Thanks [@mkucmus](https://github.com/mkucmus)! - Added a new `SwProductReviewsForm` component that allows logged-in customers to submit product reviews.
443
+
444
+ - [#1959](https://github.com/shopware/frontends/pull/1959) [`c77daa6`](https://github.com/shopware/frontends/commit/c77daa6a11e96c7f3688b16f7da010b54c7f5e8b) Thanks [@patzick](https://github.com/patzick)! - Updated default types to Shopware 6.7
445
+
446
+ - [#2176](https://github.com/shopware/frontends/pull/2176) [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602) Thanks [@mkucmus](https://github.com/mkucmus)! - - Extract product listing data early from CMS page responses to enable SSR rendering
447
+ - Remove ClientOnly wrappers from `SwProductListingFilters` and `SwFilterChips` components
448
+ - Resolve hydration mismatches on category pages with filters
136
449
 
137
450
  ### Patch Changes
138
451
 
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.
452
+ - [#2174](https://github.com/shopware/frontends/pull/2174) [`e9f3d97`](https://github.com/shopware/frontends/commit/e9f3d972d7a126ec72f405a3595e53a61f6180f9) Thanks [@mkucmus](https://github.com/mkucmus)! - Added a new image placeholder.
453
+
454
+ - [#2162](https://github.com/shopware/frontends/pull/2162) [`e1fae3e`](https://github.com/shopware/frontends/commit/e1fae3eb6430e5c8e133456fbaf7f215f80c36f6) Thanks [@mkucmus](https://github.com/mkucmus)! - Replace hardcoded colors with theme tokens, add image placeholder composable, improve URL encoding for special characters in image paths, enhance CMS block layouts, and use useTemplateRef for better type safety
455
+
456
+ - [#2128](https://github.com/shopware/frontends/pull/2128) [`efe125e`](https://github.com/shopware/frontends/commit/efe125e7bea273bb904356114cf93adf68a416fb) Thanks [@mkucmus](https://github.com/mkucmus)! - Enhanced SwProductReviews component with reviewer names, shop feedback, and star ratings using direct SVG imports.
140
457
 
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
458
+ - [#2008](https://github.com/shopware/frontends/pull/2008) [`3a1bca9`](https://github.com/shopware/frontends/commit/3a1bca983c4fc866c67b90897bd86d7488f8cac8) Thanks [@mkucmus](https://github.com/mkucmus)! - Added missing labels for `SwQuantitySelect` component.
142
459
 
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.
460
+ - [#1951](https://github.com/shopware/frontends/pull/1951) [`3f2379b`](https://github.com/shopware/frontends/commit/3f2379bdc428b481943cbcf3711a37cb91e2d298) Thanks [@mkucmus](https://github.com/mkucmus)! - Use proper paths for components configuration
144
461
 
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`
462
+ - [#2154](https://github.com/shopware/frontends/pull/2154) [`168989e`](https://github.com/shopware/frontends/commit/168989e51e5c81c4cbb746c132d6561c019e046a) Thanks [@mkucmus](https://github.com/mkucmus)! - Implicitly set public components as global to expose them for templates that extend from the base one.
146
463
 
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
464
+ - Updated dependencies [[`87771c3`](https://github.com/shopware/frontends/commit/87771c3b7a4521fcdba43cb4c967b61f5db01b3e), [`22ff62e`](https://github.com/shopware/frontends/commit/22ff62e354f024599d64ea8096af57695248851c), [`a44d871`](https://github.com/shopware/frontends/commit/a44d8712d9ae5ee196c03ac8b894f3d1392d0e68), [`e43d9b7`](https://github.com/shopware/frontends/commit/e43d9b7f559af21be8b66f2021cea2d14940e4aa), [`2cbda25`](https://github.com/shopware/frontends/commit/2cbda257a1056454e12f2fba9052f83eecb6d986), [`2cbda25`](https://github.com/shopware/frontends/commit/2cbda257a1056454e12f2fba9052f83eecb6d986), [`7fe2ef9`](https://github.com/shopware/frontends/commit/7fe2ef96a9d9d156683b85d31f0a660458c9fbfd), [`70dcf95`](https://github.com/shopware/frontends/commit/70dcf95d4370c63964d877a5cab113a53f93ca19), [`56cd178`](https://github.com/shopware/frontends/commit/56cd178e25fe2399b7170ccac3044e980621f041), [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602), [`e1fae3e`](https://github.com/shopware/frontends/commit/e1fae3eb6430e5c8e133456fbaf7f215f80c36f6), [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602), [`c77daa6`](https://github.com/shopware/frontends/commit/c77daa6a11e96c7f3688b16f7da010b54c7f5e8b)]:
465
+ - @shopware/composables@1.10.0
466
+ - @shopware/helpers@1.6.0
467
+ - @shopware/api-client@1.4.0
@@ -0,0 +1,7 @@
1
+ import { defineAppConfig } from "#imports";
2
+
3
+ export default defineAppConfig({
4
+ imagePlaceholder: {
5
+ color: "#543B95",
6
+ },
7
+ });
@@ -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,76 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import type { Schemas } from "#shopware";
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ activeCategory: Schemas["Category"];
8
+ elements: Schemas["Category"][];
9
+ level: number;
10
+ }>(),
11
+ {
12
+ level: 0,
13
+ },
14
+ );
15
+
16
+ const expandedItems = ref<Set<string>>(new Set());
17
+
18
+ function isActive(navigationElement: Schemas["Category"]) {
19
+ return navigationElement.id === props.activeCategory?.id;
20
+ }
21
+
22
+ function toggleExpanded(id: string) {
23
+ if (expandedItems.value.has(id)) {
24
+ expandedItems.value.delete(id);
25
+ } else {
26
+ expandedItems.value.add(id);
27
+ }
28
+ }
29
+
30
+ function isExpanded(id: string) {
31
+ return expandedItems.value.has(id);
32
+ }
33
+ </script>
34
+ <template>
35
+ <div v-if="props.elements?.length" class="self-stretch flex flex-col justify-start items-start gap-4">
36
+ <div
37
+ v-for="(navigationElement, index) in props.elements"
38
+ :key="index"
39
+ class="w-full"
40
+ >
41
+ <SwCategoryNavigationLink
42
+ :navigation-element="navigationElement"
43
+ :is-active="isActive(navigationElement)"
44
+ :is-expanded="isExpanded(navigationElement.id)"
45
+ :level="props.level"
46
+ @toggle="toggleExpanded(navigationElement.id)"
47
+ />
48
+ <transition name="filter-collapse">
49
+ <div v-if="navigationElement.children && isExpanded(navigationElement.id)" class="self-stretch flex flex-col justify-start items-start">
50
+ <SwCategoryNavigation
51
+ :elements="navigationElement.children"
52
+ :active-category="props.activeCategory"
53
+ :level="props.level + 1"
54
+ />
55
+ </div>
56
+ </transition>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ <style scoped>
61
+ .filter-collapse-enter-active,
62
+ .filter-collapse-leave-active {
63
+ transition: max-height 240ms ease, opacity 200ms ease;
64
+ overflow: hidden;
65
+ }
66
+ .filter-collapse-enter-from,
67
+ .filter-collapse-leave-to {
68
+ max-height: 0;
69
+ opacity: 0;
70
+ }
71
+ .filter-collapse-enter-to,
72
+ .filter-collapse-leave-from {
73
+ max-height: 800px;
74
+ opacity: 1;
75
+ }
76
+ </style>
@@ -0,0 +1,128 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ buildUrlPrefix,
4
+ getCategoryRoute,
5
+ getTranslatedProperty,
6
+ urlIsAbsolute,
7
+ } from "@shopware/helpers";
8
+ import { computed } from "vue";
9
+ import { RouterLink } from "vue-router";
10
+ import { useUrlResolver } from "#imports";
11
+ import type { Schemas } from "#shopware";
12
+
13
+ interface Props {
14
+ navigationElement: Schemas["Category"];
15
+ isActive?: boolean;
16
+ isExpanded?: boolean;
17
+ level?: number;
18
+ }
19
+
20
+ const props = defineProps<Props>();
21
+ const emit = defineEmits<{
22
+ toggle: [];
23
+ }>();
24
+
25
+ const { getUrlPrefix } = useUrlResolver();
26
+ const url = computed(() => {
27
+ return buildUrlPrefix(
28
+ getCategoryRoute(props.navigationElement),
29
+ getUrlPrefix(),
30
+ );
31
+ });
32
+
33
+ const hasChildren = computed(() => {
34
+ return (
35
+ props.navigationElement.children &&
36
+ props.navigationElement.children.length > 0
37
+ );
38
+ });
39
+ </script>
40
+ <template>
41
+ <!-- Level 1 Category (Top-level with border and toggle) -->
42
+ <div
43
+ v-if="props.level === 0"
44
+ class="self-stretch flex flex-col justify-center items-center"
45
+ >
46
+ <div class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1">
47
+ <div class="flex-1 flex justify-start items-center gap-2.5">
48
+ <RouterLink
49
+ v-if="!urlIsAbsolute(url.path)"
50
+ :to="url"
51
+ :class="[
52
+ 'flex-1 justify-start text-surface-on-surface text-base leading-normal font-bold',
53
+ ]"
54
+ >
55
+ {{ getTranslatedProperty(navigationElement, "name") }}
56
+ </RouterLink>
57
+ <a
58
+ v-else
59
+ :href="url.path"
60
+ :class="[
61
+ 'flex-1 justify-start text-surface-on-surface text-base leading-normal font-bold',
62
+ ]"
63
+ :target="
64
+ navigationElement.externalLink || navigationElement.linkNewTab
65
+ ? '_blank'
66
+ : ''
67
+ "
68
+ >
69
+ {{ getTranslatedProperty(navigationElement, "name") }}
70
+ </a>
71
+ </div>
72
+ <button
73
+ v-if="hasChildren"
74
+ @click="emit('toggle')"
75
+ class="w-6 h-6 relative flex items-center justify-center bg-transparent cursor-pointer focus:outline-none"
76
+ type="button"
77
+ :aria-label="props.isExpanded ? 'Collapse' : 'Expand'"
78
+ >
79
+ <SwChevronIcon :direction="props.isExpanded ? 'up' : 'down'" :size="20" />
80
+ </button>
81
+ </div>
82
+ </div>
83
+
84
+ <!-- Level 2+ Categories (Nested with left padding) -->
85
+ <div
86
+ v-else
87
+ class="self-stretch flex flex-col justify-center items-center"
88
+ >
89
+ <div class="self-stretch pl-4 py-1.5 inline-flex justify-start items-center gap-2">
90
+ <div class="py-0.5 flex-1 flex justify-start items-center gap-2.5">
91
+ <RouterLink
92
+ v-if="!urlIsAbsolute(url.path)"
93
+ :to="url"
94
+ :class="[
95
+ 'justify-start text-surface-on-surface text-base leading-normal',
96
+ props.isActive ? 'font-bold' : 'font-normal',
97
+ ]"
98
+ >
99
+ {{ getTranslatedProperty(navigationElement, "name") }}
100
+ </RouterLink>
101
+ <a
102
+ v-else
103
+ :href="url.path"
104
+ :class="[
105
+ 'justify-start text-surface-on-surface text-base leading-normal',
106
+ props.isActive ? 'font-bold' : 'font-normal',
107
+ ]"
108
+ :target="
109
+ navigationElement.externalLink || navigationElement.linkNewTab
110
+ ? '_blank'
111
+ : ''
112
+ "
113
+ >
114
+ {{ getTranslatedProperty(navigationElement, "name") }}
115
+ </a>
116
+ </div>
117
+ <button
118
+ v-if="hasChildren"
119
+ @click="emit('toggle')"
120
+ class="w-6 h-6 relative flex items-center justify-center bg-transparent cursor-pointer focus:outline-none"
121
+ type="button"
122
+ :aria-label="props.isExpanded ? 'Collapse' : 'Expand'"
123
+ >
124
+ <SwChevronIcon :direction="props.isExpanded ? 'up' : 'down'" :size="20" />
125
+ </button>
126
+ </div>
127
+ </div>
128
+ </template>