@shopware/cms-base-layer 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +168 -100
  2. package/app/app.config.ts +11 -0
  3. package/app/components/SwCategoryNavigation.vue +25 -18
  4. package/app/components/SwFilterDropdown.vue +54 -0
  5. package/app/components/SwListingProductPrice.vue +2 -2
  6. package/app/components/SwMedia3D.vue +4 -2
  7. package/app/components/SwProductCard.vue +20 -21
  8. package/app/components/SwProductCardDetails.vue +29 -12
  9. package/app/components/SwProductCardImage.vue +4 -1
  10. package/app/components/SwProductGallery.vue +18 -14
  11. package/app/components/SwProductListingFilter.vue +20 -9
  12. package/app/components/SwProductListingFilters.vue +1 -5
  13. package/app/components/SwProductListingFiltersHorizontal.vue +306 -0
  14. package/app/components/SwProductPrice.vue +3 -3
  15. package/app/components/SwProductRating.vue +40 -0
  16. package/app/components/SwProductReviews.vue +6 -19
  17. package/app/components/SwProductUnits.vue +10 -15
  18. package/app/components/SwQuantitySelect.vue +4 -7
  19. package/app/components/SwSlider.vue +150 -51
  20. package/app/components/SwSortDropdown.vue +10 -6
  21. package/app/components/SwVariantConfigurator.vue +12 -11
  22. package/app/components/listing-filters/SwFilterPrice.vue +45 -40
  23. package/app/components/listing-filters/SwFilterProperties.vue +40 -33
  24. package/app/components/listing-filters/SwFilterRating.vue +36 -27
  25. package/app/components/listing-filters/SwFilterShippingFree.vue +39 -32
  26. package/app/components/public/cms/CmsBlockSpatialViewer.vue +94 -0
  27. package/app/components/public/cms/CmsGenericBlock.md +17 -2
  28. package/app/components/public/cms/CmsGenericBlock.vue +15 -1
  29. package/app/components/public/cms/CmsPage.md +19 -2
  30. package/app/components/public/cms/CmsPage.vue +11 -1
  31. package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -1
  32. package/app/components/public/cms/block/CmsBlockImageText.vue +5 -5
  33. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +5 -12
  34. package/app/components/public/cms/element/CmsElementBuyBox.vue +3 -3
  35. package/app/components/public/cms/element/CmsElementCrossSelling.vue +19 -3
  36. package/app/components/public/cms/element/CmsElementImage.vue +34 -36
  37. package/app/components/public/cms/element/CmsElementImageGallery.vue +117 -50
  38. package/app/components/public/cms/element/CmsElementProductBox.vue +7 -1
  39. package/app/components/public/cms/element/CmsElementProductListing.vue +10 -3
  40. package/app/components/public/cms/element/CmsElementProductName.vue +6 -1
  41. package/app/components/public/cms/element/CmsElementProductSlider.vue +56 -35
  42. package/app/components/public/cms/element/CmsElementSidebarFilter.vue +10 -2
  43. package/app/components/public/cms/element/CmsElementText.vue +10 -11
  44. package/app/components/public/cms/element/SwProductListingPagination.vue +2 -2
  45. package/app/components/public/cms/section/CmsSectionDefault.vue +2 -2
  46. package/app/components/public/cms/section/CmsSectionSidebar.vue +6 -3
  47. package/app/components/ui/BaseButton.vue +18 -15
  48. package/app/components/ui/ChevronIcon.vue +10 -13
  49. package/app/components/ui/WishlistIcon.vue +3 -8
  50. package/app/composables/useImagePlaceholder.ts +1 -1
  51. package/app/composables/useLcpImagePreload.test.ts +229 -0
  52. package/app/composables/useLcpImagePreload.ts +39 -0
  53. package/app/helpers/cms/findFirstCmsImageUrl.ts +86 -0
  54. package/app/helpers/cms/getImageSizes.test.ts +50 -0
  55. package/app/helpers/cms/getImageSizes.ts +36 -0
  56. package/app/helpers/html-to-vue/ast.ts +53 -19
  57. package/app/helpers/html-to-vue/getOptionsFromNode.ts +1 -1
  58. package/app/helpers/html-to-vue/renderToHtml.ts +7 -11
  59. package/app/helpers/html-to-vue/renderer.ts +86 -26
  60. package/app/plugins/unocss-runtime.client.ts +23 -0
  61. package/index.d.ts +24 -0
  62. package/nuxt.config.ts +20 -0
  63. package/package.json +23 -21
  64. package/uno.config.ts +11 -0
