@shopware/cms-base-layer 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +328 -13
  2. package/app/app.config.ts +7 -0
  3. package/app/assets/icons/check-circle.svg +3 -0
  4. package/app/assets/icons/checkmark.svg +3 -0
  5. package/app/assets/icons/chevron.svg +3 -0
  6. package/app/assets/icons/exclamation-circle.svg +3 -0
  7. package/app/assets/icons/star-empty.svg +3 -0
  8. package/app/assets/icons/star-filled.svg +3 -0
  9. package/app/assets/icons/user.svg +1 -0
  10. package/app/components/SwCategoryNavigation.vue +76 -0
  11. package/app/components/SwCategoryNavigationLink.vue +128 -0
  12. package/{components → app/components}/SwContactForm.vue +27 -27
  13. package/app/components/SwFilterChips.vue +144 -0
  14. package/app/components/SwListingProductPrice.vue +89 -0
  15. package/{components → app/components}/SwNewsletterForm.vue +45 -34
  16. package/{components → app/components}/SwPagination.vue +3 -5
  17. package/{components → app/components}/SwProductAddToCart.vue +22 -27
  18. package/app/components/SwProductCard.vue +170 -0
  19. package/app/components/SwProductCardDetails.vue +57 -0
  20. package/app/components/SwProductCardImage.vue +87 -0
  21. package/app/components/SwProductCardSkeleton.vue +33 -0
  22. package/app/components/SwProductListingFilter.vue +64 -0
  23. package/app/components/SwProductListingFilters.vue +308 -0
  24. package/{components → app/components}/SwProductReviews.vue +28 -13
  25. package/app/components/SwProductReviewsForm.vue +292 -0
  26. package/app/components/SwQuantitySelect.vue +106 -0
  27. package/{components → app/components}/SwSlider.vue +4 -4
  28. package/app/components/SwSortDropdown.vue +83 -0
  29. package/app/components/SwStockInfo.vue +44 -0
  30. package/{components → app/components}/SwVariantConfigurator.vue +1 -1
  31. package/app/components/listing-filters/SwFilterPrice.vue +214 -0
  32. package/app/components/listing-filters/SwFilterProperties.vue +113 -0
  33. package/app/components/listing-filters/SwFilterRating.vue +90 -0
  34. package/app/components/listing-filters/SwFilterShippingFree.vue +107 -0
  35. package/{components → app/components}/public/cms/CmsPage.vue +19 -4
  36. package/{components → app/components}/public/cms/block/CmsBlockGalleryBuybox.vue +5 -5
  37. package/{components → app/components}/public/cms/block/CmsBlockImageBubbleRow.vue +5 -5
  38. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +41 -0
  39. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +42 -0
  40. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +37 -0
  41. package/{components → app/components}/public/cms/block/CmsBlockImageSimpleGrid.vue +11 -5
  42. package/{components → app/components}/public/cms/block/CmsBlockImageText.vue +7 -3
  43. package/{components → app/components}/public/cms/block/CmsBlockImageTextBubble.vue +13 -16
  44. package/{components → app/components}/public/cms/block/CmsBlockImageTextCover.vue +7 -9
  45. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +88 -0
  46. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +53 -0
  47. package/{components → app/components}/public/cms/block/CmsBlockImageThreeColumn.vue +10 -4
  48. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +37 -0
  49. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +37 -0
  50. package/{components → app/components}/public/cms/block/CmsBlockProductHeading.vue +1 -1
  51. package/{components → app/components}/public/cms/block/CmsBlockProductThreeColumn.vue +10 -4
  52. package/{components → app/components}/public/cms/block/CmsBlockSidebarFilter.vue +3 -1
  53. package/app/components/public/cms/block/CmsBlockTextOnImage.vue +30 -0
  54. package/{components → app/components}/public/cms/block/CmsBlockTextTeaserSection.vue +4 -4
  55. package/{components → app/components}/public/cms/block/CmsBlockTextTwoColumn.vue +3 -5
  56. package/app/components/public/cms/element/CmsElementBuyBox.vue +145 -0
  57. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +53 -0
  58. package/{components → app/components}/public/cms/element/CmsElementCrossSelling.vue +3 -3
  59. package/{components → app/components}/public/cms/element/CmsElementImage.vue +52 -13
  60. package/app/components/public/cms/element/CmsElementImageGallery.vue +158 -0
  61. package/{components → app/components}/public/cms/element/CmsElementImageSlider.vue +2 -2
  62. package/{components → app/components}/public/cms/element/CmsElementProductBox.vue +2 -1
  63. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +217 -0
  64. package/{components → app/components}/public/cms/element/CmsElementProductListing.vue +23 -94
  65. package/app/components/public/cms/element/CmsElementProductName.vue +11 -0
  66. package/{components → app/components}/public/cms/element/CmsElementProductSlider.vue +4 -4
  67. package/{components → app/components}/public/cms/element/CmsElementText.vue +8 -2
  68. package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.vue +8 -2
  69. package/app/components/public/cms/element/SwProductListingPagination.vue +70 -0
  70. package/{components → app/components}/public/cms/section/CmsSectionDefault.vue +1 -1
  71. package/app/components/public/cms/section/CmsSectionSidebar.vue +36 -0
  72. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +28 -0
  73. package/app/components/ui/BaseButton.vue +99 -0
  74. package/app/components/ui/BaseIcon.vue +15 -0
  75. package/app/components/ui/Checkbox.vue +49 -0
  76. package/app/components/ui/CheckmarkIcon.vue +23 -0
  77. package/app/components/ui/ChevronIcon.vue +37 -0
  78. package/app/components/ui/ExclamationIcon.vue +11 -0
  79. package/app/components/ui/IconButton.vue +32 -0
  80. package/app/components/ui/RadioButton.vue +26 -0
  81. package/app/components/ui/StarIcon.vue +18 -0
  82. package/app/components/ui/SwitchButton.vue +100 -0
  83. package/app/components/ui/UserIcon.vue +11 -0
  84. package/app/components/ui/WishlistIcon.vue +20 -0
  85. package/app/composables/useImagePlaceholder.ts +27 -0
  86. package/{helpers → app/helpers}/clientOnly.ts +5 -0
  87. package/app/providers/shopware.test.ts +213 -0
  88. package/app/providers/shopware.ts +107 -0
  89. package/dist/index.d.mts +3 -3
  90. package/dist/index.d.ts +3 -3
  91. package/dist/index.mjs +2 -2
  92. package/index.d.ts +12 -0
  93. package/nuxt.config.ts +80 -6
  94. package/package.json +29 -21
  95. package/uno.config.ts +83 -0
  96. package/components/SwCategoryNavigation.vue +0 -44
  97. package/components/SwCategoryNavigationLink.vue +0 -57
  98. package/components/SwListingProductPrice.vue +0 -89
  99. package/components/SwProductCard.vue +0 -286
  100. package/components/SwProductListingFilter.vue +0 -42
  101. package/components/SwProductListingFilters.vue +0 -292
  102. package/components/listing-filters/SwFilterPrice.vue +0 -160
  103. package/components/listing-filters/SwFilterProperties.vue +0 -123
  104. package/components/listing-filters/SwFilterRating.vue +0 -101
  105. package/components/listing-filters/SwFilterShippingFree.vue +0 -104
  106. package/components/public/cms/block/CmsBlockImageFourColumn.vue +0 -29
  107. package/components/public/cms/block/CmsBlockImageHighlightRow.vue +0 -27
  108. package/components/public/cms/block/CmsBlockImageTextGallery.vue +0 -85
  109. package/components/public/cms/block/CmsBlockImageTextRow.vue +0 -43
  110. package/components/public/cms/block/CmsBlockImageThreeCover.vue +0 -27
  111. package/components/public/cms/block/CmsBlockImageTwoColumn.vue +0 -25
  112. package/components/public/cms/block/CmsBlockTextOnImage.vue +0 -20
  113. package/components/public/cms/element/CmsBlockHtml.md +0 -1
  114. package/components/public/cms/element/CmsElementBuyBox.vue +0 -190
  115. package/components/public/cms/element/CmsElementCategoryNavigation.vue +0 -167
  116. package/components/public/cms/element/CmsElementImageGallery.vue +0 -249
  117. package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +0 -123
  118. package/components/public/cms/element/CmsElementProductName.vue +0 -10
  119. package/components/public/cms/section/CmsSectionSidebar.vue +0 -49
  120. package/components/public/cms/skeleton/ProductCardSkeleton.vue +0 -44
  121. /package/{components → app/components}/SwMedia3D.vue +0 -0
  122. /package/{components → app/components}/SwProductGallery.vue +0 -0
  123. /package/{components → app/components}/SwProductPrice.vue +0 -0
  124. /package/{components → app/components}/SwProductUnits.vue +0 -0
  125. /package/{components → app/components}/SwSharedPrice.vue +0 -0
  126. /package/{components → app/components}/public/cms/CmsGenericBlock.md +0 -0
  127. /package/{components → app/components}/public/cms/CmsGenericBlock.vue +0 -0
  128. /package/{components → app/components}/public/cms/CmsGenericElement.md +0 -0
  129. /package/{components → app/components}/public/cms/CmsGenericElement.vue +0 -0
  130. /package/{components → app/components}/public/cms/CmsNoComponent.vue +0 -0
  131. /package/{components → app/components}/public/cms/CmsPage.md +0 -0
  132. /package/{components → app/components}/public/cms/block/CmsBlockCategoryNavigation.vue +0 -0
  133. /package/{components → app/components}/public/cms/block/CmsBlockCenterText.vue +0 -0
  134. /package/{components → app/components}/public/cms/block/CmsBlockCrossSelling.vue +0 -0
  135. /package/{components → app/components}/public/cms/block/CmsBlockCustomForm.vue +0 -0
  136. /package/{components → app/components}/public/cms/block/CmsBlockDefault.vue +0 -0
  137. /package/{components → app/components}/public/cms/block/CmsBlockForm.vue +0 -0
  138. /package/{components/public/cms/element → app/components/public/cms/block}/CmsBlockHtml.vue +0 -0
  139. /package/{components → app/components}/public/cms/block/CmsBlockImage.vue +0 -0
  140. /package/{components → app/components}/public/cms/block/CmsBlockImageCover.vue +0 -0
  141. /package/{components → app/components}/public/cms/block/CmsBlockImageGallery.vue +0 -0
  142. /package/{components → app/components}/public/cms/block/CmsBlockImageSlider.vue +0 -0
  143. /package/{components → app/components}/public/cms/block/CmsBlockProductDescriptionReviews.vue +0 -0
  144. /package/{components → app/components}/public/cms/block/CmsBlockProductListing.vue +0 -0
  145. /package/{components → app/components}/public/cms/block/CmsBlockProductSlider.vue +0 -0
  146. /package/{components → app/components}/public/cms/block/CmsBlockText.vue +0 -0
  147. /package/{components → app/components}/public/cms/block/CmsBlockTextHero.vue +0 -0
  148. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaser.vue +0 -0
  149. /package/{components → app/components}/public/cms/block/CmsBlockTextThreeColumn.vue +0 -0
  150. /package/{components → app/components}/public/cms/block/CmsBlockVimeoVideo.vue +0 -0
  151. /package/{components → app/components}/public/cms/block/CmsBlockYoutubeVideo.vue +0 -0
  152. /package/{components → app/components}/public/cms/element/CmsElementBuyBox.md +0 -0
  153. /package/{components → app/components}/public/cms/element/CmsElementCategoryNavigation.md +0 -0
  154. /package/{components → app/components}/public/cms/element/CmsElementCrossSelling.md +0 -0
  155. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.md +0 -0
  156. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.vue +0 -0
  157. /package/{components → app/components}/public/cms/element/CmsElementForm.md +0 -0
  158. /package/{components → app/components}/public/cms/element/CmsElementForm.vue +0 -0
  159. /package/{components → app/components}/public/cms/element/CmsElementHtml.vue +0 -0
  160. /package/{components → app/components}/public/cms/element/CmsElementImage.md +0 -0
  161. /package/{components → app/components}/public/cms/element/CmsElementImageGallery.md +0 -0
  162. /package/{components → app/components}/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +0 -0
  163. /package/{components → app/components}/public/cms/element/CmsElementImageSlider.md +0 -0
  164. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.md +0 -0
  165. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.vue +0 -0
  166. /package/{components → app/components}/public/cms/element/CmsElementProductBox.md +0 -0
  167. /package/{components → app/components}/public/cms/element/CmsElementProductDescriptionReviews.md +0 -0
  168. /package/{components → app/components}/public/cms/element/CmsElementProductListing.md +0 -0
  169. /package/{components → app/components}/public/cms/element/CmsElementProductName.md +0 -0
  170. /package/{components → app/components}/public/cms/element/CmsElementProductSlider.md +0 -0
  171. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.md +0 -0
  172. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.vue +0 -0
  173. /package/{components → app/components}/public/cms/element/CmsElementText.md +0 -0
  174. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.md +0 -0
  175. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.vue +0 -0
  176. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.md +0 -0
  177. /package/{components → app/components}/public/cms/section/CmsSectionDefault.md +0 -0
  178. /package/{components → app/components}/public/cms/section/CmsSectionSidebar.md +0 -0
  179. /package/{helpers → app/helpers}/html-to-vue/ast.ts +0 -0
  180. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.test.ts +0 -0
  181. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.ts +0 -0
  182. /package/{helpers → app/helpers}/html-to-vue/renderToHtml.ts +0 -0
  183. /package/{helpers → app/helpers}/html-to-vue/renderer.ts +0 -0
  184. /package/{helpers → app/helpers}/media/isSpatial.ts +0 -0
