@iqual/nuxt-icms 1.3.1 → 1.4.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.
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 +78 -20
  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 +24 -0
  101. package/components/Section/Header/MainButtons.vue +31 -0
  102. package/components/Section/Header/MenuIcon.vue +61 -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 +183 -185
  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 +75 -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
@@ -1,24 +1,25 @@
1
1
  <template>
2
- <label for="searchterm" class="sr-only">
2
+ <label for="searchterm" :class="label({ colorScheme })">
3
3
  {{ $texts('search.search_field_label', 'Suchbegriff') }}
4
4
  </label>
5
- <div v-click-away="clickAway" class="relative flex" role="search">
6
- <SpriteSymbol
7
- name="magnifyingglass"
8
- class="absolute left-4 top-1/2 size-5 -translate-y-1/2 text-primary"
9
- />
5
+ <div
6
+ v-click-away="clickAway"
7
+ :class="themedClass('containerClass', 'relative flex')"
8
+ role="search"
9
+ >
10
+ <SpriteSymbol name="magnifyingglass" :class="icon({ colorScheme })" />
10
11
  <input
11
12
  id="searchInput"
12
- ref="input"
13
+ ref="inputElement"
13
14
  v-model="searchTermInternal"
14
15
  type="search"
15
16
  autocomplete="off"
16
17
  name="searchterm"
17
- :class="inputClass"
18
+ :class="cn(input({ colorScheme }), inputClass)"
18
19
  spellcheck="false"
19
20
  role="searchbox"
20
21
  aria-owns="searchterm-suggestions"
21
- aria-label="Search input"
22
+ :aria-label="$texts('search.search_field_aria_label', 'Search input')"
22
23
  :placeholder="
23
24
  $texts('search.search_field_placeholder', 'Suchbegriff eingeben')
24
25
  "
@@ -29,19 +30,22 @@
29
30
  </template>
30
31
 
31
32
  <script lang="ts" setup>
32
- const props = withDefaults(
33
- defineProps<{
34
- modelValue?: string | null
35
- inputClass?: string
36
- }>(),
37
- {
38
- inputClass: 'rounded-lg bg-gray-50 py-3 pr-3 pl-12',
39
- },
40
- )
33
+ import type { ColorScheme } from '@iqual/nuxt-icms/types/global'
34
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
35
+ import { tv } from 'tailwind-variants'
36
+ import cn from '@iqual/nuxt-icms/composables/useCn'
37
+
38
+ const props = defineProps<{
39
+ modelValue?: string | null
40
+ inputClass?: string
41
+ colorScheme?: ColorScheme
42
+ }>()
43
+
44
+ const themedClass = useTheme('SearchInput')
41
45
 
42
46
  const searchTermInternal = ref('')
43
47
  const isFocused = ref(false)
44
- const input = ref<HTMLInputElement | null>(null)
48
+ const inputElement = ref<HTMLInputElement | null>(null)
45
49
  const { $texts } = useNuxtApp()
46
50
 
47
51
  const emit = defineEmits(['update:modelValue'])
@@ -61,7 +65,61 @@ function clickAway() {
61
65
  isFocused.value = false
62
66
  }
63
67
 
