@shopware/cms-base-layer 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +47 -63
  2. package/app/components/SwCategoryNavigation.vue +1 -0
  3. package/app/components/SwCategoryNavigationLink.vue +16 -8
  4. package/app/components/SwContactForm.vue +1 -0
  5. package/app/components/SwFilterChips.vue +1 -0
  6. package/app/components/SwListingProductPrice.vue +16 -4
  7. package/app/components/SwMedia3D.vue +1 -4
  8. package/app/components/SwNewsletterForm.vue +4 -3
  9. package/app/components/SwProductAddToCart.vue +24 -7
  10. package/app/components/SwProductCard.vue +1 -0
  11. package/app/components/SwProductCardDetails.vue +27 -9
  12. package/app/components/SwProductCardImage.vue +31 -12
  13. package/app/components/SwProductCardSkeleton.vue +37 -14
  14. package/app/components/SwProductGallery.vue +1 -0
  15. package/app/components/SwProductListingFilter.vue +5 -1
  16. package/app/components/SwProductListingFilters.vue +21 -6
  17. package/app/components/SwProductListingFiltersHorizontal.vue +4 -1
  18. package/app/components/SwProductPrice.vue +1 -0
  19. package/app/components/SwProductReviews.vue +14 -6
  20. package/app/components/SwProductReviewsForm.vue +15 -3
  21. package/app/components/SwProductUnits.vue +1 -0
  22. package/app/components/SwQuantitySelect.vue +10 -3
  23. package/app/components/SwSharedPrice.vue +1 -0
  24. package/app/components/SwSlider.vue +1 -0
  25. package/app/components/SwStockInfo.vue +12 -6
  26. package/app/components/SwVariantConfigurator.vue +4 -1
  27. package/app/components/listing-filters/SwFilterPrice.vue +79 -30
  28. package/app/components/listing-filters/SwFilterProperties.vue +40 -25
  29. package/app/components/listing-filters/SwFilterRating.vue +21 -9
  30. package/app/components/listing-filters/SwFilterShippingFree.vue +16 -8
  31. package/app/components/public/cms/CmsBlockSpatialViewer.vue +1 -0
  32. package/app/components/public/cms/CmsGenericBlock.vue +2 -0
  33. package/app/components/public/cms/CmsGenericElement.vue +1 -0
  34. package/app/components/public/cms/CmsNoComponent.vue +1 -0
  35. package/app/components/public/cms/CmsPage.md +1 -1
  36. package/app/components/public/cms/CmsPage.vue +2 -0
  37. package/app/components/public/cms/block/CmsBlockCategoryNavigation.vue +1 -0
  38. package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -0
  39. package/app/components/public/cms/block/CmsBlockCrossSelling.vue +1 -0
  40. package/app/components/public/cms/block/CmsBlockCustomForm.vue +1 -0
  41. package/app/components/public/cms/block/CmsBlockForm.vue +1 -0
  42. package/app/components/public/cms/block/CmsBlockGalleryBuybox.vue +4 -1
  43. package/app/components/public/cms/block/CmsBlockImage.vue +1 -0
  44. package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +4 -1
  45. package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
  46. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +4 -1
  47. package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
  48. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +1 -0
  49. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +4 -1
  50. package/app/components/public/cms/block/CmsBlockImageSimpleGrid.vue +1 -0
  51. package/app/components/public/cms/block/CmsBlockImageSlider.vue +1 -0
  52. package/app/components/public/cms/block/CmsBlockImageText.vue +4 -1
  53. package/app/components/public/cms/block/CmsBlockImageTextBubble.vue +4 -1
  54. package/app/components/public/cms/block/CmsBlockImageTextCover.vue +4 -1
  55. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +4 -12
  56. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +4 -1
  57. package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
  58. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +4 -1
  59. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +4 -1
  60. package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
  61. package/app/components/public/cms/block/CmsBlockProductHeading.vue +1 -0
  62. package/app/components/public/cms/block/CmsBlockProductListing.vue +1 -0
  63. package/app/components/public/cms/block/CmsBlockProductThreeColumn.vue +1 -0
  64. package/app/components/public/cms/block/CmsBlockSidebarFilter.vue +1 -0
  65. package/app/components/public/cms/block/CmsBlockText.vue +1 -0
  66. package/app/components/public/cms/block/CmsBlockTextHero.vue +1 -0
  67. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +2 -4
  68. package/app/components/public/cms/block/CmsBlockTextThreeColumn.vue +1 -0
  69. package/app/components/public/cms/block/CmsBlockTextTwoColumn.vue +4 -1
  70. package/app/components/public/cms/block/CmsBlockVimeoVideo.vue +1 -0
  71. package/app/components/public/cms/block/CmsBlockYoutubeVideo.vue +1 -0
  72. package/app/components/public/cms/element/CmsElementBuyBox.vue +90 -72
  73. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +14 -3
  74. package/app/components/public/cms/element/CmsElementCrossSelling.vue +3 -1
  75. package/app/components/public/cms/element/CmsElementCustomForm.vue +1 -0
  76. package/app/components/public/cms/element/CmsElementForm.vue +1 -0
  77. package/app/components/public/cms/element/CmsElementHtml.vue +0 -1
  78. package/app/components/public/cms/element/CmsElementImage.vue +2 -0
  79. package/app/components/public/cms/element/CmsElementImageGallery.vue +6 -2
  80. package/app/components/public/cms/element/CmsElementManufacturerLogo.vue +1 -0
  81. package/app/components/public/cms/element/CmsElementProductBox.vue +1 -0
  82. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +77 -29
  83. package/app/components/public/cms/element/CmsElementProductListing.vue +16 -5
  84. package/app/components/public/cms/element/CmsElementProductSlider.vue +6 -4
  85. package/app/components/public/cms/element/CmsElementText.vue +2 -0
  86. package/app/components/public/cms/element/CmsElementVimeoVideo.vue +1 -0
  87. package/app/components/public/cms/element/CmsElementYoutubeVideo.vue +1 -0
  88. package/app/components/public/cms/element/SwProductListingPagination.vue +8 -2
  89. package/app/components/public/cms/section/CmsSectionSidebar.vue +13 -8
  90. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +16 -5
  91. package/app/components/ui/SwitchButton.vue +53 -14
  92. package/app/components/ui/WishlistIcon.vue +8 -2
  93. package/app/composables/useLcpImagePreload.test.ts +1 -0
  94. package/app/composables/useLcpImagePreload.ts +2 -0
  95. package/app/composables/useTypedAppConfig.ts +1 -0
  96. package/app/helpers/cms/getImageSizes.test.ts +1 -0
  97. package/app/helpers/html-to-vue/ast.ts +1 -0
  98. package/app/helpers/html-to-vue/getOptionsFromNode.test.ts +1 -0
  99. package/app/helpers/html-to-vue/renderToHtml.ts +1 -0
  100. package/app/helpers/html-to-vue/renderer.ts +1 -0
  101. package/app/providers/shopware.test.ts +1 -0
  102. package/package.json +19 -20