@@ -178,7 +178,7 @@ const invokeSubmit = async () => {
178
178
  id="salutation"
179
179
  v-model="state.salutationId"
180
180
  name="salutation"
181
- class="border-gray-300 focus:border-indigo-500 appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
181
+ class="border-outline-outline-variant focus:border-brand-primary 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"
182
182
  >
183
183
  <option disabled selected value="">
184
184
  {{ translations.form.salutationPlaceholder }}
@@ -200,20 +200,20 @@ const invokeSubmit = async () => {
200
200
  name="first-name"
201
201
  type="text"
202
202
  autocomplete="given-name"
203
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
203
+ 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"
204
204
  :class="[
205
205
  $v.firstName.$error
206
206
  ? 'border-red-600 focus:border-red-600'
207
- : 'border-gray-300 focus:border-indigo-500',
207
+ : 'border-outline-outline-variant focus:border-brand-primary',
208
208
  ]"
209
209
  :placeholder="translations.form.firstNamePlaceholder"
210
210
  @blur="$v.firstName.$touch()"
211
211
  />
212
212
  <span
213
- v-if="$v.firstName.$error"
213
+ v-if="$v.firstName.$error && $v.firstName.$errors[0]?.$message"
214
214
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
215
215
  >
216
- {{ $v.firstName.$errors[0]?.$message ?? '' }}
216
+ {{ $v.firstName.$errors[0].$message }}
217
217
  </span>
218
218
  </div>
219
219
  <div class="col-span-4">
@@ -224,20 +224,20 @@ const invokeSubmit = async () => {
224
224
  name="last-name"
225
225
  type="text"
226
226
  autocomplete="family-name"
227
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
227
+ 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"
228
228
  :class="[
229
229
  $v.lastName.$error
230
230
  ? 'border-red-600 focus:border-red-600'
231
- : 'border-gray-300 focus:border-indigo-500',
231
+ : 'border-outline-outline-variant focus:border-brand-primary',
232
232
  ]"
233
233
  :placeholder="translations.form.lastNamePlaceholder"
234
234
  @blur="$v.lastName.$touch()"
235
235
  />
236
236
  <span
237
- v-if="$v.lastName.$error"
237
+ v-if="$v.lastName.$error && $v.lastName.$errors[0]?.$message"
238
238
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
239
239
  >
240
- {{ $v.lastName.$errors[0]?.$message ?? '' }}
240
+ {{ $v.lastName.$errors[0].$message }}
241
241
  </span>
242
242
  </div>
243
243
  <div class="col-span-6">
@@ -251,17 +251,17 @@ const invokeSubmit = async () => {
251
251
  :class="[
252
252
  $v.email.$error
253
253
  ? 'border-red-600 focus:border-red-600'
254
- : 'border-gray-300 focus:border-indigo-500',
254
+ : 'border-outline-outline-variant focus:border-brand-primary',
255
255
  ]"
256
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
256
+ 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"
257
257
  :placeholder="translations.form.emailPlaceholder"
258
258
  @blur="$v.email.$touch()"
259
259
  />
260
260
  <span
261
- v-if="$v.email.$error"
261
+ v-if="$v.email.$error && $v.email.$errors[0]?.$message"
262
262
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
263
263
  >
264
- {{ $v.email.$errors[0]?.$message ?? '' }}
264
+ {{ $v.email.$errors[0].$message }}
265
265
  </span>
266
266
  </div>
267
267
  <div class="col-span-6">
@@ -272,20 +272,20 @@ const invokeSubmit = async () => {
272
272
  name="phone"
273
273
  type="text"
274
274
  autocomplete="phone"
275
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
275
+ 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"
276
276
  :class="[
277
277
  $v.phone.$error
278
278
  ? 'border-red-600 focus:border-red-600'
279
- : 'border-gray-300 focus:border-indigo-500',
279
+ : 'border-outline-outline-variant focus:border-brand-primary',
280
280
  ]"
281
281
  :placeholder="translations.form.phonePlaceholder"
282
282
  @blur="$v.phone.$touch()"
283
283
  />
284
284
  <span
285
- v-if="$v.phone.$error"
285
+ v-if="$v.phone.$error && $v.phone.$errors[0]?.$message"
286
286
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
287
287
  >
288
- {{ $v.phone.$errors[0]?.$message ?? '' }}
288
+ {{ $v.phone.$errors[0].$message }}
289
289
  </span>
290
290
  </div>
291
291
  <div class="col-span-12">
@@ -296,20 +296,20 @@ const invokeSubmit = async () => {
296
296
  name="subject"
297
297
  type="text"
298
298
  autocomplete="subject"
299
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
299
+ 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"
300
300
  :class="[
301
301
  $v.subject.$error
302
302
  ? 'border-red-600 focus:border-red-600'
303
- : 'border-gray-300 focus:border-indigo-500',
303
+ : 'border-outline-outline-variant focus:border-brand-primary',
304
304
  ]"
305
305
  :placeholder="translations.form.subjectPlaceholder"
306
306
  @blur="$v.subject.$touch()"
307
307
  />
308
308
  <span
309
- v-if="$v.subject.$error"
309
+ v-if="$v.subject.$error && $v.subject.$errors[0]?.$message"
310
310
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
311
311
  >
312
- {{ $v.subject.$errors[0]?.$message ?? '' }}
312
+ {{ $v.subject.$errors[0].$message }}
313
313
  </span>
314
314
  </div>
315
315
  <div class="col-span-12">
@@ -320,21 +320,21 @@ const invokeSubmit = async () => {
320
320
  name="comment"
321
321
  type="text"
322
322
  autocomplete="comment"
323
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
323
+ 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"
324
324
  :class="[
325
325
  $v.comment.$error
326
326
  ? 'border-red-600 focus:border-red-600'
327
- : 'border-gray-300 focus:border-indigo-500',
327
+ : 'border-outline-outline-variant focus:border-brand-primary',
328
328
  ]"
329
329
  :placeholder="translations.form.commentPlaceholder"
330
330
  rows="5"
331
331
  @blur="$v.comment.$touch()"
332
332
  />
333
333
  <span
334
- v-if="$v.comment.$error"
334
+ v-if="$v.comment.$error && $v.comment.$errors[0]?.$message"
335
335
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
336
336
  >
337
- {{ $v.comment.$errors[0]?.$message || '' }}
337
+ {{ $v.comment.$errors[0].$message }}
338
338
  </span>
339
339
  </div>
340
340
  <div class="col-span-12">
@@ -345,7 +345,7 @@ const invokeSubmit = async () => {
345
345
  v-model="state.checkbox"
346
346
  name="privacy"
347
347
  type="checkbox"
348
- class="mt-1 focus:ring-indigo-500 h-4 w-4 border text-indigo-600 rounded"
348
+ class="mt-1 focus:ring-brand-primary h-4 w-4 border text-brand-primary rounded"
349
349
  :class="[
350
350
  $v.checkbox.$error ? 'border-red-600' : 'border-gray-300',
351
351
  ]"
@@ -363,7 +363,7 @@ const invokeSubmit = async () => {
363
363
  </div>
364
364
  <div class="flex justify-end mt-10">
365
365
  <button
366
- class="group relative flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-75"
366
+ class="group relative flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-brand-primary hover:bg-brand-primary-hover focus:outline-none focus:ring-2 focus:ring-brand-primary disabled:opacity-75"
367
367
  type="submit"
368
368
  >
369
369
  {{ translations.form.submit }}
@@ -0,0 +1,144 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import type { Schemas } from "#shopware";
4
+
5
+ type FilterState = {
6
+ manufacturer: Set<string>;
7
+ properties: Set<string>;
8
+ "min-price": number | undefined;
9
+ "max-price": number | undefined;
10
+ rating: number | undefined;
11
+ "shipping-free": boolean | undefined;
12
+ };
13
+
14
+ type Filter = {
15
+ id: string;
16
+ code: string;
17
+ options?: Array<
18
+ | Schemas["PropertyGroupOption"]
19
+ | { id: string; translated?: { name?: string } }
20
+ >;
21
+ entities?: Array<
22
+ | Schemas["ProductManufacturer"]
23
+ | { id: string; translated?: { name?: string } }
24
+ >;
25
+ };
26
+
27
+ const props = defineProps<{
28
+ filters: FilterState;
29
+ availableFilters: Filter[];
30
+ }>();
31
+
32
+ const emit = defineEmits<{
33
+ remove: [{ code: string; value: string | number }];
34
+ }>();
35
+
36
+ const getTranslatedName = (
37
+ item: { translated?: { name?: string }; name?: string } | undefined,
38
+ ): string | null => {
39
+ if (!item) return null;
40
+ if ("translated" in item) {
41
+ return item.translated?.name || ("name" in item ? item.name : null) || null;
42
+ }
43
+ return null;
44
+ };
45
+
46
+ const activeChips = computed(() => {
47
+ const chips: Array<{ label: string; code: string; value: string | number }> =
48
+ [];
49
+
50
+ // Add property filters
51
+ const properties = Array.from(props.filters.properties);
52
+ for (const propertyId of properties) {
53
+ // Check all filters, not just the one with code "properties"
54
+ // because properties can be in multiple filter groups
55
+ for (const filter of props.availableFilters) {
56
+ if ("options" in filter && filter.options) {
57
+ const option = filter.options.find((o) => o.id === propertyId);
58
+ const name = getTranslatedName(option);
59
+ if (name) {
60
+ chips.push({
61
+ label: name,
62
+ code: "properties",
63
+ value: propertyId,
64
+ });
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ // Add manufacturer filters
72
+ const manufacturers = Array.from(props.filters.manufacturer);
73
+ for (const manufacturerId of manufacturers) {
74
+ const filter = props.availableFilters.find(
75
+ (f) => f.code === "manufacturer",
76
+ );
77
+ if (filter && "entities" in filter && filter.entities) {
78
+ const entity = filter.entities.find((e) => e.id === manufacturerId);
79
+ const name = getTranslatedName(entity);
80
+ if (name) {
81
+ chips.push({
82
+ label: name,
83
+ code: "manufacturer",
84
+ value: manufacturerId,
85
+ });
86
+ }
87
+ }
88
+ }
89
+
90
+ // Add price filters
91
+ if (props.filters["min-price"] || props.filters["max-price"]) {
92
+ const min = props.filters["min-price"] || 0;
93
+ const max = props.filters["max-price"] || "∞";
94
+ chips.push({
95
+ label: `Price: ${min} - ${max}`,
96
+ code: "price",
97
+ value: "price-range",
98
+ });
99
+ }
100
+
101
+ // Add rating filter
102
+ if (props.filters.rating) {
103
+ chips.push({
104
+ label: `Rating: ${props.filters.rating}★`,
105
+ code: "rating",
106
+ value: props.filters.rating,
107
+ });
108
+ }
109
+
110
+ // Add shipping free filter
111
+ if (props.filters["shipping-free"]) {
112
+ chips.push({
113
+ label: "Free Shipping",
114
+ code: "shipping-free",
115
+ value: "true",
116
+ });
117
+ }
118
+
119
+ return chips;
120
+ });
121
+
122
+ const handleRemoveChip = (chip: { code: string; value: string | number }) => {
123
+ emit("remove", chip);
124
+ };
125
+ </script>
126
+
127
+ <template>
128
+ <div
129
+ v-if="activeChips.length > 0"
130
+ class="self-stretch inline-flex justify-start items-center gap-4 flex-wrap content-center mb-6"
131
+ >
132
+ <button
133
+ v-for="(chip, index) in activeChips"
134
+ :key="`${chip.code}-${chip.value}-${index}`"
135
+ @click="handleRemoveChip(chip)"
136
+ class="px-4 py-1.5 bg-brand-tertiary rounded-full inline-flex justify-center items-center gap-1 hover:bg-brand-tertiary-hover transition-colors"
137
+ >
138
+ <span class="text-brand-on-tertiary text-base font-normal leading-normal">
139
+ {{ chip.label }}
140
+ </span>
141
+ <span class="i-carbon-close w-5 h-5 text-brand-on-tertiary"></span>
142
+ </button>
143
+ </div>
144
+ </template>
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ import { useCmsTranslations, useProductPrice } from "@shopware/composables";
3
+ import { defu } from "defu";
4
+ import { toRefs } from "vue";
5
+ import type { Schemas } from "#shopware";
6
+
7
+ const props = defineProps<{
8
+ product: Schemas["Product"];
9
+ }>();
10
+
11
+ type Translations = {
12
+ listing: {
13
+ variantsFrom: string;
14
+ previously: string;
15
+ from: string;
16
+ to: string;
17
+ };
18
+ };
19
+
20
+ let translations: Translations = {
21
+ listing: {
22
+ variantsFrom: "variants from",
23
+ previously: "previously",
24
+ from: "from",
25
+ to: "to",
26
+ },
27
+ };
28
+
29
+ translations = defu(useCmsTranslations(), translations) as Translations;
30
+
31
+ const { product } = toRefs(props);
32
+
33
+ const {
34
+ price,
35
+ unitPrice,
36
+ displayFromVariants,
37
+ displayFrom,
38
+ isListPrice,
39
+ regulationPrice,
40
+ } = useProductPrice(product);
41
+ </script>
42
+
43
+ <template>
44
+ <div :id="product.id" class="inline-flex justify-start items-center gap-2">
45
+ <!-- Sale price display -->
46
+ <div v-if="isListPrice" class="flex items-center gap-2">
47
+ <div class="text-base font-bold leading-normal">
48
+ <SwSharedPrice :value="unitPrice">
49
+ <template #beforePrice>
50
+ <span v-if="displayFrom || displayFromVariants" class="text-sm">{{
51
+ translations.listing.from
52
+ }}</span>
53
+ </template>
54
+ </SwSharedPrice>
55
+ </div>
56
+ <div class="text-surface-on-surface-variant text-sm font-normal leading-tight line-through">
57
+ <SwSharedPrice :value="price?.listPrice?.price" />
58
+ </div>
59
+ </div>
60
+
61
+ <!-- Regular price display -->
62
+ <div v-else class="text-surface-on-surface text-base font-bold leading-normal">
63
+ <SwSharedPrice :value="unitPrice">
64
+ <template #beforePrice>
65
+ <span v-if="displayFrom || displayFromVariants" class="text-sm">{{
66
+ translations.listing.from
67
+ }}</span>
68
+ </template>
69
+ </SwSharedPrice>
70
+ </div>
71
+
72
+ <!-- Variants from price -->
73
+ <div v-if="displayFromVariants" class="text-surface-on-surface text-base font-bold leading-normal">
74
+ <SwSharedPrice :value="displayFromVariants">
75
+ <template #beforePrice>
76
+ <span v-if="displayFromVariants" class="text-sm">{{
77
+ translations.listing.variantsFrom
78
+ }}</span>
79
+ </template>
80
+ </SwSharedPrice>
81
+ </div>
82
+
83
+ <!-- Regulation price -->
84
+ <div v-if="regulationPrice" class="flex gap-2 text-surface-on-surface-variant text-sm">
85
+ {{ translations.listing.previously }}
86
+ <SwSharedPrice :value="regulationPrice" />
87
+ </div>
88
+ </div>
89
+ </template>
@@ -78,8 +78,18 @@ const { getConfigValue } = useCmsElementConfig(props.content);
78
78
  const { newsletterSubscribe, newsletterUnsubscribe } = useNewsletter();
79
79
 
80
80
  const getFormTitle = computed(() => getConfigValue("title"));
81
- const state = reactive({
82
- option: subscriptionOptions[0]?.value ?? "",
81
+
82
+ type NewsletterFormState = {
83
+ option: "subscribe" | "unsubscribe";
84
+ salutationId: string;
85
+ firstName: string;
86
+ lastName: string;
87
+ email: string;
88
+ checkbox: boolean;
89
+ };
90
+
91
+ const state = reactive<NewsletterFormState>({
92
+ option: subscriptionOptions[0]?.value ?? "subscribe",
83
93
  salutationId: "",
84
94
  firstName: "",
85
95
  lastName: "",
@@ -87,7 +97,7 @@ const state = reactive({
87
97
  checkbox: false,
88
98
  });
89
99
 
90
- type Rules = {
100
+ type RequiredRules = {
91
101
  email: {
92
102
  required: ValidationRuleWithoutParams;
93
103
  email: ValidationRuleWithoutParams;
@@ -96,6 +106,9 @@ type Rules = {
96
106
  required: ValidationRuleWithoutParams;
97
107
  isTrue: (value: boolean) => boolean;
98
108
  };
109
+ };
110
+
111
+ type OptionalRules = {
99
112
  firstName: {
100
113
  required: ValidationRuleWithoutParams;
101
114
  minLength: number;
@@ -105,8 +118,9 @@ type Rules = {
105
118
  minLength: number;
106
119
  };
107
120
  };
108
- const rules = computed(() => {
109
- let temp: Partial<Rules> = {
121
+
122
+ const rules = computed((): RequiredRules & Partial<OptionalRules> => {
123
+ const temp: RequiredRules & Partial<OptionalRules> = {
110
124
  email: {
111
125
  required,
112
126
  email,
@@ -117,16 +131,13 @@ const rules = computed(() => {
117
131
  },
118
132
  };
119
133
  if (state.option === "subscribe") {
120
- temp = {
121
- ...temp,
122
- firstName: {
123
- required,
124
- minLength: 3,
125
- },
126
- lastName: {
127
- required,
128
- minLength: 3,
129
- },
134
+ temp.firstName = {
135
+ required,
136
+ minLength: 3,
137
+ };
138
+ temp.lastName = {
139
+ required,
140
+ minLength: 3,
130
141
  };
131
142
  }
132
143
  return temp;
@@ -184,7 +195,7 @@ const invokeSubmit = async () => {
184
195
  id="option"
185
196
  v-model="state.option"
186
197
  name="option"
187
- class="appearance-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
198
+ class="appearance-none relative block w-full px-3 py-2 border border-outline-outline-variant placeholder-surface-on-surface-variant text-surface-on-surface rounded-md focus:border-brand-primary focus:outline-none focus:ring-brand-primary focus:z-10 sm:text-sm"
188
199
  >
189
200
  <option
190
201
  v-for="subscription in subscriptionOptions"
@@ -204,19 +215,19 @@ const invokeSubmit = async () => {
204
215
  type="email"
205
216
  autocomplete="email"
206
217
  :class="[
207
- $v.email?.$error
218
+ $v.email.$error
208
219
  ? 'border-red-600 focus:border-red-600'
209
- : 'border-gray-300 focus:border-indigo-500',
220
+ : 'border-outline-outline-variant focus:border-brand-primary',
210
221
  ]"
211
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
222
+ 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"
212
223
  :placeholder="translations.form.emailPlaceholder"
213
- @blur="$v.email?.$touch()"
224
+ @blur="$v.email.$touch()"
214
225
  />
215
226
  <span
216
- v-if="$v.email?.$error"
227
+ v-if="$v.email.$error && $v.email.$errors[0]?.$message"
217
228
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
218
229
  >
219
- {{ $v.email?.$errors[0]?.$message || '' }}
230
+ {{ $v.email.$errors[0].$message }}
220
231
  </span>
221
232
  </div>
222
233
  <div v-if="state.option === 'subscribe'" class="col-span-4">
@@ -225,7 +236,7 @@ const invokeSubmit = async () => {
225
236
  id="salutation"
226
237
  v-model="state.salutationId"
227
238
  name="salutation"
228
- class=" border-gray-300 focus:border-indigo-500appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
239
+ class=" border-outline-outline-variant focus:border-brand-primaryappearance-none relative block w-full px-3 py-2 border placeholder-surface-on-surface-variant text-surface-on-surface rounded-md focus:outline-none focus:ring-brand-primary focus:z-10 sm:text-sm"
229
240
  >
230
241
  <option disabled selected value="">
231
242
  {{ translations.form.salutationPlaceholder }}
@@ -247,20 +258,20 @@ const invokeSubmit = async () => {
247
258
  name="first-name"
248
259
  type="text"
249
260
  autocomplete="given-name"
250
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
261
+ 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"
251
262
  :class="[
252
263
  $v.firstName?.$error
253
- ? 'border-red-600 focus:border-red-600'
254
- : 'border-gray-300 focus:border-indigo-500',
264
+ ? 'border-red-600 focus:border-red-600'
265
+ : 'border-outline-outline-variant focus:border-brand-primary',
255
266
  ]"
256
267
  :placeholder="translations.form.firstNamePlaceholder"
257
268
  @blur="$v.firstName?.$touch()"
258
269
  />
259
270
  <span
260
- v-if="$v.firstName?.$error"
271
+ v-if="$v.firstName?.$error && $v.firstName?.$errors[0]?.$message"
261
272
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
262
273
  >
263
- {{ $v.firstName?.$errors[0]?.$message || '' }}
274
+ {{ $v.firstName?.$errors[0].$message }}
264
275
  </span>
265
276
  </div>
266
277
  <div v-if="state.option === 'subscribe'" class="col-span-4">
@@ -271,20 +282,20 @@ const invokeSubmit = async () => {
271
282
  name="last-name"
272
283
  type="text"
273
284
  autocomplete="family-name"
274
- class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
285
+ 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"
275
286
  :class="[
276
287
  $v.lastName?.$error
277
288
  ? 'border-red-600 focus:border-red-600'
278
- : 'border-gray-300 focus:border-indigo-500',
289
+ : 'border-outline-outline-variant focus:border-brand-primary',
279
290
  ]"
280
291
  :placeholder="translations.form.lastNamePlaceholder"
281
292
  @blur="$v.lastName?.$touch()"
282
293
  />
283
294
  <span
284
- v-if="$v.lastName?.$error"
295
+ v-if="$v.lastName?.$error && $v.lastName?.$errors[0]?.$message"
285
296
  class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
286
297
  >
287
- {{ $v.lastName?.$errors[0]?.$message || '' }}
298
+ {{ $v.lastName?.$errors[0].$message }}
288
299
  </span>
289
300
  </div>
290
301
  <div class="col-span-12">
@@ -295,7 +306,7 @@ const invokeSubmit = async () => {
295
306
  v-model="state.checkbox"
296
307
  name="privacy"
297
308
  type="checkbox"
298
- class="mt-1 focus:ring-indigo-500 h-4 w-4 border text-indigo-600 rounded"
309
+ class="mt-1 focus:ring-brand-primary h-4 w-4 border text-brand-primary rounded"
299
310
  :class="[
300
311
  $v.checkbox?.$error ? 'border-red-600' : 'border-gray-300',
301
312
  ]"
@@ -313,7 +324,7 @@ const invokeSubmit = async () => {
313
324
  </div>
314
325
  <div class="flex justify-end mt-10">
315
326
  <button
316
- class="group relative flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-75"
327
+ class="group relative flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-brand-primary hover:bg-brand-primary-hover focus:outline-none focus:ring-2 focus:ring-brand-primary disabled:opacity-75"
317
328
  type="submit"
318
329
  >
319
330
  {{ translations.form.submit }}
@@ -36,8 +36,7 @@ defineEmits<(e: "changePage", page: number) => void>();
36
36
  @click="$emit('changePage', current - 1)"
37
37
  >
38
38
  <span class="sr-only">{{ translations.listing.previous }}</span>
39
- <!-- Heroicon name: solid/chevron-left -->
40
- <div class="w-5 h-5 i-carbon-chevron-left" />
39
+ <SwChevronIcon direction="left" :size="20" />
41
40
  </button>
42
41
  <button
43
42
  v-if="current > 2"
@@ -62,7 +61,7 @@ defineEmits<(e: "changePage", page: number) => void>();
62
61
  </button>
63
62
  <button
64
63
  aria-current="page"
65
- class="bg-indigo-50 border-indigo-500 text-indigo-600 relative inline-flex items-center px-4 py-2 border text-sm font-medium"
64
+ class="bg-surface-surface-primary border-brand-primary text-brand-primary relative inline-flex items-center px-4 py-2 border text-sm font-medium"
66
65
  :class="[
67
66
  current - 1 >= 1 ? '' : 'rounded-l-md border border-secondary-300',
68
67
  total == current ? 'rounded-r-md border border-secondary-300' : '',
@@ -99,8 +98,7 @@ defineEmits<(e: "changePage", page: number) => void>();
99
98
  @click="$emit('changePage', current + 1)"
100
99
  >
101
100
  <span class="sr-only">{{ translations.listing.next }}</span>
102
- <!-- Heroicon name: solid/chevron-right -->
103
- <div class="w-5 h-5 i-carbon-chevron-right" />
101
+ <SwChevronIcon direction="right" :size="20" />
104
102
  </button>
105
103
  </nav>
106
104
  </template>