68
+ const label = tv({
69
+ base: themedClass('labelBase', 'sr-only'),
70
+ variants: {
71
+ colorScheme: {
72
+ none: themedClass('labelVariantsColorSchemeNone'),
73
+ dark: themedClass('labelVariantsColorSchemeDark'),
74
+ light: themedClass('labelVariantsColorSchemeLight'),
75
+ accent: themedClass('labelVariantsColorSchemeAccent'),
76
+ },
77
+ },
78
+ })
79
+
80
+ const icon = tv({
81
+ base: themedClass(
82
+ 'iconBase',
83
+ 'absolute left-4 top-1/2 size-5 -translate-y-1/2',
84
+ ),
85
+ variants: {
86
+ colorScheme: {
87
+ none: themedClass('iconVariantsColorSchemeNone', 'text-primary'),
88
+ dark: themedClass('iconVariantsColorSchemeDark', 'text-white'),
89
+ light: themedClass('iconVariantsColorSchemeLight', 'text-primary'),
90
+ accent: themedClass('iconVariantsColorSchemeAccent', 'text-black'),
91
+ },
92
+ },
93
+ })
94
+
95
+ const input = tv({
96
+ base: themedClass(
97
+ 'inputBase',
98
+ 'py-3 pl-12 pr-3 focus:outline-none focus-visible:ring-2',
99
+ ),
100
+ variants: {
101
+ colorScheme: {
102
+ none: themedClass(
103
+ 'inputVariantsColorSchemeNone',
104
+ 'bg-gray-50 focus-visible:ring-primary',
105
+ ),
106
+ dark: themedClass(
107
+ 'inputVariantsColorSchemeDark',
108
+ 'bg-white focus-visible:ring-primary',
109
+ ),
110
+ light: themedClass(
111
+ 'inputVariantsColorSchemeLight',
112
+ 'bg-gray-100 focus-visible:ring-primary',
113
+ ),
114
+ accent: themedClass(
115
+ 'inputVariantsColorSchemeAccent',
116
+ 'bg-white focus-visible:ring-white',
117
+ ),
118
+ },
119
+ },
120
+ })
121
+
64
122
  onMounted(() => {
65
123
  searchTermInternal.value = props.modelValue || ''
66
124
  })
67
- </script>
125
+ </script>
@@ -2,52 +2,144 @@
2
2
  <footer>
3
3
  <DesignSystemAtomsContainer
4
4
  :spacing="{ top: 'none', bottom: 'none' }"
5
- class="border-y border-y-gray-100 pb-10 pt-16"
5
+ :class="
6
+ themedClass('containerClass', 'border-y border-y-gray-100 pb-10 pt-16')
7
+ "
6
8
  >
7
- <DesignSystemAtomsGridRow class="grid-cols-3 lg:gap-36">
8
- <div class="col-span-3 mb-6 lg:col-span-1">
9
+ <DesignSystemAtomsGridRow
10
+ :class="themedClass('rowClass', 'grid-cols-3 lg:gap-36')"
11
+ >
12
+ <!-- First column -->
13
+ <div
14
+ :class="
15
+ themedClass('firstColumnClass', 'col-span-3 mb-6 lg:col-span-1')
16
+ "
17
+ >
9
18
  <Media
10
19
  v-if="companyLogo"
11
20
  :media="companyLogo"
12
- mediaClass="mb-8 w-32"
13
21
  imageSizes="xs:128px"
22
+ :mediaClass="themedClass('mediaClass', 'mb-8 w-32')"
23
+ :figcaptionClass="themedClass('figcaptionClass')"
24
+ :elementClass="themedClass('elementClass')"
14
25
  />
15
26
 
16
27
  <DesignSystemOrganismsContentBlock
17
28
  :title="companyName"
18
- :titleWeight="'h5'"
29
+ :titleWeight="'p'"
19
30
  :titleStyle="'heading-5'"
31
+ :titleClass="themedClass('companyNameClass')"
20
32
  :text="companyDescription"
33
+ :textClass="themedClass('companyDescriptionClass')"
21
34
  />
22
35
  </div>
36
+
37
+ <!-- Second column -->
23
38
  <div
24
- class="col-span-3 mb-6 flex flex-col gap-8 lg:col-span-1 lg:flex-row"
39
+ :class="
40
+ themedClass(
41
+ 'secondColumnClass',
42
+ 'col-span-3 mb-6 flex flex-col gap-8 lg:col-span-1 lg:flex-row',
43
+ )
44
+ "
25
45
  >
