@iqual/nuxt-icms 1.3.0 → 1.4.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 (122) hide show
  1. package/assets/symbols/chevron-right.svg +1 -1
  2. package/components/Blokkli/HeroImage/index.vue +1 -1
  3. package/components/Blokkli/Teasers/icon.svg +3 -3
  4. package/components/DesignSystem/Atoms/Button.vue +86 -36
  5. package/components/DesignSystem/Atoms/Caption.vue +10 -10
  6. package/components/DesignSystem/Atoms/Container.vue +10 -6
  7. package/components/DesignSystem/Atoms/GridRow.vue +4 -3
  8. package/components/DesignSystem/Atoms/LinkListItem.vue +31 -8
  9. package/components/DesignSystem/Molecules/ButtonGroup.vue +18 -3
  10. package/components/DesignSystem/Molecules/Card.vue +86 -31
  11. package/components/DesignSystem/Molecules/IconText.vue +37 -16
  12. package/components/DesignSystem/Molecules/MediaLightbox.vue +213 -58
  13. package/components/DesignSystem/Molecules/MediaSlider.vue +88 -25
  14. package/components/DesignSystem/Molecules/Partnership.vue +40 -9
  15. package/components/DesignSystem/Molecules/Table.vue +90 -25
  16. package/components/DesignSystem/Molecules/TitleComposition.vue +15 -12
  17. package/components/DesignSystem/Organisms/ContentBlock.vue +61 -15
  18. package/components/DesignSystem/Organisms/FacetedListing.vue +170 -0
  19. package/components/DesignSystem/Organisms/FlipTeaser.vue +119 -47
  20. package/components/DesignSystem/Organisms/HeaderHero.vue +167 -0
  21. package/components/DesignSystem/Organisms/HeaderTextImage.vue +71 -0
  22. package/components/DesignSystem/Organisms/LogoElement.vue +33 -10
  23. package/components/DesignSystem/Organisms/Map.vue +11 -6
  24. package/components/DesignSystem/Organisms/SearchResults.vue +41 -0
  25. package/components/DesignSystem/Organisms/TeamElement.vue +84 -20
  26. package/components/DesignSystem/Organisms/TeaserCard.vue +87 -34
  27. package/components/DesignSystem/Organisms/Testimonial.vue +82 -12
  28. package/components/DesignSystem/Organisms/TimelineElement.vue +42 -12
  29. package/components/DesignSystem/Organisms/TwoColumnTextElement.vue +44 -9
  30. package/components/DesignSystem/Pages/Search.vue +39 -0
  31. package/components/Drupal/Breadcrumb/index.vue +16 -5
  32. package/components/Drupal/Messages/index.vue +16 -5
  33. package/components/Drupal/Webform/fragment.graphql +8 -5
  34. package/components/Drupal/Webform/index.vue +20 -9
  35. package/components/Drupal/Webform/query.graphql +18 -16
  36. package/components/Form/CheckboxInput.vue +6 -6
  37. package/components/Form/CheckboxInputs.vue +5 -5
  38. package/components/Form/FileUploadInput.vue +6 -6
  39. package/components/Form/RadioButtonsInput.vue +5 -5
  40. package/components/Form/SelectInput.vue +7 -7
  41. package/components/Form/SubmitButton.vue +1 -1
  42. package/components/Form/TextAreaInput.vue +2 -2
  43. package/components/Form/TextEmailInput.vue +2 -2
  44. package/components/Form/TextInput.vue +2 -2
  45. package/components/Form/TextPhoneInput.vue +2 -2
  46. package/components/Form/composables/useFormElement.ts +5 -5
  47. package/components/Form/utils/types.ts +8 -7
  48. package/components/Media/Icon/index.vue +10 -6
  49. package/components/Media/Image/index.vue +26 -20
  50. package/components/Media/RemoteVideo/index.vue +34 -21
  51. package/components/Media/Video/index.vue +6 -5
  52. package/components/Node/Icms/Page/Teaser/index.vue +6 -4
  53. package/components/Node/Icms/Page/index.vue +2 -2
  54. package/components/Paragraph/Button/Element/index.vue +9 -2
  55. package/components/Paragraph/CallToAction/index.vue +43 -10
  56. package/components/Paragraph/Card/Element/index.vue +2 -1
  57. package/components/Paragraph/Card/Layout/index.vue +29 -10
  58. package/components/Paragraph/Contact/index.vue +41 -12
  59. package/components/Paragraph/Downloads/index.vue +82 -33
  60. package/components/Paragraph/FAQ/Element/index.vue +41 -12
  61. package/components/Paragraph/FAQ/Layout/index.vue +47 -13
  62. package/components/Paragraph/FlipTeasers/index.vue +26 -6
  63. package/components/Paragraph/Gallery/index.vue +42 -10
  64. package/components/Paragraph/HeroImage/index.vue +62 -16
  65. package/components/Paragraph/IconText/Element/index.vue +7 -1
  66. package/components/Paragraph/IconText/Layout/index.vue +39 -12
  67. package/components/Paragraph/Introduction/index.vue +50 -15
  68. package/components/Paragraph/Logo/Element/fragment.graphql +1 -0
  69. package/components/Paragraph/Logo/Element/index.vue +7 -2
  70. package/components/Paragraph/Media/index.vue +32 -31
  71. package/components/Paragraph/Partnerships/Element/fragment.graphql +1 -0
  72. package/components/Paragraph/Partnerships/Element/index.vue +7 -0
  73. package/components/Paragraph/Partnerships/Layout/index.vue +100 -27
  74. package/components/Paragraph/SingleTestimonial/index.vue +58 -15
  75. package/components/Paragraph/Table/index.vue +32 -9
  76. package/components/Paragraph/Tabs/Element/index.vue +44 -10
  77. package/components/Paragraph/Tabs/Layout/index.vue +123 -37
  78. package/components/Paragraph/Team/Element/index.vue +18 -1
  79. package/components/Paragraph/Team/Layout/index.vue +30 -7
  80. package/components/Paragraph/Teasers/index.vue +33 -7
  81. package/components/Paragraph/Testimonial/Element/index.vue +30 -5
  82. package/components/Paragraph/Testimonial/Layout/index.vue +78 -24
  83. package/components/Paragraph/Text/index.vue +32 -10
  84. package/components/Paragraph/TextMedia/index.vue +44 -13
  85. package/components/Paragraph/Timeline/Element/index.vue +23 -7
  86. package/components/Paragraph/Timeline/Layout/index.vue +232 -67
  87. package/components/Paragraph/TwoColumnText/Element/index.vue +20 -3
  88. package/components/Paragraph/TwoColumnText/Layout/index.vue +8 -1
  89. package/components/Paragraph/iFrame/index.vue +31 -11
  90. package/components/Search/Facets/FacetDropdownUI.vue +351 -77
  91. package/components/Search/Facets/FacetedSearchUI.vue +257 -101
  92. package/components/Search/Facets/fragment.graphql +5 -1
  93. package/components/Search/Input/index.vue +125 -0
  94. package/components/Section/Footer/index.vue +170 -30
  95. package/components/Section/Header/Buttons.vue +34 -0
  96. package/components/Section/Header/DesktopMainMenu.vue +160 -0
  97. package/components/Section/Header/DesktopMainMenuDropdown.vue +170 -0
  98. package/components/Section/Header/LanguageDropdown.vue +95 -64
  99. package/components/Section/Header/LanguageLinks.vue +39 -0
  100. package/components/Section/Header/Logo.vue +23 -0
  101. package/components/Section/Header/MainButtons.vue +31 -0
  102. package/components/Section/Header/MenuIcon.vue +60 -49
  103. package/components/Section/Header/MetaButtons.vue +31 -0
  104. package/components/Section/Header/MetaMenu.vue +55 -53
  105. package/components/Section/Header/MobileMainMenu.vue +23 -0
  106. package/components/Section/Header/MobileMainMenuItem.vue +191 -0
  107. package/components/Section/Header/index.vue +182 -186
  108. package/composables/useMenu.ts +2 -2
  109. package/composables/useMobileMenuOverlayBus.ts +44 -0
  110. package/composables/useNavigationOffset.ts +2 -2
  111. package/composables/useTheme.ts +107 -0
  112. package/formkit.theme.ts +541 -586
  113. package/layouts/default.vue +7 -35
  114. package/layouts/error.vue +7 -32
  115. package/nuxt.config.ts +13 -0
  116. package/package.json +1 -1
  117. package/plugins/initData.ts +8 -5
  118. package/queries/query.initData.graphql +4 -2
  119. package/tailwind.config.ts +11 -11
  120. package/types/global.ts +1 -0
  121. package/components/Section/Header/GlobalMenu.vue +0 -335
  122. package/components/Section/Header/LanguageSwitcher.vue +0 -37
