@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
@@ -1,69 +1,71 @@
1
1
  <template>
2
- <nav class="mb-8 2xl:mb-0 2xl:py-0">
2
+ <nav
3
+ :aria-label="
4
+ $texts('meta_navigation.nav_aria_label', 'Meta navigation menu')
5
+ "
6
+ :class="themedClass('containerClass')"
7
+ >
3
8
  <ul
4
- role="menu"
5
- class="flex size-full flex-col flex-wrap justify-end text-base 2xl:flex-row 2xl:items-center 2xl:gap-8 2xl:font-medium"
9
+ :class="
10
+ themedClass(
11
+ 'listClass',
12
+ 'flex flex-col gap-2 text-base 2xl:flex-row 2xl:flex-wrap 2xl:items-center 2xl:justify-end 2xl:gap-8',
13
+ )
14
+ "
6
15
  >
7
16
  <li
8
17
  v-for="(menuItem, index) in menuItems"
9
18
  :key="`meta_${index}`"
10
- role="menuitem"
11
- class="group/main relative flex flex-col items-stretch 2xl:flex-row"
19
+ :class="
20
+ themedClass(
21
+ 'listItemClass',
22
+ 'group/main relative flex flex-col items-stretch 2xl:flex-row',
23
+ )
24
+ "
12
25
  >
13
- <VuepalLink
14
- :to="menuItem?.href"
15
- class="group-hover/main:text-primary-500 flex w-full items-center justify-between py-1 motion-safe:transition-colors motion-safe:duration-200 2xl:size-auto 2xl:py-0"
16
- :class="
17
- menuItem?.href === menuItemActive?.href
18
- ? 'text-primary-500'
19
- : 'text-black'
20
- "
21
- @click="onLangDropdownLinkClick"
22
- >
23
- {{ menuItem.label }}
24
- </VuepalLink>
26
+ <template v-if="menuItem?.href">
27
+ <NuxtLink
28
+ :to="menuItem.href"
29
+ :title="menuItem.label"
30
+ :aria-current="
31
+ menuItem?.href === menuItemActive?.href ? 'page' : undefined
32
+ "
33
+ :target="menuItem?.target"
34
+ :rel="menuItem?.rel"
35
+ @click="close()"
36
+ exact-active-class="text-primary"
37
+ :class="
38
+ cn(
39
+ themedClass(
40
+ 'linkClass',
41
+ '2xl:group-hover/main:text-primary flex w-full items-center justify-between motion-safe:transition-colors motion-safe:duration-200 2xl:size-auto',
42
+ ),
43
+ menuItem?.additionalClass,
44
+ )
45
+ "
46
+ >
47
+ {{ menuItem.label }}
48
+ </NuxtLink>
49
+ </template>
50
+ <template v-else>
51
+ <span
52
+ aria-disabled="true"
53
+ :class="themedClass('noLinkClass', 'cursor-default text-gray-400')"
54
+ >
55
+ {{ menuItem.label }}
56
+ </span>
57
+ </template>
25
58
  </li>
26
59
  </ul>
27
60
  </nav>
28
- <div
29
- v-if="props.links?.length !== 0"
30
- class="mb-8 flex gap-8 2xl:mb-0 2xl:ml-8"
31
- >
32
- <DesignSystemAtomsButton
33
- v-for="(link, key) in links"
34
- :key="key"
35
- :label="link?.label"
36
- :subline="link?.subline"
37
- :title="link?.title"
38
- :link="link?.link?.uri?.path"
39
- :params="link?.params"
40
- :target-blank="link?.targetBlank"
41
- :style="link?.buttonStyle"
42
- buttonClass="no-underline"
43
- @click="onLangDropdownLinkClick"
44
- />
45
- </div>
46
61
  </template>
47
62
 
48
63
  <script setup lang="ts">
49
- import type { GlobalConfigFragment } from '#build/graphql-operations'
64
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
65
+ import cn from '@iqual/nuxt-icms/composables/useCn'
50
66
 
51
67
  const { menuItems, menuItemActive } = await useMenu('metaMenu')