26
- <ul v-for="(link, index) in footerMenuLinks" :key="index">
27
- <p class="mb-5 font-bold" v-text="link.label" />
46
+ <ul
47
+ v-for="(link, index) in footerMenuLinks"
48
+ :key="index"
49
+ :class="themedClass('listClass')"
50
+ >
51
+ <li :class="themedClass('listItemClass', 'mb-4')">
52
+ <template v-if="link.href">
53
+ <NuxtLink
54
+ :to="link.href"
55
+ :title="link.label"
56
+ :rel="link.rel"
57
+ :target="link.target"
58
+ :class="
59
+ cn(
60
+ themedClass(
61
+ 'linkClass',
62
+ 'hover:text-primary transition-colors',
63
+ ),
64
+ link.additionalClass,
65
+ )
66
+ "
67
+ >
68
+ {{ link.label }}
69
+ </NuxtLink>
70
+ </template>
71
+ <template v-else>
72
+ <span
73
+ aria-disabled="true"
74
+ :class="
75
+ themedClass('noLinkClass', 'cursor-default font-bold')
76
+ "
77
+ >
78
+ {{ link.label }}
79
+ </span>
80
+ </template>
81
+ </li>
28
82
 
83
+ <!-- Sub-links -->
29
84
  <li
30
- v-for="(subLink, index) in link?.children"
31
- :key="index"
32
- class="mb-3 last:mb-0"
85
+ v-for="(subLink, subIndex) in link?.children"
86
+ :key="subIndex"
87
+ :class="themedClass('subListItemClass', 'mb-4 last:mb-0')"
33
88
  >
34
89
  <NuxtLink
35
90
  v-if="subLink?.href"
36
- :to="subLink?.href"
91
+ :to="subLink.href"
37
92
  :title="subLink.label"
38
- class="transition-colors hover:text-primary-500 focus:text-primary-500"
39
- >{{ subLink.label }}</NuxtLink>
93
+ :rel="subLink.rel"
94
+ :target="subLink.target"
95
+ :class="
96
+ cn(
97
+ themedClass(
98
+ 'subLinkClass',
99
+ 'hover:text-primary transition-colors',
100
+ ),
101
+ subLink.additionalClass,
102
+ )
103
+ "
104
+ >
105
+ {{ subLink.label }}
106
+ </NuxtLink>
107
+ <template v-else>
108
+ <span
109
+ aria-disabled="true"
110
+ :class="themedClass('subNoLinkClass', 'cursor-default')"
111
+ >
112
+ {{ subLink.label }}
113
+ </span>
114
+ </template>
40
115
  </li>
41
116
  </ul>
42
117
  </div>
43
- <div class="col-span-3 mb-6 flex flex-col gap-6 lg:col-span-1">
44
- <DesignSystemOrganismsContentBlock :text="address" />
45
- <div>
118
+
119
+ <!-- Third column -->
120
+ <div
121
+ :class="
122
+ themedClass(
123
+ 'thirdColumnClass',
124
+ 'col-span-3 mb-6 flex flex-col gap-6 lg:col-span-1',
125
+ )
126
+ "
127
+ >
128
+ <DesignSystemOrganismsContentBlock
129
+ :text="address"
130
+ :textClass="themedClass('addressClass')"
131
+ />
132
+ <div :class="themedClass('contactInfoContainerClass')">
46
133
  <NuxtLink
47
134
  v-if="phoneNumber"
48
135
  :to="`tel:${phoneNumber}`"
49
136
  :title="phoneNumber"
50
- class="mb-2 block w-fit font-medium text-black hover:text-primary-500 motion-safe:transition-colors motion-safe:duration-200"
137
+ :class="
138
+ themedClass(
139
+ 'phoneNumberClass',
140
+ 'hover:text-primary mb-2 block w-fit font-medium text-black motion-safe:transition-colors motion-safe:duration-200',
141
+ )
142
+ "
51
143
  >
52
144
  {{ phoneNumber }}
53
145
  </NuxtLink>
@@ -55,7 +147,12 @@
55
147
  v-if="emailAddress"
56
148
  :to="`mailto:${emailAddress}`"
57
149
  :title="emailAddress"
