@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.
- 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 +78 -20
- 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 +24 -0
- package/components/Section/Header/MainButtons.vue +31 -0
- package/components/Section/Header/MenuIcon.vue +61 -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 +183 -185
- package/composables/useMenu.ts +2 -2
- package/composables/useMobileMenuOverlayBus.ts +44 -0
- package/composables/useNavigationOffset.ts +2 -2
- package/composables/useTheme.ts +75 -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
|
@@ -1,216 +1,214 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
:class="{
|
|
6
|
-
'max-h-[70px] !overflow-y-hidden 2xl:max-h-screen 2xl:!overflow-y-visible':
|
|
7
|
-
!isMenuOpen,
|
|
8
|
-
'max-h-screen overflow-y-auto': isMenuOpen,
|
|
9
|
-
'fixed size-full max-h-dvh': isGlobalSticky,
|
|
10
|
-
sticky: !isGlobalSticky,
|
|
3
|
+
:style="{
|
|
4
|
+
'--meta-height': metaHeight + 'px',
|
|
11
5
|
}"
|
|
12
|
-
:
|
|
6
|
+
:class="
|
|
7
|
+
themedClass(
|
|
8
|
+
'headerClass',
|
|
9
|
+
'sticky top-0 z-[101] 2xl:-top-[var(--meta-height)]',
|
|
10
|
+
)
|
|
11
|
+
"
|
|
13
12
|
>
|
|
14
|
-
<!-- Meta
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
<!-- Desktop Meta Container -->
|
|
14
|
+
<DesignSystemAtomsContainer
|
|
15
|
+
:spacing="{ top: 'none', bottom: 'none' }"
|
|
16
|
+
:class="
|
|
17
|
+
themedClass(
|
|
18
|
+
'metaContainerClass',
|
|
19
|
+
'hidden border-b border-gray-100 bg-white 2xl:block',
|
|
20
|
+
)
|
|
21
|
+
"
|
|
22
|
+
>
|
|
23
|
+
<div ref="metaContainer">
|
|
24
|
+
<DesignSystemAtomsGridRow
|
|
25
|
+
:class="
|
|
26
|
+
themedClass(
|
|
27
|
+
'metaRowClass',
|
|
28
|
+
'flex items-center justify-end gap-8 py-2',
|
|
29
|
+
)
|
|
30
|
+
"
|
|
31
|
+
>
|
|
32
|
+
<div :class="themedClass('metaMenuContainer')">
|
|
33
|
+
<SectionHeaderMetaMenu />
|
|
34
|
+
</div>
|
|
35
|
+
<div :class="themedClass('metaButtonsContainer')">
|
|
36
|
+
<SectionHeaderButtons :buttons="metaLinks" />
|
|
37
|
+
</div>
|
|
22
38
|
<div
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
:class="
|
|
40
|
+
themedClass(
|
|
41
|
+
'languageDropdownContainer',
|
|
42
|
+
'flex h-full items-center',
|
|
43
|
+
)
|
|
44
|
+
"
|
|
25
45
|
>
|
|
26
|
-
|
|
27
|
-
<SectionHeaderMetaMenu :links="metaLinks" />
|
|
28
|
-
|
|
29
|
-
<!-- Desktop Language dropdown -->
|
|
30
|
-
<SectionHeaderLanguageDropdown
|
|
31
|
-
class="hidden h-full 2xl:block"
|
|
32
|
-
@lang-dropdown:change="closeMenu"
|
|
33
|
-
/>
|
|
46
|
+
<SectionHeaderLanguageDropdown />
|
|
34
47
|
</div>
|
|
48
|
+
</DesignSystemAtomsGridRow>
|
|
49
|
+
</div>
|
|
50
|
+
</DesignSystemAtomsContainer>
|
|
51
|
+
|
|
52
|
+
<!-- Main Container -->
|
|
53
|
+
<DesignSystemAtomsContainer
|
|
54
|
+
:spacing="{ top: 'none', bottom: 'none' }"
|
|
55
|
+
:class="
|
|
56
|
+
themedClass('mainContainerClass', 'border-b border-gray-100 bg-white')
|
|
57
|
+
"
|
|
58
|
+
>
|
|
59
|
+
<DesignSystemAtomsGridRow
|
|
60
|
+
:class="
|
|
61
|
+
themedClass('mainRowClass', 'flex h-14 items-center gap-8 2xl:h-20')
|
|
62
|
+
"
|
|
63
|
+
>
|
|
64
|
+
<div :class="themedClass('logoContainer', 'shrink-0')">
|
|
65
|
+
<SectionHeaderLogo />
|
|
66
|
+
</div>
|
|
67
|
+
<div
|
|
68
|
+
:class="
|
|
69
|
+
themedClass(
|
|
70
|
+
'mainMenuContainer',
|
|
71
|
+
'ml-auto hidden 2xl:flex 2xl:h-full 2xl:items-center',
|
|
72
|
+
)
|
|
73
|
+
"
|
|
74
|
+
>
|
|
75
|
+
<SectionHeaderDesktopMainMenu />
|
|
76
|
+
</div>
|
|
77
|
+
<div :class="themedClass('mainButtonsContainer', 'hidden 2xl:block')">
|
|
78
|
+
<SectionHeaderButtons :buttons="contactButtons" />
|
|
79
|
+
</div>
|
|
80
|
+
<div
|
|
81
|
+
:class="
|
|
82
|
+
themedClass(
|
|
83
|
+
'mainMenuIconContainer',
|
|
84
|
+
'ml-auto flex items-center justify-center 2xl:hidden',
|
|
85
|
+
)
|
|
86
|
+
"
|
|
87
|
+
>
|
|
88
|
+
<SectionHeaderMenuIcon :is-in-closable-state="isMobileMenuOpen" />
|
|
35
89
|
</div>
|
|
36
90
|
</DesignSystemAtomsGridRow>
|
|
37
|
-
</
|
|
38
|
-
|
|
39
|
-
<!--
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
91
|
+
</DesignSystemAtomsContainer>
|
|
92
|
+
|
|
93
|
+
<!-- Mobile Menu Overlay -->
|
|
94
|
+
<Teleport to="body">
|
|
95
|
+
<Transition
|
|
96
|
+
@after-enter="clickAwayArmed = true"
|
|
97
|
+
@after-leave="clickAwayArmed = false"
|
|
98
|
+
:enter-active-class="
|
|
99
|
+
themedClass(
|
|
100
|
+
'transitionEnterActiveClass',
|
|
101
|
+
'transition-opacity duration-200 md:motion-safe:transition-transform md:motion-safe:duration-300',
|
|
102
|
+
)
|
|
103
|
+
"
|
|
104
|
+
:enter-from-class="
|
|
105
|
+
themedClass(
|
|
106
|
+
'transitionEnterFromClass',
|
|
107
|
+
'opacity-0 md:opacity-100 md:translate-x-full',
|
|
108
|
+
)
|
|
109
|
+
"
|
|
110
|
+
:enter-to-class="
|
|
111
|
+
themedClass(
|
|
112
|
+
'transitionEnterToClass',
|
|
113
|
+
'opacity-100 md:opacity-100 md:translate-x-0',
|
|
114
|
+
)
|
|
115
|
+
"
|
|
116
|
+
:leave-active-class="
|
|
117
|
+
themedClass(
|
|
118
|
+
'transitionLeaveActiveClass',
|
|
119
|
+
'transition-opacity duration-200 md:motion-safe:transition-transform md:motion-safe:duration-300',
|
|
120
|
+
)
|
|
121
|
+
"
|
|
122
|
+
:leave-from-class="
|
|
123
|
+
themedClass(
|
|
124
|
+
'transitionLeaveFromClass',
|
|
125
|
+
'opacity-100 md:opacity-100 md:translate-x-0',
|
|
126
|
+
)
|
|
127
|
+
"
|
|
128
|
+
:leave-to-class="
|
|
129
|
+
themedClass(
|
|
130
|
+
'transitionLeaveToClass',
|
|
131
|
+
'opacity-0 md:opacity-100 md:translate-x-full',
|
|
132
|
+
)
|
|
133
|
+
"
|
|
44
134
|
>
|
|
45
|
-
<
|
|
46
|
-
|
|
135
|
+
<DesignSystemAtomsContainer
|
|
136
|
+
v-show="isMobileMenuOpen"
|
|
137
|
+
:spacing="{ top: 'none', bottom: 'none' }"
|
|
138
|
+
v-click-away="clickAwayArmed && close"
|
|
139
|
+
:class="
|
|
140
|
+
themedClass(
|
|
141
|
+
'mobileMenuContainerClass',
|
|
142
|
+
'fixed inset-0 z-[102] flex h-dvh items-start justify-center overflow-y-auto bg-white md:left-auto md:w-[420px]',
|
|
143
|
+
)
|
|
144
|
+
"
|
|
47
145
|
>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
</NuxtLink>
|
|
57
|
-
|
|
58
|
-
<!-- Desktop Main navigation menu -->
|
|
59
|
-
<!-- Hidden on mobile -->
|
|
60
|
-
<SectionHeaderGlobalMenu
|
|
61
|
-
class="ml-auto hidden 2xl:block"
|
|
62
|
-
@menu:link-click="closeMenu"
|
|
63
|
-
/>
|
|
64
|
-
|
|
65
|
-
<!-- Desktop Contact button -->
|
|
66
|
-
<!-- Hidden on mobile -->
|
|
146
|
+
<DesignSystemAtomsGridRow
|
|
147
|
+
:class="
|
|
148
|
+
themedClass(
|
|
149
|
+
'mobileMenuRowClass',
|
|
150
|
+
'flex w-full flex-col gap-10 pb-20',
|
|
151
|
+
)
|
|
152
|
+
"
|
|
153
|
+
>
|
|
67
154
|
<div
|
|
68
|
-
|
|
69
|
-
|
|
155
|
+
:class="
|
|
156
|
+
themedClass(
|
|
157
|
+
'mobileMenuHeaderContainer',
|
|
158
|
+
'flex h-14 items-center',
|
|
159
|
+
)
|
|
160
|
+
"
|
|
70
161
|
>
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
:key="key"
|
|
74
|
-
:label="contactButton?.label"
|
|
75
|
-
:subline="contactButton?.subline"
|
|
76
|
-
:title="contactButton?.title"
|
|
77
|
-
:link="contactButton?.link?.uri?.path"
|
|
78
|
-
:params="contactButton?.params"
|
|
79
|
-
:targetBlank="contactButton?.targetBlank"
|
|
80
|
-
:style="contactButton?.buttonStyle"
|
|
162
|
+
<SectionHeaderLogo
|
|
163
|
+
:class="themedClass('mobileMenuLogoClass', 'md:hidden')"
|
|
81
164
|
/>
|
|
165
|
+
<SectionHeaderMenuIcon :is-in-closable-state="isMobileMenuOpen" />
|
|
82
166
|
</div>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class="h-auto leading-none opacity-100"
|
|
93
|
-
:class="{
|
|
94
|
-
'duration-250 opacity-0 transition-all ease-in-out':
|
|
95
|
-
isMenuOpen,
|
|
96
|
-
}"
|
|
97
|
-
/>
|
|
98
|
-
<SectionHeaderMenuIcon :is-in-closable-state="isMenuOpen" />
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</DesignSystemAtomsGridRow>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<!-- Mobile Main/Meta menu container -->
|
|
106
|
-
<!-- Hidden on desktop -->
|
|
107
|
-
<!-- Initially hidden by overflow when menu closed -->
|
|
108
|
-
<div class="container w-full max-w-full 2xl:hidden">
|
|
109
|
-
<!-- Mobile Main navigation menu -->
|
|
110
|
-
<SectionHeaderGlobalMenu @menu:link-click="closeMenu" />
|
|
111
|
-
|
|
112
|
-
<!-- Mobile Language dropdown -->
|
|
113
|
-
<SectionHeaderLanguageSwitcher
|
|
114
|
-
@lang-dropdown:change="closeMenu"
|
|
115
|
-
class="mb-6"
|
|
116
|
-
/>
|
|
117
|
-
|
|
118
|
-
<!-- Mobile Meta menu -->
|
|
119
|
-
<SectionHeaderMetaMenu
|
|
120
|
-
:links="metaLinks"
|
|
121
|
-
@lang-dropdown:change="closeMenu"
|
|
122
|
-
/>
|
|
123
|
-
|
|
124
|
-
<!-- Mobile Contact button -->
|
|
125
|
-
<div v-if="contactButtons?.length" class="flex gap-8 2xl:ml-8">
|
|
126
|
-
<DesignSystemAtomsButton
|
|
127
|
-
v-for="(contactButton, key) in contactButtons"
|
|
128
|
-
:key="key"
|
|
129
|
-
:label="contactButton?.label"
|
|
130
|
-
:subline="contactButton?.subline"
|
|
131
|
-
:title="contactButton?.title"
|
|
132
|
-
:link="contactButton?.link?.uri?.path"
|
|
133
|
-
:params="contactButton?.params"
|
|
134
|
-
:targetBlank="contactButton?.targetBlank"
|
|
135
|
-
:style="contactButton?.buttonStyle"
|
|
136
|
-
/>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
167
|
+
<SectionHeaderMobileMainMenu />
|
|
168
|
+
<SectionHeaderButtons :buttons="contactButtons" />
|
|
169
|
+
<SectionHeaderMetaMenu />
|
|
170
|
+
<SectionHeaderButtons :buttons="metaLinks" />
|
|
171
|
+
<SectionHeaderLanguageLinks />
|
|
172
|
+
</DesignSystemAtomsGridRow>
|
|
173
|
+
</DesignSystemAtomsContainer>
|
|
174
|
+
</Transition>
|
|
175
|
+
</Teleport>
|
|
139
176
|
</header>
|
|
140
177
|
</template>
|
|
141
178
|
|
|
142
179
|
<script lang="ts" setup>
|
|
143
|
-
|
|
144
|
-
await useGlobalConfig()
|
|
180
|
+
import { useTheme } from '@iqual/nuxt-icms/composables/useTheme'
|
|
145
181
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const isGlobalSticky = ref(false)
|
|
150
|
-
const isMenuOpen = ref(false)
|
|
151
|
-
const isMetaHidden = ref(false)
|
|
182
|
+
const { metaLinks, contactButtons } = await useGlobalConfig()
|
|
183
|
+
const themedClass = useTheme('SectionHeader')
|
|
184
|
+
const { close, onOpen, onClose } = useMobileMenuOverlayBus()
|
|
152
185
|
const viewport = useViewport()
|
|
153
|
-
const { isMobile } = useIsMobile()
|
|
154
|
-
const currentLanguage = useCurrentLanguage()
|
|
155
|
-
const drupalUser = useDrupalUser()
|
|
156
|
-
const isUserLoggedIn = drupalUser.value.accessToolbar
|
|
157
|
-
|
|
158
|
-
onMounted(() => {
|
|
159
|
-
if (metaRef.value) {
|
|
160
|
-
metaHeight.value = metaRef.value.getBoundingClientRect().height
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
186
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
if (!isMobile.value && isUserLoggedIn) {
|
|
167
|
-
style = { top: `-${metaHeight.value}px` }
|
|
168
|
-
} else {
|
|
169
|
-
style = { top: 0 }
|
|
170
|
-
}
|
|
171
|
-
return style
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const emit = defineEmits([
|
|
175
|
-
'menu:open',
|
|
176
|
-
'menu:close:start',
|
|
177
|
-
'menu:close:finished',
|
|
178
|
-
])
|
|
179
|
-
|
|
180
|
-
function toggleNav() {
|
|
181
|
-
if (!isMenuOpen.value) {
|
|
182
|
-
openMenu()
|
|
183
|
-
} else {
|
|
184
|
-
closeMenu()
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function openMenu() {
|
|
189
|
-
emit('menu:open')
|
|
190
|
-
isMenuOpen.value = true
|
|
191
|
-
isGlobalSticky.value = true
|
|
192
|
-
}
|
|
187
|
+
const isMobileMenuOpen = ref(false)
|
|
188
|
+
const clickAwayArmed = ref(false)
|
|
193
189
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
isMenuOpen.value = false
|
|
197
|
-
if (container.value) {
|
|
198
|
-
container.value.scrollTop = 0
|
|
199
|
-
}
|
|
190
|
+
const metaContainer = ref<HTMLElement | null>(null)
|
|
191
|
+
const metaHeight = ref(0)
|
|
200
192
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
193
|
+
onOpen(() => {
|
|
194
|
+
isMobileMenuOpen.value = true
|
|
195
|
+
document.body.classList.toggle('overflow-y-hidden', isMobileMenuOpen.value)
|
|
196
|
+
})
|
|
206
197
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
198
|
+
onClose(() => {
|
|
199
|
+
isMobileMenuOpen.value = false
|
|
200
|
+
document.body.classList.toggle('overflow-y-hidden', isMobileMenuOpen.value)
|
|
201
|
+
})
|
|
210
202
|
|
|
211
203
|
watch(viewport.breakpoint, (newValue: string) => {
|
|
212
204
|
if (newValue === '2xl') {
|
|
213
|
-
|
|
205
|
+
isMobileMenuOpen.value = false
|
|
214
206
|
}
|
|
215
207
|
})
|
|
208
|
+
|
|
209
|
+
onMounted(() => {
|
|
210
|
+
requestAnimationFrame(() => {
|
|
211
|
+
metaHeight.value = metaContainer.value?.offsetHeight ?? 0
|
|
212
|
+
})
|
|
213
|
+
})
|
|
216
214
|
</script>
|
package/composables/useMenu.ts
CHANGED
|
@@ -48,7 +48,7 @@ export async function useMenu(menuName: MenuName) {
|
|
|
48
48
|
return null
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const menuItemActiveLevel3 = computed(() => {
|
|
52
52
|
const activeSubtree = menuItemActiveLevel2.value?.children
|
|
53
53
|
if (activeSubtree) {
|
|
54
54
|
return activeSubtree.find((l) => isLinkTreeActive(l, currentRoute.path))
|
|
@@ -61,6 +61,6 @@ export async function useMenu(menuName: MenuName) {
|
|
|
61
61
|
menuItemActive,
|
|
62
62
|
menuItemActiveLevel1,
|
|
63
63
|
menuItemActiveLevel2,
|
|
64
|
-
|
|
64
|
+
menuItemActiveLevel3,
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile Menu Overlay Bus Composable
|
|
3
|
+
*
|
|
4
|
+
* Emits open/close events globally using a reactive event key.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ref, watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
export enum MobileMenuOverlayEvent {
|
|
10
|
+
Open = 'open',
|
|
11
|
+
Close = 'close'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const mobileMenuOverlayBus = ref<MobileMenuOverlayEvent | null>(null)
|
|
15
|
+
|
|
16
|
+
function open() {
|
|
17
|
+
mobileMenuOverlayBus.value = MobileMenuOverlayEvent.Open
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function close() {
|
|
21
|
+
mobileMenuOverlayBus.value = MobileMenuOverlayEvent.Close
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function onOpen(callback: () => void) {
|
|
25
|
+
watch(mobileMenuOverlayBus, (val) => {
|
|
26
|
+
if (val === MobileMenuOverlayEvent.Open) callback()
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onClose(callback: () => void) {
|
|
31
|
+
watch(mobileMenuOverlayBus, (val) => {
|
|
32
|
+
if (val === MobileMenuOverlayEvent.Close) callback()
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useMobileMenuOverlayBus() {
|
|
37
|
+
return {
|
|
38
|
+
bus: mobileMenuOverlayBus,
|
|
39
|
+
open,
|
|
40
|
+
close,
|
|
41
|
+
onOpen,
|
|
42
|
+
onClose,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useIsMobile } from './useIsMobile'
|
|
2
2
|
|
|
3
|
-
const mobileNavHeight: number =
|
|
4
|
-
const desktopNavHeight: number =
|
|
3
|
+
const mobileNavHeight: number = 60 // actual height + 4
|
|
4
|
+
const desktopNavHeight: number = 85 // actual height + 4
|
|
5
5
|
|
|
6
6
|
export function useNavigationOffset(): { navigationOffset: ComputedRef<number> } {
|
|
7
7
|
const { isMobile } = useIsMobile()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getCurrentInstance } from 'vue'
|
|
2
|
+
import cn from '@iqual/nuxt-icms/composables/useCn'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A composable that provides theme-aware class generation for Vue components.
|
|
6
|
+
* Automatically detects the calling component and merges theme classes with component props and default classes.
|
|
7
|
+
*
|
|
8
|
+
* @param componentName - Component name to manually override auto-detection
|
|
9
|
+
* @param themeName - The name of the theme to use from app config (defaults to 'default')
|
|
10
|
+
* @returns A function that generates themed classes for component elements
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```vue
|
|
14
|
+
* <template>
|
|
15
|
+
* <button :class="theme('button', 'px-4 py-2')">
|
|
16
|
+
* <span :class="theme('label')">Click me</span>
|
|
17
|
+
* </button>
|
|
18
|
+
* </template>
|
|
19
|
+
*
|
|
20
|
+
* <script setup>
|
|
21
|
+
* // Uses default theme
|
|
22
|
+
* const customTheme = useTheme('CustomComponent')
|
|
23
|
+
*
|
|
24
|
+
* // Use a specific theme
|
|
25
|
+
* const darkTheme = useTheme('CustomComponent', 'dark')
|
|
26
|
+
*
|
|
27
|
+
* // The returned function merges:
|
|
28
|
+
* // 1. Default classes passed as second parameter
|
|
29
|
+
* // 2. Theme classes from app.config.ts
|
|
30
|
+
* // 3. Component prop classes (e.g., buttonClass, labelClass props)
|
|
31
|
+
* </script>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* The composable expects theme configuration in app.config.ts structured as:
|
|
36
|
+
* ```typescript
|
|
37
|
+
* export default defineAppConfig({
|
|
38
|
+
* theme: {
|
|
39
|
+
* default: {
|
|
40
|
+
* ComponentName: {
|
|
41
|
+
* elementName: 'theme-classes-here'
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function useTheme(componentName: string, themeName: string = 'default') {
|
|
49
|
+
const instance = getCurrentInstance()
|
|
50
|
+
const { props } = instance || {}
|
|
51
|
+
const appConfig = useAppConfig()
|
|
52
|
+
|
|
53
|
+
const componentTheme = (appConfig?.theme as any)?.[themeName]?.[componentName] ?? {}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generates themed classes by merging default classes, theme classes, and component prop classes.
|
|
57
|
+
*
|
|
58
|
+
* @param elementName - The name of the element to style (should match theme config keys and props)
|
|
59
|
+
* @param defaultClass - Optional default CSS classes to include
|
|
60
|
+
* @returns Merged CSS class string using the cn() utility
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* // Returns merged classes from theme config, props, and defaults
|
|
65
|
+
* const buttonClasses = theme('button', 'rounded-lg')
|
|
66
|
+
*
|
|
67
|
+
* // Just use theme and prop classes without defaults
|
|
68
|
+
* const labelClasses = theme('label')
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
return function (elementName: string, defaultClass?: string) {
|
|
72
|
+
return cn(defaultClass, componentTheme[elementName] as string, props?.[elementName] as string)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|