52
-
53
- const props = defineProps<{
54
- links?: GlobalConfigFragment['contactButtons']
55
- }>()
56
-
57
- const emit = defineEmits(['meta:empty', 'lang-dropdown:change'])
58
-
59
- if (menuItems.value?.length === 0) {
60
- emit('meta:empty')
61
- }
62
-
63
- const langSwitchDropdownOpen = ref(false)
64
-
65
- const onLangDropdownLinkClick = () => {
66
- emit('lang-dropdown:change')
67
- langSwitchDropdownOpen.value = false
68
- }
68
+ const themedClass = useTheme()
69
+ const { $texts } = useNuxtApp()
70
+ const { close } = useMobileMenuOverlayBus()
69
71
  </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <nav
3
+ :aria-label="$texts('main_menu.nav_aria_label', 'Main navigation')"
4
+ :class="themedClass('containerClass')"
5
+ >
6
+ <ul role="menu" :class="themedClass('listClass', 'flex flex-col gap-2')">
7
+ <MobileMainMenuItem
8
+ v-for="item in menuItems"
9
+ :key="item.id"
10
+ :item="item"
11
+ :menuItemActive="menuItemActive"
12
+ />
13
+ </ul>
14
+ </nav>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import MobileMainMenuItem from './MobileMainMenuItem.vue'
19
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
20
+ const { menuItems, menuItemActive } = await useMenu('mainMenu')
21
+
22
+ const themedClass = useTheme()
23
+ </script>
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <li
3
+ role="menuitem"
4
+ :class="themedClass('listItemClass', 'relative flex flex-col gap-2')"
5
+ >
6
+ <div
7
+ :class="
8
+ themedClass('linkContainerClass', 'flex items-center justify-between')
9
+ "
10
+ >
11
+ <component
12
+ :is="elementType"
13
+ v-bind="componentAttrs"
14
+ @click="isLink && close()"
15
+ >
16
+ {{ item.label }}
17
+ </component>
18
+ <SpriteSymbol
19
+ v-if="hasSubmenu"
20
+ name="chevron-down"
21
+ :aria-label="
22
+ $texts('mobile_main_menu.submenu_toggle', 'Toggle submenu')
23
+ "
24
+ :aria-expanded="isOpen"
25
+ :aria-controls="submenuId"
26
+ role="button"
27
+ tabindex="0"
28
+ @click="isOpen = !isOpen"
29
+ :class="[
30
+ themedClass(
31
+ 'submenuToggleClass',
32
+ 'size-5 cursor-pointer motion-safe:transition-transform motion-safe:duration-300',
33
+ ),
34
+ { 'rotate-180': isOpen },
35
+ ]"
36
+ />
37
+ </div>
38
+ <Transition
39
+ v-if="hasSubmenu"
40
+ :enter-active-class="
41
+ themedClass(
42
+ 'transitionEnterActiveClass',
43
+ 'transition-[max-height,opacity] duration-300 ease-in-out overflow-hidden',
44
+ )
45
+ "
46
+ :enter-from-class="
47
+ themedClass('transitionEnterFromClass', 'max-h-0 opacity-0')
48
+ "
49
+ :enter-to-class="
50
+ themedClass('transitionEnterToClass', 'max-h-96 opacity-100')
51
+ "
52
+ :leave-active-class="
53
+ themedClass(
54
+ 'transitionLeaveActiveClass',
55
+ 'transition-[max-height,opacity] duration-300 ease-in-out overflow-hidden',
56
+ )
57
+ "
58
+ :leave-from-class="
59
+ themedClass('transitionLeaveFromClass', 'max-h-96 opacity-100')
60
+ "
61
+ :leave-to-class="
62
+ themedClass('transitionLeaveToClass', 'max-h-0 opacity-0')
63
+ "
64
+ >
65
+ <ul
66
+ v-show="isOpen"
67
+ :id="submenuId"
68
+ :class="themedClass('submenuListClass', 'flex flex-col gap-2 pl-4')"
69
+ >
70
+ <MobileMainMenuItem
71
+ v-for="item in item.children"
72
+ :key="`submenu_${item.id}`"
73
+ :item="item"
74
+ :menuItemActive="menuItemActive"
75
+ />
76
+ </ul>
77
+ </Transition>
78
+ </li>
79
+ </template>
80
+
81
+ <script lang="ts" setup>
82
+ import type { MappedMenuLink } from '@/packages/nuxt-icms/plugins/initData'
83
+ import { NuxtLink } from '#components'
84
+ import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
85
+ import cn from '@iqual/nuxt-icms/composables/useCn'
86
+
87
+ const route = useRoute()
88
+ const themedClass = useTheme()
89
+ const { close } = useMobileMenuOverlayBus()
90
+
91
+ const props = defineProps<{
92
+ item: MappedMenuLink
93
+ menuItemActive?: MappedMenuLink | undefined
94
+ }>()
95
+
96
+ const { item, menuItemActive } = props
97
+
98
+ const isOpen = ref(false)
99
+ const isLink = computed(() => Boolean(item.href))
100
+ const hasSubmenu = computed(() => (item.children?.length ?? 0) > 0)
101
+ const submenuId = computed(() => `${item.id}-submenu`)
102
+ const hasActiveChild = computed(() => {
103
+ /**
104
+ * Determines whether the current menu item has any active descendant links.
105
+ *
106
+ * @remarks
107
+ * Recursively checks all nested children to see if any item's `href`
108
+ * matches the current route path. Used to keep parent menus open
109
+ * when navigating within their nested structure.
110
+ *
111
+ * @returns A computed boolean that is `true` if the current route
112
+ * matches any descendant link, otherwise `false`.
113
+ */
114
+ const matchInTree = (children?: MappedMenuLink[]): boolean =>
115
+ !!children?.some(
116
+ (child) => child.href === route.fullPath || matchInTree(child.children),
117
+ )
118
+ return matchInTree(item.children)
119
+ })
120
+
121
+ watch(
122
+ () => route.fullPath,
123
+ () => {
124
+ isOpen.value = hasActiveChild.value
125
+ },
126
+ { immediate: true },
127
+ )
128
+
129
+ /**
130
+ * Determines which element type to render for a menu item.
131
+ *
132
+ * @remarks
133
+ * - Returns `NuxtLink` if the item has a navigation link.
134
+ * - Returns `'button'` if the item opens a submenu but has no link.
135
+ * - Returns `'span'` if the item is non-interactive.
136
+ *
137
+ * @returns A computed reference containing either `NuxtLink`, `'button'`, or `'span'`.
138
+ */
139
+ const elementType = computed(() => {
140
+ if (isLink.value) return NuxtLink
141
+ else if (hasSubmenu.value) return 'button'
142
+ else return 'span'
143
+ })
144
+
145
+ /**
146
+ * Computes the appropriate HTML or component attributes for a menu item
147
+ * based on its computed element type.
148
+ *
149
+ * @returns A computed object containing attributes specific to the element type,
150
+ * including accessibility props and themed CSS classes.
151
+ */
152
+ const componentAttrs = computed(() => {
153
+ switch (elementType.value) {
154
+ case NuxtLink:
155
+ return {
156
+ id: item.id,
157
+ to: item.href,
158
+ rel: item.rel,
159
+ target: item.target,
160
+ 'aria-haspopup': hasSubmenu.value ? 'menu' : undefined,
161
+ 'aria-expanded': hasSubmenu.value ? isOpen.value : undefined,
162
+ 'aria-controls': hasSubmenu.value ? submenuId.value : undefined,
163
+ 'aria-current': menuItemActive?.id === item.id ? 'page' : undefined,
164
+ tabindex: 0,
165
+ class: cn(themedClass('linkClass'), item.additionalClass),
166
+ 'exact-active-class': 'text-primary',
167
+ }
168
+
169
+ case 'button':
170
+ return {
171
+ id: item.id,
172
+ type: 'button',
173
+ 'aria-haspopup': 'menu',
174
+ 'aria-expanded': isOpen.value,
175
+ 'aria-controls': submenuId.value,
176
+ tabindex: 0,
177
+ class: themedClass('buttonClass'),
178
+ }
179
+
180
+ case 'span':
181
+ return {
182
+ id: item.id,
183
+ 'aria-disabled': true,
184
+ class: themedClass('nolinkClass', 'text-gray-400'),
185
+ }
186
+
187
+ default:
188
+ return {}
189
+ }
190
+ })
191
+ </script>