@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
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+
4
+ export interface SwBaseButtonProps {
5
+ variant?:
6
+ | "primary"
7
+ | "secondary"
8
+ | "success"
9
+ | "warning"
10
+ | "outline"
11
+ | "ghost";
12
+ size?: "small" | "medium" | "large";
13
+ disabled?: boolean;
14
+ loading?: boolean;
15
+ type?: "button" | "submit" | "reset";
16
+ block?: boolean;
17
+ }
18
+
19
+ defineOptions({
20
+ inheritAttrs: false,
21
+ });
22
+
23
+ const props = withDefaults(defineProps<SwBaseButtonProps>(), {
24
+ variant: "primary",
25
+ size: "medium",
26
+ disabled: false,
27
+ loading: false,
28
+ type: "button",
29
+ block: false,
30
+ });
31
+
32
+ const emit = defineEmits<{
33
+ click: [event: MouseEvent];
34
+ }>();
35
+
36
+ const buttonClasses = computed(() => {
37
+ const classes = [
38
+ "inline-flex justify-center items-center gap-2 rounded font-bold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2",
39
+ ];
40
+
41
+ const sizeClasses = {
42
+ small: "px-3 py-2 text-sm",
43
+ medium: "px-4 py-3 text-base",
44
+ large: "px-6 py-4 text-lg",
45
+ };
46
+ classes.push(sizeClasses[props.size]);
47
+
48
+ const variantClasses = {
49
+ primary:
50
+ "bg-brand-primary hover:bg-brand-primary-hover text-brand-on-primary focus:ring-brand-primary",
51
+ secondary:
52
+ "bg-brand-secondary hover:bg-brand-secondary-hover text-brand-on-secondary focus:ring-brand-secondary",
53
+ success:
54
+ "bg-states-success hover:opacity-90 text-white focus:ring-states-success transition-opacity",
55
+ warning:
56
+ "bg-states-warning hover:opacity-90 text-white focus:ring-states-warning transition-opacity",
57
+ outline:
58
+ "border-2 border-brand-primary text-brand-primary hover:bg-brand-primary hover:text-brand-on-primary focus:ring-brand-primary",
59
+ ghost:
60
+ "bg-transparent text-surface-on-surface-variant hover:text-surface-on-surface focus:ring-surface-on-surface",
61
+ };
62
+
63
+ if (props.disabled || props.loading) {
64
+ classes.push(
65
+ "bg-surface-surface-disabled text-surface-on-surface cursor-not-allowed opacity-50",
66
+ );
67
+ } else {
68
+ classes.push(variantClasses[props.variant]);
69
+ }
70
+
71
+ if (props.block) {
72
+ classes.push("w-full");
73
+ }
74
+
75
+ return classes.join(" ");
76
+ });
77
+
78
+ const handleClick = (event: MouseEvent) => {
79
+ if (!props.disabled && !props.loading) {
80
+ emit("click", event);
81
+ }
82
+ };
83
+ </script>
84
+
85
+ <template>
86
+ <button
87
+ :type="type"
88
+ :class="buttonClasses"
89
+ :disabled="disabled || loading"
90
+ @click="handleClick"
91
+ v-bind="$attrs"
92
+ >
93
+ <div v-if="loading" class="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin"></div>
94
+
95
+ <span :class="{ 'opacity-0': loading }">
96
+ <slot />
97
+ </span>
98
+ </button>
99
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ src,
4
+ size = 24,
5
+ alt = "",
6
+ } = defineProps<{
7
+ src: string;
8
+ size?: number;
9
+ alt?: string;
10
+ }>();
11
+ </script>
12
+
13
+ <template>
14
+ <NuxtImg :src="src" :alt="alt" :width="size" :height="size" />
15
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ const model = defineModel<boolean>({
3
+ required: true,
4
+ });
5
+
6
+ const {
7
+ label,
8
+ description,
9
+ disabled = false,
10
+ } = defineProps<{
11
+ label?: string;
12
+ description?: string;
13
+ disabled?: boolean;
14
+ }>();
15
+ </script>
16
+
17
+ <template>
18
+ <label class="flex items-start gap-2">
19
+ <input
20
+ class="accent-brand-primary w-4 h-4 focus-within:outline-2 focus-within:outline-brand-primary focus-within:outline focus-within:outline-offset-[2px] focus-within"
21
+ type="checkbox"
22
+ v-model="model"
23
+ :disabled
24
+ />
25
+ <div v-if="label || description">
26
+ <p
27
+ v-if="label"
28
+ :class="
29
+ disabled
30
+ ? 'text-surface-on-surface-disabled'
31
+ : 'text-surface-on-surface'
32
+ "
33
+ >
34
+ {{ label }}
35
+ </p>
36
+ <p
37
+ v-if="description"
38
+ class="text-sm"
39
+ :class="
40
+ disabled
41
+ ? 'text-surface-on-surface-disabled'
42
+ : 'text-surface-on-surface-variant'
43
+ "
44
+ >
45
+ {{ description }}
46
+ </p>
47
+ </div>
48
+ </label>
49
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import CheckmarkFilledSvg from "@cms-assets/icons/check-circle.svg";
3
+ import CheckmarkSvg from "@cms-assets/icons/checkmark.svg";
4
+
5
+ const {
6
+ filled = false,
7
+ size = 24,
8
+ alt = "",
9
+ } = defineProps<{
10
+ filled?: boolean;
11
+ size?: number;
12
+ alt?: string;
13
+ }>();
14
+ </script>
15
+
16
+ <template>
17
+ <NuxtImg
18
+ :src="filled ? CheckmarkFilledSvg : CheckmarkSvg"
19
+ :alt="alt"
20
+ :width="size"
21
+ :height="size"
22
+ />
23
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ import ChevronSvg from "@cms-assets/icons/chevron.svg";
3
+ import { computed } from "vue";
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ direction?: "up" | "down" | "left" | "right";
8
+ size?: number;
9
+ alt?: string;
10
+ }>(),
11
+ {
12
+ direction: "down",
13
+ size: 24,
14
+ alt: "",
15
+ },
16
+ );
17
+
18
+ const rotationClass = computed(() => {
19
+ const rotations = {
20
+ down: "",
21
+ up: "rotate-180",
22
+ left: "rotate-90",
23
+ right: "-rotate-90",
24
+ };
25
+ return rotations[props.direction];
26
+ });
27
+ </script>
28
+
29
+ <template>
30
+ <NuxtImg
31
+ :src="ChevronSvg"
32
+ :alt="alt"
33
+ :class="['transition-transform', rotationClass]"
34
+ :width="size"
35
+ :height="size"
36
+ />
37
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import ExclamationCircleSvg from "@cms-assets/icons/exclamation-circle.svg";
3
+
4
+ const { size = 24 } = defineProps<{
5
+ size?: number;
6
+ }>();
7
+ </script>
8
+
9
+ <template>
10
+ <SwBaseIcon :src="ExclamationCircleSvg" :size="size" alt="Error" />
11
+ </template>
@@ -0,0 +1,32 @@
1
+ <script lang="ts" setup>
2
+ const { type = "primary" } = defineProps<{
3
+ type?: "primary" | "secondary" | "tertiary" | "outline" | "ghost";
4
+ }>();
5
+
6
+ const styles = {
7
+ primary:
8
+ "bg-brand-primary hover:focus:bg-brand-primary-hover text-brand-on-primary",
9
+ secondary:
10
+ "bg-brand-secondary hover:focus:bg-brand-secondary-hover text-brand-on-secondary",
11
+ tertiary:
12
+ "bg-brand-tertiary hover:focus:bg-brand-tertiary-hover text-brand-on-tertiary",
13
+ outline:
14
+ "text-brand-primary bg-transparent hover:focus:bg-surface-surface-container outline outline-2 outline-offset-[-2px] outline-brand-primary",
15
+ ghost: "bg-transparent hover:focus:bg-surface-surface-container",
16
+ };
17
+ </script>
18
+
19
+ <template>
20
+ <button
21
+ :class="[
22
+ styles[type],
23
+ {
24
+ 'bg-surface-on-surface-disabled text-surface-surface-disabled':
25
+ $attrs.disabled,
26
+ 'w-10 h-10': type !== 'ghost',
27
+ },
28
+ ]"
29
+ >
30
+ <slot />
31
+ </button>
32
+ </template>
@@ -0,0 +1,26 @@
1
+ <script setup lang="ts">
2
+ const modelValue = defineModel<string | null>();
3
+
4
+ const { selected } = defineProps<{
5
+ selected: boolean;
6
+ }>();
7
+ </script>
8
+ <template>
9
+ <input
10
+ type="radio"
11
+ class="sr-only"
12
+ v-bind="$attrs"
13
+ v-model="modelValue"
14
+ name="shipping-method"
15
+ />
16
+ <div
17
+ class="w-4 h-4 rounded-full border border-outline-outline border-spacing-1 flex items-center justify-center"
18
+ >
19
+ <div
20
+ :class="{
21
+ 'bg-brand-primary': selected,
22
+ }"
23
+ class="w-2.5 h-2.5 rounded-full"
24
+ ></div>
25
+ </div>
26
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ import StarEmptySvg from "@cms-assets/icons/star-empty.svg";
3
+ import StarFilledSvg from "@cms-assets/icons/star-filled.svg";
4
+
5
+ const { filled = true, size = 20 } = defineProps<{
6
+ filled?: boolean;
7
+ size?: number;
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <NuxtImg
13
+ :src="filled ? StarFilledSvg : StarEmptySvg"
14
+ alt="Star"
15
+ :width="size"
16
+ :height="size"
17
+ />
18
+ </template>
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from "vue";
3
+
4
+ const props = defineProps<{
5
+ name?: string;
6
+ ariaLabel?: string;
7
+ label?: string;
8
+ description?: string;
9
+ disabled?: boolean;
10
+ }>();
11
+
12
+ const modelValue = defineModel<boolean | null>();
13
+
14
+ const emits = defineEmits<{
15
+ change: [v: boolean | null];
16
+ }>();
17
+
18
+ const value = computed(() => !!modelValue.value);
19
+
20
+ const localChecked = ref<boolean>(value.value);
21
+ watch(value, (v) => {
22
+ localChecked.value = v;
23
+ });
24
+
25
+ const inputName = props.name ?? "switch-button";
26
+ const inputId = `switch-${inputName}`;
27
+ const inputRef = ref<HTMLInputElement | null>(null);
28
+
29
+ const activateByKeyboard = () => {
30
+ inputRef.value?.click();
31
+ };
32
+ const toggleState = (next?: boolean) => {
33
+ if (props.disabled) return;
34
+ const v = typeof next === "boolean" ? next : !localChecked.value;
35
+ localChecked.value = v;
36
+ modelValue.value = v;
37
+ emits("change", v);
38
+ };
39
+
40
+ const activateByClick = (ev?: Event) => {
41
+ ev?.stopPropagation();
42
+ toggleState();
43
+ };
44
+ </script>
45
+
46
+ <template>
47
+ <div class="w-full inline-flex flex-col justify-start items-start gap-2">
48
+ <div class="self-stretch inline-flex justify-start items-center gap-3">
49
+ <!-- left label that toggles the input via for="#inputId" -->
50
+ <label :for="inputId"
51
+ class="flex-1 flex justify-start items-center gap-1 text-surface-on-surface text-base font-normal leading-normal cursor-pointer"
52
+ :class="{ 'cursor-not-allowed': disabled }">
53
+ <span v-if="$slots.default">
54
+ <slot />
55
+ </span>
56
+ <span v-else-if="label">{{ label }}</span>
57
+ </label>
58
+
59
+ <div class="w-10 h-6 relative">
60
+ <label class="inline-block cursor-pointer" :class="{ 'cursor-not-allowed': disabled }">
61
+ <input ref="inputRef" :id="inputId" type="checkbox" :name="inputName" class="sr-only" :checked="localChecked"
62
+ @change="toggleState()"
63
+ :disabled="disabled" :aria-label="ariaLabel || undefined" v-bind="$attrs" />
64
+ <span role="switch" :aria-checked="localChecked" :tabindex="disabled ? -1 : 0"
65
+ class="w-10 h-6 relative rounded-full flex-shrink-0 inline-block switch-track cursor-pointer"
66
+ :class="localChecked ? 'bg-brand-secondary switch-track--on' : 'bg-surface-surface-container-highest'"
67
+ @keydown.space.prevent="activateByKeyboard" @click.prevent="activateByClick">
68
+ <span class="w-4 h-4 rounded-full absolute switch-knob"
69
+ :style="{ left: localChecked ? '19px' : '4px', top: '4px' }"
70
+ :class="localChecked ? 'bg-brand-on-secondary' : 'bg-surface-on-surface-variant'"></span>
71
+ </span>
72
+ </label>
73
+ </div>
74
+ </div>
75
+ <div v-if="description || $slots.description" class="self-stretch inline-flex justify-start items-center gap-2.5">
76
+ <div class="flex-1 justify-start text-surface-on-surface-variant text-sm font-normal leading-tight">
77
+ <slot name="description">{{ description }}</slot>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <style scoped>
84
+ .switch-track {
85
+ transition: background-color 180ms ease-in-out, box-shadow 180ms ease-in-out;
86
+ }
87
+
88
+ .switch-track--on {
89
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.08);
90
+ /* subtle glow when on */
91
+ }
92
+
93
+ .switch-knob {
94
+ transition: left 180ms cubic-bezier(.2, .9, .2, 1), top 180ms cubic-bezier(.2, .9, .2, 1), background-color 120ms linear;
95
+ }
96
+
97
+ .switch-track:focus {
98
+ outline: none;
99
+ }
100
+ </style>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import UserSvg from "@cms-assets/icons/user.svg";
3
+
4
+ const { size = 24 } = defineProps<{
5
+ size?: number;
6
+ }>();
7
+ </script>
8
+
9
+ <template>
10
+ <SwBaseIcon :src="UserSvg" :size="size" alt="User" />
11
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ filled?: boolean;
5
+ }>(),
6
+ {
7
+ filled: false,
8
+ },
9
+ );
10
+ </script>
11
+ <template>
12
+ <div class="relative">
13
+ <!-- use when filled icon is ready -->
14
+ <!-- <Icon size="1rem" name="shopware:heart" v-if="type !== 'filled'" class="w-6 h-5 block hover:cursor-pointer" :class="[
15
+ styles[type]
16
+ ]" /> -->
17
+ <div class="i-carbon-favorite w-6 h-5 hover:cursor-pointer" v-if="!filled"></div>
18
+ <div class="i-carbon-favorite-filled w-6 h-5 hover:cursor-pointer" v-else></div>
19
+ </div>
20
+ </template>
@@ -0,0 +1,27 @@
1
+ import { useAppConfig } from "nuxt/app";
2
+
3
+ /**
4
+ * Composable that provides an SVG placeholder image as a data URI
5
+ * Note: CSS variables and currentColor don't work in data URIs since SVGs are rendered in isolation
6
+ *
7
+ * @param color - Hex color for the placeholder (optional - defaults to appConfig.imagePlaceholder.color or #543B95)
8
+ * @returns Base64-encoded SVG data URI
9
+ */
10
+ export function useImagePlaceholder(color?: string) {
11
+ const appConfig = useAppConfig();
12
+ const placeholderColor =
13
+ color || appConfig.imagePlaceholder?.color || "#543B95";
14
+
15
+ const placeholderSvg = `data:image/svg+xml;base64,${btoa(
16
+ `
17
+ <svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
18
+ <rect width="96" height="96" rx="8" fill="${placeholderColor}" opacity="0.08"/>
19
+ <g transform="translate(36, 36)">
20
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M3 22H21C21.5523 22 22 21.5523 22 21V17L17.7071 12.7071C17.3166 12.3166 16.6834 12.3166 16.2929 12.7071L10.5 18.5C10.2239 18.7761 9.77614 18.7761 9.5 18.5C9.22386 18.2239 9.22386 17.7761 9.5 17.5L11 16L8.70711 13.7071C8.31658 13.3166 7.68342 13.3166 7.29289 13.7071L2 19V21C2 21.5523 2.44772 22 3 22ZM21 24H3C1.34315 24 0 22.6569 0 21V3C0 1.34315 1.34315 0 3 0H21C22.6569 0 24 1.34315 24 3V21C24 22.6569 22.6569 24 21 24ZM6.5 9C7.88071 9 9 7.88071 9 6.5C9 5.11929 7.88071 4 6.5 4C5.11929 4 4 5.11929 4 6.5C4 7.88071 5.11929 9 6.5 9Z" fill="${placeholderColor}" opacity="0.4"/>
21
+ </g>
22
+ </svg>
23
+ `.trim(),
24
+ )}`;
25
+
26
+ return placeholderSvg;
27
+ }
@@ -1,5 +1,10 @@
1
1
  import { defineComponent, onMounted, ref } from "vue";
2
2
 
3
+ /**
4
+ * @deprecated This component is deprecated and will be removed in the next major release.
5
+ * Use Nuxt's built-in `<ClientOnly>` component instead.
6
+ * @see https://nuxt.com/docs/api/components/client-only
7
+ */
3
8
  export const ClientOnly = defineComponent({
4
9
  setup(_, { slots }) {
5
10
  const init = ref(false);