package/README.md CHANGED
@@ -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 '@unocss/core'
118
- import baseConfig from './.nuxt/uno.config.mjs'
119
-
120
- export default mergeConfigs([baseConfig, {
121
- theme: {
122
- colors: {
123
- 'brand-primary': '#ff3e00',
124
- 'brand-secondary': '#1c1c1c',
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 | 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* |
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: ['avif', 'webp', 'jpg'],
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: 'avif',
265
+ format: "avif",
260
266
  quality: 80,
261
- fit: 'cover',
262
- }
267
+ fit: "cover",
268
+ },
263
269
  },
264
270
  // Add custom preset
265
271
  categoryBanner: {
266
272
  modifiers: {
267
- format: 'webp',
273
+ format: "webp",
268
274
  quality: 90,
269
275
  width: 1200,
270
276
  height: 400,
271
- fit: 'cover',
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 preset="productCard"
397
+ <NuxtImg
398
+ preset="productCard"
390
399
  :src="coverSrcPath"
391
- width="400" height="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
@@ -474,36 +485,9 @@ 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.0.0
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.
482
-
483
- ### Minor Changes
484
-
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
488
+ ### Latest changes: 3.0.1
486
489
 
487
490
  ### Patch Changes
488
491
 
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.
494
-
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`
496
-
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.
498
-
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.
502
-
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.
504
-
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.
506
-
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
492
+ - Updated dependencies [[`8be060d`](https://github.com/shopware/frontends/commit/8be060de825ca799f98a8f045a5e7fea61f5d1a2), [`5678fb0`](https://github.com/shopware/frontends/commit/5678fb008cbd86eaddd061e004de89e6f45bb7ec), [`22611e5`](https://github.com/shopware/frontends/commit/22611e542b8f42a4f34dce5186f628f9a17f457b)]:
493
+ - @shopware/composables@1.12.0
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from "vue";
3
+
3
4
  import type { Schemas } from "#shopware";
4
5
 
5
6
  const {
@@ -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 class="self-stretch py-3 border-b border-outline-outline-variant inline-flex justify-start items-center gap-1">
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 :direction="props.isExpanded ? 'up' : 'down'" :size="20" />
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
- 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">
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 :direction="props.isExpanded ? 'up' : 'down'" :size="20" />
129
+ <SwChevronIcon
130
+ :direction="props.isExpanded ? 'up' : 'down'"
131
+ :size="20"
132
+ />
125
133
  </button>
126
134
  </div>
127
135
  </div>
@@ -7,6 +7,7 @@ import { useVuelidate } from "@vuelidate/core";
7
7
  import { email, minLength, required } from "@vuelidate/validators";
8
8
  import { defu } from "defu";
9
9
  import { computed, reactive, ref } from "vue";
10
+
10
11
  import {
11
12
  useCmsElementConfig,
12
13
  useNavigationContext,
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from "vue";
3
+
3
4
  import type { Schemas } from "#shopware";
4
5
 
5
6
  type FilterState = {
@@ -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 class="text-surface-on-surface-variant text-sm font-normal leading-tight line-through">
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 v-else class="text-surface-on-surface text-base font-bold leading-normal">
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 v-if="displayFromVariants" class="text-surface-on-surface text-base font-bold leading-normal">
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 v-if="regulationPrice" class="flex gap-2 text-surface-on-surface-variant text-sm">
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=" 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
+ 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
- ? 'border-red-600 focus:border-red-600'
265
- : 'border-outline-outline-variant focus:border-brand-primary',
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 v-model="quantity" :min="product.minPurchase" :max="product.maxPurchase"
76
- :steps="product.purchaseSteps" />
77
- <SwStockInfo :availableStock="availableStock" :minPurchase="minPurchase" :deliveryTime="deliveryTime"
78
- :restockTime="restockTime" />
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 variant="primary" size="medium" :disabled="!product?.available" block data-testid="add-to-cart-button"
81
- @click="addToCartProxy">
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 class="self-stretch text-surface-on-surface text-xs font-normal leading-none">
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,6 +12,7 @@ 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,
@@ -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 class="self-stretch p-2 flex flex-col justify-between items-start gap-4 flex-1">
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 v-if="productManufacturer"
36
- class="self-stretch text-surface-on-surface text-sm font-bold leading-tight">
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 :to="productLink"
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 v-else :product="product" data-testid="product-box-product-price" />
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 variant="primary" v-if="!fromPrice" size="medium" :disabled="!product?.available" block
63
- data-testid="add-to-cart-button" @click="addToCartProxy">
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>
@@ -6,6 +6,7 @@ import {
6
6
  isProductTopSeller,
7
7
  } from "@shopware/helpers";
8
8
  import { computed, inject } from "vue";
9
+
9
10
  import { useUser } from "#imports";
10
11
  import type { Schemas } from "#shopware";
11
12
 
@@ -57,32 +58,50 @@ function handleWishlistClick() {
57
58
  </script>
58
59
 
59
60
  <template>
60
- <div class="self-stretch min-h-[350px] relative flex flex-col justify-start items-start overflow-hidden aspect-square">
61
- <RouterLink :to="productLink" class="self-stretch h-full relative overflow-hidden">
62
- <NuxtImg preset="productCard"
61
+ <div
62
+ class="self-stretch min-h-[350px] relative flex flex-col justify-start items-start overflow-hidden aspect-square"
63
+ >
64
+ <RouterLink
65
+ :to="productLink"
66
+ class="self-stretch h-full relative overflow-hidden"
67
+ >
68
+ <NuxtImg
69
+ preset="productCard"
63
70
  class="w-full h-full absolute top-0 left-0 object-cover"
64
- :src="coverSrcPath" :alt="coverAlt"
65
- width="400" height="400"
71
+ :src="coverSrcPath"
72
+ :alt="coverAlt"
73
+ width="400"
74
+ height="400"
66
75
  densities="1x"
67
76
  loading="lazy"
68
- data-testid="product-box-img" />
77
+ data-testid="product-box-img"
78
+ />
69
79
  </RouterLink>
70
80
 
71
- <div v-if="isTopseller || isOnSale"
72
- class="px-1.5 py-1 left-2 bottom-2 absolute bg-other-sale rounded inline-flex justify-center items-center">
81
+ <div
82
+ v-if="isTopseller || isOnSale"
83
+ class="px-1.5 py-1 left-2 bottom-2 absolute bg-other-sale rounded inline-flex justify-center items-center"
84
+ >
73
85
  <div class="text-states-on-error text-xs font-bold leading-none">
74
86
  {{ translations.product.badges.topseller }}
75
87
  </div>
76
88
  </div>
77
89
 
78
90
  <client-only>
79
- <SwIconButton type="secondary"
80
- :aria-label="isInWishlist ? translations.product.removeFromWishlist : translations.product.addToWishlist"
91
+ <SwIconButton
92
+ type="secondary"
93
+ :aria-label="
94
+ isInWishlist
95
+ ? translations.product.removeFromWishlist
96
+ : translations.product.addToWishlist
97
+ "
81
98
  :disabled="isLoading"
82
99
  class="w-10 h-10 right-4 top-4 absolute bg-brand-secondary rounded-full flex items-center justify-center"
83
- data-testid="product-box-toggle-wishlist-button" @click="handleWishlistClick">
100
+ data-testid="product-box-toggle-wishlist-button"
101
+ @click="handleWishlistClick"
102
+ >
84
103
  <SwWishlistIcon :filled="isInWishlist" />
85
104
  </SwIconButton>
86
105
  </client-only>
87
106
  </div>
88
- </template>
107
+ </template>
@@ -1,33 +1,56 @@
1
1
  <template>
2
- <div class="inline-flex flex-col items-start justify-start self-stretch overflow-hidden p-px w-full"
3
- aria-hidden="true">
2
+ <div
3
+ class="inline-flex flex-col items-start justify-start self-stretch overflow-hidden p-px w-full"
4
+ aria-hidden="true"
5
+ >
4
6
  <!-- image skeleton -->
5
- <div class="relative flex h-80 flex-col items-start justify-start self-stretch overflow-hidden">
6
- <div class="relative h-80 w-full bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
7
+ <div
8
+ class="relative flex h-80 flex-col items-start justify-start self-stretch overflow-hidden"
9
+ >
10
+ <div
11
+ class="relative h-80 w-full bg-gray-200 dark:bg-gray-700 animate-pulse"
12
+ ></div>
7
13
 
8
14
  <div
9
- class="absolute top-[281px] left-2 inline-flex items-center justify-center rounded bg-gray-300 dark:bg-gray-600 px-3 py-1 animate-pulse">
10
- <span class="h-3 w-16 bg-gray-200 dark:bg-gray-700 rounded block"></span>
15
+ class="absolute top-[281px] left-2 inline-flex items-center justify-center rounded bg-gray-300 dark:bg-gray-600 px-3 py-1 animate-pulse"
16
+ >
17
+ <span
18
+ class="h-3 w-16 bg-gray-200 dark:bg-gray-700 rounded block"
19
+ ></span>
11
20
  </div>
12
21
 
13
- <div class="absolute top-4 right-4 h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse"></div>
22
+ <div
23
+ class="absolute top-4 right-4 h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse"
24
+ ></div>
14
25
  </div>
15
26
 
16
27
  <!-- details skeleton -->
17
28
  <div class="flex flex-col items-start justify-start gap-4 self-stretch p-2">
18
- <div class="h-4 w-32 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
29
+ <div
30
+ class="h-4 w-32 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
31
+ ></div>
19
32
 
20
33
  <div class="w-full">
21
- <div class="h-7 w-3/4 rounded bg-gray-200 dark:bg-gray-700 mb-2 animate-pulse"></div>
22
- <div class="h-7 w-1/2 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
34
+ <div
35
+ class="h-7 w-3/4 rounded bg-gray-200 dark:bg-gray-700 mb-2 animate-pulse"
36
+ ></div>
37
+ <div
38
+ class="h-7 w-1/2 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
39
+ ></div>
23
40
  </div>
24
41
 
25
- <div class="h-6 w-24 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
42
+ <div
43
+ class="h-6 w-24 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
44
+ ></div>
26
45
 
27
46
  <div class="flex w-full gap-3">
28
- <div class="flex-1 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
29
- <div class="w-24 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"></div>
47
+ <div
48
+ class="flex-1 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
49
+ ></div>
50
+ <div
51
+ class="w-24 h-10 rounded bg-gray-200 dark:bg-gray-700 animate-pulse"
52
+ ></div>
30
53
  </div>
31
54
  </div>
32
55
  </div>
33
- </template>
56
+ </template>