@shopware/cms-base-layer 2.0.0 → 3.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 (67) hide show
  1. package/README.md +167 -125
  2. package/app/app.config.ts +12 -0
  3. package/app/components/SwCategoryNavigation.vue +25 -18
  4. package/app/components/SwFilterDropdown.vue +54 -0
  5. package/app/components/SwListingProductPrice.vue +2 -2
  6. package/app/components/SwMedia3D.vue +14 -5
  7. package/app/components/SwProductCard.vue +24 -21
  8. package/app/components/SwProductCardDetails.vue +29 -12
  9. package/app/components/SwProductCardImage.vue +30 -29
  10. package/app/components/SwProductGallery.vue +18 -14
  11. package/app/components/SwProductListingFilter.vue +20 -9
  12. package/app/components/SwProductListingFilters.vue +3 -7
  13. package/app/components/SwProductListingFiltersHorizontal.vue +306 -0
  14. package/app/components/SwProductPrice.vue +3 -3
  15. package/app/components/SwProductRating.vue +40 -0
  16. package/app/components/SwProductReviews.vue +6 -19
  17. package/app/components/SwProductUnits.vue +10 -15
  18. package/app/components/SwQuantitySelect.vue +4 -7
  19. package/app/components/SwSlider.vue +150 -51
  20. package/app/components/SwSortDropdown.vue +10 -6
  21. package/app/components/SwVariantConfigurator.vue +13 -13
  22. package/app/components/listing-filters/SwFilterPrice.vue +45 -40
  23. package/app/components/listing-filters/SwFilterProperties.vue +40 -33
  24. package/app/components/listing-filters/SwFilterRating.vue +36 -27
  25. package/app/components/listing-filters/SwFilterShippingFree.vue +39 -32
  26. package/app/components/public/cms/CmsBlockSpatialViewer.vue +94 -0
  27. package/app/components/public/cms/CmsGenericBlock.md +17 -2
  28. package/app/components/public/cms/CmsGenericBlock.vue +21 -2
  29. package/app/components/public/cms/CmsGenericElement.vue +7 -2
  30. package/app/components/public/cms/CmsNoComponent.vue +87 -8
  31. package/app/components/public/cms/CmsPage.md +19 -2
  32. package/app/components/public/cms/CmsPage.vue +7 -0
  33. package/app/components/public/cms/FrontendAccountCustomerGroupRegistrationPage.vue +52 -0
  34. package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -1
  35. package/app/components/public/cms/block/CmsBlockImageText.vue +5 -5
  36. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +5 -12
  37. package/app/components/public/cms/element/CmsElementBuyBox.vue +3 -3
  38. package/app/components/public/cms/element/CmsElementCrossSelling.vue +19 -3
  39. package/app/components/public/cms/element/CmsElementImage.vue +12 -35
  40. package/app/components/public/cms/element/CmsElementImageGallery.vue +117 -50
  41. package/app/components/public/cms/element/CmsElementProductBox.vue +7 -1
  42. package/app/components/public/cms/element/CmsElementProductListing.vue +15 -4
  43. package/app/components/public/cms/element/CmsElementProductName.vue +6 -1
  44. package/app/components/public/cms/element/CmsElementProductSlider.vue +56 -35
  45. package/app/components/public/cms/element/CmsElementSidebarFilter.vue +10 -2
  46. package/app/components/public/cms/element/CmsElementText.vue +10 -11
  47. package/app/components/public/cms/element/SwProductListingPagination.vue +2 -2
  48. package/app/components/public/cms/section/CmsSectionDefault.vue +2 -2
  49. package/app/components/public/cms/section/CmsSectionSidebar.vue +6 -3
  50. package/app/components/ui/BaseButton.vue +18 -15
  51. package/app/components/ui/ChevronIcon.vue +10 -13
  52. package/app/components/ui/WishlistIcon.vue +3 -8
  53. package/app/composables/useImagePlaceholder.ts +3 -3
  54. package/app/composables/useLcpImagePreload.test.ts +229 -0
  55. package/app/composables/useLcpImagePreload.ts +43 -0
  56. package/app/composables/useTypedAppConfig.ts +15 -0
  57. package/app/helpers/cms/findFirstCmsImageUrl.ts +86 -0
  58. package/app/helpers/cms/getImageSizes.test.ts +50 -0
  59. package/app/helpers/cms/getImageSizes.ts +36 -0
  60. package/app/helpers/html-to-vue/ast.ts +53 -19
  61. package/app/helpers/html-to-vue/getOptionsFromNode.ts +1 -1
  62. package/app/helpers/html-to-vue/renderToHtml.ts +7 -11
  63. package/app/helpers/html-to-vue/renderer.ts +86 -26
  64. package/index.d.ts +37 -5
  65. package/nuxt.config.ts +25 -0
  66. package/package.json +21 -21
  67. package/uno.config.ts +0 -83