58
- class="mb-2 block w-fit font-medium text-black hover:text-primary-500 motion-safe:transition-colors motion-safe:duration-200"
150
+ :class="
151
+ themedClass(
152
+ 'emailAddressClass',
153
+ 'hover:text-primary mb-2 block w-fit font-medium text-black motion-safe:transition-colors motion-safe:duration-200',
154
+ )
155
+ "
59
156
  >
60
157
  {{ emailAddress }}
61
158
  </NuxtLink>
@@ -63,42 +160,82 @@
63
160
  </div>
64
161
  </DesignSystemAtomsGridRow>
65
162
  </DesignSystemAtomsContainer>
163
+
66
164
  <DesignSystemAtomsContainer
67
165
  v-if="socialMediaLinks || footerMetaMenuLinks"
68
166
  :spacing="{ top: 'none', bottom: 'none' }"
69
- class="py-6"
167
+ :class="themedClass('metaContainerClass', 'py-6')"
70
168
  >
71
- <DesignSystemAtomsGridRow>
169
+ <DesignSystemAtomsGridRow :class="themedClass('metaRowClass')">
72
170
  <div
73
- class="col-span-12 flex flex-col-reverse justify-between gap-6 lg:flex-row"
171
+ :class="
172
+ themedClass(
173
+ 'metaColumnClass',
174
+ 'col-span-12 flex flex-col-reverse justify-between gap-6 lg:flex-row',
175
+ )
176
+ "
74
177
  >
75
- <div class="flex gap-4">
178
+ <div :class="themedClass('socialMediaContainerClass', 'flex gap-4')">
76
179
  <template v-for="(link, index) in socialMediaLinks" :key="index">
77
180
  <NuxtLink
78
181
  v-if="link?.uri?.path"
79
182
  :to="link?.uri?.path"
80
183
  :title="getSocialMediaIconMeta(link.uri.path).title"
81
184
  target="_blank"
82
- class="group"
185
+ :class="themedClass('socialMediaIconLinkClass', 'group')"
83
186
  >
84
187
  <SpriteSymbol
85
188
  v-if="getSocialMediaIconMeta(link.uri.path)?.sprite"
86
189
  :name="getSocialMediaIconMeta(link.uri.path).sprite"
87
- class="size-8 transition-colors group-hover:text-primary-500"
190
+ :class="
191
+ themedClass(
192
+ 'socialMediaIconClass',
193
+ 'group-hover:text-primary size-8 transition-colors',
194
+ )
195
+ "
88
196
  />
89
197
  </NuxtLink>
90
198
  </template>
91
199
  </div>
92
- <div v-if="footerMetaMenuLinks" class="flex items-center">
93
- <ul class="flex flex-col gap-4 md:flex-row md:gap-6">
94
- <li v-for="(link, index) in footerMetaMenuLinks" :key="index">
200
+
201
+ <div
202
+ v-if="footerMetaMenuLinks"
203
+ :class="
204
+ themedClass('footerMetaMenuContainerClass', 'flex items-center')
205
+ "
206
+ >
207
+ <ul
208
+ :class="
209
+ themedClass(
210
+ 'footerMetaMenuListClass',
211
+ 'flex flex-col gap-4 md:flex-row md:gap-6',
212
+ )
213
+ "
214
+ >
215
+ <li
216
+ v-for="(link, index) in footerMetaMenuLinks"
217
+ :key="index"
218
+ :class="themedClass('footerMetaMenuListItemClass')"
219
+ >
95
220
  <NuxtLink
96
221
  v-if="link?.href"
97
222
  :to="link?.href"
98
223
  :title="link.label"
99
- class="underline transition-colors hover:text-primary-500 focus:text-primary-500"
224
+ :rel="link.rel"
225
+ :target="link.target"
226
+ :class="
227
+ cn(
228
+ themedClass(
229
+ 'footerMetaMenuLinkClass',
230
+ 'hover:text-primary focus:text-primary underline transition-colors',
231
+ ),
232
+ link.additionalClass,
233
+ )
234
+ "
100
235
  prefetch
101
- >{{ link.label }}</NuxtLink>
236
+ >
237
+ {{ link.label }}
238
+ </NuxtLink>
102
239
  </li>
