@shopware/cms-base-layer 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -63
- package/app/components/SwCategoryNavigation.vue +1 -0
- package/app/components/SwCategoryNavigationLink.vue +16 -8
- package/app/components/SwContactForm.vue +1 -0
- package/app/components/SwFilterChips.vue +1 -0
- package/app/components/SwListingProductPrice.vue +16 -4
- package/app/components/SwMedia3D.vue +1 -4
- package/app/components/SwNewsletterForm.vue +4 -3
- package/app/components/SwProductAddToCart.vue +24 -7
- package/app/components/SwProductCard.vue +1 -0
- package/app/components/SwProductCardDetails.vue +27 -9
- package/app/components/SwProductCardImage.vue +31 -12
- package/app/components/SwProductCardSkeleton.vue +37 -14
- package/app/components/SwProductGallery.vue +1 -0
- package/app/components/SwProductListingFilter.vue +5 -1
- package/app/components/SwProductListingFilters.vue +21 -6
- package/app/components/SwProductListingFiltersHorizontal.vue +4 -1
- package/app/components/SwProductPrice.vue +1 -0
- package/app/components/SwProductReviews.vue +14 -6
- package/app/components/SwProductReviewsForm.vue +15 -3
- package/app/components/SwProductUnits.vue +1 -0
- package/app/components/SwQuantitySelect.vue +10 -3
- package/app/components/SwSharedPrice.vue +1 -0
- package/app/components/SwSlider.vue +1 -0
- package/app/components/SwStockInfo.vue +12 -6
- package/app/components/SwVariantConfigurator.vue +4 -1
- package/app/components/listing-filters/SwFilterPrice.vue +79 -30
- package/app/components/listing-filters/SwFilterProperties.vue +40 -25
- package/app/components/listing-filters/SwFilterRating.vue +21 -9
- package/app/components/listing-filters/SwFilterShippingFree.vue +16 -8
- package/app/components/public/cms/CmsBlockSpatialViewer.vue +1 -0
- package/app/components/public/cms/CmsGenericBlock.vue +2 -0
- package/app/components/public/cms/CmsGenericElement.vue +1 -0
- package/app/components/public/cms/CmsNoComponent.vue +1 -0
- package/app/components/public/cms/CmsPage.md +1 -1
- package/app/components/public/cms/CmsPage.vue +2 -0
- package/app/components/public/cms/block/CmsBlockCategoryNavigation.vue +1 -0
- package/app/components/public/cms/block/CmsBlockCenterText.vue +1 -0
- package/app/components/public/cms/block/CmsBlockCrossSelling.vue +1 -0
- package/app/components/public/cms/block/CmsBlockCustomForm.vue +1 -0
- package/app/components/public/cms/block/CmsBlockForm.vue +1 -0
- package/app/components/public/cms/block/CmsBlockGalleryBuybox.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImage.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageCover.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageGallery.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageSimpleGrid.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageSlider.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageText.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextBubble.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextCover.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +4 -12
- package/app/components/public/cms/block/CmsBlockImageTextRow.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +4 -1
- package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductHeading.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductListing.vue +1 -0
- package/app/components/public/cms/block/CmsBlockProductThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockSidebarFilter.vue +1 -0
- package/app/components/public/cms/block/CmsBlockText.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextHero.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextOnImage.vue +2 -4
- package/app/components/public/cms/block/CmsBlockTextThreeColumn.vue +1 -0
- package/app/components/public/cms/block/CmsBlockTextTwoColumn.vue +4 -1
- package/app/components/public/cms/block/CmsBlockVimeoVideo.vue +1 -0
- package/app/components/public/cms/block/CmsBlockYoutubeVideo.vue +1 -0
- package/app/components/public/cms/element/CmsElementBuyBox.vue +90 -72
- package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +14 -3
- package/app/components/public/cms/element/CmsElementCrossSelling.vue +3 -1
- package/app/components/public/cms/element/CmsElementCustomForm.vue +1 -0
- package/app/components/public/cms/element/CmsElementForm.vue +1 -0
- package/app/components/public/cms/element/CmsElementHtml.vue +0 -1
- package/app/components/public/cms/element/CmsElementImage.vue +2 -0
- package/app/components/public/cms/element/CmsElementImageGallery.vue +6 -2
- package/app/components/public/cms/element/CmsElementManufacturerLogo.vue +1 -0
- package/app/components/public/cms/element/CmsElementProductBox.vue +1 -0
- package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +77 -29
- package/app/components/public/cms/element/CmsElementProductListing.vue +16 -5
- package/app/components/public/cms/element/CmsElementProductSlider.vue +6 -4
- package/app/components/public/cms/element/CmsElementText.vue +2 -0
- package/app/components/public/cms/element/CmsElementVimeoVideo.vue +1 -0
- package/app/components/public/cms/element/CmsElementYoutubeVideo.vue +1 -0
- package/app/components/public/cms/element/SwProductListingPagination.vue +8 -2
- package/app/components/public/cms/section/CmsSectionSidebar.vue +13 -8
- package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +16 -5
- package/app/components/ui/SwitchButton.vue +53 -14
- package/app/components/ui/WishlistIcon.vue +8 -2
- package/app/composables/useLcpImagePreload.test.ts +1 -0
- package/app/composables/useLcpImagePreload.ts +2 -0
- package/app/composables/useTypedAppConfig.ts +1 -0
- package/app/helpers/cms/getImageSizes.test.ts +1 -0
- package/app/helpers/html-to-vue/ast.ts +1 -0
- package/app/helpers/html-to-vue/getOptionsFromNode.test.ts +1 -0
- package/app/helpers/html-to-vue/renderToHtml.ts +1 -0
- package/app/helpers/html-to-vue/renderer.ts +1 -0
- package/app/providers/shopware.test.ts +1 -0
- package/package.json +19 -20
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts" generic="ListingFilter extends { code: string }">
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import type { Component } from "vue";
|
|
4
|
+
|
|
4
5
|
import SwFilterPriceVue from "./listing-filters/SwFilterPrice.vue";
|
|
5
6
|
import SwFilterPropertiesVue from "./listing-filters/SwFilterProperties.vue";
|
|
6
7
|
import SwFilterRatingVue from "./listing-filters/SwFilterRating.vue";
|
|
@@ -58,7 +59,10 @@ const filterComponent = computed<Component | undefined>(() => {
|
|
|
58
59
|
const handleSelectValue = ({
|
|
59
60
|
code,
|
|
60
61
|
value,
|
|
61
|
-
}: {
|
|
62
|
+
}: {
|
|
63
|
+
code: string;
|
|
64
|
+
value: string | number | boolean;
|
|
65
|
+
}) => {
|
|
62
66
|
emit("filter-change", { code, value });
|
|
63
67
|
};
|
|
64
68
|
</script>
|
|
@@ -8,6 +8,7 @@ import { defu } from "defu";
|
|
|
8
8
|
import { computed, reactive } from "vue";
|
|
9
9
|
import type { ComputedRef, UnwrapNestedRefs } from "vue";
|
|
10
10
|
import type { LocationQueryRaw } from "vue-router";
|
|
11
|
+
|
|
11
12
|
import { useCategoryListing, useRoute, useRouter } from "#imports";
|
|
12
13
|
import type { Schemas, operations } from "#shopware";
|
|
13
14
|
|
|
@@ -268,8 +269,11 @@ const handleRemoveFilterChip = async (chip: {
|
|
|
268
269
|
<!-- Filters Header -->
|
|
269
270
|
<div class="self-stretch flex flex-col justify-start items-start gap-4">
|
|
270
271
|
<div
|
|
271
|
-
class="flex flex-row items-center justify-between w-full mb-4 py-3 border-b border-outline-outline-variant"
|
|
272
|
-
|
|
272
|
+
class="flex flex-row items-center justify-between w-full mb-4 py-3 border-b border-outline-outline-variant"
|
|
273
|
+
>
|
|
274
|
+
<div
|
|
275
|
+
class="flex-1 text-surface-on-surface text-base font-bold leading-normal"
|
|
276
|
+
>
|
|
273
277
|
{{ translations.listing.filters }}
|
|
274
278
|
</div>
|
|
275
279
|
<SwSortDropdown
|
|
@@ -283,7 +287,9 @@ const handleRemoveFilterChip = async (chip: {
|
|
|
283
287
|
|
|
284
288
|
<!-- Filters List -->
|
|
285
289
|
<div class="self-stretch flex flex-col justify-start items-start gap-4">
|
|
286
|
-
<SwProductListingFilter
|
|
290
|
+
<SwProductListingFilter
|
|
291
|
+
v-for="filter in getInitialFilters"
|
|
292
|
+
:key="filter.id"
|
|
287
293
|
:filter="filter"
|
|
288
294
|
:selected-manufacturer="sidebarSelectedFilters.manufacturer"
|
|
289
295
|
:selected-properties="sidebarSelectedFilters.properties"
|
|
@@ -292,11 +298,20 @@ const handleRemoveFilterChip = async (chip: {
|
|
|
292
298
|
:selected-rating="sidebarSelectedFilters.rating"
|
|
293
299
|
:selected-shipping-free="sidebarSelectedFilters['shipping-free']"
|
|
294
300
|
@filter-change="handleFilterChange"
|
|
295
|
-
class="w-full"
|
|
301
|
+
class="w-full"
|
|
302
|
+
/>
|
|
296
303
|
<div v-if="showResetFiltersButton" class="w-full">
|
|
297
|
-
<SwBaseButton
|
|
304
|
+
<SwBaseButton
|
|
305
|
+
variant="primary"
|
|
306
|
+
size="medium"
|
|
307
|
+
block
|
|
308
|
+
@click="invokeCleanFilters"
|
|
309
|
+
type="button"
|
|
310
|
+
>
|
|
298
311
|
{{ translations.listing.resetFilters }}
|
|
299
|
-
<span
|
|
312
|
+
<span
|
|
313
|
+
class="w-6 h-6 i-carbon-close-filled inline-block align-middle ml-2"
|
|
314
|
+
></span>
|
|
300
315
|
</SwBaseButton>
|
|
301
316
|
</div>
|
|
302
317
|
</div>
|
|
@@ -8,6 +8,7 @@ import { defu } from "defu";
|
|
|
8
8
|
import { computed, reactive } from "vue";
|
|
9
9
|
import type { ComputedRef, UnwrapNestedRefs } from "vue";
|
|
10
10
|
import type { LocationQueryRaw } from "vue-router";
|
|
11
|
+
|
|
11
12
|
import { useCategoryListing, useRoute, useRouter } from "#imports";
|
|
12
13
|
import type { Schemas, operations } from "#shopware";
|
|
13
14
|
|
|
@@ -299,7 +300,9 @@ const hasActiveFilter = (filter: { code: string }) => {
|
|
|
299
300
|
type="button"
|
|
300
301
|
>
|
|
301
302
|
{{ translations.listing.resetFilters }}
|
|
302
|
-
<span
|
|
303
|
+
<span
|
|
304
|
+
class="w-5 h-5 i-carbon-close inline-block align-middle ml-1"
|
|
305
|
+
></span>
|
|
303
306
|
</SwBaseButton>
|
|
304
307
|
</div>
|
|
305
308
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { computed, onMounted, ref, toRefs } from "vue";
|
|
4
|
+
|
|
4
5
|
import {
|
|
5
6
|
useCmsTranslations,
|
|
6
7
|
useProductReviews,
|
|
@@ -77,7 +78,10 @@ const formatDate = (date: string) => {
|
|
|
77
78
|
v-for="(review, index) in reviewsList"
|
|
78
79
|
:key="review.id"
|
|
79
80
|
class="pb-6"
|
|
80
|
-
:class="{
|
|
81
|
+
:class="{
|
|
82
|
+
'border-b border-surface-surface-container-highest':
|
|
83
|
+
index < reviewsList.length - 1,
|
|
84
|
+
}"
|
|
81
85
|
>
|
|
82
86
|
<div
|
|
83
87
|
v-if="review.createdAt"
|
|
@@ -87,19 +91,23 @@ const formatDate = (date: string) => {
|
|
|
87
91
|
<span>{{ formatDate(review.createdAt) }}</span>
|
|
88
92
|
</div>
|
|
89
93
|
<div
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
v-if="!review.status"
|
|
95
|
+
class="mt-2 text-3 p-2 bg-states-info-container text-states-on-info-container flex gap-2 items-center"
|
|
96
|
+
>
|
|
93
97
|
<div class="w-6 h-6 i-carbon-warning" />
|
|
94
98
|
{{ translations.product.reviewNotAccepted }}
|
|
95
99
|
</div>
|
|
96
|
-
<div
|
|
100
|
+
<div
|
|
101
|
+
class="cms-block-product-description-reviews__reviews-rating inline-flex items-center mt-2"
|
|
102
|
+
>
|
|
97
103
|
<SwProductRating
|
|
98
104
|
:rating="review.points ?? 0"
|
|
99
105
|
:star-size="20"
|
|
100
106
|
:show-count="false"
|
|
101
107
|
/>
|
|
102
|
-
<div
|
|
108
|
+
<div
|
|
109
|
+
class="cms-block-product-description-reviews__reviews-title font-semibold ml-2"
|
|
110
|
+
>
|
|
103
111
|
<p>{{ review.title }}</p>
|
|
104
112
|
</div>
|
|
105
113
|
</div>
|
|
@@ -7,6 +7,7 @@ import type { ValidationRuleWithoutParams } from "@vuelidate/core";
|
|
|
7
7
|
import { minLength, required } from "@vuelidate/validators";
|
|
8
8
|
import { defu } from "defu";
|
|
9
9
|
import { computed, reactive, ref } from "vue";
|
|
10
|
+
|
|
10
11
|
import { useShopwareContext } from "#imports";
|
|
11
12
|
|
|
12
13
|
const props = defineProps<{
|
|
@@ -154,7 +155,10 @@ const invokeRating = (value: number) => {
|
|
|
154
155
|
</script>
|
|
155
156
|
|
|
156
157
|
<template>
|
|
157
|
-
<form
|
|
158
|
+
<form
|
|
159
|
+
class="flex flex-col gap-4 md:gap-5 relative"
|
|
160
|
+
@submit.prevent="invokeSend"
|
|
161
|
+
>
|
|
158
162
|
<div
|
|
159
163
|
v-if="isLoading"
|
|
160
164
|
class="absolute inset-0 flex items-center justify-center z-10 bg-surface-surface/80 rounded-md"
|
|
@@ -171,7 +175,11 @@ const invokeRating = (value: number) => {
|
|
|
171
175
|
<span class="text-sm text-surface-on-surface-variant">{{
|
|
172
176
|
translations.product.reviewsForm.rating
|
|
173
177
|
}}</span>
|
|
174
|
-
<div
|
|
178
|
+
<div
|
|
179
|
+
class="flex flex-row gap-2"
|
|
180
|
+
role="group"
|
|
181
|
+
:aria-label="translations.product.reviewsForm.rating"
|
|
182
|
+
>
|
|
175
183
|
<SwStarIcon
|
|
176
184
|
v-for="index in state.rating || 0"
|
|
177
185
|
:key="`filled-${index}`"
|
|
@@ -217,7 +225,11 @@ const invokeRating = (value: number) => {
|
|
|
217
225
|
<SwExclamationIcon :size="20" />
|
|
218
226
|
</div>
|
|
219
227
|
<div class="flex-1">
|
|
220
|
-
<p
|
|
228
|
+
<p
|
|
229
|
+
v-for="(error, index) in errorMessages"
|
|
230
|
+
:key="index"
|
|
231
|
+
class="text-sm text-states-error"
|
|
232
|
+
>
|
|
221
233
|
{{ error.detail }}
|
|
222
234
|
</p>
|
|
223
235
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { computed, useId } from "vue";
|
|
4
|
+
|
|
4
5
|
import { useCmsTranslations } from "#imports";
|
|
5
6
|
|
|
6
7
|
type Translations = {
|
|
@@ -63,7 +64,9 @@ const sizeClasses = {
|
|
|
63
64
|
};
|
|
64
65
|
</script>
|
|
65
66
|
<template>
|
|
66
|
-
<div
|
|
67
|
+
<div
|
|
68
|
+
class="rounded outline outline-1 outline-offset-[-1px] outline-outline-outline inline-flex"
|
|
69
|
+
>
|
|
67
70
|
<button
|
|
68
71
|
type="button"
|
|
69
72
|
:class="sizeClasses[size]"
|
|
@@ -73,9 +76,13 @@ const sizeClasses = {
|
|
|
73
76
|
>
|
|
74
77
|
-
|
|
75
78
|
</button>
|
|
76
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
class="bg-white border-l border-r border-outline-outline inline-flex flex-col justify-center items-center"
|
|
81
|
+
>
|
|
77
82
|
<!-- visually hidden label for screen readers -->
|
|
78
|
-
<label :for="inputId" class="sr-only">{{
|
|
83
|
+
<label :for="inputId" class="sr-only">{{
|
|
84
|
+
translations.form.quantitySelect.label
|
|
85
|
+
}}</label>
|
|
79
86
|
|
|
80
87
|
<input
|
|
81
88
|
:id="inputId"
|
|
@@ -31,14 +31,20 @@ translations = defu(useCmsTranslations(), translations) as Translations;
|
|
|
31
31
|
</script>
|
|
32
32
|
<template>
|
|
33
33
|
<div class="inline-flex justify-start items-center gap-2">
|
|
34
|
-
<div
|
|
34
|
+
<div
|
|
35
|
+
class="w-2 h-2 bg-states-success rounded-full"
|
|
36
|
+
v-if="availableStock > 0"
|
|
37
|
+
></div>
|
|
35
38
|
<div class="w-2 h-2 bg-states-error rounded-full" v-else></div>
|
|
36
|
-
<span v-if="availableStock >= minPurchase && deliveryTime"
|
|
37
|
-
deliveryTime?.name }}
|
|
39
|
+
<span v-if="availableStock >= minPurchase && deliveryTime"
|
|
40
|
+
>{{ translations.product.deliveryTime }} {{ deliveryTime?.name }}
|
|
38
41
|
</span>
|
|
39
|
-
<span
|
|
42
|
+
<span
|
|
43
|
+
v-else-if="availableStock < minPurchase && deliveryTime && restockTime"
|
|
44
|
+
>
|
|
40
45
|
{{ translations.product.deliveryTime }} {{ restockTime }}
|
|
41
|
-
{{ translations.product.days }} {{ deliveryTime?.name }}</span
|
|
46
|
+
{{ translations.product.days }} {{ deliveryTime?.name }}</span
|
|
47
|
+
>
|
|
42
48
|
<span v-else>{{ translations.product.noAvailable }}</span>
|
|
43
49
|
</div>
|
|
44
|
-
</template>
|
|
50
|
+
</template>
|
|
@@ -4,6 +4,7 @@ import { buildUrlPrefix, getProductRoute } from "@shopware/helpers";
|
|
|
4
4
|
import { defu } from "defu";
|
|
5
5
|
import { computed, ref, unref } from "vue";
|
|
6
6
|
import type { ComputedRef } from "vue";
|
|
7
|
+
|
|
7
8
|
import { useProductConfigurator, useRouter, useUrlResolver } from "#imports";
|
|
8
9
|
import type { Schemas } from "#shopware";
|
|
9
10
|
|
|
@@ -84,7 +85,9 @@ const onHandleChange = async () => {
|
|
|
84
85
|
:key="optionGroup.id"
|
|
85
86
|
class="mt-6"
|
|
86
87
|
>
|
|
87
|
-
<div class="text-sm text-gray-900 font-medium">
|
|
88
|
+
<div class="text-sm text-gray-900 font-medium">
|
|
89
|
+
{{ optionGroup.name }}
|
|
90
|
+
</div>
|
|
88
91
|
<fieldset class="mt-4 flex-1">
|
|
89
92
|
<legend class="sr-only">
|
|
90
93
|
{{ translations.product.chooseA }} {{ optionGroup.name }}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
<script
|
|
1
|
+
<script
|
|
2
|
+
setup
|
|
3
|
+
lang="ts"
|
|
4
|
+
generic="
|
|
2
5
|
ListingFilter extends {
|
|
3
6
|
code: string;
|
|
4
7
|
min?: number;
|
|
5
8
|
max?: number;
|
|
6
9
|
label: string;
|
|
7
10
|
}
|
|
8
|
-
"
|
|
11
|
+
"
|
|
12
|
+
>
|
|
9
13
|
import { useCmsTranslations } from "@shopware/composables";
|
|
10
14
|
import { onClickOutside, useDebounceFn, useEventListener } from "@vueuse/core";
|
|
11
15
|
import { defu } from "defu";
|
|
12
16
|
import { onMounted, reactive, ref, watch } from "vue";
|
|
17
|
+
|
|
13
18
|
import type { Schemas } from "#shopware";
|
|
14
19
|
|
|
15
20
|
const emits =
|
|
@@ -149,15 +154,17 @@ const startDrag = (type: "min" | "max", event: MouseEvent | TouchEvent) => {
|
|
|
149
154
|
@keydown.space.prevent="toggle"
|
|
150
155
|
>
|
|
151
156
|
<div class="flex-1 flex items-center gap-2.5">
|
|
152
|
-
<div
|
|
157
|
+
<div
|
|
158
|
+
class="flex-1 text-surface-on-surface text-base font-bold leading-normal text-left"
|
|
159
|
+
>
|
|
153
160
|
{{ filter.label }}
|
|
154
161
|
</div>
|
|
155
162
|
</div>
|
|
156
|
-
<span
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
<span class="flex items-center justify-center" aria-hidden="true">
|
|
164
|
+
<SwChevronIcon
|
|
165
|
+
:direction="isFilterVisible ? 'up' : 'down'"
|
|
166
|
+
:size="24"
|
|
167
|
+
/>
|
|
161
168
|
</span>
|
|
162
169
|
</div>
|
|
163
170
|
</div>
|
|
@@ -165,52 +172,94 @@ const startDrag = (type: "min" | "max", event: MouseEvent | TouchEvent) => {
|
|
|
165
172
|
|
|
166
173
|
<!-- Filter content -->
|
|
167
174
|
<transition name="filter-collapse">
|
|
168
|
-
<div
|
|
169
|
-
|
|
175
|
+
<div
|
|
176
|
+
v-if="isFilterVisible || displayMode === 'dropdown'"
|
|
177
|
+
:id="filter.code"
|
|
178
|
+
class="self-stretch flex flex-col justify-start items-start gap-2.5"
|
|
179
|
+
>
|
|
170
180
|
<div class="self-stretch flex flex-col justify-start items-start gap-1">
|
|
171
|
-
<div
|
|
181
|
+
<div
|
|
182
|
+
class="self-stretch inline-flex justify-between items-center gap-2"
|
|
183
|
+
>
|
|
172
184
|
<div
|
|
173
|
-
class="w-16 h-10 px-2 py-1 rounded-lg outline outline-1 outline-offset-[-1px] outline-outline-outline-variant inline-flex flex-col justify-center items-start gap-2.5"
|
|
174
|
-
|
|
185
|
+
class="w-16 h-10 px-2 py-1 rounded-lg outline outline-1 outline-offset-[-1px] outline-outline-outline-variant inline-flex flex-col justify-center items-start gap-2.5"
|
|
186
|
+
>
|
|
187
|
+
<input
|
|
188
|
+
type="number"
|
|
189
|
+
:placeholder="translations.listing.min"
|
|
190
|
+
v-model.number="prices.min"
|
|
175
191
|
class="w-full bg-transparent border-none outline-none text-surface-on-surface text-sm font-normal leading-tight"
|
|
176
|
-
@change="
|
|
177
|
-
|
|
192
|
+
@change="
|
|
193
|
+
emits('select-value', {
|
|
194
|
+
code: filter.code,
|
|
195
|
+
value: { min: prices.min, max: prices.max },
|
|
196
|
+
})
|
|
197
|
+
"
|
|
198
|
+
:min="filter.min"
|
|
199
|
+
:max="prices.max"
|
|
200
|
+
/>
|
|
178
201
|
</div>
|
|
179
202
|
<div
|
|
180
|
-
class="w-16 h-10 px-2 py-1 rounded-lg outline outline-1 outline-offset-[-1px] outline-outline-outline-variant inline-flex flex-col justify-center items-start gap-2.5"
|
|
181
|
-
|
|
203
|
+
class="w-16 h-10 px-2 py-1 rounded-lg outline outline-1 outline-offset-[-1px] outline-outline-outline-variant inline-flex flex-col justify-center items-start gap-2.5"
|
|
204
|
+
>
|
|
205
|
+
<input
|
|
206
|
+
type="number"
|
|
207
|
+
:placeholder="translations.listing.max"
|
|
208
|
+
v-model.number="prices.max"
|
|
182
209
|
class="w-full bg-transparent border-none outline-none text-surface-on-surface text-sm font-normal leading-tight"
|
|
183
|
-
@change="
|
|
184
|
-
|
|
210
|
+
@change="
|
|
211
|
+
emits('select-value', {
|
|
212
|
+
code: filter.code,
|
|
213
|
+
value: { min: prices.min, max: prices.max },
|
|
214
|
+
})
|
|
215
|
+
"
|
|
216
|
+
:min="prices.min"
|
|
217
|
+
:max="filter.max"
|
|
218
|
+
/>
|
|
185
219
|
</div>
|
|
186
220
|
</div>
|
|
187
221
|
<!-- Custom slider UI -->
|
|
188
|
-
<div
|
|
222
|
+
<div
|
|
223
|
+
class="relative w-64 h-10 mt-2 mx-auto flex items-center select-none"
|
|
224
|
+
>
|
|
189
225
|
<!-- Track -->
|
|
190
226
|
<div
|
|
191
|
-
class="absolute left-0 top-1/2 -translate-y-1/2 w-full h-2 bg-surface-surface-container-highest rounded-full"
|
|
192
|
-
|
|
227
|
+
class="absolute left-0 top-1/2 -translate-y-1/2 w-full h-2 bg-surface-surface-container-highest rounded-full"
|
|
228
|
+
></div>
|
|
193
229
|
<!-- Active range -->
|
|
194
|
-
<div
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
230
|
+
<div
|
|
231
|
+
class="absolute top-1/2 -translate-y-1/2 h-2 bg-surface-surface-primary rounded-full"
|
|
232
|
+
:style="{
|
|
233
|
+
left:
|
|
234
|
+
((prices.min - (filter.min ?? 0)) /
|
|
235
|
+
((filter.max ?? 100) - (filter.min ?? 0))) *
|
|
236
|
+
100 +
|
|
237
|
+
'%',
|
|
238
|
+
width:
|
|
239
|
+
((prices.max - prices.min) /
|
|
240
|
+
((filter.max ?? 100) - (filter.min ?? 0))) *
|
|
241
|
+
100 +
|
|
242
|
+
'%',
|
|
243
|
+
}"
|
|
244
|
+
></div>
|
|
198
245
|
<!-- Min thumb -->
|
|
199
246
|
<div
|
|
200
247
|
class="absolute top-1/2 -translate-y-1/2 w-5 h-5 bg-brand-primary rounded-full shadow-[2px_2px_10px_0px_rgba(0,0,0,0.15)] cursor-pointer touch-none"
|
|
201
248
|
:style="{
|
|
202
|
-
left: `calc(${((prices.min - (filter.min ?? 0)) / ((filter.max ?? 100) - (filter.min ?? 0))) * 100}% - 10px)
|
|
249
|
+
left: `calc(${((prices.min - (filter.min ?? 0)) / ((filter.max ?? 100) - (filter.min ?? 0))) * 100}% - 10px)`,
|
|
203
250
|
}"
|
|
204
251
|
@mousedown.prevent="startDrag('min', $event)"
|
|
205
|
-
@touchstart.prevent="startDrag('min', $event)"
|
|
252
|
+
@touchstart.prevent="startDrag('min', $event)"
|
|
253
|
+
></div>
|
|
206
254
|
<!-- Max thumb -->
|
|
207
255
|
<div
|
|
208
256
|
class="absolute top-1/2 -translate-y-1/2 w-5 h-5 bg-brand-primary rounded-full shadow-[2px_2px_10px_0px_rgba(0,0,0,0.15)] cursor-pointer touch-none"
|
|
209
257
|
:style="{
|
|
210
|
-
left: `calc(${((prices.max - (filter.min ?? 0)) / ((filter.max ?? 100) - (filter.min ?? 0))) * 100}% - 10px)
|
|
258
|
+
left: `calc(${((prices.max - (filter.min ?? 0)) / ((filter.max ?? 100) - (filter.min ?? 0))) * 100}% - 10px)`,
|
|
211
259
|
}"
|
|
212
260
|
@mousedown.prevent="startDrag('max', $event)"
|
|
213
|
-
@touchstart.prevent="startDrag('max', $event)"
|
|
261
|
+
@touchstart.prevent="startDrag('max', $event)"
|
|
262
|
+
></div>
|
|
214
263
|
</div>
|
|
215
264
|
</div>
|
|
216
265
|
</div>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
>
|
|
14
14
|
import { getTranslatedProperty } from "@shopware/helpers";
|
|
15
15
|
import { computed, ref } from "vue";
|
|
16
|
+
|
|
16
17
|
import type { Schemas } from "#shopware";
|
|
17
18
|
|
|
18
19
|
const {
|
|
@@ -75,15 +76,17 @@ const selectValue = (id: string) => {
|
|
|
75
76
|
@keydown.space.prevent="toggle"
|
|
76
77
|
>
|
|
77
78
|
<div class="flex-1 flex items-center gap-2.5">
|
|
78
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
class="flex-1 text-surface-on-surface text-base font-bold leading-normal text-left"
|
|
81
|
+
>
|
|
79
82
|
{{ filter.label }}
|
|
80
83
|
</div>
|
|
81
84
|
</div>
|
|
82
|
-
<span
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
<span class="flex items-center justify-center" aria-hidden="true">
|
|
86
|
+
<SwChevronIcon
|
|
87
|
+
:direction="isFilterVisible ? 'up' : 'down'"
|
|
88
|
+
:size="24"
|
|
89
|
+
/>
|
|
87
90
|
</span>
|
|
88
91
|
</div>
|
|
89
92
|
</div>
|
|
@@ -91,28 +94,40 @@ const selectValue = (id: string) => {
|
|
|
91
94
|
|
|
92
95
|
<!-- Filter content -->
|
|
93
96
|
<transition name="filter-collapse">
|
|
94
|
-
<div
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class="self-stretch
|
|
97
|
+
<div
|
|
98
|
+
v-if="isFilterVisible || displayMode === 'dropdown'"
|
|
99
|
+
:id="filter.code"
|
|
100
|
+
class="self-stretch flex flex-col justify-start items-start gap-4"
|
|
101
|
+
>
|
|
102
|
+
<fieldset
|
|
103
|
+
class="self-stretch flex flex-col justify-start items-start gap-4"
|
|
101
104
|
>
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
<legend class="sr-only">{{ filter.name }}</legend>
|
|
106
|
+
<label
|
|
107
|
+
v-for="option in filter.options || filter.entities"
|
|
108
|
+
:key="`${option.id}-${isChecked(option.id)}`"
|
|
109
|
+
class="self-stretch inline-flex justify-start items-start gap-2 cursor-pointer"
|
|
110
|
+
>
|
|
111
|
+
<div
|
|
112
|
+
class="w-4 self-stretch pt-[3px] flex justify-start items-start gap-2.5"
|
|
113
|
+
>
|
|
114
|
+
<SwCheckbox
|
|
115
|
+
:model-value="isChecked(option.id)"
|
|
116
|
+
@update:model-value="() => selectValue(option.id)"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<div
|
|
120
|
+
class="flex-1 inline-flex flex-col justify-start items-start gap-0.5"
|
|
121
|
+
>
|
|
122
|
+
<div class="inline-flex justify-start items-center gap-1">
|
|
123
|
+
<div
|
|
124
|
+
class="flex-1 text-surface-on-surface text-base font-normal leading-normal"
|
|
125
|
+
>
|
|
126
|
+
{{ getTranslatedProperty(option, "name") }}
|
|
127
|
+
</div>
|
|
112
128
|
</div>
|
|
113
129
|
</div>
|
|
114
|
-
</
|
|
115
|
-
</label>
|
|
130
|
+
</label>
|
|
116
131
|
</fieldset>
|
|
117
132
|
</div>
|
|
118
133
|
</transition>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"
|
|
10
10
|
>
|
|
11
11
|
import { computed, ref } from "vue";
|
|
12
|
+
|
|
12
13
|
import type { Schemas } from "#shopware";
|
|
13
14
|
|
|
14
15
|
const emits =
|
|
@@ -65,15 +66,17 @@ const toggle = () => {
|
|
|
65
66
|
@keydown.space.prevent="toggle"
|
|
66
67
|
>
|
|
67
68
|
<div class="flex-1 flex items-center gap-2.5">
|
|
68
|
-
<div
|
|
69
|
+
<div
|
|
70
|
+
class="flex-1 text-surface-on-surface text-base font-bold leading-normal text-left"
|
|
71
|
+
>
|
|
69
72
|
{{ filter.label }}
|
|
70
73
|
</div>
|
|
71
74
|
</div>
|
|
72
|
-
<span
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
<span class="flex items-center justify-center" aria-hidden="true">
|
|
76
|
+
<SwChevronIcon
|
|
77
|
+
:direction="isFilterVisible ? 'up' : 'down'"
|
|
78
|
+
:size="24"
|
|
79
|
+
/>
|
|
77
80
|
</span>
|
|
78
81
|
</div>
|
|
79
82
|
</div>
|
|
@@ -81,14 +84,23 @@ const toggle = () => {
|
|
|
81
84
|
|
|
82
85
|
<!-- Filter content -->
|
|
83
86
|
<transition name="filter-collapse">
|
|
84
|
-
<div
|
|
87
|
+
<div
|
|
88
|
+
v-if="isFilterVisible || displayMode === 'dropdown'"
|
|
89
|
+
class="self-stretch flex flex-col justify-start items-start gap-4"
|
|
90
|
+
>
|
|
85
91
|
<div class="flex flex-row items-center gap-2 mt-2">
|
|
86
92
|
<div
|
|
87
93
|
v-for="i in 5"
|
|
88
94
|
:key="i"
|
|
89
|
-
:class="[
|
|
95
|
+
:class="[
|
|
96
|
+
'h-6 w-6 cursor-pointer',
|
|
97
|
+
displayedScore >= i ? 'i-carbon-star-filled' : 'i-carbon-star',
|
|
98
|
+
]"
|
|
90
99
|
@mouseleave="isHoverActive = false"
|
|
91
|
-
@click="
|
|
100
|
+
@click="
|
|
101
|
+
hoverRating(i);
|
|
102
|
+
onChangeRating();
|
|
103
|
+
"
|
|
92
104
|
@mouseover="hoverRating(i)"
|
|
93
105
|
:aria-label="`${i} star${i !== 1 ? 's' : ''}`"
|
|
94
106
|
/>
|