package/README.md CHANGED
@@ -5,14 +5,14 @@
5
5
  [![](https://img.shields.io/github/issues/shopware/frontends/cms-base?label=cms-base%20issues&logo=github)](https://github.com/shopware/frontends/issues?q=is%3Aopen+is%3Aissue+label%3Acms-base)
6
6
  [![](https://img.shields.io/github/license/shopware/frontends?color=blue)](#)
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://frontends.shopware.com/framework/styling.html) using atomic css syntax (UnoCss / Tailwind).
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://frontends.shopware.com/framework/styling.html).
9
9
 
10
- It is useful for projects that want to use the CMS components but design their own layout.
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
 
12
12
  ## Features
13
13
 
14
14
  - Vue components for [Shopping Experiences](https://www.shopware.com/en/products/shopping-experiences/) CMS
15
- - CMS sections, blocks and elements styled using [Tailwind CSS](https://tailwindcss.com/) classes
15
+ - CMS sections, blocks and elements implemented with utility-class-based markup
16
16
  - 🚀 Empowered by [@shopware/composables](https://www.npmjs.com/package/@shopware/composables)
17
17
 
18
18
  ## Setup
@@ -32,25 +32,30 @@ npm install -D @shopware/cms-base-layer
32
32
  yarn add -D @shopware/cms-base-layer
33
33
 
34
34
  # pnpm
35
- pnpm install -D @shopware/cms-base-layer
35
+ pnpm add -D @shopware/cms-base-layer
36
36
 
37
37
  # bun
38
38
  bun install -D @shopware/cms-base-layer
39
39
 
40
40
  # deno
41
- deno install --dev @shopware/cms-base-layer
41
+ deno install --dev npm:@shopware/cms-base-layer
42
42
  ```
43
43
 
44
44
  <!-- /automd -->
45
45
 
46
+ If you also want the shared Shopware Frontends UnoCSS setup, install `@shopware/unocss-design-tokens-layer` in your app and extend it alongside `@shopware/cms-base-layer`.
47
+
46
48
  Then, register the Nuxt layer in `nuxt.config.ts` file:
47
49
 
48
50
  <!-- automd:file src="templates/vue-blank/nuxt.config.ts" code -->
49
51
 
50
52
  ```ts [nuxt.config.ts]
51
53
  // https://v3.nuxtjs.org/api/configuration/nuxt.config
54
+ const isStackBlitz = process.env.SHOPWARE_STACKBLITZ === "true";
55
+
52
56
  export default defineNuxtConfig({
53
57
  extends: ["@shopware/composables/nuxt-layer", "@shopware/cms-base-layer"],
58
+ ...(isStackBlitz ? { devtools: { enabled: false } } : {}),
54
59
  shopware: {
55
60
  endpoint: "https://demo-frontends.shopware.store/store-api/",
56
61
  accessToken: "SWSCBHFSNTVMAWNZDNFKSHLAYW",
@@ -83,15 +88,20 @@ Since all CMS components are registered in your Nuxt application, you can now st
83
88
  </template>
84
89
  ```
85
90
 
86
- > You can use default styling by installing/importing Tailwind CSS stylesheet in your project.
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
+
93
+ See a [short guide](https://frontends.shopware.com/getting-started/cms/content-pages.html#use-the-cms-base-package) on how to use `cms-base-layer` in your Nuxt project.
87
94
 
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.
95
+ ## Styling and Design Tokens
89
96
 
90
- ## Default styling
97
+ The components use utility classes, but the shared UnoCSS configuration, design tokens, and runtime handling for dynamic CMS classes are now provided by `@shopware/unocss-design-tokens-layer`.
91
98
 
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.
99
+ This means you have two options:
93
100
 
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:
101
+ - extend `@shopware/unocss-design-tokens-layer` to use the shared Shopware Frontends token palette and UnoCSS defaults
102
+ - keep only `@shopware/cms-base-layer` and provide your own UnoCSS or Tailwind setup
103
+
104
+ When you use the design-tokens layer, you can customize the generated config in your project's `uno.config.ts`:
95
105
 
96
106
  ```ts [nuxt.config.ts]
97
107
  // nuxt.config.ts
@@ -104,24 +114,14 @@ export default defineNuxtConfig({
104
114
  ```
105
115
 
106
116
  ```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
117
  import { mergeConfigs } from '@unocss/core'
118
- import config from './.nuxt/uno.config.mjs'
118
+ import baseConfig from './.nuxt/uno.config.mjs'
119
119
 
120
- export default mergeConfigs([config, {
120
+ export default mergeConfigs([baseConfig, {
121
121
  theme: {
122
122
  colors: {
123
- primary: '#ff3e00',
124
- secondary: '#1c1c1c',
123
+ 'brand-primary': '#ff3e00',
124
+ 'brand-secondary': '#1c1c1c',
125
125
  },
126
126
  },
127
127
  }])
@@ -304,6 +304,131 @@ const customPlaceholder = useImagePlaceholder("#FF0000");
304
304
  </template>
305
305
  ```
306
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
+ Images are optimized to prevent the browser from downloading images larger than their displayed dimensions — a common Lighthouse performance issue.
376
+
377
+ ### Product Card Images (`SwProductCardImage`)
378
+
379
+ The `productCard` preset only defines URL modifiers (format/quality/fit). `width`/`height`/`densities`/`loading` stay on the component — NuxtImg presets don't propagate these reliably:
380
+
381
+ ```ts
382
+ // nuxt.config.ts
383
+ productCard: {
384
+ modifiers: { format: "webp", quality: 90, fit: "cover" },
385
+ }
386
+ ```
387
+
388
+ ```vue
389
+ <NuxtImg preset="productCard"
390
+ :src="coverSrcPath"
391
+ width="400" height="400"
392
+ densities="1x"
393
+ loading="lazy" />
394
+ ```
395
+
396
+ - **Fixed `width`/`height`** (400px) — avoid hydration mismatches caused by dynamic DOM measurement
397
+ - **`densities="1x"`** — prevents duplicate retina requests
398
+ - **`loading="lazy"`** — defers off-viewport images
399
+
400
+ > **⚠️ Avoid** adding `decoding` or `sizes` props on the component — they trigger Vue hydration attribute mismatches with NuxtImg, which cause duplicate image requests.
401
+
402
+ ### CMS Images (`CmsElementImage`)
403
+
404
+ CMS image elements use `useElementSize()` to measure the rendered container and pass the size to `<NuxtImg>` via `width`/`height` props:
405
+
406
+ - During SSR, no image is fetched (size is `undefined`)
407
+ - After hydration, the container is measured and a single correctly-sized image is requested
408
+ - The size is multiplied by 2 (for retina) and rounded up to the nearest 100px
409
+
410
+ ### Slider Components
411
+
412
+ **Slider components** (`CmsElementProductSlider`, `CmsElementCrossSelling`) `inject` the slot count via `cms-block-slot-count` to scale their SSR breakpoints — ensuring media queries account for the container being a fraction of the viewport.
413
+
414
+ ### LCP Image Preloading
415
+
416
+ **`useLcpImagePreload`** scans CMS sections for the first image and injects `<link rel="preload" as="image" fetchpriority="high">` during SSR.
417
+
418
+ ## 🔄 UnoCSS Runtime
419
+
420
+ When you extend `@shopware/unocss-design-tokens-layer`, you also get 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 (for example inline utility classes configured in the admin panel).
421
+
422
+ The runtime is **enabled by default**. To disable it, set `unocssRuntime` to `false` in your project's `app.config.ts`:
423
+
424
+ ```ts
425
+ export default defineAppConfig({
426
+ unocssRuntime: false,
427
+ });
428
+ ```
429
+
430
+ > **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.
431
+
307
432
  ## 📘 Available components
308
433
 
309
434
  The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base-layer.html#available-components).
@@ -349,119 +474,36 @@ No additional packages needed to be installed.
349
474
 
350
475
  Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
351
476
 
352
- ### Latest changes: 2.0.0
477
+ ### Latest changes: 3.0.0
353
478
 
354
479
  ### Major Changes
355
480
 
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
- ```
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.
402
482
 
403
483
  ### Minor Changes
404
484
 
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
485
+ - [#2420](https://github.com/shopware/frontends/pull/2420) [`9e37ab6`](https://github.com/shopware/frontends/commit/9e37ab6897f501ed3d261fa619aee349e46342c2) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Add FrontendAccountCustomerGroupRegistrationPage component for customer group view
449
486
 
450
487
  ### Patch Changes
451
488
 
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.
489
+ - [#2439](https://github.com/shopware/frontends/pull/2439) [`a992e39`](https://github.com/shopware/frontends/commit/a992e396ea6aa3b44783d183561fa8b0605b77f0) Thanks [@patzick](https://github.com/patzick)! - Use Nuxt route composables in CMS components to keep route query access available during SSR.
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`.
492
+
493
+ - [#2378](https://github.com/shopware/frontends/pull/2378) [`c36bc1f`](https://github.com/shopware/frontends/commit/c36bc1ff17e8e34c52fa91e6388ce210fffb7e8e) Thanks [@patzick](https://github.com/patzick)! - Add UnoCSS directive transformation for CMS block styles so layered Nuxt apps do not emit CSS minification warnings from raw `@apply` directives during production builds.
453
494
 
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
495
+ - [#2369](https://github.com/shopware/frontends/pull/2369) [`3c16985`](https://github.com/shopware/frontends/commit/3c16985ddf3878bc207c514a5ab8e4a6409f809c) Thanks [@mkucmus](https://github.com/mkucmus)! - Fixed `xss` library loading issue in Vite dev server by adding it to `optimizeDeps.include`
455
496
 
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.
497
+ - [#2371](https://github.com/shopware/frontends/pull/2371) [`33e0c69`](https://github.com/shopware/frontends/commit/33e0c69afc3de854733ab61f866ba65cce1489f6) Thanks [@patzick](https://github.com/patzick)! - Disable automatic CMS LCP image preload by default.
457
498
 
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.
499
+ The preload helper now only injects image preload tags when
500
+ `appConfig.lcpImagePreload` is explicitly enabled, which avoids noisy preload
501
+ warnings on storefront pages that do not immediately use the detected image.
459
502
 
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
503
+ - [#2326](https://github.com/shopware/frontends/pull/2326) [`e7efff8`](https://github.com/shopware/frontends/commit/e7efff8c615ae8d0858572933285216cc533dd0b) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Gate wishlist button behind login when useLoginModal is provided via provide/inject. For guests, show login modal on wishlist click and add product to wishlist after successful login.
461
504
 
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.
505
+ - [#2346](https://github.com/shopware/frontends/pull/2346) [`a47143a`](https://github.com/shopware/frontends/commit/a47143a670f49deecc35dce4bb8b6bd12d9a3b47) Thanks [@joberthel](https://github.com/joberthel)! - Improve `SwMedia3D` model framing by fitting loaded 3D models into the viewport automatically. This fixes cases where very small 3D files appeared tiny and hard to inspect.
463
506
 
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
507
+ - Updated dependencies [[`22fc8a7`](https://github.com/shopware/frontends/commit/22fc8a7301f6a7d2612d907ab73555978b651c00), [`bea7f58`](https://github.com/shopware/frontends/commit/bea7f5882cb58c6d47c84a82db5c8ecaf9bcf8ef), [`b8c0091`](https://github.com/shopware/frontends/commit/b8c00913c3afb5e1e63de9565105f8f8e3bf299f)]:
508
+ - @shopware/helpers@1.7.1
509
+ - @shopware/composables@1.11.1
package/app/app.config.ts CHANGED
@@ -4,4 +4,16 @@ export default defineAppConfig({
4
4
  imagePlaceholder: {
5
5
  color: "#543B95",
6
6
  },
7
+ backgroundImage: {
8
+ format: "webp",
9
+ quality: 90,
10
+ },
11
+ lcpImagePreload: false,
12
+ imageSizes: {
13
+ 1: "(max-width: 768px) 100vw, 100vw",
14
+ 2: "(max-width: 768px) 100vw, 50vw",
15
+ 3: "(max-width: 768px) 100vw, 33vw",
16
+ default: "(max-width: 768px) 50vw, 25vw",
17
+ },
18
+ unocssRuntime: true,
7
19
  });
@@ -2,21 +2,20 @@
2
2
  import { ref } from "vue";
3
3
  import type { Schemas } from "#shopware";
4
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
- );
5
+ const {
6
+ activeCategory,
7
+ elements,
8
+ level = 0,
9
+ } = defineProps<{
10
+ activeCategory: Schemas["Category"];
11
+ elements: Schemas["Category"][];
12
+ level: number;
13
+ }>();
15
14
 
16
15
  const expandedItems = ref<Set<string>>(new Set());
17
16
 
18
17
  function isActive(navigationElement: Schemas["Category"]) {
19
- return navigationElement.id === props.activeCategory?.id;
18
+ return navigationElement.id === activeCategory?.id;
20
19
  }
21
20
 
22
21
  function toggleExpanded(id: string) {
@@ -32,9 +31,12 @@ function isExpanded(id: string) {
32
31
  }
33
32
  </script>
34
33
  <template>
35
- <div v-if="props.elements?.length" class="self-stretch flex flex-col justify-start items-start gap-4">
34
+ <div
35
+ v-if="elements?.length"
36
+ class="self-stretch flex flex-col justify-start items-start gap-4"
37
+ >
36
38
  <div
37
- v-for="(navigationElement, index) in props.elements"
39
+ v-for="(navigationElement, index) in elements"
38
40
  :key="index"
39
41
  class="w-full"
40
42
  >
@@ -42,15 +44,18 @@ function isExpanded(id: string) {
42
44
  :navigation-element="navigationElement"
43
45
  :is-active="isActive(navigationElement)"
44
46
  :is-expanded="isExpanded(navigationElement.id)"
45
- :level="props.level"
47
+ :level="level"
46
48
  @toggle="toggleExpanded(navigationElement.id)"
47
49
  />
48
50
  <transition name="filter-collapse">
49
- <div v-if="navigationElement.children && isExpanded(navigationElement.id)" class="self-stretch flex flex-col justify-start items-start">
51
+ <div
52
+ v-if="navigationElement.children && isExpanded(navigationElement.id)"
53
+ class="self-stretch flex flex-col justify-start items-start"
54
+ >
50
55
  <SwCategoryNavigation
51
56
  :elements="navigationElement.children"
52
- :active-category="props.activeCategory"
53
- :level="props.level + 1"
57
+ :active-category="activeCategory"
58
+ :level="level + 1"
54
59
  />
55
60
  </div>
56
61
  </transition>
@@ -60,7 +65,9 @@ function isExpanded(id: string) {
60
65
  <style scoped>
61
66
  .filter-collapse-enter-active,
62
67
  .filter-collapse-leave-active {
63
- transition: max-height 240ms ease, opacity 200ms ease;
68
+ transition:
69
+ max-height 240ms ease,
70
+ opacity 200ms ease;
64
71
  overflow: hidden;
65
72
  }
66
73
  .filter-collapse-enter-from,
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { onClickOutside } from "@vueuse/core";
3
+ import { ref, useTemplateRef } from "vue";
4
+
5
+ defineProps<{
6
+ label: string;
7
+ isActive?: boolean;
8
+ }>();
9
+
10
+ const isOpen = ref(false);
11
+ const dropdownElement = useTemplateRef<HTMLDivElement>("dropdownElement");
12
+
13
+ onClickOutside(dropdownElement, () => {
14
+ isOpen.value = false;
15
+ });
16
+
17
+ function toggle() {
18
+ isOpen.value = !isOpen.value;
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <div ref="dropdownElement" class="relative">
24
+ <!-- Pill button -->
25
+ <button
26
+ type="button"
27
+ class="bg-brand-tertiary rounded-full px-4 py-1.5 inline-flex items-center hover:bg-brand-tertiary-hover transition-colors"
28
+ :class="{ 'ring-2 ring-brand-primary': isActive }"
29
+ @click="toggle"
30
+ :aria-expanded="isOpen"
31
+ aria-haspopup="true"
32
+ >
33
+ <div class="py-1 inline-flex items-center gap-1">
34
+ <span class="text-brand-on-tertiary text-base font-normal leading-6">
35
+ {{ label }}
36
+ </span>
37
+ <SwChevronIcon
38
+ :direction="isOpen ? 'up' : 'down'"
39
+ :size="24"
40
+ class="text-brand-on-tertiary"
41
+ />
42
+ </div>
43
+ </button>
44
+
45
+ <!-- Dropdown panel -->
46
+ <div
47
+ v-if="isOpen"
48
+ class="absolute top-full left-0 mt-2 min-w-64 bg-surface-surface rounded-lg shadow-lg ring-1 ring-outline-outline-variant z-50 p-4"
49
+ role="menu"
50
+ >
51
+ <slot />
52
+ </div>
53
+ </div>
54
+ </template>
@@ -35,7 +35,7 @@ const {
35
35
  unitPrice,
36
36
  displayFromVariants,
37
37
  displayFrom,
38
- isListPrice,
38
+ hasListPrice,
39
39
  regulationPrice,
40
40
  } = useProductPrice(product);
41
41
  </script>
@@ -43,7 +43,7 @@ const {
43
43
  <template>
44
44
  <div :id="product.id" class="inline-flex justify-start items-center gap-2">
45
45
  <!-- Sale price display -->
46
- <div v-if="isListPrice" class="flex items-center gap-2">
46
+ <div v-if="hasListPrice" class="flex items-center gap-2">
47
47
  <div class="text-base font-bold leading-normal">
48
48
  <SwSharedPrice :value="unitPrice">
49
49
  <template #beforePrice>
@@ -1,7 +1,8 @@
1
1
  <script lang="ts" setup>
2
- import { OrbitControls, useGLTF } from "@tresjs/cientos";
2
+ import { Bounds, OrbitControls, useGLTF } from "@tresjs/cientos";
3
3
  import { TresCanvas } from "@tresjs/core";
4
4
  import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from "three";
5
+ import { computed, shallowRef } from "vue";
5
6
 
6
7
  const props = defineProps<{
7
8
  src: string;
@@ -17,17 +18,25 @@ const gl = {
17
18
  windowSize: false,
18
19
  };
19
20
 
20
- const { scene: model } = await useGLTF(props.src);
21
+ const { state } = await useGLTF(props.src);
22
+ const model = computed(() => state.value?.scene);
23
+
24
+ const boundsRef = shallowRef();
25
+
26
+ function focusObject() {
27
+ boundsRef.value?.instance.lookAt(model.value);
28
+ }
21
29
  </script>
22
30
  <template>
23
31
  <TresCanvas v-bind="gl">
24
32
  <TresPerspectiveCamera
25
33
  :args="[75, 1, 0.1, 2000]"
26
34
  :position="[0, 0, 500]"
27
- :look-at="[0, 0, 0]"
28
35
  />
29
- <OrbitControls />
30
- <primitive :object="model" />
36
+ <OrbitControls make-default />
37
+ <Bounds ref="boundsRef" clip use-mounted>
38
+ <primitive v-if="model" :object="model" @click="focusObject" />
39
+ </Bounds>
31
40
  <TresDirectionalLight :position="[3, 3, 3]" :intensity="1" />
32
41
  <TresAmbientLight :intensity="2" />
33
42
  </TresCanvas>