srcdev-nuxt-components 2.4.0 → 2.5.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.
@@ -18,10 +18,9 @@
18
18
  // Create a global store to track open details elements by name
19
19
  const openDetailsByName = reactive(new Map<string, HTMLDetailsElement>());
20
20
 
21
- export const useDetailsTransition = (detailsRef: Ref<HTMLDetailsElement | null>, summaryRef: Ref<HTMLElement | null>, contentRef: Ref<HTMLDivElement | null>, name: string) => {
21
+ export const useDetailsTransition = (detailsRef: Ref<HTMLDetailsElement | null>, summaryRef: Ref<HTMLElement | null>, contentRef: Ref<HTMLDivElement | null>, name: string, animationDuration: number) => {
22
22
  // State
23
23
  const animation = ref<Animation | null>(null);
24
- const animationDuration = 400;
25
24
  const isClosing = ref(false);
26
25
  const isExpanding = ref(false);
27
26
 
@@ -183,6 +182,10 @@ const props = defineProps({
183
182
  type: String,
184
183
  required: true,
185
184
  },
185
+ animationDuration: {
186
+ type: Number,
187
+ default: 400,
188
+ },
186
189
  iconSize: {
187
190
  type: String,
188
191
  default: 'small',
@@ -225,7 +228,7 @@ watch(
225
228
  onMounted(() => {
226
229
  // Initialize the composable once the component is mounted and refs are available
227
230
  if (detailsRef.value && contentRef.value && summaryRef.value) {
228
- const details = useDetailsTransition(detailsRef, summaryRef, contentRef, props.name);
231
+ const details = useDetailsTransition(detailsRef, summaryRef, contentRef, props.name, props.animationDuration);
229
232
  clickAction = details.clickAction; // Assign the real click handler
230
233
  } else {
231
234
  console.error('Refs not available after mounting');
@@ -234,7 +237,6 @@ onMounted(() => {
234
237
  </script>
235
238
 
236
239
  <style lang="css">
237
- @scope (.display-details) {
238
240
  .display-details {
239
241
  /* Component setup */
240
242
  --_display-details-icon-transform: scaleY(1);
@@ -270,6 +272,11 @@ onMounted(() => {
270
272
  margin-block-end: var(--_display-details-mbe);
271
273
 
272
274
  .display-details-summary {
275
+
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: space-between;
279
+
273
280
  list-style: none;
274
281
 
275
282
  &::-webkit-details-marker,
@@ -301,5 +308,5 @@ onMounted(() => {
301
308
  padding: var(--_display-details-content-padding);
302
309
  }
303
310
  }
304
- }
311
+
305
312
  </style>
@@ -57,6 +57,7 @@ const accordianName = useId();
57
57
  }
58
58
 
59
59
  --_grid-template-rows: 0fr;
60
+ --_details-content-height: 0px;
60
61
  --_icon-transform: scaleY(1);
61
62
  --_accordian-content-transform: scaleY(0);
62
63
 
@@ -92,28 +93,33 @@ const accordianName = useId();
92
93
 
93
94
  .accordion-content {
94
95
  /* display: block; */
95
- display: grid;
96
- grid-template-rows: 0fr;
96
+ /* display: grid; */
97
+ /* grid-template-rows: 0fr; */
97
98
  /* grid-template-rows: var(--_grid-template-rows); */
98
99
 
99
100
  /* transform: var(--_accordian-content-transform); */
100
101
  /* transform: scaleY(0); */
101
102
  /* transform-origin: top; */
102
- transition: all 2000ms;
103
+ /* transition: all 2000ms; */
103
104
 
104
105
  .accordion-content-inner {
105
- /* display: grid; */
106
- /* grid-template-rows: 0fr; */
106
+ background-color: red;
107
107
 
108
- /* transform: scaleY(0); */
109
- /* transform-origin: top; */
108
+ overflow: clip;
109
+ height: var(--_details-content-height);
110
+ transition: all 2000ms;
111
+ transition-behavior: allow-discrete;
110
112
 
111
- /* transition: all 2000ms; */
113
+ @starting-style {
114
+ height: 0;
115
+ }
112
116
  }
113
117
  }
114
118
 
115
119
  &[open] {
116
120
  --_grid-template-rows: 1fr;
121
+ --_details-content-height: calc-size(auto, size);
122
+
117
123
  --_icon-transform: scaleY(-1);
118
124
  --_accordian-content-transform: scaleY(1);
119
125
 
@@ -0,0 +1,188 @@
1
+ <template>
2
+ <div class="overflow-navigation-wrapper" :class="elementClasses">
3
+ <ul
4
+ v-for="(navGroup, groupKey) in mainNavigationState.clonedNavLinks"
5
+ :key="groupKey"
6
+ class="overflow-navigation-list"
7
+ :class="[{visible: !mainNavigationState.navListVisibility[groupKey]}]"
8
+ :style="{ '--_overflow-navigation-list-min-width': widestNavLinkWidthInMainNavigationState + 'px' }"
9
+ >
10
+ <template v-for="(link, localIndex) in navGroup" :key="localIndex">
11
+ <li
12
+ v-if="link.path"
13
+ class="overflow-navigation-item"
14
+ :class="{ 'visible': !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
15
+ :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
16
+ :data-group-key="groupKey"
17
+ :data-local-index="localIndex"
18
+ >
19
+ <NuxtLink class="overflow-navigation-link" :to="link.path"><span class="overflow-navigation-text">{{ link.name }}</span></NuxtLink>
20
+ </li>
21
+ <li
22
+ v-else
23
+ class="overflow-navigation-item"
24
+ :class="{ 'visible': !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
25
+ :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
26
+ :data-group-key="groupKey"
27
+ :data-local-index="localIndex"
28
+ >
29
+ <DisplayDetailsCore :id="useId()" name="overflow-navigation-group" :animation-duration="detailsAanimationDuration" icon-size="medium" :style-class-passthrough="['overflow-navigation-details']">
30
+ <template #summary>
31
+ <span class="overflow-navigation-text">{{ link.childLinksTitle }}</span>
32
+ </template>
33
+ <template #summaryIcon>
34
+ <Icon name="mdi:chevron-down" class="icon" />
35
+ </template>
36
+ <template #content>
37
+ <div class="overflow-navigation-sub-nav-inner">
38
+ <ul class="overflow-navigation-sub-nav-list">
39
+ <li v-for="childLink in link.childLinks" :key="childLink.name" class="overflow-navigation-sub-nav-item">
40
+ <NuxtLink :to="childLink.path" class="overflow-navigation-sub-nav-link"><span class="overflow-navigation-sub-nav-text">{{ childLink.name }}</span></NuxtLink>
41
+ </li>
42
+ </ul>
43
+ </div>
44
+ </template>
45
+ </DisplayDetailsCore>
46
+ </li>
47
+ </template>
48
+ </ul>
49
+ </div>
50
+ </template>
51
+
52
+ <script lang="ts">
53
+ interface NavLinkConfig {
54
+ left: number;
55
+ right: number;
56
+ width?: number;
57
+ visible: boolean;
58
+ }
59
+
60
+ interface INavLink {
61
+ name: string;
62
+ path?: string;
63
+ isExternal?: boolean;
64
+ childLinksTitle?: string;
65
+ childLinks?: INavLink[];
66
+ config?: NavLinkConfig;
67
+ }
68
+
69
+ interface IResponsiveNavLinks {
70
+ [key: string]: INavLink[];
71
+ }
72
+
73
+ interface INavigationRefTrackState {
74
+ navListVisibility: Record<string, boolean>;
75
+ clonedNavLinks?: IResponsiveNavLinks;
76
+ }
77
+ </script>
78
+
79
+ <script setup lang="ts">
80
+ const props = defineProps({
81
+ mainNavigationState: {
82
+ type: Object as PropType<INavigationRefTrackState>,
83
+ default: () => [],
84
+ },
85
+ styleClassPassthrough: {
86
+ type: Array as PropType<string[]>,
87
+ default: () => [],
88
+ },
89
+ });
90
+
91
+ const detailsAanimationDuration = 200;
92
+
93
+ const widestNavLinkWidthInMainNavigationState = computed(() => {
94
+ return Object.values(props.mainNavigationState.clonedNavLinks || {}).reduce((maxWidth, group) => {
95
+ return Math.max(
96
+ maxWidth,
97
+ ...group.map(link => link.config?.width || 0)
98
+ );
99
+ }, 0);
100
+ });
101
+
102
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
103
+
104
+ watch(
105
+ () => props.styleClassPassthrough,
106
+ () => {
107
+ resetElementClasses(props.styleClassPassthrough);
108
+ }
109
+ );
110
+ </script>
111
+
112
+ <style lang="css">
113
+
114
+ .overflow-navigation-wrapper {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: 12px;
118
+
119
+ .overflow-navigation-list {
120
+
121
+ display: none;
122
+
123
+ &.visible {
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 12px;
127
+ min-width: var(--_overflow-navigation-list-min-width, auto);
128
+ }
129
+
130
+ .overflow-navigation-item {
131
+
132
+ display: none;
133
+
134
+ &.visible {
135
+ display: block;
136
+ }
137
+
138
+ .overflow-navigation-link {
139
+ text-decoration: none;
140
+ color: inherit;
141
+ }
142
+
143
+ .overflow-navigation-details {
144
+ --_overflow-navigation-sub-nav-list-margin-block-start: 0;
145
+
146
+ &[open] {
147
+ --_overflow-navigation-sub-nav-list-margin-block-start: 12px;
148
+ }
149
+
150
+ &.display-details {
151
+ margin-block-end: 0;
152
+
153
+ .display-details-summary {
154
+ .label {
155
+ .overflow-navigation-text {
156
+ text-wrap: nowrap;
157
+ }
158
+ }
159
+ /* .icon {} */
160
+ }
161
+ .display-details-content {
162
+ .overflow-navigation-sub-nav-inner {
163
+ .overflow-navigation-sub-nav-list {
164
+
165
+ display: flex;
166
+ flex-direction: column;
167
+ gap: 12px;
168
+ margin-block-start: var(--_overflow-navigation-sub-nav-list-margin-block-start);
169
+ transition: margin-block-start v-bind(`${detailsAanimationDuration}ms`) ease;
170
+
171
+ .overflow-navigation-sub-nav-item {
172
+
173
+ .overflow-navigation-sub-nav-link {
174
+ text-decoration: none;
175
+ color: inherit;
176
+
177
+ /* .overflow-navigation-sub-nav-text {} */
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ </style>
@@ -0,0 +1,569 @@
1
+ <template>
2
+ <div class="navigation" :class="[elementClasses, { loaded: navLoaded }]" ref="navigationWrapper">
3
+ <nav class="main-navigation" ref="mainNav">
4
+
5
+ <ul v-for="(navGroup, groupKey) in responsiveNavLinks" :key="groupKey" class="main-navigation-list"
6
+ :ref="el => setNavRef(String(groupKey), el as HTMLUListElement | null)">
7
+ <template v-for="(link, localIndex) in navGroup" :key="localIndex">
8
+ <li v-if="link.path" class="main-navigation-item"
9
+ :class="{ 'visually-hidden': !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
10
+ :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
11
+ ref="mainNavigationItems" :data-group-key="groupKey" :data-local-index="localIndex">
12
+ <NuxtLink class="main-navigation-link" :to="link.path">{{ link.name }}</NuxtLink>
13
+ </li>
14
+ <li v-else class="main-navigation-item"
15
+ :class="{ 'visually-hidden': !mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.visible }"
16
+ :style="{ '--_main-navigation-item-width': mainNavigationState.clonedNavLinks?.[groupKey]?.[localIndex]?.config?.width + 'px' }"
17
+ ref="mainNavigationItems" :data-group-key="groupKey" :data-local-index="localIndex">
18
+ <details class="main-navigation-details" name="navigation-group" ref="navigationDetails">
19
+ <summary @mouseover="handleSummaryHover($event)" @focusin="handleSummaryHover($event)"
20
+ class="main-navigation-details-summary has-toggle-icon">
21
+ <Icon name="mdi:chevron-down" class="icon" />
22
+ {{ link.childLinksTitle }}
23
+ </summary>
24
+ <div class="main-navigation-sub-nav">
25
+ <ul class="main-navigation-sub-nav-list">
26
+ <li class="main-navigation-sub-nav-item" v-for="childLink in link.childLinks" :key="childLink.name">
27
+ <NuxtLink :to="childLink.path" class="main-navigation-sub-nav-link">{{ childLink.name }}</NuxtLink>
28
+ </li>
29
+ </ul>
30
+ </div>
31
+ </details>
32
+ </li>
33
+ </template>
34
+ </ul>
35
+
36
+ </nav>
37
+ <nav class="secondary-navigation" ref="secondaryNav">
38
+ <details class="overflow-details" :class="[{ 'visually-hidden': !navLoaded || !showOverflowDetails }]"
39
+ ref="overflowDetails" name="overflow-group">
40
+ <summary class="overflow-details-summary has-toggle-icon">
41
+ <Icon name="gravity-ui:ellipsis" class="icon" />
42
+ </summary>
43
+ <div class="overflow-details-nav">
44
+ <NavigationItems :main-navigation-state="mainNavigationState" />
45
+ </div>
46
+ </details>
47
+ <ul class="secondary-navigation-list">
48
+ <li class="secondary-navigation-item">
49
+ <NuxtLink class="secondary-navigation-link" to="/">
50
+ <Icon name="material-symbols:settings-outline-rounded" class="icon" />
51
+ </NuxtLink>
52
+ </li>
53
+ </ul>
54
+ </nav>
55
+ </div>
56
+
57
+
58
+ <LayoutRow tag="div" variant="full" :style-class-passthrough="['mb-20', 'debug-grid']">
59
+ <ClientOnly>
60
+ <div>
61
+ <h2 class="heading-4">navigationWrapperRects</h2>
62
+ <pre>{{ navigationWrapperRects }}</pre>
63
+ <hr>
64
+ <h2 class="heading-4">secondaryNavRects</h2>
65
+ <pre>{{ secondaryNavRects }}</pre>
66
+ </div>
67
+ <div>
68
+ <h2 class="heading-4">mainNavigationState</h2>
69
+ <pre>{{ mainNavigationState }}</pre>
70
+ </div>
71
+ </ClientOnly>
72
+
73
+ </LayoutRow>
74
+ </template>
75
+
76
+ <script lang="ts">
77
+
78
+ interface INavLink {
79
+ name: string;
80
+ path?: string;
81
+ isExternal?: boolean;
82
+ childLinksTitle?: string;
83
+ childLinks?: INavLink[];
84
+ config?: NavLinkConfig;
85
+ }
86
+
87
+ interface IResponsiveNavLinks {
88
+ [key: string]: INavLink[];
89
+ }
90
+
91
+ interface IFlooredRect {
92
+ left: number;
93
+ right: number;
94
+ top: number;
95
+ bottom: number;
96
+ width: number;
97
+ height: number;
98
+ }
99
+
100
+ interface NavLinkConfig {
101
+ left: number;
102
+ right: number;
103
+ width?: number;
104
+ visible: boolean;
105
+ }
106
+
107
+ interface INavigationRefTrackState {
108
+ navListVisibility: Record<string, boolean>;
109
+ clonedNavLinks?: IResponsiveNavLinks;
110
+ }
111
+
112
+
113
+ </script>
114
+
115
+ <script setup lang="ts">
116
+ import { useResizeObserver, onClickOutside } from '@vueuse/core';
117
+
118
+ const props = defineProps({
119
+ responsiveNavLinks: {
120
+ type: Object as PropType<IResponsiveNavLinks>,
121
+ default: () => [],
122
+ },
123
+ gapBetweenFirstAndSecondNav: {
124
+ type: Number,
125
+ default: 12, // px
126
+ },
127
+ styleClassPassthrough: {
128
+ type: Array as PropType<string[]>,
129
+ default: () => [],
130
+ },
131
+ });
132
+
133
+
134
+ const navLoaded = ref(false);
135
+ const navigationWrapperRef = useTemplateRef('navigationWrapper');
136
+
137
+ const handleSummaryHover = (event: MouseEvent | FocusEvent) => {
138
+ const summaryElement = event.currentTarget as HTMLElement;
139
+ const parentDetailsElement = summaryElement.closest('details');
140
+ if (!parentDetailsElement) return;
141
+ if (parentDetailsElement.hasAttribute('open')) {
142
+ parentDetailsElement.removeAttribute('open');
143
+ } else {
144
+ parentDetailsElement.setAttribute('open', '');
145
+ }
146
+ };
147
+
148
+ const mainNavigationState = ref<INavigationRefTrackState>({
149
+ navListVisibility: {
150
+ firstNav: false,
151
+ secondNav: false,
152
+ },
153
+ clonedNavLinks: props.responsiveNavLinks,
154
+ });
155
+
156
+ const navRefs = ref<Record<string, HTMLUListElement | null>>({});
157
+
158
+ const setNavRef = (key: string, el: HTMLUListElement | null) => {
159
+ navRefs.value[key] = el
160
+ }
161
+
162
+ const navigationWrapperRects = ref<IFlooredRect | null>(null);
163
+ const firstNavRef = ref<HTMLUListElement | null>(null);
164
+ const firstNavRects = ref<IFlooredRect | null>(null);
165
+
166
+ const secondNavRef = ref<HTMLUListElement | null>(null);
167
+ const secondNavRects = ref<IFlooredRect | null>(null);
168
+
169
+ const secondaryNavRef = useTemplateRef('secondaryNav');
170
+ const secondaryNavRects = ref<IFlooredRect | null>(null);
171
+
172
+ const mainNavigationItemsRefs = useTemplateRef<HTMLLIElement[]>('mainNavigationItems');
173
+
174
+ const navigationDetailsRefs = useTemplateRef<HTMLElement[]>('navigationDetails');
175
+
176
+ const overflowDetailsRef = useTemplateRef('overflowDetails');
177
+
178
+ const showOverflowDetails = computed(() => {
179
+ const hasHiddenNav = !mainNavigationState.value.navListVisibility['firstNav'] || !mainNavigationState.value.navListVisibility['secondNav'];
180
+ return hasHiddenNav;
181
+ });
182
+
183
+ const mainNavigationMarginBlockEnd = computed(() => {
184
+ return secondaryNavRects.value ? secondaryNavRects.value.width + props.gapBetweenFirstAndSecondNav : 0;
185
+ });
186
+
187
+ const initTemplateRefs = async () => {
188
+ firstNavRef.value = navRefs.value['firstNav'] as HTMLUListElement | null;
189
+ secondNavRef.value = navRefs.value['secondNav'] as HTMLUListElement | null;
190
+ return;
191
+ }
192
+
193
+ const getFlooredRect = (rect: DOMRect | null) => {
194
+ if (!rect) return null;
195
+ return {
196
+ left: Math.floor(rect.left),
197
+ right: Math.floor(rect.right),
198
+ top: Math.floor(rect.top),
199
+ bottom: Math.floor(rect.bottom),
200
+ width: Math.floor(rect.width),
201
+ height: Math.floor(rect.height),
202
+ };
203
+ }
204
+
205
+ const updateNavigationConfig = async (source: string) => {
206
+ navigationWrapperRects.value = getFlooredRect((navigationWrapperRef.value && navigationWrapperRef.value.getBoundingClientRect()) ?? null) || null;
207
+ secondaryNavRects.value = getFlooredRect((secondaryNavRef.value && secondaryNavRef.value.getBoundingClientRect()) ?? null) || null;
208
+ firstNavRects.value = getFlooredRect((firstNavRef.value && firstNavRef.value.getBoundingClientRect()) ?? null) || null;
209
+ secondNavRects.value = getFlooredRect((secondNavRef.value && secondNavRef.value.getBoundingClientRect()) ?? null) || null;
210
+ }
211
+
212
+ const initMainNavigationState = () => {
213
+ if (!mainNavigationItemsRefs.value) return;
214
+
215
+ mainNavigationItemsRefs.value.forEach((item, index) => {
216
+
217
+ const rect = item.getBoundingClientRect();
218
+
219
+ const groupKey = item.dataset.groupKey;
220
+ const localIndex = item.dataset.localIndex ? parseInt(item.dataset.localIndex, 10) : 0;
221
+ if (
222
+ groupKey !== undefined &&
223
+ groupKey !== null &&
224
+ mainNavigationState.value.clonedNavLinks &&
225
+ mainNavigationState.value.clonedNavLinks[groupKey] &&
226
+ mainNavigationState.value.clonedNavLinks[groupKey][localIndex]
227
+ ) {
228
+ mainNavigationState.value.clonedNavLinks[groupKey][localIndex] = {
229
+ ...mainNavigationState.value.clonedNavLinks[groupKey][localIndex],
230
+ config: {
231
+ left: item.offsetLeft,
232
+ right: item.offsetLeft + item.offsetWidth,
233
+ width: item.offsetWidth,
234
+ visible: navigationWrapperRects.value ? Math.floor(rect.right + mainNavigationMarginBlockEnd.value + props.gapBetweenFirstAndSecondNav) < navigationWrapperRects.value.right : true,
235
+ },
236
+ };
237
+ }
238
+
239
+ // Check if a single item has visible set to false and set the visibility of the group accordingly
240
+ if (
241
+ typeof groupKey === 'string' &&
242
+ mainNavigationState.value.clonedNavLinks &&
243
+ mainNavigationState.value.clonedNavLinks[groupKey] &&
244
+ mainNavigationState.value.clonedNavLinks[groupKey][localIndex].config?.visible === false
245
+ ) {
246
+ mainNavigationState.value.navListVisibility[groupKey] = false;
247
+ } else if (typeof groupKey === 'string') {
248
+ mainNavigationState.value.navListVisibility[groupKey] = true;
249
+ }
250
+ })
251
+
252
+ }
253
+
254
+ onMounted(async() => {
255
+ await initTemplateRefs().then(() => {
256
+ setTimeout(() => {
257
+ navLoaded.value = true;
258
+ }, 100);
259
+ });
260
+
261
+ navigationDetailsRefs.value?.forEach((element, index) => {
262
+ onClickOutside(element, () => {
263
+ navigationDetailsRefs.value?.[index]?.removeAttribute('open');
264
+ });
265
+ });
266
+ // Add onClickOutside to overflowDetailsRef
267
+ overflowDetailsRef.value && onClickOutside(overflowDetailsRef.value, () => {
268
+ overflowDetailsRef.value?.removeAttribute('open');
269
+ });
270
+ });
271
+
272
+ useResizeObserver(navigationWrapperRef, async () => {
273
+ await updateNavigationConfig("useResizeObserver").then(() => {
274
+ initMainNavigationState()
275
+ });
276
+ });
277
+
278
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
279
+
280
+ watch(
281
+ () => props.styleClassPassthrough,
282
+ () => {
283
+ resetElementClasses(props.styleClassPassthrough);
284
+ }
285
+ );
286
+ </script>
287
+
288
+ <style lang="css">
289
+
290
+ .navigation {
291
+
292
+ ul,
293
+ ol {
294
+ list-style-type: none;
295
+ margin: 0;
296
+ padding: 0;
297
+
298
+ li {
299
+ /* text-box-trim: trim-both; */
300
+ /* text-box-edge: cap alphabetic; */
301
+ display: flex;
302
+ align-items: center;
303
+ }
304
+ }
305
+
306
+ --_link-visibility-transition: none;
307
+
308
+ &.loaded {
309
+ --_link-visibility-transition: all 0.2s ease-in-out;
310
+ }
311
+
312
+ display: grid;
313
+ grid-template-areas: 'navStack';
314
+
315
+ margin: 12px;
316
+ border-radius: 8px;
317
+ background-color: #efefef05;
318
+ border: 1px solid #efefef75;
319
+ padding: 12px;
320
+
321
+ .main-navigation {
322
+ grid-area: navStack;
323
+ display: flex;
324
+ flex-wrap: nowrap;
325
+ flex-grow: 1;
326
+ justify-content: space-between;
327
+ gap: 60px;
328
+
329
+ overflow-x: hidden;
330
+ margin-inline-end: v-bind(`${mainNavigationMarginBlockEnd}px`);
331
+
332
+ &.collapsed {
333
+ justify-content: flex-start;
334
+ }
335
+
336
+ .main-navigation-list {
337
+ display: flex;
338
+ flex-wrap: nowrap;
339
+
340
+ &:nth-of-type(1) {
341
+ gap: 30px;
342
+ }
343
+
344
+ &:nth-of-type(2) {
345
+ gap: 30px;
346
+ }
347
+
348
+ .main-navigation-item {
349
+
350
+ width: var(--_main-navigation-item-width);
351
+ overflow: hidden;
352
+ transition:
353
+ opacity 0.2s ease-in-out,
354
+ visibility 0.2s ease-in-out;
355
+
356
+ .main-navigation-link {
357
+ display: flex;
358
+ text-wrap-mode: nowrap;
359
+ color: inherit;
360
+ text-decoration: none;
361
+ margin-inline-start: 0;
362
+ transition: var(--_link-visibility-transition);
363
+ }
364
+
365
+ .main-navigation-details {
366
+
367
+ --_icon-transform: scaleY(1);
368
+
369
+ margin-inline-start: 0;
370
+ transition: var(--_link-visibility-transition);
371
+
372
+ &[open] {
373
+ --_icon-transform: scaleY(-1);
374
+ }
375
+
376
+ .has-toggle-icon {
377
+ display: flex;
378
+ gap: 6px;
379
+ text-wrap-mode: nowrap;
380
+
381
+ .icon {
382
+ display: block;
383
+ transform: var(--_icon-transform);
384
+ transition: transform 0.2s ease-in-out;
385
+ }
386
+ }
387
+
388
+ .main-navigation-details-summary {
389
+
390
+ &::-webkit-details-marker,
391
+ &::marker {
392
+ display: none;
393
+ }
394
+
395
+ &:hover {
396
+ cursor: pointer;
397
+ }
398
+
399
+ }
400
+
401
+ .main-navigation-sub-nav {
402
+ position: absolute;
403
+ padding: 12px;
404
+ border: 1px solid #efefef75;
405
+ border-radius: 8px;
406
+ background-color: #000;
407
+ translate: 0 12px;
408
+
409
+ min-width: var(--_main-navigation-item-width);
410
+
411
+ .main-navigation-sub-nav-list {
412
+
413
+ display: grid;
414
+ grid-template-columns: repeat(2, auto);
415
+ gap: 12px;
416
+
417
+ .main-navigation-sub-nav-item {
418
+ margin-bottom: 8px;
419
+
420
+ &:last-child {
421
+ margin-bottom: 0;
422
+ }
423
+
424
+ .main-navigation-sub-nav-link {
425
+ display: block;
426
+ text-wrap-mode: nowrap;
427
+ text-decoration: none;
428
+ color: inherit;
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+
435
+ &.visually-hidden {
436
+ visibility: hidden;
437
+ opacity: 0;
438
+
439
+ .main-navigation-details,
440
+ .main-navigation-link {
441
+ margin-inline-start: var(--_main-navigation-item-width);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ .secondary-navigation {
449
+ grid-area: navStack;
450
+ justify-self: end;
451
+
452
+ display: flex;
453
+ gap: 12px;
454
+ align-items: center;
455
+
456
+ .secondary-navigation-list {
457
+
458
+ .secondary-navigation-item {
459
+
460
+ .secondary-navigation-link {
461
+ display: flex;
462
+ align-items: center;
463
+ font: inherit;
464
+ color: inherit;
465
+
466
+ .icon {
467
+ height: 1.35em;
468
+ width: 1.35em;
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ .main-navigation-link {
475
+ .icon {
476
+ height: 1.35em;
477
+ width: 1.35em;
478
+ }
479
+ }
480
+
481
+ .overflow-details {
482
+ list-style: none;
483
+ padding: 0;
484
+ margin: 0;
485
+ position: relative;
486
+ cursor: pointer;
487
+ width: fit-content;
488
+ /* overflow: hidden; */
489
+
490
+ transition: all 0.2s ease-in-out;
491
+
492
+ &.visually-hidden {
493
+ opacity: 0;
494
+ visibility: hidden;
495
+ /* width: 0; */
496
+ }
497
+
498
+ .overflow-details-summary {
499
+ --_icon-zoom: 1;
500
+ display: flex;
501
+ align-items: center;
502
+ justify-content: center;
503
+ padding-inline: 5px;
504
+ text-wrap: nowrap;
505
+
506
+ aspect-ratio: 1;
507
+ border-radius: 4px;
508
+ border: 1px solid #ffffff90;
509
+ outline: 1px solid #ffffff10;
510
+ background-color: Canvas;
511
+
512
+ width: 28px;
513
+ overflow: hidden;
514
+
515
+
516
+ &::-webkit-details-marker,
517
+ &::marker {
518
+ display: none;
519
+ }
520
+
521
+ &:hover {
522
+ --_icon-zoom: 1.2;
523
+ outline: 1px solid #ffffff;
524
+ }
525
+
526
+ .icon {
527
+ scale: var(--_icon-zoom);
528
+ transition: scale 0.2s ease-in-out;
529
+ }
530
+ }
531
+
532
+
533
+ .overflow-details-nav {
534
+ position: absolute;
535
+ top: 135%;
536
+ right: 0;
537
+ background-color: #000;
538
+ border: 1px solid #ffffff90;
539
+ border-radius: 8px;
540
+ padding: 12px;
541
+ margin: 0;
542
+ z-index: 999;
543
+ min-width: var(--_overflow-drop-down-width, fit-content);
544
+
545
+ display: grid;
546
+ grid-auto-flow: row;
547
+ gap: 8px;
548
+ }
549
+ }
550
+ }
551
+ }
552
+
553
+ .debug-grid {
554
+ display: none;
555
+
556
+ .layout-row-inner > div {
557
+ display: flex;
558
+ flex-wrap: wrap;
559
+ gap: 12px;
560
+
561
+ margin-inline: 12px;
562
+
563
+ > div {
564
+ outline: 1px solid gray;
565
+ padding: 12px;
566
+ }
567
+ }
568
+ }
569
+ </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "2.4.0",
4
+ "version": "2.5.1",
5
5
  "main": "nuxt.config.ts",
6
6
  "scripts": {
7
7
  "clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",