103
240
  </ul>
104
241
  </div>
@@ -110,7 +247,10 @@
110
247
 
111
248
  <script lang="ts" setup>
112
249
  import type { NuxtSvgSpriteSymbol } from '#nuxt-svg-icon-sprite/runtime'
250
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
251
+ import cn from '@iqual/nuxt-icms/composables/useCn'
113
252
 
253
+ const themedClass = useTheme('SectionFooter')
114
254
  const { menuItems: footerMenuLinks } = await useMenu('footerMenu')
115
255
  const { menuItems: footerMetaMenuLinks } = await useMenu('footerMetaMenu')
116
256
  const {
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div
3
+ v-if="buttons?.length"
4
+ :class="themedClass('containerClass', 'flex flex-col gap-2')"
5
+ >
6
+ <DesignSystemAtomsButton
7
+ v-for="(link, key) in buttons"
8
+ :key="key"
9
+ :label="link?.label"
10
+ :subline="link?.subline"
11
+ :title="link?.title"
12
+ :link="link?.link?.uri?.path"
13
+ :params="link?.params"
14
+ :target-blank="link?.targetBlank"
15
+ :style="link?.buttonStyle"
16
+ :textAlignment="link?.textAlignment"
17
+ :buttonClass="themedClass('buttonClass', 'xs:w-fit')"
18
+ :labelClass="themedClass('labelClass')"
19
+ :sublineClass="themedClass('sublineClass')"
20
+ @click="close()"
21
+ />
22
+ </div>
23
+ </template>
24
+
25
+ <script lang="ts" setup>
26
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
27
+
28
+ defineProps<{
29
+ buttons: any[] | undefined
30
+ }>()
31
+
32
+ const themedClass = useTheme('SectionHeaderButtons')
33
+ const { close } = useMobileMenuOverlayBus()
34
+ </script>
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <nav
3
+ :aria-label="$texts('main_menu.nav_aria_label', 'Main navigation')"
4
+ :class="themedClass('containerClass', 'flex h-full')"
5
+ >
6
+ <ul
7
+ :class="themedClass('listClass', 'flex items-stretch justify-end gap-8')"
8
+ >
9
+ <li
10
+ v-for="item in menuItems"
11
+ @mouseenter="setOpen(item, true)"
12
+ @mouseleave="setOpen(item, false)"
13
+ @focusin="setOpen(item, true)"
14
+ @focusout="setOpen(item, false)"
15
+ :class="
16
+ themedClass('listItemClass', 'relative flex h-full items-stretch')
17
+ "
18
+ >
19
+ <component :is="elementType(item)" v-bind="componentAttrs(item)">
20
+ {{ item.label }}
21
+ <SpriteSymbol
22
+ v-if="hasSubmenu(item)"
23
+ name="chevron-down"
24
+ aria-hidden="true"
25
+ focusable="false"
26
+ :class="[
27
+ themedClass(
28
+ 'submenuToggleClass',
29
+ 'size-5 shrink-0 cursor-pointer text-black motion-safe:transition-transform motion-safe:duration-300',
30
+ ),
31
+ { 'rotate-180': isOpen(item) },
32
+ ]"
33
+ />
34
+ </component>
35
+
36
+ <Transition
37
+ :enter-active-class="
38
+ themedClass(
39
+ 'transitionEnterActiveClass',
40
+ 'transition-opacity duration-200 ease-in-out',
41
+ )
42
+ "
43
+ :enter-from-class="
44
+ themedClass('transitionEnterFromClass', 'opacity-0')
45
+ "
46
+ :enter-to-class="themedClass('transitionEnterToClass', 'opacity-100')"
47
+ :leave-active-class="
48
+ themedClass(
49
+ 'transitionLeaveActiveClass',
50
+ 'transition-opacity duration-200 ease-in-out',
51
+ )
52
+ "
53
+ :leave-from-class="
54
+ themedClass('transitionLeaveFromClass', 'opacity-100')
55
+ "
56
+ :leave-to-class="themedClass('transitionLeaveToClass', 'opacity-0')"
57
+ >
58
+ <ul
59
+ v-if="hasSubmenu(item) && isOpen(item)"
60
+ :id="submenuId(item)"
61
+ :class="
62
+ themedClass(
63
+ 'listClass',
64
+ 'absolute left-0 top-full w-fit min-w-48 border border-gray-100 bg-white',
65
+ )
66
+ "
67
+ >
68
+ <DesktopMainMenuDropdown
69
+ :item="item"
70
+ :menuItemActive="menuItemActive"
71
+ />
72
+ </ul>
73
+ </Transition>
74
+ </li>
75
+ </ul>
76
+ </nav>
77
+ </template>
78
+
79
+ <script lang="ts" setup>
80
+ import type { MappedMenuLink } from '@iqual/nuxt-icms/plugins/initData'
81
+ import { NuxtLink } from '#components'
82
+ import DesktopMainMenuDropdown from './DesktopMainMenuDropdown.vue'
83
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
84
+ import cn from '@iqual/nuxt-icms/composables/useCn'
85
+
86
+ const { menuItems, menuItemActive } = await useMenu('mainMenu')
87
+
88
+ const themedClass = useTheme('SectionHeaderDesktopMainMenu')
89
+
90
+ const isActive = (item: MappedMenuLink) => menuItemActive.value?.id === item.id
91
+ const hasSubmenu = (item: MappedMenuLink) => (item.children?.length ?? 0) > 0
92
+ const submenuId = (item: MappedMenuLink) => `${item.id}-submenu`
93
+
94
+ const openMap = ref<Record<string, boolean>>({})
95
+ const isOpen = (item: MappedMenuLink) => !!openMap.value[item.id]
96
+ const setOpen = (item: MappedMenuLink, value: boolean) =>
97
+ (openMap.value[item.id] = value)
98
+
99
+ function elementType(item: MappedMenuLink) {
100
+ if (item.href) return NuxtLink
101
+ if (item.children?.length) return 'button'
102
+ return 'span'
103
+ }
104
+
105
+ function componentAttrs(item: MappedMenuLink) {
106
+ const type = elementType(item)
107
+ const active = isActive(item)
108
+ const submenu = hasSubmenu(item)
109
+
110
+ switch (type) {
111
+ case NuxtLink:
112
+ return {
113
+ id: item.id,
114
+ to: item.href,
115
+ rel: item.rel,
116
+ target: item.target,
117
+ 'aria-haspopup': submenu ? 'menu' : undefined,
118
+ 'aria-expanded': submenu ? isOpen(item) : undefined,
119
+ 'aria-controls': submenu ? submenuId(item) : undefined,
120
+ 'aria-current': active ? 'page' : undefined,
121
+ tabindex: 0,
122
+ class: cn(
123
+ themedClass(
124
+ 'linkClass',
125
+ 'flex items-center justify-between gap-1 hover:text-primary duration-200 whitespace-normal break-keep hyphens-none',
126
+ ),
127
+ item.additionalClass,
128
+ ),
129
+ 'exact-active-class': 'text-primary',
130
+ }
131
+
132
+ case 'button':
133
+ return {
134
+ id: item.id,
135
+ type: 'button',
136
+ 'aria-haspopup': 'menu',
137
+ 'aria-expanded': isOpen(item),
138
+ 'aria-controls': submenuId(item),
139
+ tabindex: 0,
140
+ class: themedClass(
141
+ 'buttonClass',
142
+ 'flex items-center justify-between gap-1 cursor-default whitespace-normal break-keep hyphens-none',
143
+ ),
144
+ }
145
+
146
+ case 'span':
147
+ return {
148
+ id: item.id,
149
+ 'aria-disabled': true,
150
+ class: themedClass(
151
+ 'nolinkClass',
152
+ 'flex items-center text-gray-400 whitespace-normal break-keep hyphens-none',
153
+ ),
154
+ }
155
+
156
+ default:
157
+ return {}
158
+ }
159
+ }
160
+ </script>