@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.
- package/README.md +47 -63
- 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 +1 -0
- 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 +21 -6
- package/app/components/SwProductListingFiltersHorizontal.vue +4 -1
- 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 +2 -0
- package/app/components/public/cms/CmsGenericElement.vue +1 -0
- package/app/components/public/cms/CmsNoComponent.vue +1 -0
- 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 +1 -0
- 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 +4 -1
- package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +4 -1
- 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 +4 -1
- package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductHeading.vue +1 -0
- 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 -0
- 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 +77 -29
- 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/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
|
|
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
|
|
@@ -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.
|
|
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
|
-
- [
|
|
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
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -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
|
|
61
|
-
|
|
62
|
-
|
|
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"
|
|
65
|
-
|
|
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
|
|
72
|
-
|
|
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
|
|
80
|
-
|
|
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"
|
|
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
|
|
3
|
-
|
|
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
|
|
6
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
22
|
-
|
|
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
|
|
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
|
|
29
|
-
|
|
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>
|