@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
@@ -1,57 +0,0 @@
1
- <script setup lang="ts">
2
- import {
3
- buildUrlPrefix,
4
- getCategoryRoute,
5
- getTranslatedProperty,
6
- urlIsAbsolute,
7
- } from "@shopware/helpers";
8
- import { computed } from "vue";
9
- import { RouterLink } from "vue-router";
10
- import { useUrlResolver } from "#imports";
11
- import type { Schemas } from "#shopware";
12
-
13
- interface Props {
14
- navigationElement: Schemas["Category"];
15
- isActive?: boolean;
16
- isHighlighted?: boolean;
17
- }
18
-
19
- const props = defineProps<Props>();
20
- const { getUrlPrefix } = useUrlResolver();
21
- const url = computed(() => {
22
- return buildUrlPrefix(
23
- getCategoryRoute(props.navigationElement),
24
- getUrlPrefix(),
25
- );
26
- });
27
- </script>
28
- <template>
29
- <div
30
- class="flex items-center py-2 px-5 text-base rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 my-2"
31
- >
32
- <RouterLink
33
- v-if="!urlIsAbsolute(url.path)"
34
- :to="url"
35
- :class="[
36
- props.isHighlighted ? 'font-bold' : 'font-normal',
37
- props.isActive ? 'text-indigo-600' : 'text-gray-900',
38
- ]"
39
- >
40
- <span>{{ getTranslatedProperty(navigationElement, "name") }}</span>
41
- </RouterLink>
42
- <a
43
- v-else
44
- :href="url.path"
45
- :class="[
46
- props.isHighlighted ? 'font-bold' : 'font-normal',
47
- props.isActive ? 'text-indigo-600' : 'text-gray-900',
48
- ]"
49
- :target="
50
- navigationElement.externalLink || navigationElement.linkNewTab
51
- ? '_blank'
52
- : ''
53
- "
54
- ><span>{{ getTranslatedProperty(navigationElement, "name") }}</span></a
55
- >
56
- </div>
57
- </template>
@@ -1,89 +0,0 @@
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">
45
- <SwSharedPrice
46
- v-if="isListPrice"
47
- class="text-l text-gray-900 basis-2/6 justify-end line-through"
48
- :value="price?.listPrice?.price"
49
- />
50
- <template v-if="!isListPrice">
51
- <div class="h-6"><!-- placeholder --></div>
52
- </template>
53
- <SwSharedPrice
54
- v-if="displayFromVariants"
55
- class="text-xl text-gray-900 basis-2/6 justify-end"
56
- :value="displayFromVariants"
57
- >
58
- <template #beforePrice
59
- ><span v-if="displayFromVariants" class="text-sm">{{
60
- translations.listing.variantsFrom
61
- }}</span></template
62
- >
63
- </SwSharedPrice>
64
- <SwSharedPrice
65
- class="text-gray-900 basis-2/6"
66
- :class="{
67
- 'text-red-600 font-bold': isListPrice,
68
- 'justify-end text-xl':
69
- regulationPrice || !regulationPrice || !displayFromVariants,
70
- }"
71
- :value="unitPrice"
72
- >
73
- <template #beforePrice
74
- ><span v-if="displayFrom || displayFromVariants" class="text-sm">{{
75
- translations.listing.from
76
- }}</span></template
77
- >
78
- </SwSharedPrice>
79
- <template v-if="regulationPrice">
80
- <div class="flex gap-2 justify-end text-gray-500 text-3.5 mb-2">
81
- {{ translations.listing.previously }}
82
- <SwSharedPrice :value="regulationPrice" />
83
- </div>
84
- </template>
85
- <template v-if="!regulationPrice">
86
- <div class="h-7"><!-- placeholder --></div>
87
- </template>
88
- </div>
89
- </template>
@@ -1,286 +0,0 @@
1
- <script setup lang="ts">
2
- import { ApiClientError } from "@shopware/api-client";
3
- import type { BoxLayout, DisplayMode } from "@shopware/composables";
4
- import { useCmsTranslations } from "@shopware/composables";
5
- import {
6
- buildUrlPrefix,
7
- getProductFromPrice,
8
- getProductName,
9
- getProductRoute,
10
- getSmallestThumbnailUrl,
11
- } from "@shopware/helpers";
12
- import { getCmsTranslate } from "@shopware/helpers";
13
- import { useElementSize } from "@vueuse/core";
14
- import { defu } from "defu";
15
- import { computed, ref, toRefs, useTemplateRef } from "vue";
16
- import { RouterLink } from "vue-router";
17
- import {
18
- useAddToCart,
19
- useCartErrorParamsResolver,
20
- useCartNotification,
21
- useNotifications,
22
- useProductWishlist,
23
- useUrlResolver,
24
- } from "#imports";
25
- import type { Schemas } from "#shopware";
26
-
27
- const { pushSuccess, pushError } = useNotifications();
28
- const { getErrorsCodes } = useCartNotification();
29
- const { resolveCartError } = useCartErrorParamsResolver();
30
-
31
- const props = withDefaults(
32
- defineProps<{
33
- product: Schemas["Product"];
34
- layoutType?: BoxLayout;
35
- isProductListing?: boolean;
36
- displayMode?: DisplayMode;
37
- }>(),
38
- {
39
- layoutType: "standard",
40
- displayMode: "standard",
41
- isProductListing: false,
42
- },
43
- );
44
-
45
- type Translations = {
46
- product: {
47
- addedToWishlist: string;
48
- removedFromTheWishlist: string;
49
- reason: string;
50
- cannotAddToWishlist: string;
51
- addedToCart: string;
52
- addToCart: string;
53
- details: string;
54
- badges: {
55
- topseller: string;
56
- };
57
- };
58
- errors: {
59
- [key: string]: string;
60
- };
61
- };
62
-
63
- let translations: Translations = {
64
- product: {
65
- addedToWishlist: "has been added to wishlist.",
66
- removedFromTheWishlist: "has been removed from wishlist.",
67
- reason: "Reason",
68
- cannotAddToWishlist: "cannot be added to wishlist.",
69
- addedToCart: "has been added to cart.",
70
- addToCart: "Add to cart",
71
- details: "Details",
72
- badges: {
73
- topseller: "Tip",
74
- },
75
- },
76
- errors: {
77
- "product-stock-reached":
78
- "The product {name} is only available {quantity} times",
79
- },
80
- };
81
-
82
- translations = defu(useCmsTranslations(), translations) as Translations;
83
-
84
- const { product } = toRefs(props);
85
-
86
- const { addToCart, isInCart, count } = useAddToCart(product);
87
-
88
- const { addToWishlist, removeFromWishlist, isInWishlist } = useProductWishlist(
89
- product.value.id,
90
- );
91
- const isLoading = ref(false);
92
-
93
- const toggleWishlistProduct = async () => {
94
- isLoading.value = true;
95
-
96
- try {
97
- if (!isInWishlist.value) {
98
- await addToWishlist();
99
- pushSuccess(
100
- `${props.product?.translated.name} ${translations.product.addedToWishlist}`,
101
- );
102
- } else {
103
- await removeFromWishlist();
104
- pushError(
105
- `${props.product?.translated.name} ${translations.product.removedFromTheWishlist}`,
106
- );
107
- }
108
- } catch (error) {
109
- if (error instanceof ApiClientError) {
110
- const reason = error.details.errors?.[0]?.detail
111
- ? `${translations.product.reason}: ${error.details.errors?.[0]?.detail}`
112
- : "";
113
- return pushError(
114
- `${props.product?.translated.name} ${translations.product.cannotAddToWishlist}\n${reason}`,
115
- {
116
- timeout: 5000,
117
- },
118
- );
119
- }
120
- } finally {
121
- isLoading.value = false;
122
- }
123
- };
124
-
125
- const addToCartProxy = async () => {
126
- await addToCart();
127
- const errors = getErrorsCodes();
128
- for (const element of errors) {
129
- const { messageKey, params } = resolveCartError(element);
130
- if (translations.errors[messageKey])
131
- pushError(getCmsTranslate(translations.errors[messageKey], params));
132
- }
133
-
134
- if (!errors.length)
135
- pushSuccess(
136
- `${props.product?.translated.name} ${translations.product.addedToCart}`,
137
- );
138
- };
139
-
140
- const fromPrice = getProductFromPrice(props.product);
141
- const { getUrlPrefix } = useUrlResolver();
142
-
143
- const imageElement = useTemplateRef("imageElement");
144
- const { height } = useElementSize(imageElement);
145
-
146
- const DEFAULT_THUMBNAIL_SIZE = 10;
147
- function roundUp(num: number) {
148
- return num ? Math.ceil(num / 100) * 100 : DEFAULT_THUMBNAIL_SIZE;
149
- }
150
-
151
- const srcPath = computed(() => {
152
- return `${getSmallestThumbnailUrl(
153
- product.value?.cover?.media,
154
- )}?&height=${roundUp(height.value)}&fit=cover`;
155
- });
156
- </script>
157
-
158
- <template>
159
- <div
160
- class="sw-product-card group relative max-w-full inline-block max-w-sm bg-white border border-gray-200 rounded-md shadow transform transition duration-300 hover:scale-101"
161
- data-testid="product-box"
162
- >
163
- <div
164
- :class="[
165
- 'w-full rounded-md overflow-hidden hover:opacity-75',
166
- layoutType === 'image' ? 'h-80' : 'h-60',
167
- ]"
168
- >
169
- <div class="absolute top-5 -left-1 z-10">
170
- <span
171
- v-if="product.markAsTopseller"
172
- class="bg-[#FFBD5D] px-2.5 py-1.5 color-black text-xl"
173
- >{{ translations.product.badges.topseller }}</span
174
- >
175
- </div>
176
- <RouterLink
177
- :to="buildUrlPrefix(getProductRoute(product), getUrlPrefix())"
178
- class="overflow-hidden"
179
- >
180
- <img
181
- ref="imageElement"
182
- loading="lazy"
183
- :src="srcPath"
184
- :alt="getProductName({ product }) || ''"
185
- class="transform transition duration-400 hover:scale-120"
186
- :class="{
187
- 'w-full h-full': true,
188
- 'object-cover':
189
- displayMode === 'cover' ||
190
- (displayMode === 'standard' && layoutType === 'image'),
191
- 'object-contain': displayMode === 'contain',
192
- 'object-scale-down':
193
- displayMode === 'standard' && layoutType !== 'image',
194
- }"
195
- data-testid="product-box-img"
196
- />
197
- </RouterLink>
198
- </div>
199
- <button
200
- aria-label="Add to wishlist"
201
- type="button"
202
- :disabled="isLoading"
203
- class="absolute bg-transparent top-2 right-2 hover:animate-count-infinite hover:animate-heart-beat"
204
- data-testid="product-box-toggle-wishlist-button"
205
- @click="toggleWishlistProduct"
206
- >
207
- <client-only>
208
- <div
209
- v-if="isInWishlist"
210
- class="h-9 w-9 i-carbon-favorite-filled c-red-500"
211
- data-testid="product-box-wishlist-icon-in"
212
- ></div>
213
- <div
214
- v-else
215
- class="h-9 w-9 i-carbon-favorite c-black"
216
- data-testid="product-box-wishlist-icon-not-in"
217
- ></div>
218
- <template #placeholder>
219
- <div class="h-9 w-9 i-carbon-favorite"></div>
220
- </template>
221
- </client-only>
222
- </button>
223
- <div class="h-8 mx-4 my-2">
224
- <p
225
- v-for="option in product?.options"
226
- :key="option.id"
227
- class="items-center line-clamp-2 rounded-md text-xs font-medium text-gray-600 mt-3"
228
- >
229
- {{ option.group.name }}:
230
- <span class="font-bold">{{ option.name }} </span>
231
- </p>
232
- </div>
233
- <div class="px-4 pb-4">
234
- <RouterLink
235
- class="line-clamp-2"
236
- :to="buildUrlPrefix(getProductRoute(product), getUrlPrefix())"
237
- data-testid="product-box-product-name-link"
238
- >
239
- <div
240
- class="text-xl font-semibold tracking-tight text-gray-900 dark:text-white min-h-60px"
241
- >
242
- {{ getProductName({ product }) }}
243
- </div>
244
- </RouterLink>
245
-
246
- <SwListingProductPrice
247
- :product="product"
248
- class="ml-auto"
249
- data-testid="product-box-product-price"
250
- />
251
-
252
- <div>
253
- <button
254
- v-if="!fromPrice"
255
- type="button"
256
- class="w-full justify-center my-8 md-m-0 py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transform transition duration-400 flex"
257
- :class="{
258
- 'text-white bg-blue-600 hover:bg-blue-700': !isInCart,
259
- 'text-gray-600 bg-gray-100': isInCart,
260
- 'opacity-50 cursor-not-allowed': !product.available,
261
- }"
262
- data-testid="add-to-cart-button"
263
- :disabled="!product.available"
264
- @click="addToCartProxy"
265
- >
266
- {{ translations.product.addToCart }}
267
- <div v-if="isInCart" class="flex ml-2">
268
- <div class="w-5 h-5 i-carbon-shopping-bag text-gray-600" />
269
- {{ count }}
270
- </div>
271
- </button>
272
- <RouterLink
273
- v-else
274
- :to="buildUrlPrefix(getProductRoute(product), getUrlPrefix())"
275
- class=""
276
- >
277
- <div
278
- class="text-center justify-center w-full md:w-auto my-8 md-m-0 py-2 px-3 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-black hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transform transition duration-400"
279
- >
280
- <span data-testid="product-box-product-show-details">Details</span>
281
- </div>
282
- </RouterLink>
283
- </div>
284
- </div>
285
- </div>
286
- </template>
@@ -1,42 +0,0 @@
1
- <script setup lang="ts" generic="ListingFilter extends { code: string }">
2
- import type { Component } from "vue";
3
- import SwFilterPriceVue from "./listing-filters/SwFilterPrice.vue";
4
- import SwFilterPropertiesVue from "./listing-filters/SwFilterProperties.vue";
5
- import SwFilterRatingVue from "./listing-filters/SwFilterRating.vue";
6
- import SwFilterShippingFreeVue from "./listing-filters/SwFilterShippingFree.vue";
7
-
8
- const props = defineProps<{
9
- filter: ListingFilter;
10
- selectedFilters?: {
11
- [key: string]: unknown;
12
- };
13
- }>();
14
-
15
- const emit = defineEmits<{
16
- selectFilterValue: [{ code: string; value: string | number | boolean }];
17
- }>();
18
-
19
- const cmsMap = () => {
20
- const map: {
21
- [key: string]: Component;
22
- } = {
23
- manufacturer: SwFilterPropertiesVue,
24
- properties: SwFilterPropertiesVue,
25
- price: SwFilterPriceVue,
26
- rating: SwFilterRatingVue,
27
- "shipping-free": SwFilterShippingFreeVue,
28
- };
29
-
30
- return map[props.filter?.code];
31
- };
32
- </script>
33
- <template>
34
- <div>
35
- <component
36
- :is="cmsMap()"
37
- :filter="filter"
38
- :selected-filters="selectedFilters"
39
- @select-value="emit('selectFilterValue', $event)"
40
- />
41
- </div>
42
- </template>