@@ -4,38 +4,55 @@
4
4
  <Media
5
5
  v-if="icon"
6
6
  :media="icon"
7
- mediaClass="size-10"
7
+ :mediaClass="themedClass('mediaClass', 'size-10')"
8
+ :figcaptionClass="themedClass('figcaptionClass')"
9
+ :elementClass="themedClass('elementClass')"
8
10
  />
9
11
  </div>
10
12
  <DesignSystemOrganismsContentBlock
11
13
  :title="title"
12
14
  :titleWeight="titleWeight"
13
- titleClass="text-xl lg:text-xl font-semibold mb-2"
15
+ :titleClass="
16
+ themedClass('titleClass', 'text-xl lg:text-xl font-semibold mb-2')
17
+ "
14
18
  :text="text"
19
+ :textClass="themedClass('textClass')"
15
20
  />
16
21
  </div>
17
22
  </template>
18
23
 
19
24
  <script lang="ts" setup>
20
- import { tv } from 'tailwind-variants'
25
+ import type { ColorScheme } from '@iqual/nuxt-icms/types/global'
21
26
  import type { MediaFragment } from '#graphql-operations'
27
+ import { tv } from 'tailwind-variants'
28
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
29
+
30
+ const themedClass = useTheme()
22
31
 
23
32
  defineProps<{
24
33
  title?: string
25
34
  titleWeight?: string
26
35
  text?: string
27
36
  icon?: MediaFragment
28
- colorScheme?: string
37
+ colorScheme?: ColorScheme
38
+ titleClass?: string
39
+ textClass?: string
29
40
  }>()