package/README.md CHANGED
@@ -304,6 +304,151 @@ 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
+ CMS image elements (`CmsElementImage`) automatically serve appropriately-sized images using responsive `srcset` and `sizes` attributes. This prevents the browser from downloading images larger than their displayed dimensions — a common Lighthouse performance issue.
376
+
377
+ ### How it works
378
+
379
+ 1. **`CmsGenericBlock`** counts the number of slots in each block, `provide`s a responsive `sizes` value (e.g., a 2-slot block means images are ~50% viewport width on desktop), and `provide`s the slot count via `cms-block-slot-count` for slider SSR breakpoint scaling.
380
+ 2. **`CmsElementImage`** `inject`s the sizes hint and applies it to `<NuxtImg>`.
381
+ 3. If the media has **pre-generated thumbnails** from Shopware, the existing `srcset` from thumbnails is used.
382
+ 4. If **no thumbnails** exist, a synthetic `srcset` is generated using CDN width-based resizing (`?width=400`, `?width=800`, etc.) via the `generateCdnSrcSet` helper from `@shopware/helpers`.
383
+ 5. **Slider components** (`CmsElementProductSlider`, `CmsElementCrossSelling`) `inject` the slot count to scale their SSR breakpoints — ensuring media queries account for the container being a fraction of the viewport (e.g., a 2-slot block doubles the breakpoint thresholds).
384
+
385
+ The browser combines `sizes` + `srcset` to download only the image size it actually needs — during HTML parsing, before any JavaScript runs.
386
+
387
+ ### Configuration
388
+
389
+ Default slot-count-to-sizes mappings are set in `app.config.ts` and can be overridden:
390
+
391
+ ```ts
392
+ export default defineAppConfig({
393
+ imageSizes: {
394
+ // slot count → sizes attribute value
395
+ 1: "(max-width: 768px) 100vw, 100vw", // full-width blocks
396
+ 2: "(max-width: 768px) 100vw, 50vw", // two-column blocks (e.g., image-text)
397
+ 3: "(max-width: 768px) 100vw, 33vw", // three-column blocks
398
+ default: "(max-width: 768px) 50vw, 25vw", // 4+ columns
399
+ },
400
+ });
401
+ ```
402
+
403
+ For example, to cap image sizes at a fixed pixel width for boxed layouts:
404
+
405
+ ```ts
406
+ export default defineAppConfig({
407
+ imageSizes: {
408
+ 1: "(max-width: 768px) 100vw, 1200px",
409
+ 2: "(max-width: 768px) 100vw, 600px",
410
+ 3: "(max-width: 768px) 100vw, 400px",
411
+ default: "(max-width: 768px) 50vw, 300px",
412
+ },
413
+ });
414
+ ```
415
+
416
+ ### Per-block override
417
+
418
+ Individual block components can override the sizes value by calling `provide("cms-image-sizes", "custom value")` — this takes precedence over the default from `CmsGenericBlock`.
419
+
420
+ ### Synthetic srcset fallback
421
+
422
+ When Shopware media has no thumbnails (common in Cloud/SaaS setups using CDN-based resizing), the layer generates a synthetic `srcset` using CDN query parameters:
423
+
424
+ ```html
425
+ <img srcset="
426
+ ...image.jpg?width=400&fit=crop,smart&format=webp&quality=90 400w,
427
+ ...image.jpg?width=800&fit=crop,smart&format=webp&quality=90 800w,
428
+ ...image.jpg?width=1200&fit=crop,smart&format=webp&quality=90 1200w,
429
+ ...image.jpg?width=1600&fit=crop,smart&format=webp&quality=90 1600w"
430
+ sizes="(max-width: 768px) 100vw, 50vw"
431
+ >
432
+ ```
433
+
434
+ The `format` and `quality` values are taken from the `backgroundImage` config in `app.config.ts`.
435
+
436
+ > **Note:** Synthetic srcset requires CDN-based image resizing support. See the [Image Optimization](#%EF%B8%8F-image-optimization) section for requirements.
437
+
438
+ ## 🔄 UnoCSS Runtime
439
+
440
+ This layer includes 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 (e.g., inline styles or dynamic class bindings from the admin panel).
441
+
442
+ The runtime is **enabled by default**. To disable it, set `unocssRuntime` to `false` in your project's `app.config.ts`:
443
+
444
+ ```ts
445
+ export default defineAppConfig({
446
+ unocssRuntime: false,
447
+ });
448
+ ```
449
+
450
+ > **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.
451
+
307
452
  ## 📘 Available components
308
453
 
309
454
  The list of available blocks and elements is [here](https://frontends.shopware.com/packages/cms-base-layer.html#available-components).
@@ -349,119 +494,42 @@ No additional packages needed to be installed.
349
494
 
350
495
  Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
351
496
 
352
- ### Latest changes: 2.0.0
497
+ ### Latest changes: 2.1.0
353
498
 
354
- ### Major Changes
499
+ ### Minor Changes
355
500
 
356
- - [#1944](https://github.com/shopware/frontends/pull/1944) [`c41a839`](https://github.com/shopware/frontends/commit/c41a8397538e5b18475134635cc44295c34dde2d) Thanks [@mkucmus](https://github.com/mkucmus)! - Updates the `@shopware/cms-base-layer` package with the following changes:
357
- - Adds support for the new `SwQuantitySelect` component
358
- - Updates the `SwProductAddToCart` component to use the new `SwQuantitySelect` component
359
- - Fixes the `Status` component to use the new state classes
360
- - Updates the `uno.config.ts` file to include default styling that can be used and extended in the end-project:
501
+ - [#2275](https://github.com/shopware/frontends/pull/2275) [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c) Thanks [@mkucmus](https://github.com/mkucmus)! - - Add configurable UnoCSS runtime plugin for dynamic CMS class support
502
+ - Extend theme with overlay and fixed color tokens
361
503
 
362
- ## Nuxt UnoCSS Configuration Example
504
+ - [#2223](https://github.com/shopware/frontends/pull/2223) [`1db8704`](https://github.com/shopware/frontends/commit/1db870413dcea13c690504ffcaee13526bc8035f) Thanks [@mkucmus](https://github.com/mkucmus)! - Add horizontal filter layout for product listings. When the sidebar filter element is placed outside a sidebar section, filters now display as horizontal dropdowns.
363
505
 
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
- ```
506
+ Includes new `SwFilterDropdown` and `SwProductListingFiltersHorizontal` components, with a `displayMode` prop added to all filter components to support both layouts.
372
507
 
373
- ## UnoCSS Configuration Example
508
+ - [#2287](https://github.com/shopware/frontends/pull/2287) [`c9bde38`](https://github.com/shopware/frontends/commit/c9bde38d497d5c6c2fbd97700a362eb44ce8881f) Thanks [@mkucmus](https://github.com/mkucmus)! - Add responsive image sizing (srcset/sizes) via provide/inject, LCP preload with fetchpriority, configurable imageSizes in app.config, ISR route rules, inline styles, and AppConfig type declarations
374
509
 
375
- ```ts
376
- // uno.config.ts in your end-project
377
- import { mergeConfigs } from "@unocss/core";
378
- import baseConfig from "./.nuxt/uno.config.mjs";
510
+ - [#2241](https://github.com/shopware/frontends/pull/2241) [`9ccbaa1`](https://github.com/shopware/frontends/commit/9ccbaa1fb6cc1f790d979c3dd3745c5402b6d8d1) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Replace `withDefaults` by props destructure
379
511
 
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
- ```
512
+ - [#2273](https://github.com/shopware/frontends/pull/2273) [`18455e7`](https://github.com/shopware/frontends/commit/18455e77221fcc77b119d0ba7eae89dfce0e2941) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Remove SwMedia3D.vue component from autoload
402
513
 
403
- ### Minor Changes
514
+ - [#2268](https://github.com/shopware/frontends/pull/2268) [`c3fff84`](https://github.com/shopware/frontends/commit/c3fff847e46a17c9c905bd893f1c1de287426c65) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Improve accessibility (A11y) of CMS base layer components.
404
515
 
405
- - [#2030](https://github.com/shopware/frontends/pull/2030) [`22ff62e`](https://github.com/shopware/frontends/commit/22ff62e354f024599d64ea8096af57695248851c) Thanks [@mkucmus](https://github.com/mkucmus)! - Introduce new UI components, refine listing filters (structure and UX), add global collapse animations, and improve type safety.
406
-
407
- ### Features
408
- - New UI components:
409
- - SwFilterChips – shows active filters as removable chips
410
- - SwSortDropdown – sorting dropdown
411
- - SwProductListingPagination – listing pagination
412
- - Checkbox – reusable checkbox
413
- - ChevronIcon – configurable chevron (up/down/left/right)
414
- - RadioButton – reusable radio button
415
- - SwitchButton – toggle switch
416
-
417
- ### Refactors
418
- - SwFilterProperties:
419
- - Replace computed factory with `isChecked()` and `selectValue()` helpers for better performance and readability.
420
- - Filter collapse animation:
421
- - Unified expand/collapse animations for SwFilterProperties, SwFilterRating, SwFilterShippingFree, and SwFilterPrice using UnoCSS preflights.
422
-
423
- ### TypeScript fixes
424
- - SwProductListingFilters:
425
- - Provide fallbacks (`?? []`, `?? ''`) when passing `getSortingOrders` and `getCurrentSortingOrder`.
426
- - SwFilterChips:
427
- - Relax prop types to accept union types compatible with both full Shopware schemas and simplified helper types.
428
-
429
- ### Code quality improvements
430
- - SwFilterPrice:
431
- - Remove unnecessary optional chaining on `props.selectedFilters` to prevent masking undefined errors
432
- - Checkbox component:
433
- - Replace `outline-blue-500` with `outline-brand-primary` for brand consistency
434
- - Make `label` prop optional to support checkbox-only pattern
435
- - SwFilterShippingFree:
436
- - Add i18n support using `useCmsTranslations` instead of hardcoded "free delivery" text
437
- - SwFilterProperties:
438
- - Remove unnecessary empty label prop from Checkbox usage
439
-
440
- **Note:** Transition classes are globally available via UnoCSS preflights.
441
-
442
- - [#2139](https://github.com/shopware/frontends/pull/2139) [`20d1066`](https://github.com/shopware/frontends/commit/20d106638958170dd191ac3d95e3e536f3fcc787) Thanks [@mkucmus](https://github.com/mkucmus)! - Added a new `SwProductReviewsForm` component that allows logged-in customers to submit product reviews.
443
-
444
- - [#1959](https://github.com/shopware/frontends/pull/1959) [`c77daa6`](https://github.com/shopware/frontends/commit/c77daa6a11e96c7f3688b16f7da010b54c7f5e8b) Thanks [@patzick](https://github.com/patzick)! - Updated default types to Shopware 6.7
445
-
446
- - [#2176](https://github.com/shopware/frontends/pull/2176) [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602) Thanks [@mkucmus](https://github.com/mkucmus)! - - Extract product listing data early from CMS page responses to enable SSR rendering
447
- - Remove ClientOnly wrappers from `SwProductListingFilters` and `SwFilterChips` components
448
- - Resolve hydration mismatches on category pages with filters
516
+ - [#2214](https://github.com/shopware/frontends/pull/2214) [`ccb9384`](https://github.com/shopware/frontends/commit/ccb93849be07f1b6a4e192de02579a528b5b6ac4) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Add full TypeScript typing to html-to-vue helper functions
449
517
 
450
518
  ### Patch Changes
451
519
 
452
- - [#2174](https://github.com/shopware/frontends/pull/2174) [`e9f3d97`](https://github.com/shopware/frontends/commit/e9f3d972d7a126ec72f405a3595e53a61f6180f9) Thanks [@mkucmus](https://github.com/mkucmus)! - Added a new image placeholder.
453
-
454
- - [#2162](https://github.com/shopware/frontends/pull/2162) [`e1fae3e`](https://github.com/shopware/frontends/commit/e1fae3eb6430e5c8e133456fbaf7f215f80c36f6) Thanks [@mkucmus](https://github.com/mkucmus)! - Replace hardcoded colors with theme tokens, add image placeholder composable, improve URL encoding for special characters in image paths, enhance CMS block layouts, and use useTemplateRef for better type safety
455
-
456
- - [#2128](https://github.com/shopware/frontends/pull/2128) [`efe125e`](https://github.com/shopware/frontends/commit/efe125e7bea273bb904356114cf93adf68a416fb) Thanks [@mkucmus](https://github.com/mkucmus)! - Enhanced SwProductReviews component with reviewer names, shop feedback, and star ratings using direct SVG imports.
520
+ - [#2226](https://github.com/shopware/frontends/pull/2226) [`d77eacc`](https://github.com/shopware/frontends/commit/d77eaccdec6c56a6f2d999048c751fb9f01177d4) Thanks [@mkucmus](https://github.com/mkucmus)! - Add config prop support to `SwProductGallery` and make `CmsElementImageGallery` respect `minHeight`, `navigationArrows`, and `navigationDots` config values.
457
521
 
458
- - [#2008](https://github.com/shopware/frontends/pull/2008) [`3a1bca9`](https://github.com/shopware/frontends/commit/3a1bca983c4fc866c67b90897bd86d7488f8cac8) Thanks [@mkucmus](https://github.com/mkucmus)! - Added missing labels for `SwQuantitySelect` component.
522
+ - [#2275](https://github.com/shopware/frontends/pull/2275) [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c) Thanks [@mkucmus](https://github.com/mkucmus)! - - Fix CMS block layout: height propagation in CmsBlockImageText, conditional `h-full` in CmsElementImage, `backgroundSize` forwarding in CmsGenericBlock, `w-full` for full_width sizing mode, and exclude `sizingMode` from section inline styles
523
+ - Fix vertical alignment support in CmsElementText and CmsElementProductSlider using `align-content` CSS property
524
+ - Remove rounded corners from image placeholder SVG and simplify CmsBlockTextOnImage structure
459
525
 
460
- - [#1951](https://github.com/shopware/frontends/pull/1951) [`3f2379b`](https://github.com/shopware/frontends/commit/3f2379bdc428b481943cbcf3711a37cb91e2d298) Thanks [@mkucmus](https://github.com/mkucmus)! - Use proper paths for components configuration
526
+ - [#2210](https://github.com/shopware/frontends/pull/2210) [`c6b88b7`](https://github.com/shopware/frontends/commit/c6b88b7d2c50054188356aeb0f83053554d442f5) Thanks [@mkucmus](https://github.com/mkucmus)! - Anchor tags with "btn btn-primary" classes from the API were not being
527
+ transformed to Tailwind utility classes due to condition matching issues
528
+ in the html-to-vue renderer.
461
529
 
462
- - [#2154](https://github.com/shopware/frontends/pull/2154) [`168989e`](https://github.com/shopware/frontends/commit/168989e51e5c81c4cbb746c132d6561c019e046a) Thanks [@mkucmus](https://github.com/mkucmus)! - Implicitly set public components as global to expose them for templates that extend from the base one.
530
+ - [#2318](https://github.com/shopware/frontends/pull/2318) [`b40305f`](https://github.com/shopware/frontends/commit/b40305f9e2ec51f29c279650e411bb773438faed) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Components (CmsElementBuyBox, SwListingProductPrice, SwProductPrice) updated to use `hasListPrice` instead of deprecated `isListPrice`.
463
531
 
464
- - Updated dependencies [[`87771c3`](https://github.com/shopware/frontends/commit/87771c3b7a4521fcdba43cb4c967b61f5db01b3e), [`22ff62e`](https://github.com/shopware/frontends/commit/22ff62e354f024599d64ea8096af57695248851c), [`a44d871`](https://github.com/shopware/frontends/commit/a44d8712d9ae5ee196c03ac8b894f3d1392d0e68), [`e43d9b7`](https://github.com/shopware/frontends/commit/e43d9b7f559af21be8b66f2021cea2d14940e4aa), [`2cbda25`](https://github.com/shopware/frontends/commit/2cbda257a1056454e12f2fba9052f83eecb6d986), [`2cbda25`](https://github.com/shopware/frontends/commit/2cbda257a1056454e12f2fba9052f83eecb6d986), [`7fe2ef9`](https://github.com/shopware/frontends/commit/7fe2ef96a9d9d156683b85d31f0a660458c9fbfd), [`70dcf95`](https://github.com/shopware/frontends/commit/70dcf95d4370c63964d877a5cab113a53f93ca19), [`56cd178`](https://github.com/shopware/frontends/commit/56cd178e25fe2399b7170ccac3044e980621f041), [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602), [`e1fae3e`](https://github.com/shopware/frontends/commit/e1fae3eb6430e5c8e133456fbaf7f215f80c36f6), [`c647baf`](https://github.com/shopware/frontends/commit/c647baf93e7174b849f5961ee5803add99d78602), [`c77daa6`](https://github.com/shopware/frontends/commit/c77daa6a11e96c7f3688b16f7da010b54c7f5e8b)]:
465
- - @shopware/composables@1.10.0
466
- - @shopware/helpers@1.6.0
467
- - @shopware/api-client@1.4.0
532
+ - Updated dependencies [[`9604f22`](https://github.com/shopware/frontends/commit/9604f22678150d04c3c3156fd8ee2ce440c8c8bf), [`b40305f`](https://github.com/shopware/frontends/commit/b40305f9e2ec51f29c279650e411bb773438faed), [`432dd24`](https://github.com/shopware/frontends/commit/432dd246571dfa8c149293da97d5bb16f505e54c), [`b5f7e2a`](https://github.com/shopware/frontends/commit/b5f7e2a20c9dfdde1690e9006252d847f732bc0a), [`b5f7e2a`](https://github.com/shopware/frontends/commit/b5f7e2a20c9dfdde1690e9006252d847f732bc0a), [`9604f22`](https://github.com/shopware/frontends/commit/9604f22678150d04c3c3156fd8ee2ce440c8c8bf), [`a871c7b`](https://github.com/shopware/frontends/commit/a871c7b6256b75c2e40d93fc0354ba1971420062), [`c9bde38`](https://github.com/shopware/frontends/commit/c9bde38d497d5c6c2fbd97700a362eb44ce8881f)]:
533
+ - @shopware/api-client@1.5.0
534
+ - @shopware/composables@1.11.0
535
+ - @shopware/helpers@1.7.0
package/app/app.config.ts CHANGED
@@ -4,4 +4,15 @@ export default defineAppConfig({
4
4
  imagePlaceholder: {
5
5
  color: "#543B95",
6
6
  },
7
+ backgroundImage: {
8
+ format: "webp",
9
+ quality: 90,
10
+ },
11
+ imageSizes: {
12
+ 1: "(max-width: 768px) 100vw, 100vw",
13
+ 2: "(max-width: 768px) 100vw, 50vw",
14
+ 3: "(max-width: 768px) 100vw, 33vw",
15
+ default: "(max-width: 768px) 50vw, 25vw",
16
+ },
17
+ unocssRuntime: true,
7
18
  });
@@ -2,21 +2,20 @@
2
2
  import { ref } from "vue";
3
3
  import type { Schemas } from "#shopware";
4
4
 
5
- const props = withDefaults(
6
- defineProps<{
7
- activeCategory: Schemas["Category"];
8
- elements: Schemas["Category"][];
9
- level: number;
10
- }>(),
11
- {
12
- level: 0,
13
- },
14
- );
5
+ const {
6
+ activeCategory,
7
+ elements,
8
+ level = 0,
9
+ } = defineProps<{
10
+ activeCategory: Schemas["Category"];
11
+ elements: Schemas["Category"][];
12
+ level: number;
13
+ }>();
15
14
 
16
15
  const expandedItems = ref<Set<string>>(new Set());
17
16
 
18
17
  function isActive(navigationElement: Schemas["Category"]) {
19
- return navigationElement.id === props.activeCategory?.id;
18
+ return navigationElement.id === activeCategory?.id;
20
19
  }
21
20
 
22
21
  function toggleExpanded(id: string) {
@@ -32,9 +31,12 @@ function isExpanded(id: string) {
32
31
  }
33
32
  </script>
34
33
  <template>
35
- <div v-if="props.elements?.length" class="self-stretch flex flex-col justify-start items-start gap-4">
34
+ <div
35
+ v-if="elements?.length"
36
+ class="self-stretch flex flex-col justify-start items-start gap-4"
37
+ >
36
38
  <div
37
- v-for="(navigationElement, index) in props.elements"
39
+ v-for="(navigationElement, index) in elements"
38
40
  :key="index"
39
41
  class="w-full"
40
42
  >
@@ -42,15 +44,18 @@ function isExpanded(id: string) {
42
44
  :navigation-element="navigationElement"
43
45
  :is-active="isActive(navigationElement)"
44
46
  :is-expanded="isExpanded(navigationElement.id)"
45
- :level="props.level"
47
+ :level="level"
46
48
  @toggle="toggleExpanded(navigationElement.id)"
47
49
  />
48
50
  <transition name="filter-collapse">
49
- <div v-if="navigationElement.children && isExpanded(navigationElement.id)" class="self-stretch flex flex-col justify-start items-start">
51
+ <div
52
+ v-if="navigationElement.children && isExpanded(navigationElement.id)"
53
+ class="self-stretch flex flex-col justify-start items-start"
54
+ >
50
55
  <SwCategoryNavigation
51
56
  :elements="navigationElement.children"
52
- :active-category="props.activeCategory"
53
- :level="props.level + 1"
57
+ :active-category="activeCategory"
58
+ :level="level + 1"
54
59
  />
55
60
  </div>
56
61
  </transition>
@@ -60,7 +65,9 @@ function isExpanded(id: string) {
60
65
  <style scoped>
61
66
  .filter-collapse-enter-active,
62
67
  .filter-collapse-leave-active {
63
- transition: max-height 240ms ease, opacity 200ms ease;
68
+ transition:
69
+ max-height 240ms ease,
70
+ opacity 200ms ease;
64
71
  overflow: hidden;
65
72
  }
66
73
  .filter-collapse-enter-from,
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { onClickOutside } from "@vueuse/core";
3
+ import { ref, useTemplateRef } from "vue";
4
+
5
+ defineProps<{
6
+ label: string;
7
+ isActive?: boolean;
8
+ }>();
9
+
10
+ const isOpen = ref(false);
11
+ const dropdownElement = useTemplateRef<HTMLDivElement>("dropdownElement");
12
+
13
+ onClickOutside(dropdownElement, () => {
14
+ isOpen.value = false;
15
+ });
16
+
17
+ function toggle() {
18
+ isOpen.value = !isOpen.value;
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <div ref="dropdownElement" class="relative">
24
+ <!-- Pill button -->
25
+ <button
26
+ type="button"
27
+ class="bg-brand-tertiary rounded-full px-4 py-1.5 inline-flex items-center hover:bg-brand-tertiary-hover transition-colors"
28
+ :class="{ 'ring-2 ring-brand-primary': isActive }"
29
+ @click="toggle"
30
+ :aria-expanded="isOpen"
31
+ aria-haspopup="true"
32
+ >
33
+ <div class="py-1 inline-flex items-center gap-1">
34
+ <span class="text-brand-on-tertiary text-base font-normal leading-6">
35
+ {{ label }}
36
+ </span>
37
+ <SwChevronIcon
38
+ :direction="isOpen ? 'up' : 'down'"
39
+ :size="24"
40
+ class="text-brand-on-tertiary"
41
+ />
42
+ </div>
43
+ </button>
44
+
45
+ <!-- Dropdown panel -->
46
+ <div
47
+ v-if="isOpen"
48
+ class="absolute top-full left-0 mt-2 min-w-64 bg-surface-surface rounded-lg shadow-lg ring-1 ring-outline-outline-variant z-50 p-4"
49
+ role="menu"
50
+ >
51
+ <slot />
52
+ </div>
53
+ </div>
54
+ </template>
@@ -35,7 +35,7 @@ const {
35
35
  unitPrice,
36
36
  displayFromVariants,
37
37
  displayFrom,
38
- isListPrice,
38
+ hasListPrice,
39
39
  regulationPrice,
40
40
  } = useProductPrice(product);
41
41
  </script>
@@ -43,7 +43,7 @@ const {
43
43
  <template>
44
44
  <div :id="product.id" class="inline-flex justify-start items-center gap-2">
45
45
  <!-- Sale price display -->
46
- <div v-if="isListPrice" class="flex items-center gap-2">
46
+ <div v-if="hasListPrice" class="flex items-center gap-2">
47
47
  <div class="text-base font-bold leading-normal">
48
48
  <SwSharedPrice :value="unitPrice">
49
49
  <template #beforePrice>
@@ -2,6 +2,7 @@
2
2
  import { OrbitControls, useGLTF } from "@tresjs/cientos";
3
3
  import { TresCanvas } from "@tresjs/core";
4
4
  import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from "three";
5
+ import { computed } from "vue";
5
6
 
6
7
  const props = defineProps<{
7
8
  src: string;
@@ -17,7 +18,8 @@ const gl = {
17
18
  windowSize: false,
18
19
  };
19
20
 
20
- const { scene: model } = await useGLTF(props.src);
21
+ const { state } = await useGLTF(props.src);
22
+ const model = computed(() => state.value?.scene);
21
23
  </script>
22
24
  <template>
23
25
  <TresCanvas v-bind="gl">
@@ -27,7 +29,7 @@ const { scene: model } = await useGLTF(props.src);
27
29
  :look-at="[0, 0, 0]"
28
30
  />
29
31
  <OrbitControls />
30
- <primitive :object="model" />
32
+ <primitive v-if="model" :object="model" />
31
33
  <TresDirectionalLight :position="[3, 3, 3]" :intensity="1" />
32
34
  <TresAmbientLight :intensity="2" />
33
35
  </TresCanvas>
@@ -11,7 +11,7 @@ import {
11
11
  } from "@shopware/helpers";
12
12
  import { getCmsTranslate } from "@shopware/helpers";
13
13
  import { defu } from "defu";
14
- import { computed, ref, toRefs } from "vue";
14
+ import { computed, ref, toRef } from "vue";
15
15
  import {
16
16
  useAddToCart,
17
17
  useCartErrorParamsResolver,
@@ -26,19 +26,17 @@ const { pushSuccess, pushError } = useNotifications();
26
26
  const { getErrorsCodes } = useCartNotification();
27
27
  const { resolveCartError } = useCartErrorParamsResolver();
28
28
 
29
- const props = withDefaults(
30
- defineProps<{
31
- product: Schemas["Product"];
32
- layoutType?: BoxLayout;
33
- isProductListing?: boolean;
34
- displayMode?: DisplayMode;
35
- }>(),
36
- {
37
- layoutType: "standard",
38
- displayMode: "standard",
39
- isProductListing: false,
40
- },
41
- );
29
+ const {
30
+ product: productProp,
31
+ layoutType = "standard",
32
+ displayMode = "standard",
33
+ isProductListing = false,
34
+ } = defineProps<{
35
+ product: Schemas["Product"];
36
+ layoutType?: BoxLayout;
37
+ displayMode?: DisplayMode;
38
+ isProductListing?: boolean;
39
+ }>();
42
40
 
43
41
  type Translations = {
44
42
  product: {
@@ -79,7 +77,7 @@ let translations: Translations = {
79
77
 
80
78
  translations = defu(useCmsTranslations(), translations) as Translations;
81
79
 
82
- const { product } = toRefs(props);
80
+ const product = toRef(() => productProp);
83
81
 
84
82
  const { addToCart } = useAddToCart(product);
85
83
 
@@ -95,12 +93,12 @@ const toggleWishlistProduct = async () => {
95
93
  if (!isInWishlist.value) {
96
94
  await addToWishlist();
97
95
  pushSuccess(
98
- `${props.product?.translated.name} ${translations.product.addedToWishlist}`,
96
+ `${product?.value.translated.name} ${translations.product.addedToWishlist}`,
99
97
  );
100
98
  } else {
101
99
  await removeFromWishlist();
102
100
  pushSuccess(
103
- `${props.product?.translated.name} ${translations.product.removedFromTheWishlist}`,
101
+ `${product?.value.translated.name} ${translations.product.removedFromTheWishlist}`,
104
102
  );
105
103
  }
106
104
  } catch (error) {
@@ -109,7 +107,7 @@ const toggleWishlistProduct = async () => {
109
107
  ? `${translations.product.reason}: ${error.details.errors?.[0]?.detail}`
110
108
  : "";
111
109
  return pushError(
112
- `${props.product?.translated.name} ${translations.product.cannotAddToWishlist}\n${reason}`,
110
+ `${product?.value.translated.name} ${translations.product.cannotAddToWishlist}\n${reason}`,
113
111
  {
114
112
  timeout: 5000,
115
113
  },
@@ -131,11 +129,11 @@ const addToCartProxy = async () => {
131
129
 
132
130
  if (!errors.length)
133
131
  pushSuccess(
134
- `${props.product?.translated.name} ${translations.product.addedToCart}`,
132
+ `${product?.value.translated.name} ${translations.product.addedToCart}`,
135
133
  );
136
134
  };
137
135
 
138
- const fromPrice = getProductFromPrice(props.product);
136
+ const fromPrice = getProductFromPrice(product.value);
139
137
  const productName = computed(() => getProductName({ product: product.value }));
140
138
  const productManufacturer = computed(() =>
141
139
  getProductManufacturerName(product.value),
@@ -165,6 +163,7 @@ const productLink = computed(() =>
165
163
  :fromPrice="fromPrice"
166
164
  :addToCartProxy="addToCartProxy"
167
165
  :productLink="productLink"
166
+ :layoutType="layoutType"
168
167
  />
169
168
  </div>
170
- </template>
169
+ </template>