@codecavepro/brand 3.0.0 → 3.1.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.
|
@@ -237,7 +237,7 @@ withDefaults(defineProps<{
|
|
|
237
237
|
position: absolute;
|
|
238
238
|
top: 100%;
|
|
239
239
|
left: 0;
|
|
240
|
-
margin-top:
|
|
240
|
+
margin-top: 14px;
|
|
241
241
|
width: max-content;
|
|
242
242
|
opacity: 0;
|
|
243
243
|
visibility: hidden;
|
|
@@ -245,6 +245,8 @@ withDefaults(defineProps<{
|
|
|
245
245
|
padding: 1.25rem;
|
|
246
246
|
z-index: 100;
|
|
247
247
|
background: var(--color-surface-primary);
|
|
248
|
+
border: 1px solid var(--color-surface-quaternary);
|
|
249
|
+
border-top: 0px;
|
|
248
250
|
-webkit-backdrop-filter: blur(24px);
|
|
249
251
|
backdrop-filter: blur(24px);
|
|
250
252
|
transition: opacity 0.2s ease, visibility 0.2s;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
2
|
+
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
3
|
+
import Shevron from '../assets/icons/shevron.vue';
|
|
3
4
|
import { type MenuItem } from '../lib/menus/menuTypes.ts';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -10,48 +11,131 @@ interface Props {
|
|
|
10
11
|
|
|
11
12
|
const props = defineProps<Props>();
|
|
12
13
|
|
|
14
|
+
const openItem = ref<string | null>(null);
|
|
15
|
+
|
|
16
|
+
const itemRefs = new Map<string, HTMLElement>();
|
|
17
|
+
|
|
18
|
+
const submenuStyle = ref({
|
|
19
|
+
left: '0px',
|
|
20
|
+
width: '200px'
|
|
21
|
+
});
|
|
22
|
+
|
|
13
23
|
const items = computed<MenuItem[]>(() => {
|
|
14
24
|
return props.section ? props.subMenu[props.section] ?? [] : [];
|
|
15
25
|
});
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const activeSubmenu = computed(() => {
|
|
28
|
+
const item = items.value.find(i => i.name === openItem.value);
|
|
29
|
+
return item?.subMenu ?? [];
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function setItemRef(name: string, el: Element | null) {
|
|
33
|
+
if (el instanceof HTMLElement) {
|
|
34
|
+
itemRefs.set(name, el);
|
|
35
|
+
} else {
|
|
36
|
+
itemRefs.delete(name);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function updateSubmenuPosition(itemName: string) {
|
|
41
|
+
if (window.innerWidth < 768) {
|
|
42
|
+
submenuStyle.value = {
|
|
43
|
+
left: '0px',
|
|
44
|
+
width: '100vw'
|
|
45
|
+
};
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const el = itemRefs.get(itemName);
|
|
50
|
+
const nav = document.querySelector('.ds-subnav');
|
|
51
|
+
|
|
52
|
+
if (!el || !nav) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const elRect = el.getBoundingClientRect();
|
|
57
|
+
const navRect = nav.getBoundingClientRect();
|
|
58
|
+
|
|
59
|
+
submenuStyle.value = {
|
|
60
|
+
left: `${elRect.left + elRect.width / 2 - navRect.left}px`,
|
|
61
|
+
width: `${elRect.width}px`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openSubmenu(item: MenuItem) {
|
|
66
|
+
if (!item.subMenu?.length) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (openItem.value === item.name) {
|
|
71
|
+
openItem.value = null;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateSubmenuPosition(item.name);
|
|
76
|
+
openItem.value = item.name;
|
|
77
|
+
}
|
|
78
|
+
function closeSubmenu(event?: Event) {
|
|
79
|
+
event?.stopPropagation();
|
|
80
|
+
openItem.value = null;
|
|
81
|
+
}
|
|
82
|
+
async function initSubmenu() {
|
|
83
|
+
await nextTick();
|
|
84
|
+
|
|
85
|
+
const activeItem = items.value.find(
|
|
86
|
+
item => item.name === props.current && item.subMenu?.length
|
|
24
87
|
);
|
|
88
|
+
|
|
89
|
+
if (!activeItem) {
|
|
90
|
+
openItem.value = null;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
updateSubmenuPosition(activeItem.name);
|
|
95
|
+
openItem.value = activeItem.name;
|
|
25
96
|
}
|
|
97
|
+
|
|
98
|
+
onMounted(async () => {
|
|
99
|
+
await initSubmenu();
|
|
100
|
+
});
|
|
101
|
+
|
|
26
102
|
</script>
|
|
27
103
|
|
|
28
104
|
<template>
|
|
29
|
-
<nav
|
|
30
|
-
|
|
31
|
-
class="ds-subnav"
|
|
32
|
-
aria-label="Section"
|
|
33
|
-
>
|
|
34
|
-
<div class="ds-subnav-inner">
|
|
105
|
+
<nav v-if="items.length" class="ds-subnav" aria-label="Section">
|
|
106
|
+
<div class="ds-subnav-inner" :class="{ 'is-submenu-open': openItem }">
|
|
35
107
|
<ul>
|
|
36
|
-
<li
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</a>
|
|
108
|
+
<li v-for="item in items" :key="item.name" :ref="el => setItemRef(item.name, el)">
|
|
109
|
+
<div class="ds-subnav-item" @click="item.subMenu?.length ? openSubmenu(item) : closeSubmenu()">
|
|
110
|
+
<component :is="item.href ? 'a' : 'span'" class="ds-subnav-link" :href="item.href" :aria-current="item.name === current ? 'page' : undefined">
|
|
111
|
+
{{ item.label }}
|
|
112
|
+
</component>
|
|
113
|
+
|
|
114
|
+
<span v-if="item.subMenu?.length" class="ds-subnav-toggle" aria-hidden="true">
|
|
115
|
+
<Shevron class="ds-subnav-chevron" :class="{ 'is-open': openItem === item.name }" />
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
47
118
|
</li>
|
|
48
119
|
</ul>
|
|
49
120
|
</div>
|
|
121
|
+
|
|
122
|
+
<Transition name="submenu">
|
|
123
|
+
<div class="ds-submenu-overlay" :class="{ 'is-open': activeSubmenu.length }" :style="submenuStyle">
|
|
124
|
+
<ul class="ds-submenu-list">
|
|
125
|
+
<li v-for="(subItem, index) in activeSubmenu" :key="subItem.name" class="ds-submenu-item" :style="{ '--delay': `${index * 80}ms` }">
|
|
126
|
+
<a :href="subItem.href" @click="closeSubmenu" class="ds-submenu-link">
|
|
127
|
+
{{ subItem.label }}
|
|
128
|
+
</a>
|
|
129
|
+
</li>
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</Transition>
|
|
50
133
|
</nav>
|
|
51
134
|
</template>
|
|
52
135
|
|
|
53
136
|
<style>
|
|
54
137
|
.ds-subnav {
|
|
138
|
+
position: relative;
|
|
55
139
|
border-bottom: 1px solid var(--color-surface-quaternary);
|
|
56
140
|
background: var(--color-surface-primary-transparent);
|
|
57
141
|
-webkit-backdrop-filter: blur(24px);
|
|
@@ -67,6 +151,10 @@ if (
|
|
|
67
151
|
overflow-y: hidden;
|
|
68
152
|
}
|
|
69
153
|
|
|
154
|
+
.ds-subnav-inner.is-submenu-open {
|
|
155
|
+
overflow: hidden;
|
|
156
|
+
}
|
|
157
|
+
|
|
70
158
|
@media (min-width: 768px) {
|
|
71
159
|
.ds-subnav-inner {
|
|
72
160
|
padding-left: var(--gutter-md);
|
|
@@ -81,7 +169,6 @@ if (
|
|
|
81
169
|
}
|
|
82
170
|
}
|
|
83
171
|
|
|
84
|
-
/* center links when there is enough space */
|
|
85
172
|
.ds-subnav ul {
|
|
86
173
|
display: flex;
|
|
87
174
|
align-items: center;
|
|
@@ -89,13 +176,11 @@ if (
|
|
|
89
176
|
|
|
90
177
|
margin: 0;
|
|
91
178
|
padding: 0;
|
|
179
|
+
|
|
92
180
|
list-style: none;
|
|
93
181
|
|
|
94
|
-
/* allow overflow while staying centered */
|
|
95
182
|
width: max-content;
|
|
96
183
|
min-width: 100%;
|
|
97
|
-
|
|
98
|
-
/* prevents wrapping */
|
|
99
184
|
flex-wrap: nowrap;
|
|
100
185
|
}
|
|
101
186
|
|
|
@@ -104,6 +189,12 @@ if (
|
|
|
104
189
|
flex-shrink: 0;
|
|
105
190
|
}
|
|
106
191
|
|
|
192
|
+
.ds-subnav-item {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
}
|
|
197
|
+
|
|
107
198
|
.ds-subnav-link {
|
|
108
199
|
box-sizing: border-box;
|
|
109
200
|
display: inline-flex;
|
|
@@ -125,7 +216,139 @@ if (
|
|
|
125
216
|
|
|
126
217
|
.ds-subnav-link:hover,
|
|
127
218
|
.ds-subnav-link:active,
|
|
128
|
-
.ds-subnav-link[aria-current=
|
|
219
|
+
.ds-subnav-link[aria-current='page'] {
|
|
220
|
+
color: var(--color-hovered);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.ds-subnav-toggle {
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
|
|
228
|
+
margin-left: -0.75rem;
|
|
229
|
+
padding: 0;
|
|
230
|
+
|
|
231
|
+
border: 0;
|
|
232
|
+
background: transparent;
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ds-subnav-chevron {
|
|
237
|
+
width: 1rem;
|
|
238
|
+
height: 1rem;
|
|
239
|
+
|
|
240
|
+
color: var(--color-heading);
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
|
|
243
|
+
transition:
|
|
244
|
+
transform 0.25s ease,
|
|
245
|
+
color 0.25s ease;
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.ds-subnav-item:hover .ds-subnav-chevron {
|
|
129
250
|
color: var(--color-hovered);
|
|
130
251
|
}
|
|
252
|
+
|
|
253
|
+
.ds-subnav-chevron.is-open {
|
|
254
|
+
transform: rotate(180deg);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.ds-submenu-overlay {
|
|
258
|
+
position: absolute;
|
|
259
|
+
top: calc(100% + 1px);
|
|
260
|
+
transform: translateX(-50%);
|
|
261
|
+
z-index: 100;
|
|
262
|
+
|
|
263
|
+
min-width: max-content;
|
|
264
|
+
|
|
265
|
+
visibility: hidden;
|
|
266
|
+
opacity: 0;
|
|
267
|
+
pointer-events: none;
|
|
268
|
+
|
|
269
|
+
background: var(--color-surface-primary);
|
|
270
|
+
|
|
271
|
+
border: 1px solid var(--color-surface-quaternary);
|
|
272
|
+
border-top: 0;
|
|
273
|
+
border-radius: 0 0 1rem 1rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.ds-submenu-overlay.is-open {
|
|
277
|
+
visibility: visible;
|
|
278
|
+
opacity: 1;
|
|
279
|
+
pointer-events: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.ds-submenu-list {
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
|
|
286
|
+
margin: 0;
|
|
287
|
+
padding: 0.5rem 0;
|
|
288
|
+
|
|
289
|
+
min-width: 200px;
|
|
290
|
+
max-width: 280px;
|
|
291
|
+
|
|
292
|
+
list-style: none;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.ds-submenu-item {
|
|
296
|
+
margin: 0;
|
|
297
|
+
padding: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.ds-submenu-link {
|
|
301
|
+
box-sizing: border-box;
|
|
302
|
+
--delay: 0ms;
|
|
303
|
+
display: block;
|
|
304
|
+
|
|
305
|
+
padding: 0.5rem 1.5rem;
|
|
306
|
+
|
|
307
|
+
color: var(--color-body-primary);
|
|
308
|
+
|
|
309
|
+
font-family: var(--font-sans);
|
|
310
|
+
font-size: var(--text-body);
|
|
311
|
+
font-weight: var(--font-weight-bold);
|
|
312
|
+
line-height: var(--leading-body);
|
|
313
|
+
|
|
314
|
+
text-align: center;
|
|
315
|
+
text-decoration: none;
|
|
316
|
+
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transform: translateY(8px);
|
|
319
|
+
|
|
320
|
+
animation: submenuTextIn 300ms ease forwards;
|
|
321
|
+
animation-delay: var(--delay);
|
|
322
|
+
|
|
323
|
+
transition: color var(--transition-colors);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.ds-submenu-link:hover {
|
|
327
|
+
color: var(--color-hovered);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
@keyframes submenuTextIn {
|
|
331
|
+
from {
|
|
332
|
+
opacity: 0;
|
|
333
|
+
transform: translateY(8px);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
to {
|
|
337
|
+
opacity: 1;
|
|
338
|
+
transform: translateY(0);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@media (max-width: 767px) {
|
|
343
|
+
.ds-submenu-overlay {
|
|
344
|
+
left: 0 !important;
|
|
345
|
+
width: 100vw !important;
|
|
346
|
+
transform: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.ds-submenu-list {
|
|
350
|
+
width: 100%;
|
|
351
|
+
max-width: none;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
131
354
|
</style>
|
package/package.json
CHANGED