30
41
 
31
42
  const iconText = tv({
32
- base: 'flex items-center gap-6 rounded-lg py-5 px-6 shadow-custom-small',
43
+ base: themedClass('iconTextBase', 'flex items-center gap-6 py-5 px-6'),
33
44
  variants: {
34
45
  colorScheme: {
35
- none: 'bg-white',
36
- dark: 'bg-gray-900 text-white',
37
- light: 'bg-white',
38
- accent: 'bg-primary-700 text-white',
46
+ none: themedClass('iconTextVariantsColorSchemeNone', 'bg-gray-50'),
47
+ dark: themedClass(
48
+ 'iconTextVariantsColorSchemeDark',
49
+ 'bg-gray-900 text-white',
50
+ ),
51
+ light: themedClass('iconTextVariantsColorSchemeLight', 'bg-white'),
52
+ accent: themedClass(
53
+ 'iconTextVariantsColorSchemeAccent',
54
+ 'bg-primary-700 text-white',
55
+ ),
39
56
  },
40
57
  },
41
58
  defaultVariants: {
@@ -44,19 +61,23 @@ const iconText = tv({
44
61
  })
45
62
 
46
63
  const iconTextIcon = tv({
47
- base: 'w-20 shrink-0 rounded-full flex justify-center items-center aspect-square',
64
+ base: themedClass(
65
+ 'iconTextIconBase',
66
+ 'w-20 shrink-0 rounded-full flex justify-center items-center aspect-square',
67
+ ),
48
68
  variants: {
49
69
  colorScheme: {
50
- none: 'bg-gray-50',
51
- dark: 'bg-gray-400',
52
- light: 'bg-gray-50',
53
- accent: 'bg-primary-400',
70
+ none: themedClass('iconTextIconVariantsColorSchemeNone', 'bg-white'),
71
+ dark: themedClass('iconTextIconVariantsColorSchemeDark', 'bg-gray-950'),
72
+ light: themedClass('iconTextIconVariantsColorSchemeLight', 'bg-gray-50'),
73
+ accent: themedClass(
74
+ 'iconTextIconVariantsColorSchemeAccent',
75
+ 'bg-primary-600',
76
+ ),
54
77
  },
55
78
  },
56
79
  defaultVariants: {
57
80
  colorScheme: 'none',
58
81
  },
59
82
  })
60
-
61
-
62
83
  </script>
@@ -1,100 +1,221 @@
1
1
  <template>
2
2
  <Teleport to="body">
3
3
  <Transition
4
- enter-active-class="transition-opacity duration-300"
5
- enter-from-class="opacity-0"
6
- enter-to-class="opacity-100"
7
- leave-active-class="transition-opacity duration-300"
8
- leave-from-class="opacity-100"
9
- leave-to-class="opacity-0"
4
+ :enter-active-class="
5
+ themedClass(
6
+ 'transitionEnterActiveClass',
7
+ 'transition-opacity duration-300',
8
+ )
9
+ "
10
+ :enter-from-class="themedClass('transitionEnterFromClass', 'opacity-0')"
11
+ :enter-to-class="themedClass('transitionEnterToClass', 'opacity-100')"
12
+ :leave-active-class="
13
+ themedClass(
14
+ 'transitionLeaveActiveClass',
15
+ 'transition-opacity duration-300',
16
+ )
17
+ "
18
+ :leave-from-class="themedClass('transitionLeaveFromClass', 'opacity-100')"
19
+ :leave-to-class="themedClass('transitionLeaveToClass', 'opacity-0')"
10
20
  >
11
21
  <div
12
22
  v-if="isOpen"
13
23
  ref="lightboxContainer"
14
- class="fixed inset-0 z-50 flex items-center justify-center bg-black/90 backdrop-blur-sm"
15
24
  @click.self="$emit('close')"
25
+ :class="
26
+ themedClass(
27
+ 'lightboxContainerClass',
28
+ 'fixed inset-0 z-[103] flex items-center justify-center bg-black/90 backdrop-blur-sm',
29
+ )
30
+ "
16
31
  >
17
32
  <!-- Close button -->
18
33
  <button
19
34
  type="button"
20
- class="absolute right-4 top-4 z-10 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black"
21
- aria-label="Close lightbox"
35
+ :aria-label="
36
+ $texts('lightbox.close_button_aria_label', 'Close lightbox')
37
+ "
22
38
  @click="$emit('close')"
39
+ :class="
40
+ themedClass(
41
+ 'lightboxCloseButtonClass',
42
+ 'absolute right-4 top-4 z-10 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black',
43
+ )
44
+ "
23
45
  >
24
- <X :size="24" />
46
+ <X :size="24" :class="themedClass('lightboxCloseButtonIconClass')" />
25
47
  </button>
26
48
 
27
49
  <!-- Loading spinner -->
28
50
  <div
29
51
  v-if="isLoading"
30
- class="absolute inset-0 flex items-center justify-center"
31
52
  aria-live="polite"
32
- aria-label="Loading media"
53
+ :aria-label="
54
+ $texts('lightbox.loading_spinner_aria_label', 'Loading media...')
55
+ "
56
+ :class="
57
+ themedClass(
58
+ 'lightboxLoadingSpinnerContainerClass',
59
+ 'absolute inset-0 flex items-center justify-center',
60
+ )
61
+ "
33
62
  >
34
- <div class="h-8 w-8 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
63
+ <div
64
+ :class="
65
+ themedClass(
66
+ 'lightboxLoadingSpinnerClass',
67
+ 'h-8 w-8 animate-spin rounded-full border-2 border-white border-t-transparent',
68
+ )
69
+ "
70
+ ></div>
35
71
  </div>
36
72
 
37
73
  <!-- Carousel-based media container -->
38
- <div class="relative h-full w-full max-h-[90vh] max-w-[90vw] flex items-center justify-center">
74
+ <div
75
+ :class="
76
+ themedClass(
77
+ 'carouselContainerClass',
78
+ 'relative flex h-full max-h-[90vh] w-full max-w-[90vw] items-center justify-center',
79
+ )
80
+ "
81
+ >
39
82
  <Carousel
40
83
  v-if="mediaObjects.length > 0"
84
+ @init-api="onCarouselInit"
41
85
  :opts="carouselOptions"
42
86
  orientation="horizontal"
43
- class="w-full h-full flex flex-col"
44
- @init-api="onCarouselInit"
87
+ :class="themedClass('carouselClass', 'flex h-full w-full flex-col')"
45
88
  >
46
- <template #default="{ scrollPrev, scrollNext, canScrollPrev, canScrollNext }">
89
+ <template
90
+ #default="{
91
+ scrollPrev,
92
+ scrollNext,
93
+ canScrollPrev,
94
+ canScrollNext,
95
+ }"
96
+ >
47
97
  <!-- Navigation buttons using carousel methods -->
48
98
  <button
49
99
  v-if="mediaObjects.length > 1"
50
100
  type="button"
51
- class="absolute left-4 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black disabled:opacity-50 disabled:cursor-not-allowed"
52
- aria-label="Previous media"
53
101
  :disabled="!canScrollPrev"
54
102
  @click="scrollPrev"
103
+ :aria-label="
104
+ $texts('carousel.prev_button_aria_label', 'Previous media')
105
+ "
106
+ :class="
107
+ themedClass(
108
+ 'carouselPrevButtonClass',
109
+ 'absolute left-4 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50',
110
+ )
111
+ "
55
112
  >
56
- <ChevronLeft :size="24" />
113
+ <ChevronLeft
114
+ :size="24"
115
+ :class="themedClass('carouselPrevButtonIconClass')"
116
+ />
57
117
  </button>
58
118
 
59
119
  <button
60
120
  v-if="mediaObjects.length > 1"
61
121
  type="button"
62
- class="absolute right-4 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black disabled:opacity-50 disabled:cursor-not-allowed"
63
- aria-label="Next media"
64
122
  :disabled="!canScrollNext"
65
123
  @click="scrollNext"
124
+ :aria-label="
125
+ $texts('carousel.next_button_aria_label', 'Next media')
126
+ "
127
+ :class="
128
+ themedClass(
129
+ 'carouselNextButtonClass',
130
+ 'absolute right-4 top-1/2 z-10 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black disabled:cursor-not-allowed disabled:opacity-50',
131
+ )
132
+ "
66
133
  >
67
- <ChevronRight :size="24" />
134
+ <ChevronRight
135
+ :size="24"
136
+ :class="themedClass('carouselNextButtonIconClass')"
137
+ />
68
138
  </button>
69
139
 
70
- <CarouselContent class="h-full flex-1">
140
+ <CarouselContent
141
+ :class="themedClass('carouselContentClass', 'h-full flex-1')"
142
+ >
71
143
  <CarouselItem
72
144
  v-for="(media, index) in mediaObjects"
73
145
  :key="index"
74
- class="flex h-full w-full items-center justify-center min-h-0"
146
+ :class="
147
+ themedClass(
148
+ 'carouselItemClass',
149
+ 'flex h-full min-h-0 w-full items-center justify-center',
150
+ )
151
+ "
75
152
  >
76
153
  <!-- Use Media component directly -->
77
154
  <Media
78
155
  :media="media"
79
156
  :show-caption="false"
80
- :video-autoplay="index === currentIndex && media.type === 'MediaVideo'"
157
+ :video-autoplay="
158
+ index === currentIndex && media.type === 'MediaVideo'
159
+ "
81
160
  :video-muted="true"
82
161
  :video-controls="true"
83
- :media-class="getMediaWrapperClass(media)"
84
- :element-class="getMediaElementClass(media)"
85
- class="max-h-full max-w-full"
86
162
  @load="handleMediaLoad"
87
163
  @error="handleMediaError"
164
+ :mediaClass="getMediaWrapperClass(media)"
165
+ :elementClass="getMediaElementClass(media)"
88
166
  />
89
167
 
90
168
  <!-- Fallback/error state -->
91
169
  <div
92
- v-if="!['MediaImage', 'MediaVideo', 'MediaRemoteVideo', 'MediaIcon'].includes(media.type)"
93
- class="flex flex-col items-center text-white"
170
+ v-if="
171
+ ![
172
+ 'MediaImage',
173
+ 'MediaVideo',
174
+ 'MediaRemoteVideo',
175
+ 'MediaIcon',
176
+ ].includes(media.type)
177
+ "
178
+ :class="
179
+ themedClass(
180
+ 'mediaErrorContainerClass',
181
+ 'flex flex-col items-center text-white',
182
+ )
183
+ "
94
184
  >
95
- <AlertCircle :size="48" class="mb-4 text-red-400" />
96
- <p class="text-lg">Unable to display this media</p>
97
- <p class="text-sm text-gray-400">Unsupported format: {{ media.type }}</p>
185
+ <AlertCircle
186
+ :size="48"
187
+ :class="
188
+ themedClass(
189
+ 'mediaErrorAlertCircleClass',
190
+ 'mb-4 text-red-400',
191
+ )
192
+ "
193
+ />
194
+ <p
195
+ :class="themedClass('mediaErrorMessageClass', 'text-lg')"
196
+ >
197
+ {{
198
+ $texts(
199
+ 'carousel.media_error_message',
200
+ 'Unable to display this media',
201
+ )
202
+ }}
203
+ </p>
204
+ <p
205
+ :class="
206
+ themedClass(
207
+ 'mediaErrorDescriptionClass',
208
+ 'text-sm text-gray-400',
209
+ )
210
+ "
211
+ >
212
+ {{
213
+ $texts(
214
+ 'carousel.media_unsupported_format',
215
+ 'Unsupported format',
216
+ )
217
+ }}: {{ media.type }}
218
+ </p>
98
219
  </div>
99
220
  </CarouselItem>
100
221
  </CarouselContent>
@@ -105,9 +226,14 @@
105
226
  <!-- Caption display -->
106
227
  <div
107
228
  v-if="currentCaption"
108
- class="absolute bottom-0 left-4 h-[5vh] right-4 z-10 text-center leading-[5vh]"
229
+ :class="
230
+ themedClass(
231
+ 'captionContainerClass',
232
+ 'absolute bottom-0 left-4 right-4 z-10 h-[5vh] text-center leading-[5vh]',
233
+ )
234
+ "
109
235
  >
110
- <p class="text-white">
236
+ <p :class="themedClass('captionClass', 'text-white')">
111
237
  {{ currentCaption }}
112
238
  </p>
113
239
  </div>
@@ -115,10 +241,17 @@
115
241
  <!-- Counter -->
116
242
  <div
117
243
  v-if="mediaObjects.length > 1"
118
- class="absolute top-2 left-2 z-10 rounded px-3 py-1 text-sm text-white"
244
+ :class="
245
+ themedClass(
246
+ 'counterContainerClass',
247
+ 'absolute left-2 top-2 z-10 px-3 py-1 text-sm text-white',
248
+ )
249
+ "
119
250
  aria-live="polite"
120
251
  >
121
- {{ currentIndex + 1 }} / {{ mediaObjects.length }}
252
+ <span :class="themedClass('counterClass')">
253
+ {{ currentIndex + 1 }} / {{ mediaObjects.length }}
254
+ </span>
122
255
  </div>
123
256
  </div>
124
257
  </Transition>
@@ -126,12 +259,16 @@
126
259
  </template>
127
260
 
128
261
  <script lang="ts" setup>
262
+ import type { MediaFragment } from '#graphql-operations'
263
+ import type { CarouselApi } from '@iqual/nuxt-icms/components/ui/carousel'
129
264
  import { ref, computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
130
265
  import { ChevronLeft, ChevronRight, X, AlertCircle } from 'lucide-vue-next'
131
- import { Carousel, CarouselContent, CarouselItem } from '@iqual/nuxt-icms/components/ui/carousel'
132
- import type { CarouselApi } from '@iqual/nuxt-icms/components/ui/carousel'
133
- import { Media } from '#components'
134
- import type { MediaFragment } from '#graphql-operations'
266
+ import {
267
+ Carousel,
268
+ CarouselContent,
269
+ CarouselItem,
270
+ } from '@iqual/nuxt-icms/components/ui/carousel'
271
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
135
272
 
136
273
  interface Props {
137
274
  mediaObjects: MediaFragment[]
@@ -146,11 +283,14 @@ interface Emits {
146
283
  }
147
284
 
148
285
  const props = withDefaults(defineProps<Props>(), {
149
- captions: () => []
286
+ captions: () => [],
150
287
  })
151
288
 
152
289
  const emit = defineEmits<Emits>()
153
290
 
291
+ const themedClass = useTheme()
292
+ const { $texts } = useNuxtApp()
293
+
154
294
  // Refs
155
295
  const lightboxContainer = ref<HTMLElement>()
156
296
  const isLoading = ref(false)
@@ -165,16 +305,22 @@ const carouselOptions = computed(() => ({
165
305
  loop: true,
166
306
  skipSnaps: false,
167
307
  dragFree: false,
168
- containScroll: 'trimSnaps' as const
308
+ containScroll: 'trimSnaps' as const,
169
309
  }))
170
310
 
171
311
  // Helper to get wrapper classes for media containers
172
312
  const getMediaWrapperClass = (media: MediaFragment) => {
173
313
  switch (media.type) {
174
314
  case 'MediaRemoteVideo':
175
- return 'w-full h-full flex items-center justify-center'
315
+ return themedClass(
316
+ 'mediaClassMediaRemoteVideo',
317
+ 'max-w-full max-h-full w-full h-full flex items-center justify-center',
318
+ )
176
319
  default:
177
- return 'w-full h-full flex items-center justify-center'
320
+ return themedClass(
321
+ 'mediaClassDefault',
322
+ 'max-w-full max-h-full w-full h-full flex items-center justify-center',
323
+ )
178
324
  }
179
325
  }
180
326
 
@@ -182,13 +328,19 @@ const getMediaWrapperClass = (media: MediaFragment) => {
182
328
  const getMediaElementClass = (media: MediaFragment) => {
183
329
  switch (media.type) {
184
330
  case 'MediaImage':
185
- return 'max-h-full max-w-full object-contain'
331
+ return themedClass(
332
+ 'elementClassMediaImage',
333
+ 'max-h-full max-w-full object-contain',
334
+ )
186
335
  case 'MediaVideo':
187
- return 'max-h-full max-w-full'
336
+ return themedClass('elementClassMediaVideo', 'max-h-full max-w-full')
188
337
  case 'MediaRemoteVideo':
189
- return 'w-full h-full max-w-[90vw] max-h-[90vh]'
338
+ return themedClass(
339
+ 'elementClassMediaRemoteVideo',
340
+ 'w-full h-full max-w-[90vw] max-h-[90vh]',
341
+ )
190
342
  default:
191
- return ''
343
+ return themedClass('elementClassMediaRemoteVideo')
192
344
  }
193
345
  }
194
346
 
@@ -205,7 +357,7 @@ const handleMediaError = () => {
205
357
  // Carousel event handlers
206
358
  const onCarouselInit = (api: CarouselApi) => {
207
359
  carouselApi.value = api
208
-
360
+
209
361
  if (api) {
210
362
  api.on('select', () => {
211
363
  const selectedIndex = api.selectedScrollSnap()
@@ -264,10 +416,13 @@ onUnmounted(() => {
264
416
  })
265
417
 
266
418
  // Watch for lightbox opening
267
- watch(() => props.isOpen, (isOpen) => {
268
- if (isOpen) {
269
- focusLightbox()
270
- updateCarousel()
271
- }
272
- })
273
- </script>
419
+ watch(
420
+ () => props.isOpen,
421
+ (isOpen) => {
422
+ if (isOpen) {
423
+ focusLightbox()
424
+ updateCarousel()
425
+ }
426
+ },
427
+ )
428
+ </script>
@@ -1,42 +1,76 @@
1
1
  <template>
2
- <div :class="cn('@container', 'my-6', sliderClass)">
3
- <Carousel :active="media && media?.length > 1">
4
- <CarouselContent>
2
+ <div :class="themedClass('containerClass', '@container my-6')">
3
+ <Carousel
4
+ :active="media && media?.length > 1"
5
+ :class="themedClass('carouselClass')"
6
+ >
7
+ <CarouselContent :class="themedClass('carouselContentClass')">
5
8
  <CarouselItem
6
9
  v-for="(element, index) in media"
7
10
  :key="index"
8
- class="flex cursor-pointer flex-col justify-center pl-4"
11
+ :class="
12
+ themedClass(
13
+ 'carouselItemClass',
14
+ 'flex cursor-pointer flex-col justify-center pl-4',
15
+ )
16
+ "
9
17
  @click="openLightbox(index)"
10
18
  >
11
19
  <Media
12
20
  v-if="element"
13
21
  :media="element"
14
22
  :showCaption="showCaption"
15
- :mediaClass="mediaClass"
16
- :elementClass="elementClass"
17
- :figcaptionClass="figcaptionClass"
23
+ :mediaClass="themedClass('mediaClass')"
24
+ :figcaptionClass="
25
+ themedClass('figcaptionClass', 'mt-2 italic text-center')
26
+ "
27
+ :elementClass="themedClass('elementClass', 'w-full')"
18
28
  />
19
29
  </CarouselItem>
20
30
  </CarouselContent>
21
31
  <CarouselPrevious
22
32
  v-if="media && media?.length > 1"
23
- class="@3xs:left-8 @2xs:left-10 @md:left-16 left-4"
24
- :class="carouselButton({ colorScheme })"
33
+ :class="
34
+ cn(
35
+ carouselButton({ colorScheme }),
36
+ themedClass(
37
+ 'carouselButtonPrevClass',
38
+ '@3xs:left-8 @2xs:left-10 @md:left-16 left-4',
39
+ ),
40
+ )
41
+ "
25
42
  >
26
- <ChevronLeft :size="24" :stroke-width="1" />
43
+ <ChevronLeft
44
+ :size="24"
45
+ :stroke-width="1"
46
+ :class="themedClass('chevronLeftClass')"
47
+ />
27
48
  </CarouselPrevious>
28
49
  <CarouselNext
29
50
  v-if="media && media?.length > 1"
30
- class="@3xs:right-8 @2xs:right-10 @md:right-16 right-4"
31
- :class="carouselButton({ colorScheme })"
51
+ :class="
52
+ cn(
53
+ carouselButton({ colorScheme }),
54
+ themedClass(
55
+ 'carouselButtonNextClass',
56
+ '@3xs:right-8 @2xs:right-10 @md:right-16 right-4',
57
+ ),
58
+ )
59
+ "
32
60
  >
33
- <ChevronRight :size="24" :stroke-width="1" />
61
+ <ChevronRight
62
+ :size="12"
63
+ :stroke-width="1"
64
+ :class="themedClass('chevronRightClass')"
65
+ />
34
66
  </CarouselNext>
35
67
  </Carousel>
36
68
 
37
69
  <!-- MediaLightbox with Media component integration -->
38
70
  <DesignSystemMoleculesMediaLightbox
39
- :media-objects="media?.filter((item): item is MediaFragment => Boolean(item)) || []"
71
+ :media-objects="
72
+ media?.filter((item): item is MediaFragment => Boolean(item)) || []
73
+ "
40
74
  :is-open="isOpen"
41
75
  :current-index="currentIndex"
42
76
  :captions="captions"
@@ -47,20 +81,34 @@
47
81
  </template>
48
82
 
49
83
  <script lang="ts" setup>
84
+ import type { ColorScheme } from '@iqual/nuxt-icms/types/global'
85
+ import type { MediaFragment } from '#graphql-operations'
86
+ import { tv } from 'tailwind-variants'
50
87
  import cn from '@iqual/nuxt-icms/composables/useCn'
88
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
51
89
  import { useMediaLightbox } from '@iqual/nuxt-icms/composables/useMediaLightbox'
52
- import { tv } from 'tailwind-variants'
53
90
  import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
54
- import type { MediaFragment } from '#graphql-operations'
91
+
92
+ const themedClass = useTheme()
55
93
 
56
94
  const props = defineProps<{
57
95
  media?: (MediaFragment | null)[] | undefined
58
- sliderClass?: string
59
- mediaClass?: string
60
- colorScheme?: 'white'
96
+ imageAspectRatios?: string
97
+ imageSizes?: string
98
+ imageLoading?: 'lazy' | 'eager' | undefined
99
+ colorScheme?: ColorScheme
61
100
  showCaption?: boolean
62
- figcaptionClass?: string
101
+ containerClass?: string
102
+ mediaClass?: string
63
103
  elementClass?: string
104
+ figcaptionClass?: string
105
+ carouselClass?: string
106
+ carouselContentClass?: string
107
+ carouselItemClass?: string
108
+ carouselButtonPrevClass?: string
109
+ carouselButtonPrevIconClass?: string
110
+ carouselButtonNextClass?: string
111
+ carouselButtonNextIconClass?: string
64
112
  }>()
65
113
 
66
114
  const {
@@ -73,11 +121,26 @@ const {
73
121
  } = useMediaLightbox(props.media)
74
122
 
75
123
  const carouselButton = tv({
76
- base: 'bg-white text-black ring-black',
124
+ base: themedClass('carouselButtonBase', 'border-0 shadow-none'),
77
125
  variants: {
78
126
  colorScheme: {
79
- white: 'bg-black text-white ring-white'
80
- }
81
- }
127
+ none: themedClass(
128
+ 'carouselButtonVariantsColorSchemeNone',
129
+ 'bg-gray-50 text-black hover:bg-gray-200',
130
+ ),
131
+ dark: themedClass(
132
+ 'carouselButtonVariantsColorSchemeDark',
133
+ 'bg-gray-900 text-white hover:bg-black',
134
+ ),
135
+ light: themedClass(
136
+ 'carouselButtonVariantsColorSchemeLight',
137
+ 'bg-white text-black hover:bg-gray-200',
138
+ ),
139
+ accent: themedClass(
140
+ 'carouselButtonVariantsColorSchemeAccent',
141
+ 'bg-primary text-white hover:bg-primary-600',
142
+ ),
143
+ },
144
+ },
82
145
  })
83
- </script>
146
+ </script>