@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.
- package/README.md +167 -125
- package/app/app.config.ts +12 -0
- package/app/components/SwCategoryNavigation.vue +25 -18
- package/app/components/SwFilterDropdown.vue +54 -0
- package/app/components/SwListingProductPrice.vue +2 -2
- package/app/components/SwMedia3D.vue +14 -5
- package/app/components/SwProductCard.vue +24 -21
- package/app/components/SwProductCardDetails.vue +29 -12
- package/app/components/SwProductCardImage.vue +30 -29
- package/app/components/SwProductGallery.vue +18 -14
- package/app/components/SwProductListingFilter.vue +20 -9
- package/app/components/SwProductListingFilters.vue +3 -7
- package/app/components/SwProductListingFiltersHorizontal.vue +306 -0
- package/app/components/SwProductPrice.vue +3 -3
- package/app/components/SwProductRating.vue +40 -0
- package/app/components/SwProductReviews.vue +6 -19
- package/app/components/SwProductUnits.vue +10 -15
- package/app/components/SwQuantitySelect.vue +4 -7
- package/app/components/SwSlider.vue +150 -51
- package/app/components/SwSortDropdown.vue +10 -6
- package/app/components/SwVariantConfigurator.vue +13 -13
- package/app/components/listing-filters/SwFilterPrice.vue +45 -40
- package/app/components/listing-filters/SwFilterProperties.vue +40 -33
- package/app/components/listing-filters/SwFilterRating.vue +36 -27
- package/app/components/listing-filters/SwFilterShippingFree.vue +39 -32
- package/app/components/public/cms/CmsBlockSpatialViewer.vue +94 -0
- package/app/components/public/cms/CmsGenericBlock.md +17 -2
- package/app/components/public/cms/CmsGenericBlock.vue +21 -2
- package/app/components/public/cms/CmsGenericElement.vue +7 -2
- package/app/components/public/cms/CmsNoComponent.vue +87 -8
- package/app/components/public/cms/CmsPage.md +19 -2
- package/app/components/public/cms/CmsPage.vue +7 -0
- package/app/components/public/cms/FrontendAccountCustomerGroupRegistrationPage.vue +52 -0
- package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -1
- package/app/components/public/cms/block/CmsBlockImageText.vue +5 -5
- package/app/components/public/cms/block/CmsBlockTextOnImage.vue +5 -12
- package/app/components/public/cms/element/CmsElementBuyBox.vue +3 -3
- package/app/components/public/cms/element/CmsElementCrossSelling.vue +19 -3
- package/app/components/public/cms/element/CmsElementImage.vue +12 -35
- package/app/components/public/cms/element/CmsElementImageGallery.vue +117 -50
- package/app/components/public/cms/element/CmsElementProductBox.vue +7 -1
- package/app/components/public/cms/element/CmsElementProductListing.vue +15 -4
- package/app/components/public/cms/element/CmsElementProductName.vue +6 -1
- package/app/components/public/cms/element/CmsElementProductSlider.vue +56 -35
- package/app/components/public/cms/element/CmsElementSidebarFilter.vue +10 -2
- package/app/components/public/cms/element/CmsElementText.vue +10 -11
- package/app/components/public/cms/element/SwProductListingPagination.vue +2 -2
- package/app/components/public/cms/section/CmsSectionDefault.vue +2 -2
- package/app/components/public/cms/section/CmsSectionSidebar.vue +6 -3
- package/app/components/ui/BaseButton.vue +18 -15
- package/app/components/ui/ChevronIcon.vue +10 -13
- package/app/components/ui/WishlistIcon.vue +3 -8
- package/app/composables/useImagePlaceholder.ts +3 -3
- package/app/composables/useLcpImagePreload.test.ts +229 -0
- package/app/composables/useLcpImagePreload.ts +43 -0
- package/app/composables/useTypedAppConfig.ts +15 -0
- package/app/helpers/cms/findFirstCmsImageUrl.ts +86 -0
- package/app/helpers/cms/getImageSizes.test.ts +50 -0
- package/app/helpers/cms/getImageSizes.ts +36 -0
- package/app/helpers/html-to-vue/ast.ts +53 -19
- package/app/helpers/html-to-vue/getOptionsFromNode.ts +1 -1
- package/app/helpers/html-to-vue/renderToHtml.ts +7 -11
- package/app/helpers/html-to-vue/renderer.ts +86 -26
- package/index.d.ts +37 -5
- package/nuxt.config.ts +25 -0
- package/package.json +21 -21
- package/uno.config.ts +0 -83
package/README.md
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
[](https://github.com/shopware/frontends/issues?q=is%3Aopen+is%3Aissue+label%3Acms-base)
|
|
6
6
|
[](#)
|
|
7
7
|
|
|
8
|
-
Nuxt [layer](https://nuxt.com/docs/getting-started/layers) that provides an implementation of all CMS components in Shopware [based on utility-classes](https://frontends.shopware.com/framework/styling.html)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
>
|
|
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
|
-
|
|
95
|
+
## Styling and Design Tokens
|
|
89
96
|
|
|
90
|
-
|
|
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
|
-
|
|
99
|
+
This means you have two options:
|
|
93
100
|
|
|
94
|
-
|
|
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
|
|
118
|
+
import baseConfig from './.nuxt/uno.config.mjs'
|
|
119
119
|
|
|
120
|
-
export default mergeConfigs([
|
|
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:
|
|
477
|
+
### Latest changes: 3.0.0
|
|
353
478
|
|
|
354
479
|
### Major Changes
|
|
355
480
|
|
|
356
|
-
- [#
|
|
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
|
-
- [#
|
|
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
|
-
- [#
|
|
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
|
-
- [#
|
|
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
|
-
- [#
|
|
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
|
-
|
|
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
|
-
- [#
|
|
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
|
-
- [#
|
|
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 [[`
|
|
465
|
-
- @shopware/
|
|
466
|
-
- @shopware/
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 ===
|
|
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
|
|
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
|
|
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="
|
|
47
|
+
:level="level"
|
|
46
48
|
@toggle="toggleExpanded(navigationElement.id)"
|
|
47
49
|
/>
|
|
48
50
|
<transition name="filter-collapse">
|
|
49
|
-
<div
|
|
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="
|
|
53
|
-
:level="
|
|
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:
|
|
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
|
-
|
|
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="
|
|
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 {
|
|
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
|
-
<
|
|
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>
|