@shopware/cms-base-layer 3.0.0 → 3.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.
- package/README.md +64 -58
- package/app/components/SwCategoryNavigation.vue +1 -0
- package/app/components/SwCategoryNavigationLink.vue +16 -8
- package/app/components/SwContactForm.vue +1 -0
- package/app/components/SwFilterChips.vue +1 -0
- package/app/components/SwListingProductPrice.vue +16 -4
- package/app/components/SwMedia3D.vue +1 -4
- package/app/components/SwNewsletterForm.vue +4 -3
- package/app/components/SwProductAddToCart.vue +24 -7
- package/app/components/SwProductCard.vue +20 -3
- package/app/components/SwProductCardDetails.vue +27 -9
- package/app/components/SwProductCardImage.vue +31 -12
- package/app/components/SwProductCardSkeleton.vue +37 -14
- package/app/components/SwProductGallery.vue +1 -0
- package/app/components/SwProductListingFilter.vue +5 -1
- package/app/components/SwProductListingFilters.vue +57 -76
- package/app/components/SwProductListingFiltersHorizontal.vue +40 -71
- package/app/components/SwProductPrice.vue +1 -0
- package/app/components/SwProductReviews.vue +14 -6
- package/app/components/SwProductReviewsForm.vue +15 -3
- package/app/components/SwProductUnits.vue +1 -0
- package/app/components/SwQuantitySelect.vue +10 -3
- package/app/components/SwSharedPrice.vue +1 -0
- package/app/components/SwSlider.vue +1 -0
- package/app/components/SwStockInfo.vue +12 -6
- package/app/components/SwVariantConfigurator.vue +4 -1
- package/app/components/listing-filters/SwFilterPrice.vue +79 -30
- package/app/components/listing-filters/SwFilterProperties.vue +40 -25
- package/app/components/listing-filters/SwFilterRating.vue +21 -9
- package/app/components/listing-filters/SwFilterShippingFree.vue +16 -8
- package/app/components/public/cms/CmsBlockSpatialViewer.vue +1 -0
- package/app/components/public/cms/CmsGenericBlock.vue +3 -1
- package/app/components/public/cms/CmsGenericElement.vue +2 -1
- package/app/components/public/cms/CmsNoComponent.vue +2 -1
- package/app/components/public/cms/CmsPage.md +1 -1
- package/app/components/public/cms/CmsPage.vue +2 -0
- package/app/components/public/cms/block/CmsBlockCategoryNavigation.vue +1 -0
- package/app/components/public/cms/block/CmsBlockCenterText.vue +3 -1
- package/app/components/public/cms/block/CmsBlockCrossSelling.vue +1 -0
- package/app/components/public/cms/block/CmsBlockCustomForm.vue +1 -0
- package/app/components/public/cms/block/CmsBlockForm.vue +1 -0
- package/app/components/public/cms/block/CmsBlockGalleryBuybox.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImage.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +9 -2
- package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +10 -3
- package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +10 -5
- package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +11 -3
- package/app/components/public/cms/block/CmsBlockImageSimpleGrid.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageSlider.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageText.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextBubble.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextCover.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +4 -12
- package/app/components/public/cms/block/CmsBlockImageTextRow.vue +13 -4
- package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +11 -3
- package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +11 -4
- package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductHeading.vue +3 -1
- package/app/components/public/cms/block/CmsBlockProductListing.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockSidebarFilter.vue +1 -0
- package/app/components/public/cms/block/CmsBlockText.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextHero.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextOnImage.vue +2 -4
- package/app/components/public/cms/block/CmsBlockTextThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextTwoColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockVimeoVideo.vue +1 -0
- package/app/components/public/cms/block/CmsBlockYoutubeVideo.vue +1 -0
- package/app/components/public/cms/element/CmsElementBuyBox.vue +90 -72
- package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +14 -3
- package/app/components/public/cms/element/CmsElementCrossSelling.vue +3 -1
- package/app/components/public/cms/element/CmsElementCustomForm.vue +1 -0
- package/app/components/public/cms/element/CmsElementForm.vue +1 -0
- package/app/components/public/cms/element/CmsElementHtml.vue +0 -1
- package/app/components/public/cms/element/CmsElementImage.vue +2 -1
- package/app/components/public/cms/element/CmsElementImageGallery.vue +6 -2
- package/app/components/public/cms/element/CmsElementManufacturerLogo.vue +1 -0
- package/app/components/public/cms/element/CmsElementProductBox.vue +1 -0
- package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +93 -36
- package/app/components/public/cms/element/CmsElementProductListing.vue +16 -5
- package/app/components/public/cms/element/CmsElementProductSlider.vue +6 -4
- package/app/components/public/cms/element/CmsElementText.vue +2 -0
- package/app/components/public/cms/element/CmsElementVimeoVideo.vue +1 -0
- package/app/components/public/cms/element/CmsElementYoutubeVideo.vue +1 -0
- package/app/components/public/cms/element/SwProductListingPagination.vue +8 -2
- package/app/components/public/cms/section/CmsSectionSidebar.vue +13 -8
- package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +16 -5
- package/app/components/ui/SwitchButton.vue +53 -14
- package/app/components/ui/WishlistIcon.vue +8 -2
- package/app/composables/useLcpImagePreload.test.ts +1 -0
- package/app/composables/useLcpImagePreload.ts +2 -0
- package/app/composables/useTypedAppConfig.ts +1 -0
- package/app/helpers/cms/getImageSizes.test.ts +1 -0
- package/app/helpers/html-to-vue/ast.ts +1 -0
- package/app/helpers/html-to-vue/getOptionsFromNode.test.ts +1 -0
- package/app/helpers/html-to-vue/renderToHtml.ts +1 -0
- package/app/helpers/html-to-vue/renderer.ts +1 -0
- package/app/providers/shopware.test.ts +1 -0
- package/app/utils/routeQuery.test.ts +65 -0
- package/app/utils/routeQuery.ts +26 -0
- package/app/utils/useSelectedListingFilters.test.ts +82 -0
- package/app/utils/useSelectedListingFilters.ts +105 -0
- package/nuxt.config.ts +24 -1
- package/package.json +22 -24
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/shopware/frontends/issues?q=is%3Aopen+is%3Aissue+label%3Acms-base)
|
|
6
6
|
[](#)
|
|
7
7
|
|
|
8
|
-
Nuxt [layer](https://nuxt.com/docs/getting-started/layers) that provides an implementation of all CMS components in Shopware [based on utility-classes](https://
|
|
8
|
+
Nuxt [layer](https://nuxt.com/docs/getting-started/layers) that provides an implementation of all CMS components in Shopware [based on utility-classes](https://developer.shopware.com/frontends/concepts/styling.html).
|
|
9
9
|
|
|
10
10
|
It is useful for projects that want to use the CMS components while keeping CMS functionality separate from the styling system and design tokens.
|
|
11
11
|
|
|
@@ -90,7 +90,7 @@ Since all CMS components are registered in your Nuxt application, you can now st
|
|
|
90
90
|
|
|
91
91
|
> `@shopware/cms-base-layer` no longer owns the default UnoCSS theme. If you want the shared Shopware Frontends design tokens and UnoCSS defaults, extend `@shopware/unocss-design-tokens-layer` as shown above.
|
|
92
92
|
|
|
93
|
-
See a [short guide](https://
|
|
93
|
+
See a [short guide](https://developer.shopware.com/frontends/guides/cms/content-pages.html#use-the-cms-base-package) on how to use `cms-base-layer` in your Nuxt project.
|
|
94
94
|
|
|
95
95
|
## Styling and Design Tokens
|
|
96
96
|
|
|
@@ -110,22 +110,24 @@ export default defineNuxtConfig({
|
|
|
110
110
|
unocss: {
|
|
111
111
|
nuxtLayers: true, // enable Nuxt layers for UnoCSS
|
|
112
112
|
},
|
|
113
|
-
})
|
|
113
|
+
});
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
```ts [uno.config.ts]
|
|
117
|
-
import { mergeConfigs } from
|
|
118
|
-
import baseConfig from
|
|
119
|
-
|
|
120
|
-
export default mergeConfigs([
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
import { mergeConfigs } from "@unocss/core";
|
|
118
|
+
import baseConfig from "./.nuxt/uno.config.mjs";
|
|
119
|
+
|
|
120
|
+
export default mergeConfigs([
|
|
121
|
+
baseConfig,
|
|
122
|
+
{
|
|
123
|
+
theme: {
|
|
124
|
+
colors: {
|
|
125
|
+
"brand-primary": "#ff3e00",
|
|
126
|
+
"brand-secondary": "#1c1c1c",
|
|
127
|
+
},
|
|
125
128
|
},
|
|
126
129
|
},
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
]);
|
|
129
131
|
```
|
|
130
132
|
|
|
131
133
|
See the [UnoCSS reference](https://unocss.dev/integrations/nuxt#configuration) for more information on how to configure UnoCSS in Nuxt when work with layers.
|
|
@@ -150,12 +152,14 @@ This layer includes [Nuxt Image](https://image.nuxt.com/) configuration optimize
|
|
|
150
152
|
The layer comes pre-configured with optimized settings. No additional setup is required! The configuration includes:
|
|
151
153
|
|
|
152
154
|
**Available Presets:**
|
|
155
|
+
|
|
153
156
|
- `productCard` - Product listing images (WebP, quality 90, cover fit)
|
|
154
157
|
- `productDetail` - Product detail page images (WebP, quality 90, contain fit)
|
|
155
158
|
- `thumbnail` - Small thumbnails (150x150, WebP, quality 90)
|
|
156
159
|
- `hero` - Hero banners (WebP, quality 95, cover fit)
|
|
157
160
|
|
|
158
161
|
**Responsive Breakpoints:**
|
|
162
|
+
|
|
159
163
|
- `xs: 320px`, `sm: 640px`, `md: 768px`, `lg: 1024px`, `xl: 1280px`, `xxl: 1536px`
|
|
160
164
|
|
|
161
165
|
### Usage in Components
|
|
@@ -196,21 +200,23 @@ Replace standard `<img>` tags with `<NuxtImg>` to enable automatic optimization:
|
|
|
196
200
|
|
|
197
201
|
Shopware supports the following URL parameters for image transformation:
|
|
198
202
|
|
|
199
|
-
| Modifier
|
|
200
|
-
|
|
201
|
-
| `width`
|
|
202
|
-
| `height`
|
|
203
|
-
| `quality` | Image quality (0-100)
|
|
204
|
-
| `format`
|
|
205
|
-
| `fit`
|
|
203
|
+
| Modifier | Description | Example | Support |
|
|
204
|
+
| --------- | ---------------------- | ---------------------------- | -------------------------- |
|
|
205
|
+
| `width` | Image width in pixels | `400` | ✅ Always supported |
|
|
206
|
+
| `height` | Image height in pixels | `600` | ✅ Always supported |
|
|
207
|
+
| `quality` | Image quality (0-100) | `85` | ⚠️ Cloud/Plugin required\* |
|
|
208
|
+
| `format` | Output format | `webp`, `avif`, `jpg`, `png` | ⚠️ Cloud/Plugin required\* |
|
|
209
|
+
| `fit` | Resize behavior | `cover`, `contain`, `fill` | ⚠️ Cloud/Plugin required\* |
|
|
210
|
+
|
|
211
|
+
\*Advanced transformations (quality, format, fit) are available in:
|
|
206
212
|
|
|
207
|
-
*Advanced transformations (quality, format, fit) are available in:
|
|
208
213
|
- **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
214
|
- **Self-hosted instances**: Require thumbnail processor plugins like [FroshPlatformThumbnailProcessor](https://github.com/FriendsOfShopware/FroshPlatformThumbnailProcessor) or third-party CDN integration
|
|
210
215
|
|
|
211
216
|
### How It Works
|
|
212
217
|
|
|
213
218
|
This layer includes a custom Shopware provider for Nuxt Image that maps modifiers to Shopware's query parameters:
|
|
219
|
+
|
|
214
220
|
- `width` modifier → `?width=400`
|
|
215
221
|
- `height` modifier → `?height=300`
|
|
216
222
|
- `quality` modifier → `?quality=85`
|
|
@@ -249,31 +255,31 @@ export default defineNuxtConfig({
|
|
|
249
255
|
quality: 85,
|
|
250
256
|
|
|
251
257
|
// Add/change formats
|
|
252
|
-
formats: [
|
|
258
|
+
formats: ["avif", "webp", "jpg"],
|
|
253
259
|
|
|
254
260
|
// Override or add presets
|
|
255
261
|
presets: {
|
|
256
262
|
// Override existing preset
|
|
257
263
|
productCard: {
|
|
258
264
|
modifiers: {
|
|
259
|
-
format:
|
|
265
|
+
format: "avif",
|
|
260
266
|
quality: 80,
|
|
261
|
-
fit:
|
|
262
|
-
}
|
|
267
|
+
fit: "cover",
|
|
268
|
+
},
|
|
263
269
|
},
|
|
264
270
|
// Add custom preset
|
|
265
271
|
categoryBanner: {
|
|
266
272
|
modifiers: {
|
|
267
|
-
format:
|
|
273
|
+
format: "webp",
|
|
268
274
|
quality: 90,
|
|
269
275
|
width: 1200,
|
|
270
276
|
height: 400,
|
|
271
|
-
fit:
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
})
|
|
277
|
+
fit: "cover",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
277
283
|
```
|
|
278
284
|
|
|
279
285
|
## 🖼️ Image Placeholder
|
|
@@ -335,6 +341,7 @@ When a CMS section or block has a `backgroundMedia` set, the components call `ge
|
|
|
335
341
|
4. Appends `format` and `quality` from `app.config.ts` if provided
|
|
336
342
|
|
|
337
343
|
**Example generated URL:**
|
|
344
|
+
|
|
338
345
|
```
|
|
339
346
|
url("https://cdn.shopware.store/.../image.jpg?width=1000&fit=crop,smart&format=webp&quality=85")
|
|
340
347
|
```
|
|
@@ -348,6 +355,7 @@ This layer includes a `useLcpImagePreload` composable that automatically preload
|
|
|
348
355
|
### How it works
|
|
349
356
|
|
|
350
357
|
The composable scans CMS sections in document order, checking:
|
|
358
|
+
|
|
351
359
|
1. Section background images (`section.backgroundMedia`)
|
|
352
360
|
2. Block background images (`block.backgroundMedia`)
|
|
353
361
|
3. Image element media (`slot.data.media`)
|
|
@@ -386,11 +394,14 @@ productCard: {
|
|
|
386
394
|
```
|
|
387
395
|
|
|
388
396
|
```vue
|
|
389
|
-
<NuxtImg
|
|
397
|
+
<NuxtImg
|
|
398
|
+
preset="productCard"
|
|
390
399
|
:src="coverSrcPath"
|
|
391
|
-
width="400"
|
|
400
|
+
width="400"
|
|
401
|
+
height="400"
|
|
392
402
|
densities="1x"
|
|
393
|
-
loading="lazy"
|
|
403
|
+
loading="lazy"
|
|
404
|
+
/>
|
|
394
405
|
```
|
|
395
406
|
|
|
396
407
|
- **Fixed `width`/`height`** (400px) — avoid hydration mismatches caused by dynamic DOM measurement
|
|
@@ -431,13 +442,13 @@ export default defineAppConfig({
|
|
|
431
442
|
|
|
432
443
|
## 📘 Available components
|
|
433
444
|
|
|
434
|
-
The list of available blocks and elements is [here](https://
|
|
445
|
+
The list of available blocks and elements is [here](https://developer.shopware.com/frontends/packages/cms-base-layer.html#available-components).
|
|
435
446
|
|
|
436
447
|
## 🔄 Overwriting components
|
|
437
448
|
|
|
438
449
|
The procedure is:
|
|
439
450
|
|
|
440
|
-
- find a component in component's [list](https://
|
|
451
|
+
- find a component in component's [list](https://developer.shopware.com/frontends/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)
|
|
441
452
|
- take its name
|
|
442
453
|
- create a file with the same name and place it into `~/components` dir in your nuxt project (or wherever according your nuxt config)
|
|
443
454
|
|
|
@@ -464,7 +475,7 @@ No additional packages needed to be installed.
|
|
|
464
475
|
|
|
465
476
|
## Links
|
|
466
477
|
|
|
467
|
-
- [📘 Documentation](https://
|
|
478
|
+
- [📘 Documentation](https://developer.shopware.com/frontends)
|
|
468
479
|
|
|
469
480
|
- [👥 Community](https://discord.com/channels/1308047705309708348/1405501315160739951) (`#composable-frontend`)
|
|
470
481
|
|
|
@@ -474,36 +485,31 @@ No additional packages needed to be installed.
|
|
|
474
485
|
|
|
475
486
|
Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
|
|
476
487
|
|
|
477
|
-
### Latest changes: 3.
|
|
478
|
-
|
|
479
|
-
### Major Changes
|
|
480
|
-
|
|
481
|
-
- [#2406](https://github.com/shopware/frontends/pull/2406) [`df93461`](https://github.com/shopware/frontends/commit/df93461434cb79ec9d722cdbd42a37a9af07fb03) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Remove bundled UnoCSS configuration and design tokens from the CMS layer. Consumers who relied on the previous default UnoCSS setup should extend `@shopware/unocss-design-tokens-layer` alongside this package. See the package README and framework docs for migration steps.
|
|
488
|
+
### Latest changes: 3.1.0
|
|
482
489
|
|
|
483
490
|
### Minor Changes
|
|
484
491
|
|
|
485
|
-
- [#
|
|
492
|
+
- [#2503](https://github.com/shopware/frontends/pull/2503) [`1b0ccc9`](https://github.com/shopware/frontends/commit/1b0ccc95fcef89de42931155fb40119b9075d21b) Thanks [@mkucmus](https://github.com/mkucmus)! - `SwProductListingFilters` and `SwProductListingFiltersHorizontal`: honor the `listingType` prop. When set to `productSearchListing` the components drive `useProductSearchListing` instead of `useCategoryListing` and keep the active search term in the request criteria and resulting URL, so the shared filter sidebar can be reused on the product search page without resetting the query. Category listing behavior is unchanged.
|
|
486
493
|
|
|
487
494
|
### Patch Changes
|
|
488
495
|
|
|
489
|
-
- [#
|
|
490
|
-
|
|
491
|
-
- [#2389](https://github.com/shopware/frontends/pull/2389) [`05438c6`](https://github.com/shopware/frontends/commit/05438c636a6c99b48e87d8f2ff5b03bf313c4e67) Thanks [@mkucmus](https://github.com/mkucmus)! - Fix product card and CMS image sizing to prevent duplicate/oversized image requests. Move fixed dimensions and `densities="1x"` into the `productCard` preset, and use `useElementSize`-based `width`/`height` props for `CmsElementImage`.
|
|
496
|
+
- [#2593](https://github.com/shopware/frontends/pull/2593) [`760635c`](https://github.com/shopware/frontends/commit/760635cdbaf3c80ae99fbe0f42a31a8ebaa14675) Thanks [@patzick](https://github.com/patzick)! - Stop registering the `@unocss/nuxt` module from the CMS layer. UnoCSS is not a dependency of this package anymore (it lives in `@shopware/unocss-design-tokens-layer`), so registering the module made every app that doesn't install UnoCSS itself fail to build with `Could not load @unocss/nuxt. Is it installed?` - for example the blank template. Apps that want the shared UnoCSS setup keep getting it from `@shopware/unocss-design-tokens-layer` or their own `@unocss/nuxt` registration.
|
|
492
497
|
|
|
493
|
-
- [#
|
|
498
|
+
- [#2568](https://github.com/shopware/frontends/pull/2568) [`6315350`](https://github.com/shopware/frontends/commit/6315350add0464abef153343897d42f5808f2003) Thanks [@patzick](https://github.com/patzick)! - Add cacheable GET support for category details and product reviews while preserving the existing POST behavior when `cacheableReads` is disabled.
|
|
494
499
|
|
|
495
|
-
-
|
|
500
|
+
- `useCategorySearch.search` now calls `GET /category/{navigationId}` and sends the complete encoded Criteria in the `_criteria` query parameter when cacheable reads are enabled.
|
|
501
|
+
- `useProductReviews.loadProductReviews` now calls `GET /product/{productId}/reviews` and sends its encoded Criteria in `_criteria` when cacheable reads are enabled.
|
|
502
|
+
- The CMS product description reviews element follows the same flag and endpoint behavior when it needs to fetch reviews directly.
|
|
496
503
|
|
|
497
|
-
- [#
|
|
504
|
+
- [#2571](https://github.com/shopware/frontends/pull/2571) [`c64ae1c`](https://github.com/shopware/frontends/commit/c64ae1c6e2d11e33bd52df4b55177cc26b646433) Thanks [@patzick](https://github.com/patzick)! - Show the category a shopper came from in product breadcrumbs, while direct product links continue to use the default category.
|
|
498
505
|
|
|
499
|
-
|
|
500
|
-
`appConfig.lcpImagePreload` is explicitly enabled, which avoids noisy preload
|
|
501
|
-
warnings on storefront pages that do not immediately use the detected image.
|
|
506
|
+
- [#2596](https://github.com/shopware/frontends/pull/2596) [`6572aa8`](https://github.com/shopware/frontends/commit/6572aa84431e1f4a34d6cf04e549037692d638a6) Thanks [@patzick](https://github.com/patzick)! - Reduce Rolldown/Vite build noise: scope Nuxt global components, keep the three.js async chunk warning intentional, avoid shipping full carbon icon JSON via UnoCSS runtime, and bump @vueuse to 14.4.0.
|
|
502
507
|
|
|
503
|
-
- [#
|
|
508
|
+
- [#2592](https://github.com/shopware/frontends/pull/2592) [`fcc3d71`](https://github.com/shopware/frontends/commit/fcc3d71af05c2f2d409aa63ecb562714e6bc0e69) Thanks [@patzick](https://github.com/patzick)! - Replace CMS layer `@apply` styles with plain CSS declarations to avoid Nuxt build warnings.
|
|
504
509
|
|
|
505
|
-
- [#
|
|
510
|
+
- [#2568](https://github.com/shopware/frontends/pull/2568) [`6315350`](https://github.com/shopware/frontends/commit/6315350add0464abef153343897d42f5808f2003) Thanks [@patzick](https://github.com/patzick)! - Preserve CMS image aspect ratios when responsive dimensions are calculated after hydration, preventing visible image scaling as optimized sources load.
|
|
506
511
|
|
|
507
|
-
- Updated dependencies [[`
|
|
508
|
-
- @shopware/
|
|
509
|
-
- @shopware/
|
|
512
|
+
- Updated dependencies [[`6315350`](https://github.com/shopware/frontends/commit/6315350add0464abef153343897d42f5808f2003), [`b767721`](https://github.com/shopware/frontends/commit/b767721847bf3391f9067eca7a045089fb22fce0), [`f16c5a0`](https://github.com/shopware/frontends/commit/f16c5a0785d6187b73c3edcf37feab7c90bd7988), [`978b02c`](https://github.com/shopware/frontends/commit/978b02c969ca4b16f5fc1d7a953ec4cce3d98173), [`6572aa8`](https://github.com/shopware/frontends/commit/6572aa84431e1f4a34d6cf04e549037692d638a6), [`744833b`](https://github.com/shopware/frontends/commit/744833b9d7d2f8ea1f5dfe65be3fa554dbe4a09f), [`e03c91b`](https://github.com/shopware/frontends/commit/e03c91be172374894d90b7a0111855b76719fee1), [`33facb1`](https://github.com/shopware/frontends/commit/33facb178792c8cb26b47ab984ac48c08ab4b72b), [`9137475`](https://github.com/shopware/frontends/commit/91374753cedb2034385f642e6af11314f2971caa), [`474d3fe`](https://github.com/shopware/frontends/commit/474d3fed346816135b0c7c797990b215a8b691c0), [`38379a5`](https://github.com/shopware/frontends/commit/38379a5d52ab09008774533ef417ebe2cde3f7fd)]:
|
|
513
|
+
- @shopware/composables@1.12.1
|
|
514
|
+
- @shopware/api-client@1.5.1
|
|
515
|
+
- @shopware/helpers@1.7.2
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@shopware/helpers";
|
|
8
8
|
import { computed } from "vue";
|
|
9
9
|
import { RouterLink } from "vue-router";
|
|
10
|
+
|
|
10
11
|
import { useUrlResolver } from "#imports";
|
|
11
12
|
import type { Schemas } from "#shopware";
|
|
12
13
|
|
|
@@ -43,7 +44,9 @@ const hasChildren = computed(() => {
|
|
|
43
44
|
v-if="props.level === 0"
|
|
44
45
|
class="self-stretch flex flex-col justify-center items-center"
|
|
45
46
|
>
|
|
46
|
-
<div
|
|
47
|
+
<div
|
|
48
|
+
class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1"
|
|
49
|
+
>
|
|
47
50
|
<div class="flex-1 flex justify-start items-center gap-2.5">
|
|
48
51
|
<RouterLink
|
|
49
52
|
v-if="!urlIsAbsolute(url.path)"
|
|
@@ -76,17 +79,19 @@ const hasChildren = computed(() => {
|
|
|
76
79
|
type="button"
|
|
77
80
|
:aria-label="props.isExpanded ? 'Collapse' : 'Expand'"
|
|
78
81
|
>
|
|
79
|
-
<SwChevronIcon
|
|
82
|
+
<SwChevronIcon
|
|
83
|
+
:direction="props.isExpanded ? 'up' : 'down'"
|
|
84
|
+
:size="20"
|
|
85
|
+
/>
|
|
80
86
|
</button>
|
|
81
87
|
</div>
|
|
82
88
|
</div>
|
|
83
89
|
|
|
84
90
|
<!-- Level 2+ Categories (Nested with left padding) -->
|
|
85
|
-
<div
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<div class="self-stretch pl-4 py-1.5 inline-flex justify-start items-center gap-2">
|
|
91
|
+
<div v-else class="self-stretch flex flex-col justify-center items-center">
|
|
92
|
+
<div
|
|
93
|
+
class="self-stretch pl-4 py-1.5 inline-flex justify-start items-center gap-2"
|
|
94
|
+
>
|
|
90
95
|
<div class="py-0.5 flex-1 flex justify-start items-center gap-2.5">
|
|
91
96
|
<RouterLink
|
|
92
97
|
v-if="!urlIsAbsolute(url.path)"
|
|
@@ -121,7 +126,10 @@ const hasChildren = computed(() => {
|
|
|
121
126
|
type="button"
|
|
122
127
|
:aria-label="props.isExpanded ? 'Collapse' : 'Expand'"
|
|
123
128
|
>
|
|
124
|
-
<SwChevronIcon
|
|
129
|
+
<SwChevronIcon
|
|
130
|
+
:direction="props.isExpanded ? 'up' : 'down'"
|
|
131
|
+
:size="20"
|
|
132
|
+
/>
|
|
125
133
|
</button>
|
|
126
134
|
</div>
|
|
127
135
|
</div>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useCmsTranslations, useProductPrice } from "@shopware/composables";
|
|
3
3
|
import { defu } from "defu";
|
|
4
4
|
import { toRefs } from "vue";
|
|
5
|
+
|
|
5
6
|
import type { Schemas } from "#shopware";
|
|
6
7
|
|
|
7
8
|
const props = defineProps<{
|
|
@@ -53,13 +54,18 @@ const {
|
|
|
53
54
|
</template>
|
|
54
55
|
</SwSharedPrice>
|
|
55
56
|
</div>
|
|
56
|
-
<div
|
|
57
|
+
<div
|
|
58
|
+
class="text-surface-on-surface-variant text-sm font-normal leading-tight line-through"
|
|
59
|
+
>
|
|
57
60
|
<SwSharedPrice :value="price?.listPrice?.price" />
|
|
58
61
|
</div>
|
|
59
62
|
</div>
|
|
60
63
|
|
|
61
64
|
<!-- Regular price display -->
|
|
62
|
-
<div
|
|
65
|
+
<div
|
|
66
|
+
v-else
|
|
67
|
+
class="text-surface-on-surface text-base font-bold leading-normal"
|
|
68
|
+
>
|
|
63
69
|
<SwSharedPrice :value="unitPrice">
|
|
64
70
|
<template #beforePrice>
|
|
65
71
|
<span v-if="displayFrom || displayFromVariants" class="text-sm">{{
|
|
@@ -70,7 +76,10 @@ const {
|
|
|
70
76
|
</div>
|
|
71
77
|
|
|
72
78
|
<!-- Variants from price -->
|
|
73
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
v-if="displayFromVariants"
|
|
81
|
+
class="text-surface-on-surface text-base font-bold leading-normal"
|
|
82
|
+
>
|
|
74
83
|
<SwSharedPrice :value="displayFromVariants">
|
|
75
84
|
<template #beforePrice>
|
|
76
85
|
<span v-if="displayFromVariants" class="text-sm">{{
|
|
@@ -81,7 +90,10 @@ const {
|
|
|
81
90
|
</div>
|
|
82
91
|
|
|
83
92
|
<!-- Regulation price -->
|
|
84
|
-
<div
|
|
93
|
+
<div
|
|
94
|
+
v-if="regulationPrice"
|
|
95
|
+
class="flex gap-2 text-surface-on-surface-variant text-sm"
|
|
96
|
+
>
|
|
85
97
|
{{ translations.listing.previously }}
|
|
86
98
|
<SwSharedPrice :value="regulationPrice" />
|
|
87
99
|
</div>
|
|
@@ -29,10 +29,7 @@ function focusObject() {
|
|
|
29
29
|
</script>
|
|
30
30
|
<template>
|
|
31
31
|
<TresCanvas v-bind="gl">
|
|
32
|
-
<TresPerspectiveCamera
|
|
33
|
-
:args="[75, 1, 0.1, 2000]"
|
|
34
|
-
:position="[0, 0, 500]"
|
|
35
|
-
/>
|
|
32
|
+
<TresPerspectiveCamera :args="[75, 1, 0.1, 2000]" :position="[0, 0, 500]" />
|
|
36
33
|
<OrbitControls make-default />
|
|
37
34
|
<Bounds ref="boundsRef" clip use-mounted>
|
|
38
35
|
<primitive v-if="model" :object="model" @click="focusObject" />
|
|
@@ -8,6 +8,7 @@ import type { ValidationRuleWithoutParams } from "@vuelidate/core";
|
|
|
8
8
|
import { email, required } from "@vuelidate/validators";
|
|
9
9
|
import { defu } from "defu";
|
|
10
10
|
import { computed, reactive, ref } from "vue";
|
|
11
|
+
|
|
11
12
|
import { useCmsElementConfig, useNewsletter, useSalutations } from "#imports";
|
|
12
13
|
|
|
13
14
|
const props = defineProps<{
|
|
@@ -236,7 +237,7 @@ const invokeSubmit = async () => {
|
|
|
236
237
|
id="salutation"
|
|
237
238
|
v-model="state.salutationId"
|
|
238
239
|
name="salutation"
|
|
239
|
-
class="
|
|
240
|
+
class="border-outline-outline-variant focus:border-brand-primaryappearance-none relative block w-full px-3 py-2 border placeholder-surface-on-surface-variant text-surface-on-surface rounded-md focus:outline-none focus:ring-brand-primary focus:z-10 sm:text-sm"
|
|
240
241
|
>
|
|
241
242
|
<option disabled selected value="">
|
|
242
243
|
{{ translations.form.salutationPlaceholder }}
|
|
@@ -261,8 +262,8 @@ const invokeSubmit = async () => {
|
|
|
261
262
|
class="appearance-none relative block w-full px-3 py-2 border placeholder-surface-on-surface-variant text-surface-on-surface rounded-md focus:outline-none focus:ring-brand-primary focus:z-10 sm:text-sm"
|
|
262
263
|
:class="[
|
|
263
264
|
$v.firstName?.$error
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
? 'border-red-600 focus:border-red-600'
|
|
266
|
+
: 'border-outline-outline-variant focus:border-brand-primary',
|
|
266
267
|
]"
|
|
267
268
|
:placeholder="translations.form.firstNamePlaceholder"
|
|
268
269
|
@blur="$v.firstName?.$touch()"
|
|
@@ -3,6 +3,7 @@ import { useCmsTranslations } from "@shopware/composables";
|
|
|
3
3
|
import { getCmsTranslate } from "@shopware/helpers";
|
|
4
4
|
import { defu } from "defu";
|
|
5
5
|
import { computed, toRefs } from "vue";
|
|
6
|
+
|
|
6
7
|
import {
|
|
7
8
|
useAddToCart,
|
|
8
9
|
useCartErrorParamsResolver,
|
|
@@ -72,16 +73,32 @@ const addToCartProxy = async () => {
|
|
|
72
73
|
|
|
73
74
|
<template>
|
|
74
75
|
<div class="w-full inline-flex flex-col justify-start items-start gap-8">
|
|
75
|
-
<SwQuantitySelect
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
:
|
|
76
|
+
<SwQuantitySelect
|
|
77
|
+
v-model="quantity"
|
|
78
|
+
:min="product.minPurchase"
|
|
79
|
+
:max="product.maxPurchase"
|
|
80
|
+
:steps="product.purchaseSteps"
|
|
81
|
+
/>
|
|
82
|
+
<SwStockInfo
|
|
83
|
+
:availableStock="availableStock"
|
|
84
|
+
:minPurchase="minPurchase"
|
|
85
|
+
:deliveryTime="deliveryTime"
|
|
86
|
+
:restockTime="restockTime"
|
|
87
|
+
/>
|
|
79
88
|
<div class="self-stretch flex flex-col justify-start items-start gap-1">
|
|
80
|
-
<SwBaseButton
|
|
81
|
-
|
|
89
|
+
<SwBaseButton
|
|
90
|
+
variant="primary"
|
|
91
|
+
size="medium"
|
|
92
|
+
:disabled="!product?.available"
|
|
93
|
+
block
|
|
94
|
+
data-testid="add-to-cart-button"
|
|
95
|
+
@click="addToCartProxy"
|
|
96
|
+
>
|
|
82
97
|
{{ translations.product.addToCart }}
|
|
83
98
|
</SwBaseButton>
|
|
84
|
-
<div
|
|
99
|
+
<div
|
|
100
|
+
class="self-stretch text-surface-on-surface text-xs font-normal leading-none"
|
|
101
|
+
>
|
|
85
102
|
{{ translations.product.productNumber }}: {{ productNumber }}
|
|
86
103
|
</div>
|
|
87
104
|
</div>
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
import { getCmsTranslate } from "@shopware/helpers";
|
|
13
13
|
import { defu } from "defu";
|
|
14
14
|
import { computed, ref, toRef } from "vue";
|
|
15
|
+
|
|
15
16
|
import {
|
|
16
17
|
useAddToCart,
|
|
17
18
|
useCartErrorParamsResolver,
|
|
18
19
|
useCartNotification,
|
|
20
|
+
useNavigationContext,
|
|
19
21
|
useNotifications,
|
|
20
22
|
useProductWishlist,
|
|
21
23
|
useUrlResolver,
|
|
@@ -143,10 +145,25 @@ const productManufacturer = computed(() =>
|
|
|
143
145
|
getProductManufacturerName(product.value),
|
|
144
146
|
);
|
|
145
147
|
|
|
146
|
-
const {
|
|
147
|
-
const
|
|
148
|
-
|
|
148
|
+
const { foreignKey: currentNavigationId, routeName } = useNavigationContext();
|
|
149
|
+
const referrerCategoryId = computed(() =>
|
|
150
|
+
isProductListing && routeName.value === "frontend.navigation.page"
|
|
151
|
+
? currentNavigationId.value
|
|
152
|
+
: undefined,
|
|
149
153
|
);
|
|
154
|
+
const { getUrlPrefix } = useUrlResolver();
|
|
155
|
+
const productLink = computed(() => {
|
|
156
|
+
const productRoute = getProductRoute(product.value);
|
|
157
|
+
const productRouteWithReferrer = {
|
|
158
|
+
...productRoute,
|
|
159
|
+
state: {
|
|
160
|
+
...productRoute.state,
|
|
161
|
+
referrerCategoryId: referrerCategoryId.value,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return buildUrlPrefix(productRouteWithReferrer, getUrlPrefix());
|
|
166
|
+
});
|
|
150
167
|
</script>
|
|
151
168
|
|
|
152
169
|
<template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { BoxLayout } from "@shopware/composables";
|
|
3
3
|
import type { UrlRouteOutput } from "@shopware/helpers";
|
|
4
4
|
import { computed } from "vue";
|
|
5
|
+
|
|
5
6
|
import type { Schemas } from "#shopware";
|
|
6
7
|
|
|
7
8
|
type Translations = {
|
|
@@ -28,18 +29,24 @@ const props = defineProps<{
|
|
|
28
29
|
const isMinimalLayout = computed(() => props.layoutType === "minimal");
|
|
29
30
|
</script>
|
|
30
31
|
<template>
|
|
31
|
-
<div
|
|
32
|
+
<div
|
|
33
|
+
class="self-stretch p-2 flex flex-col justify-between items-start gap-4 flex-1"
|
|
34
|
+
>
|
|
32
35
|
<div class="self-stretch flex flex-col justify-start items-start gap-4">
|
|
33
36
|
<div class="self-stretch flex flex-col justify-start items-start gap-2">
|
|
34
37
|
<div class="self-stretch flex flex-col justify-start items-start gap-1">
|
|
35
|
-
<div
|
|
36
|
-
|
|
38
|
+
<div
|
|
39
|
+
v-if="productManufacturer"
|
|
40
|
+
class="self-stretch text-surface-on-surface text-sm font-bold leading-tight"
|
|
41
|
+
>
|
|
37
42
|
{{ productManufacturer }}
|
|
38
43
|
</div>
|
|
39
44
|
|
|
40
|
-
<RouterLink
|
|
45
|
+
<RouterLink
|
|
46
|
+
:to="productLink"
|
|
41
47
|
class="self-stretch text-surface-on-surface text-2xl font-normal font-serif leading-9 overflow-hidden line-clamp-2 break-words min-h-[4.5rem]"
|
|
42
|
-
data-testid="product-box-product-name-link"
|
|
48
|
+
data-testid="product-box-product-name-link"
|
|
49
|
+
>
|
|
43
50
|
{{ productName }}
|
|
44
51
|
</RouterLink>
|
|
45
52
|
</div>
|
|
@@ -54,13 +61,24 @@ const isMinimalLayout = computed(() => props.layoutType === "minimal");
|
|
|
54
61
|
/>
|
|
55
62
|
|
|
56
63
|
<!-- Price for standard layout -->
|
|
57
|
-
<SwListingProductPrice
|
|
64
|
+
<SwListingProductPrice
|
|
65
|
+
v-else
|
|
66
|
+
:product="product"
|
|
67
|
+
data-testid="product-box-product-price"
|
|
68
|
+
/>
|
|
58
69
|
</div>
|
|
59
70
|
|
|
60
71
|
<!-- CTA buttons only for non-minimal layout -->
|
|
61
72
|
<template v-if="!isMinimalLayout">
|
|
62
|
-
<SwBaseButton
|
|
63
|
-
|
|
73
|
+
<SwBaseButton
|
|
74
|
+
variant="primary"
|
|
75
|
+
v-if="!fromPrice"
|
|
76
|
+
size="medium"
|
|
77
|
+
:disabled="!product?.available"
|
|
78
|
+
block
|
|
79
|
+
data-testid="add-to-cart-button"
|
|
80
|
+
@click="addToCartProxy"
|
|
81
|
+
>
|
|
64
82
|
{{ translations.product.addToCart }}
|
|
65
83
|
</SwBaseButton>
|
|
66
84
|
|
|
@@ -71,4 +89,4 @@ const isMinimalLayout = computed(() => props.layoutType === "minimal");
|
|
|
71
89
|
</RouterLink>
|
|
72
90
|
</template>
|
|
73
91
|
</div>
|
|
74
|
-
</template>
|
|
92
|
+
</template>
|