@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.
- package/assets/symbols/chevron-right.svg +1 -1
- package/components/Blokkli/HeroImage/index.vue +1 -1
- package/components/Blokkli/Teasers/icon.svg +3 -3
- package/components/DesignSystem/Atoms/Button.vue +86 -36
- package/components/DesignSystem/Atoms/Caption.vue +10 -10
- package/components/DesignSystem/Atoms/Container.vue +10 -6
- package/components/DesignSystem/Atoms/GridRow.vue +4 -3
- package/components/DesignSystem/Atoms/LinkListItem.vue +31 -8
- package/components/DesignSystem/Molecules/ButtonGroup.vue +18 -3
- package/components/DesignSystem/Molecules/Card.vue +86 -31
- package/components/DesignSystem/Molecules/IconText.vue +37 -16
- package/components/DesignSystem/Molecules/MediaLightbox.vue +213 -58
- package/components/DesignSystem/Molecules/MediaSlider.vue +88 -25
- package/components/DesignSystem/Molecules/Partnership.vue +40 -9
- package/components/DesignSystem/Molecules/Table.vue +90 -25
- package/components/DesignSystem/Molecules/TitleComposition.vue +15 -12
- package/components/DesignSystem/Organisms/ContentBlock.vue +61 -15
- package/components/DesignSystem/Organisms/FacetedListing.vue +170 -0
- package/components/DesignSystem/Organisms/FlipTeaser.vue +119 -47
- package/components/DesignSystem/Organisms/HeaderHero.vue +167 -0
- package/components/DesignSystem/Organisms/HeaderTextImage.vue +71 -0
- package/components/DesignSystem/Organisms/LogoElement.vue +33 -10
- package/components/DesignSystem/Organisms/Map.vue +11 -6
- package/components/DesignSystem/Organisms/SearchResults.vue +41 -0
- package/components/DesignSystem/Organisms/TeamElement.vue +84 -20
- package/components/DesignSystem/Organisms/TeaserCard.vue +87 -34
- package/components/DesignSystem/Organisms/Testimonial.vue +82 -12
- package/components/DesignSystem/Organisms/TimelineElement.vue +42 -12
- package/components/DesignSystem/Organisms/TwoColumnTextElement.vue +44 -9
- package/components/DesignSystem/Pages/Search.vue +39 -0
- package/components/Drupal/Breadcrumb/index.vue +16 -5
- package/components/Drupal/Messages/index.vue +16 -5
- package/components/Drupal/Webform/fragment.graphql +8 -5
- package/components/Drupal/Webform/index.vue +20 -9
- package/components/Drupal/Webform/query.graphql +18 -16
- package/components/Form/CheckboxInput.vue +6 -6
- package/components/Form/CheckboxInputs.vue +5 -5
- package/components/Form/FileUploadInput.vue +6 -6
- package/components/Form/RadioButtonsInput.vue +5 -5
- package/components/Form/SelectInput.vue +7 -7
- package/components/Form/SubmitButton.vue +1 -1
- package/components/Form/TextAreaInput.vue +2 -2
- package/components/Form/TextEmailInput.vue +2 -2
- package/components/Form/TextInput.vue +2 -2
- package/components/Form/TextPhoneInput.vue +2 -2
- package/components/Form/composables/useFormElement.ts +5 -5
- package/components/Form/utils/types.ts +8 -7
- package/components/Media/Icon/index.vue +10 -6
- package/components/Media/Image/index.vue +26 -20
- package/components/Media/RemoteVideo/index.vue +34 -21
- package/components/Media/Video/index.vue +6 -5
- package/components/Node/Icms/Page/Teaser/index.vue +6 -4
- package/components/Node/Icms/Page/index.vue +2 -2
- package/components/Paragraph/Button/Element/index.vue +9 -2
- package/components/Paragraph/CallToAction/index.vue +43 -10
- package/components/Paragraph/Card/Element/index.vue +2 -1
- package/components/Paragraph/Card/Layout/index.vue +29 -10
- package/components/Paragraph/Contact/index.vue +41 -12
- package/components/Paragraph/Downloads/index.vue +82 -33
- package/components/Paragraph/FAQ/Element/index.vue +41 -12
- package/components/Paragraph/FAQ/Layout/index.vue +47 -13
- package/components/Paragraph/FlipTeasers/index.vue +26 -6
- package/components/Paragraph/Gallery/index.vue +42 -10
- package/components/Paragraph/HeroImage/index.vue +62 -16
- package/components/Paragraph/IconText/Element/index.vue +7 -1
- package/components/Paragraph/IconText/Layout/index.vue +39 -12
- package/components/Paragraph/Introduction/index.vue +50 -15
- package/components/Paragraph/Logo/Element/fragment.graphql +1 -0
- package/components/Paragraph/Logo/Element/index.vue +7 -2
- package/components/Paragraph/Media/index.vue +32 -31
- package/components/Paragraph/Partnerships/Element/fragment.graphql +1 -0
- package/components/Paragraph/Partnerships/Element/index.vue +7 -0
- package/components/Paragraph/Partnerships/Layout/index.vue +100 -27
- package/components/Paragraph/SingleTestimonial/index.vue +58 -15
- package/components/Paragraph/Table/index.vue +32 -9
- package/components/Paragraph/Tabs/Element/index.vue +44 -10
- package/components/Paragraph/Tabs/Layout/index.vue +123 -37
- package/components/Paragraph/Team/Element/index.vue +18 -1
- package/components/Paragraph/Team/Layout/index.vue +30 -7
- package/components/Paragraph/Teasers/index.vue +33 -7
- package/components/Paragraph/Testimonial/Element/index.vue +30 -5
- package/components/Paragraph/Testimonial/Layout/index.vue +78 -24
- package/components/Paragraph/Text/index.vue +32 -10
- package/components/Paragraph/TextMedia/index.vue +44 -13
- package/components/Paragraph/Timeline/Element/index.vue +23 -7
- package/components/Paragraph/Timeline/Layout/index.vue +232 -67
- package/components/Paragraph/TwoColumnText/Element/index.vue +20 -3
- package/components/Paragraph/TwoColumnText/Layout/index.vue +8 -1
- package/components/Paragraph/iFrame/index.vue +31 -11
- package/components/Search/Facets/FacetDropdownUI.vue +351 -77
- package/components/Search/Facets/FacetedSearchUI.vue +257 -101
- package/components/Search/Facets/fragment.graphql +5 -1
- package/components/Search/Input/index.vue +125 -0
- package/components/Section/Footer/index.vue +170 -30
- package/components/Section/Header/Buttons.vue +34 -0
- package/components/Section/Header/DesktopMainMenu.vue +160 -0
- package/components/Section/Header/DesktopMainMenuDropdown.vue +170 -0
- package/components/Section/Header/LanguageDropdown.vue +95 -64
- package/components/Section/Header/LanguageLinks.vue +39 -0
- package/components/Section/Header/Logo.vue +23 -0
- package/components/Section/Header/MainButtons.vue +31 -0
- package/components/Section/Header/MenuIcon.vue +60 -49
- package/components/Section/Header/MetaButtons.vue +31 -0
- package/components/Section/Header/MetaMenu.vue +55 -53
- package/components/Section/Header/MobileMainMenu.vue +23 -0
- package/components/Section/Header/MobileMainMenuItem.vue +191 -0
- package/components/Section/Header/index.vue +182 -186
- package/composables/useMenu.ts +2 -2
- package/composables/useMobileMenuOverlayBus.ts +44 -0
- package/composables/useNavigationOffset.ts +2 -2
- package/composables/useTheme.ts +107 -0
- package/formkit.theme.ts +541 -586
- package/layouts/default.vue +7 -35
- package/layouts/error.vue +7 -32
- package/nuxt.config.ts +13 -0
- package/package.json +1 -1
- package/plugins/initData.ts +8 -5
- package/queries/query.initData.graphql +4 -2
- package/tailwind.config.ts +11 -11
- package/types/global.ts +1 -0
- package/components/Section/Header/GlobalMenu.vue +0 -335
- 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="
|
|
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 {
|
|
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?:
|
|
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
|
|
43
|
+
base: themedClass('iconTextBase', 'flex items-center gap-6 py-5 px-6'),
|
|
33
44
|
variants: {
|
|
34
45
|
colorScheme: {
|
|
35
|
-
none: 'bg-
|
|
36
|
-
dark:
|
|
37
|
-
|
|
38
|
-
|
|
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:
|
|
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-
|
|
51
|
-
dark: 'bg-gray-
|
|
52
|
-
light: 'bg-gray-50',
|
|
53
|
-
accent:
|
|
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="
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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="
|
|
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
|
|
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
|
|
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="
|
|
44
|
-
@init-api="onCarouselInit"
|
|
87
|
+
:class="themedClass('carouselClass', 'flex h-full w-full flex-col')"
|
|
45
88
|
>
|
|
46
|
-
<template
|
|
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
|
|
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
|
|
134
|
+
<ChevronRight
|
|
135
|
+
:size="24"
|
|
136
|
+
:class="themedClass('carouselNextButtonIconClass')"
|
|
137
|
+
/>
|
|
68
138
|
</button>
|
|
69
139
|
|
|
70
|
-
<CarouselContent
|
|
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="
|
|
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="
|
|
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="
|
|
93
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
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="
|
|
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="
|
|
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
|
-
|
|
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 {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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="
|
|
3
|
-
<Carousel
|
|
4
|
-
|
|
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="
|
|
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
|
-
:
|
|
17
|
-
|
|
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="
|
|
24
|
-
|
|
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
|
|
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="
|
|
31
|
-
|
|
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
|
|
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="
|
|
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
|
-
|
|
91
|
+
|
|
92
|
+
const themedClass = useTheme()
|
|
55
93
|
|
|
56
94
|
const props = defineProps<{
|
|
57
95
|
media?: (MediaFragment | null)[] | undefined
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
imageAspectRatios?: string
|
|
97
|
+
imageSizes?: string
|
|
98
|
+
imageLoading?: 'lazy' | 'eager' | undefined
|
|
99
|
+
colorScheme?: ColorScheme
|
|
61
100
|
showCaption?: boolean
|
|
62
|
-
|
|
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: '
|
|
124
|
+
base: themedClass('carouselButtonBase', 'border-0 shadow-none'),
|
|
77
125
|
variants: {
|
|
78
126
|
colorScheme: {
|
|
79
|
-
|
|
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>
|