@apptegy/nuxt-navigation 0.1.6 → 0.1.8
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/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Nav/HelpArticles.vue +3 -1
- package/dist/runtime/components/Nav/Icons/SidebarToggle.vue +10 -2
- package/dist/runtime/components/Nav/OrgSelect.vue +76 -71
- package/dist/runtime/components/Nav/Sidebar.vue +35 -21
- package/dist/runtime/components/Nav/SidebarItem.vue +12 -3
- package/dist/runtime/components/Nav/SidebarSection.vue +10 -15
- package/dist/runtime/components/Nav/SidebarToggle.vue +2 -2
- package/dist/runtime/components/Nav/SubMenuItem.vue +2 -2
- package/dist/runtime/components/Nav/{UserDropdown/index.vue → UserDropdown.vue} +55 -8
- package/dist/runtime/composables/useDropdown.d.ts +2 -2
- package/dist/runtime/composables/useDropdown.js +6 -0
- package/package.json +12 -12
- /package/dist/runtime/assets/{thrillshare-icon-white.svg → icon.svg} +0 -0
package/dist/module.d.mts
CHANGED
|
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
|
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
4
|
}
|
|
5
|
-
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
5
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
6
|
|
|
7
7
|
export { type ModuleOptions, _default as default };
|
package/dist/module.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import * as _nuxt_schema from '@nuxt/schema';
|
|
|
2
2
|
|
|
3
3
|
interface ModuleOptions {
|
|
4
4
|
}
|
|
5
|
-
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions>;
|
|
5
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
6
6
|
|
|
7
7
|
export { type ModuleOptions, _default as default };
|
package/dist/module.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
>
|
|
23
23
|
<img v-if="isDropdown" src="../../assets/img/help-toggle.svg" alt="" class="help-center-btn-icon">
|
|
24
24
|
<img v-else src="../../assets/img/help.svg" alt="" class="help-center-btn-icon">
|
|
25
|
-
<span>
|
|
25
|
+
<span v-if="expandedSidebar">
|
|
26
26
|
{{ isDropdown ? $t('navigation.helpCenter') : $t('navigation.helpArticles') }}
|
|
27
27
|
</span>
|
|
28
28
|
<img
|
|
@@ -47,6 +47,8 @@ const props = withDefaults(defineProps<IHelpArticlesProps>(), {
|
|
|
47
47
|
helpArticleOptions: () => [],
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
const { state: expandedSidebar } = useSidebar();
|
|
51
|
+
|
|
50
52
|
const expanded = ref(false);
|
|
51
53
|
const helpBtnActions = ref();
|
|
52
54
|
const dropdownScrollHeight = ref(0);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden>
|
|
3
|
-
<rect x="4.3335" y="5.5" width="15.3333"
|
|
4
|
-
<
|
|
3
|
+
<rect x="4.3335" y="5.5" width="15.3333" height="13" rx="0.666667" stroke="currentColor"/>
|
|
4
|
+
<rect x="4.3335" y="5.5" :width="expanded ? 6: 2" height="13" rx="0.666667" fill="currentColor" stroke="currentColor" stroke-linecap="round"/>
|
|
5
5
|
</svg>
|
|
6
6
|
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
const { state: expanded } = useSidebar();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<style scoped>
|
|
13
|
+
rect{transition:width .2s}
|
|
14
|
+
</style>
|
|
@@ -1,69 +1,11 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="org-selection">
|
|
3
|
-
<label id="org-dropdown-label" class="sr-only">
|
|
4
|
-
{{ $t("navigation.orgDropdown") }}
|
|
5
|
-
</label>
|
|
6
|
-
<div
|
|
7
|
-
ref="dropdownTriggerRef"
|
|
8
|
-
aria-labelledby="org-dropdown-label"
|
|
9
|
-
aria-controls="organizations-options-list"
|
|
10
|
-
:aria-expanded="active"
|
|
11
|
-
aria-haspopup="listbox"
|
|
12
|
-
role="combobox"
|
|
13
|
-
class="dropdown-trigger"
|
|
14
|
-
>
|
|
15
|
-
<input
|
|
16
|
-
ref="inputRef"
|
|
17
|
-
type="text"
|
|
18
|
-
class="org-title"
|
|
19
|
-
:placeholder="$t('navigation.orgPlaceholder')"
|
|
20
|
-
:value="active ? searchQuery : selectedOrg?.name"
|
|
21
|
-
@keydown="keydownActionsOnTrigger"
|
|
22
|
-
@click.prevent="toggleDropdown"
|
|
23
|
-
@input="handleDropdownSearch"
|
|
24
|
-
>
|
|
25
|
-
<span v-if="selectedOrg?.intranet" class="badge selected">
|
|
26
|
-
<a-icon icon="16:lock-locked" />
|
|
27
|
-
{{ $t("navigation.orgBadge") }}
|
|
28
|
-
</span>
|
|
29
|
-
</div>
|
|
30
|
-
<div
|
|
31
|
-
v-show="active"
|
|
32
|
-
id="organizations-options-list"
|
|
33
|
-
class="dropdown"
|
|
34
|
-
role="listbox"
|
|
35
|
-
>
|
|
36
|
-
<div
|
|
37
|
-
v-for="(option) in sortedOrgs"
|
|
38
|
-
:key="option.id"
|
|
39
|
-
ref="optionRef"
|
|
40
|
-
v-on-click-outside="onClickOutsideHandler"
|
|
41
|
-
role="option"
|
|
42
|
-
class="org-option"
|
|
43
|
-
:class="{ selected: option.id === modelValue }"
|
|
44
|
-
:aria-selected="option.id === modelValue"
|
|
45
|
-
tabindex="-1"
|
|
46
|
-
@keydown="handleSelectedOrg($event, option.id)"
|
|
47
|
-
@click="handleSelectedOrg($event, option.id)"
|
|
48
|
-
>
|
|
49
|
-
<span :class="{ pinned: option.pinned, 'item-flex': true }"
|
|
50
|
-
>{{ option.name }}
|
|
51
|
-
<span v-if="option.intranet" class="badge selected">
|
|
52
|
-
<a-icon icon="16:lock-locked" />
|
|
53
|
-
{{ $t("navigation.orgBadge") }}
|
|
54
|
-
</span>
|
|
55
|
-
</span>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</template>
|
|
60
|
-
|
|
61
1
|
<script setup lang="ts">
|
|
62
|
-
import { useDropdown, useNuxtApp } from "#imports";
|
|
2
|
+
import { useDropdown, useNuxtApp, useAppConfig } from "#imports";
|
|
63
3
|
import { vOnClickOutside } from "@vueuse/components";
|
|
64
4
|
import { ref, computed } from "vue";
|
|
65
5
|
|
|
66
6
|
const { $t } = useNuxtApp();
|
|
7
|
+
const appConfig = useAppConfig();
|
|
8
|
+
const zIndex = computed(() => appConfig?.navigation?.zIndex || 100)
|
|
67
9
|
|
|
68
10
|
interface IOrg {
|
|
69
11
|
name: string;
|
|
@@ -74,17 +16,17 @@ interface IOrg {
|
|
|
74
16
|
|
|
75
17
|
interface IOrgSelectProps {
|
|
76
18
|
orgs: Array<IOrg>;
|
|
77
|
-
modelValue: number
|
|
19
|
+
modelValue: number;
|
|
78
20
|
}
|
|
79
21
|
|
|
80
22
|
const emit = defineEmits<{
|
|
81
|
-
|
|
82
|
-
|
|
23
|
+
"update:model-value": [index: number];
|
|
24
|
+
}>();
|
|
83
25
|
|
|
84
26
|
const dropdownTriggerRef = ref();
|
|
85
27
|
const optionRef = ref();
|
|
86
28
|
const selectedOrg = computed(() => {
|
|
87
|
-
return props.orgs.find(({id}) => id == props.modelValue);
|
|
29
|
+
return props.orgs.find(({ id }) => id == props.modelValue);
|
|
88
30
|
});
|
|
89
31
|
|
|
90
32
|
const props = defineProps<IOrgSelectProps>();
|
|
@@ -116,16 +58,76 @@ const sortedOrgs = computed(() => {
|
|
|
116
58
|
});
|
|
117
59
|
|
|
118
60
|
function handleSelectedOrg($event: KeyboardEvent | MouseEvent, orgId: number) {
|
|
119
|
-
if($event.type ==
|
|
61
|
+
if ($event.type == "click") {
|
|
120
62
|
emit("update:model-value", orgId);
|
|
121
63
|
closeDropdown();
|
|
122
|
-
} else if ($event.type ==
|
|
64
|
+
} else if ($event.type == "keydown") {
|
|
123
65
|
emit("update:model-value", orgId);
|
|
124
66
|
handleKeydown($event as KeyboardEvent, orgId);
|
|
125
67
|
}
|
|
126
68
|
}
|
|
127
69
|
</script>
|
|
128
70
|
|
|
71
|
+
<template>
|
|
72
|
+
<div class="org-selection">
|
|
73
|
+
<label id="org-dropdown-label" class="sr-only">
|
|
74
|
+
{{ $t("navigation.orgDropdown") }}
|
|
75
|
+
</label>
|
|
76
|
+
<div
|
|
77
|
+
ref="dropdownTriggerRef"
|
|
78
|
+
aria-labelledby="org-dropdown-label"
|
|
79
|
+
aria-controls="organizations-options-list"
|
|
80
|
+
:aria-expanded="active"
|
|
81
|
+
aria-haspopup="listbox"
|
|
82
|
+
role="combobox"
|
|
83
|
+
class="dropdown-trigger"
|
|
84
|
+
>
|
|
85
|
+
<input
|
|
86
|
+
ref="inputRef"
|
|
87
|
+
type="text"
|
|
88
|
+
class="org-title"
|
|
89
|
+
:placeholder="$t('navigation.orgPlaceholder')"
|
|
90
|
+
:value="active ? searchQuery : selectedOrg?.name"
|
|
91
|
+
@keydown="keydownActionsOnTrigger"
|
|
92
|
+
@click.prevent="toggleDropdown"
|
|
93
|
+
@input="handleDropdownSearch"
|
|
94
|
+
>
|
|
95
|
+
<span v-if="selectedOrg?.intranet" class="badge selected">
|
|
96
|
+
<a-icon icon="16:lock-locked" />
|
|
97
|
+
{{ $t("navigation.orgBadge") }}
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div
|
|
101
|
+
v-show="active"
|
|
102
|
+
id="organizations-options-list"
|
|
103
|
+
class="dropdown"
|
|
104
|
+
role="listbox"
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
v-for="option in sortedOrgs"
|
|
108
|
+
:key="option.id"
|
|
109
|
+
ref="optionRef"
|
|
110
|
+
v-on-click-outside="onClickOutsideHandler"
|
|
111
|
+
role="option"
|
|
112
|
+
class="org-option"
|
|
113
|
+
:class="{ selected: option.id === modelValue }"
|
|
114
|
+
:aria-selected="option.id === modelValue"
|
|
115
|
+
tabindex="-1"
|
|
116
|
+
@keydown="handleSelectedOrg($event, option.id)"
|
|
117
|
+
@click="handleSelectedOrg($event, option.id)"
|
|
118
|
+
>
|
|
119
|
+
<span :class="{ pinned: option.pinned, 'item-flex': true }"
|
|
120
|
+
>{{ option.name }}
|
|
121
|
+
<span v-if="option.intranet" class="badge selected">
|
|
122
|
+
<a-icon icon="16:lock-locked" />
|
|
123
|
+
{{ $t("navigation.orgBadge") }}
|
|
124
|
+
</span>
|
|
125
|
+
</span>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
129
131
|
<style scoped>
|
|
130
132
|
#organizations-options-list {
|
|
131
133
|
position: absolute;
|
|
@@ -133,7 +135,7 @@ function handleSelectedOrg($event: KeyboardEvent | MouseEvent, orgId: number) {
|
|
|
133
135
|
max-height: 300px;
|
|
134
136
|
overflow-y: auto;
|
|
135
137
|
background-color: var(--primary-white);
|
|
136
|
-
z-index:
|
|
138
|
+
z-index: v-bind(zIndex);
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
.org-selection {
|
|
@@ -168,11 +170,14 @@ function handleSelectedOrg($event: KeyboardEvent | MouseEvent, orgId: number) {
|
|
|
168
170
|
white-space: nowrap;
|
|
169
171
|
cursor: pointer;
|
|
170
172
|
}
|
|
171
|
-
.org-selection .dropdown-trigger .org-title:focus {
|
|
173
|
+
.org-selection .dropdown-trigger .org-title:focus-visible {
|
|
172
174
|
outline: none;
|
|
173
175
|
}
|
|
176
|
+
.org-selection .dropdown {
|
|
177
|
+
border-radius: 0 0 4px 4px;
|
|
178
|
+
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.06);
|
|
179
|
+
}
|
|
174
180
|
.org-selection .dropdown .org-option {
|
|
175
|
-
display: block;
|
|
176
181
|
padding: 12px;
|
|
177
182
|
min-height: 40px;
|
|
178
183
|
line-height: 16px;
|
|
@@ -200,7 +205,7 @@ function handleSelectedOrg($event: KeyboardEvent | MouseEvent, orgId: number) {
|
|
|
200
205
|
background-color: var(--purple-50);
|
|
201
206
|
padding: 2px 4px;
|
|
202
207
|
color: white;
|
|
203
|
-
border-radius:
|
|
208
|
+
border-radius: 2px;
|
|
204
209
|
text-transform: uppercase;
|
|
205
210
|
}
|
|
206
211
|
.org-selection .badge svg {
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
role="navigation"
|
|
5
5
|
aria-label="Main"
|
|
6
6
|
:class="{ expanded }"
|
|
7
|
+
@mouseenter="onHover"
|
|
8
|
+
@mouseleave="onHoverLeave"
|
|
7
9
|
>
|
|
8
10
|
<a
|
|
9
11
|
ref="skip-to-content"
|
|
@@ -13,14 +15,15 @@
|
|
|
13
15
|
>
|
|
14
16
|
{{ $t('navigation.skipToContent') }}
|
|
15
17
|
</a>
|
|
18
|
+
<div class="navbar--header">
|
|
19
|
+
<slot name="switcher" />
|
|
20
|
+
</div>
|
|
16
21
|
<div class="navbar--content">
|
|
17
|
-
<slot name="switcher" />
|
|
18
22
|
<ul class="navbar-applications">
|
|
19
23
|
<li v-for="application of applications" :key="application.id">
|
|
20
24
|
<NavSidebarSection
|
|
21
25
|
:application="application"
|
|
22
26
|
:active="currentApplication === application.text"
|
|
23
|
-
:navbar-expanded="expanded"
|
|
24
27
|
>
|
|
25
28
|
<slot v-if="currentApplication === application.text" />
|
|
26
29
|
</NavSidebarSection>
|
|
@@ -28,37 +31,32 @@
|
|
|
28
31
|
<li>
|
|
29
32
|
<NavHelpArticles
|
|
30
33
|
v-if="showHelpArticles"
|
|
31
|
-
:navbar-expanded="expanded"
|
|
32
34
|
:help-article-options="helpArticleOptions"
|
|
33
35
|
/>
|
|
34
36
|
</li>
|
|
35
37
|
</ul>
|
|
36
|
-
|
|
37
38
|
</div>
|
|
38
39
|
<div class="navbar--footer">
|
|
39
40
|
<NavImpersonateInfoPanel
|
|
40
41
|
v-if="impersonatedUser.show"
|
|
41
42
|
:impersonated-user="impersonatedUser"
|
|
42
43
|
/>
|
|
43
|
-
</div>
|
|
44
44
|
|
|
45
45
|
<slot name="logo">
|
|
46
46
|
<NuxtLink :to="homepageUrl">
|
|
47
47
|
<div class="navbar--logo">
|
|
48
|
-
<img
|
|
49
|
-
<img v-else :src="logo.iconSrc" :alt="logo.alt">
|
|
48
|
+
<img :src="expanded ? logo.src : logo.iconSrc" :alt="logo.alt">
|
|
50
49
|
</div>
|
|
51
50
|
</NuxtLink>
|
|
52
51
|
</slot>
|
|
52
|
+
</div>
|
|
53
53
|
</nav>
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
56
|
<script setup lang="ts">
|
|
57
|
-
import { useNuxtApp, useSidebar
|
|
57
|
+
import { useNuxtApp, useSidebar } from '#imports';
|
|
58
58
|
const { $t } = useNuxtApp();
|
|
59
|
-
const { state } = useSidebar();
|
|
60
|
-
|
|
61
|
-
const expanded = ref(state);
|
|
59
|
+
const { state: expanded } = useSidebar();
|
|
62
60
|
|
|
63
61
|
interface IApplications {
|
|
64
62
|
id: number;
|
|
@@ -107,10 +105,24 @@ withDefaults(defineProps<ISidebarProps>(), {
|
|
|
107
105
|
logo: () => ({
|
|
108
106
|
alt: 'Thrillshare',
|
|
109
107
|
src: 'logo.svg',
|
|
110
|
-
iconSrc: '
|
|
108
|
+
iconSrc: 'icon.svg',
|
|
111
109
|
})
|
|
112
110
|
})
|
|
113
111
|
|
|
112
|
+
// Debounced Expand on Hover
|
|
113
|
+
let expandedTimer;
|
|
114
|
+
function onHover() {
|
|
115
|
+
if(!expanded.value) {
|
|
116
|
+
expandedTimer = setTimeout(() => {
|
|
117
|
+
expanded.value = true;
|
|
118
|
+
}, 280);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function onHoverLeave() {
|
|
122
|
+
if(!expanded.value) {
|
|
123
|
+
clearTimeout(expandedTimer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
114
126
|
|
|
115
127
|
</script>
|
|
116
128
|
|
|
@@ -118,15 +130,14 @@ withDefaults(defineProps<ISidebarProps>(), {
|
|
|
118
130
|
.navbar {
|
|
119
131
|
grid-area: navsidebar;
|
|
120
132
|
display: grid;
|
|
121
|
-
grid-template-rows:
|
|
133
|
+
grid-template-rows: 0fr auto 0fr;
|
|
122
134
|
font: var(--font-regular);
|
|
123
135
|
background-color: var(--dscl-nav-background-color, var(--purple-70));
|
|
124
136
|
color: var(--dscl-nav-font-color, var(--dscl-light-font, var(--primary-white)));
|
|
125
|
-
transition:
|
|
137
|
+
transition: width 0.3s ease-in-out, translate 0.2s ease-in-out;
|
|
126
138
|
height: 100vh;
|
|
127
139
|
overflow-y: auto;
|
|
128
|
-
|
|
129
|
-
translate: -256px;
|
|
140
|
+
width: 64px;
|
|
130
141
|
}
|
|
131
142
|
.navbar a:focus-visible {
|
|
132
143
|
outline: var(--orange) auto 2px;
|
|
@@ -134,10 +145,13 @@ withDefaults(defineProps<ISidebarProps>(), {
|
|
|
134
145
|
box-shadow: inset 0 0 0px 4px var(--primary-white);
|
|
135
146
|
}
|
|
136
147
|
.navbar.expanded {
|
|
137
|
-
transition:
|
|
138
|
-
|
|
148
|
+
transition: width 0.3s ease-in-out, translate 0.2s ease-in-out;
|
|
149
|
+
width: 256px;
|
|
139
150
|
translate: 0px;
|
|
140
151
|
}
|
|
152
|
+
.navbar.expanded .navbar--content ul {
|
|
153
|
+
width: 256px;
|
|
154
|
+
}
|
|
141
155
|
.navbar--logo {
|
|
142
156
|
padding: 16px;
|
|
143
157
|
background-color: var(--dscl-nav-background-color, var(--purple-70));
|
|
@@ -146,7 +160,6 @@ withDefaults(defineProps<ISidebarProps>(), {
|
|
|
146
160
|
.navbar--content {
|
|
147
161
|
display: flex;
|
|
148
162
|
flex-direction: column;
|
|
149
|
-
flex-basis: auto;
|
|
150
163
|
margin-top: 24px;
|
|
151
164
|
background-color: var(--dscl-nav-background-color, var(--purple-70));
|
|
152
165
|
}
|
|
@@ -154,15 +167,16 @@ withDefaults(defineProps<ISidebarProps>(), {
|
|
|
154
167
|
list-style: none;
|
|
155
168
|
padding: 0;
|
|
156
169
|
margin: 0;
|
|
157
|
-
|
|
170
|
+
width: 64px;
|
|
158
171
|
}
|
|
159
172
|
.navbar--footer {
|
|
160
173
|
display: flex;
|
|
161
174
|
flex-direction: column;
|
|
175
|
+
height: fit-content;
|
|
162
176
|
}
|
|
163
177
|
.navbar .skip-to-content {
|
|
164
178
|
overflow: hidden;
|
|
165
|
-
padding:
|
|
179
|
+
padding: 16px 24px 14px;
|
|
166
180
|
position: absolute;
|
|
167
181
|
left: 5px;
|
|
168
182
|
top: 5px;
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
icon: string;
|
|
4
|
+
}>();
|
|
5
|
+
const { state: expanded } = useSidebar();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<template>
|
|
2
|
-
<NuxtLink class="a-navbar-item">
|
|
3
|
-
<
|
|
9
|
+
<NuxtLink class="a-navbar-item" :class="{expanded}">
|
|
10
|
+
<a-icon :icon="icon" font-size="32px"/>
|
|
11
|
+
<slot v-if="expanded" />
|
|
4
12
|
</NuxtLink>
|
|
5
13
|
</template>
|
|
6
14
|
|
|
7
15
|
<style scoped>
|
|
8
16
|
.a-navbar-item {
|
|
9
17
|
color: var(--dscl-nav-font-color, var(--dscl-light-font, var(--primary-white)));
|
|
10
|
-
margin: 4px
|
|
18
|
+
margin: 4px 12px;
|
|
11
19
|
display: flex;
|
|
12
20
|
align-items: center;
|
|
21
|
+
gap: 4px;
|
|
13
22
|
text-decoration: none;
|
|
14
23
|
text-transform: capitalize;
|
|
15
24
|
padding: 4px;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="a-nav-section" :class="{ active }">
|
|
3
|
-
<NuxtLink class="a-nav-section--link" :to="application.url">
|
|
4
|
-
<img v-if="application.img_src" :src="
|
|
5
|
-
<span>{{ application.text.replace("_", " ") }}</span>
|
|
6
|
-
<a-icon v-if="active" class="caret-icon" icon="16:carret-up" />
|
|
3
|
+
<NuxtLink class="a-nav-section--link" :to="active ? '' : application.url">
|
|
4
|
+
<img v-if="application.img_src" :src="active ? getActiveIcon(application.img_src) : application.img_src" alt="icon">
|
|
5
|
+
<span v-if="expanded">{{ application.text.replace("_", " ") }}</span>
|
|
6
|
+
<a-icon v-if="expanded && active" class="caret-icon" icon="16:carret-up" />
|
|
7
7
|
</NuxtLink>
|
|
8
8
|
<div class="a-nav-section--content">
|
|
9
9
|
<slot />
|
|
@@ -12,18 +12,16 @@
|
|
|
12
12
|
</template>
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const props = defineProps<{
|
|
15
|
+
defineProps<{
|
|
18
16
|
application: IApplication;
|
|
19
17
|
active: boolean;
|
|
20
18
|
}>();
|
|
21
19
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const { state: expanded } = useSidebar();
|
|
21
|
+
|
|
22
|
+
function getActiveIcon(src){
|
|
23
|
+
return src.replace(".svg", "_active.svg");
|
|
24
|
+
};
|
|
27
25
|
</script>
|
|
28
26
|
|
|
29
27
|
<style scoped>
|
|
@@ -31,9 +29,6 @@ const iconUrl = computed(() =>
|
|
|
31
29
|
box-shadow: inset 0px -1px 0px 0px var(--dscl-nav-section-border-bottom-color, var(--purple-65));
|
|
32
30
|
cursor: pointer;
|
|
33
31
|
min-height: 48px;
|
|
34
|
-
/* &--content {
|
|
35
|
-
margin-bottom: 8px;
|
|
36
|
-
} */
|
|
37
32
|
}
|
|
38
33
|
.a-nav-section.active {
|
|
39
34
|
box-shadow: inset 0px -1px 0px 0px var(--dscl-nav-section-border-bottom-color, var(--purple-65));
|
|
@@ -19,11 +19,11 @@ const { toggleSidebar } = useSidebar();
|
|
|
19
19
|
background: var(--purple-20);
|
|
20
20
|
border: none;
|
|
21
21
|
border-radius: 4px;
|
|
22
|
-
padding: 8px
|
|
22
|
+
padding: 4px 8px;
|
|
23
23
|
color: var(--purple);
|
|
24
24
|
cursor: pointer;
|
|
25
25
|
}
|
|
26
|
-
.sidebar-toggle:focus {
|
|
26
|
+
.sidebar-toggle:focus-visible {
|
|
27
27
|
outline: none;
|
|
28
28
|
box-shadow: 0 0 0 2px var(--primary-white), 0 0 0 3px var(--purple);
|
|
29
29
|
}
|
|
@@ -48,13 +48,13 @@ defineProps({
|
|
|
48
48
|
border-top: 2px solid transparent;
|
|
49
49
|
border-radius: 4px;
|
|
50
50
|
}
|
|
51
|
-
.menu-item:hover, .menu-item:focus, .menu-item.nuxt-link-exact-active, .menu-item.router-link-exact-active {
|
|
51
|
+
.menu-item:hover, .menu-item:focus-visible, .menu-item.nuxt-link-exact-active, .menu-item.router-link-exact-active {
|
|
52
52
|
background-color: var(--purple-10);
|
|
53
53
|
border-radius: 4px 4px 0px 0px;
|
|
54
54
|
color: var(--purple);
|
|
55
55
|
outline: none;
|
|
56
56
|
}
|
|
57
|
-
.menu-item:hover:after, .menu-item:focus:after, .menu-item.nuxt-link-exact-active:after, .menu-item.router-link-exact-active:after {
|
|
57
|
+
.menu-item:hover:after, .menu-item:focus-visible:after, .menu-item.nuxt-link-exact-active:after, .menu-item.router-link-exact-active:after {
|
|
58
58
|
border-top: 2px solid var(--purple);
|
|
59
59
|
}
|
|
60
60
|
.menu-item.nuxt-link-exact-active, .menu-item.router-link-exact-active {
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { useDropdown, useAppConfig } from "#imports";
|
|
2
3
|
import { ref } from "vue";
|
|
3
|
-
import { useDropdown } from "#imports";
|
|
4
4
|
import { vOnClickOutside } from "@vueuse/components";
|
|
5
5
|
|
|
6
6
|
interface IUserDropdown {
|
|
7
7
|
user: {
|
|
8
8
|
name: string;
|
|
9
9
|
avatar?: string;
|
|
10
|
+
email: string;
|
|
10
11
|
};
|
|
11
|
-
userDropdownOptions: Array<{ label: string; url: string;
|
|
12
|
+
userDropdownOptions: Array<{ label: string; url: string; }>;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
const appConfig = useAppConfig();
|
|
16
|
+
const zIndex = computed(() => appConfig?.navigation?.zIndex || 100)
|
|
17
|
+
|
|
18
|
+
|
|
14
19
|
const props = defineProps<IUserDropdown>();
|
|
15
20
|
const dropdownTriggerRef = ref<HTMLDivElement>();
|
|
16
21
|
const dropdownOptionsRef = ref<Array<HTMLDivElement>>();
|
|
@@ -29,7 +34,7 @@ const {
|
|
|
29
34
|
);
|
|
30
35
|
|
|
31
36
|
const emit = defineEmits<{
|
|
32
|
-
|
|
37
|
+
"select-option": [index: number];
|
|
33
38
|
}>();
|
|
34
39
|
|
|
35
40
|
function handleSelectedClick(index: number) {
|
|
@@ -60,7 +65,7 @@ function handleKeydownAction($event: KeyboardEvent, index: number) {
|
|
|
60
65
|
>
|
|
61
66
|
<NavAvatar :name="user.name" :src="user.avatar" />
|
|
62
67
|
<NavIconsUserDropdownMenu />
|
|
63
|
-
<span class="sr-only">{{ $t(
|
|
68
|
+
<span class="sr-only">{{ $t("navigation.userDropdown") }}</span>
|
|
64
69
|
</div>
|
|
65
70
|
<div
|
|
66
71
|
v-show="active"
|
|
@@ -68,6 +73,17 @@ function handleKeydownAction($event: KeyboardEvent, index: number) {
|
|
|
68
73
|
class="options-dropdown"
|
|
69
74
|
role="listbox"
|
|
70
75
|
>
|
|
76
|
+
<div class="user-info">
|
|
77
|
+
<span class="user-info-title">{{ $t("navigation.userDropdown") }}</span>
|
|
78
|
+
<div class="user-info-details">
|
|
79
|
+
<NavAvatar :name="user.name" :src="user.avatar" style="--size: 40px"/>
|
|
80
|
+
<div class="user-info-details-container">
|
|
81
|
+
<span class="user-info-name"> {{ user.name }} </span>
|
|
82
|
+
<span class="user-info-email"> {{ user.email }} </span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<hr>
|
|
71
87
|
<div
|
|
72
88
|
v-for="(option, idx) in userDropdownOptions"
|
|
73
89
|
:key="option.label"
|
|
@@ -79,9 +95,9 @@ function handleKeydownAction($event: KeyboardEvent, index: number) {
|
|
|
79
95
|
@keydown="handleKeydownAction($event, idx)"
|
|
80
96
|
@click="handleSelectedClick(idx)"
|
|
81
97
|
>
|
|
98
|
+
<!-- because of the the above handler, this component is never treated as a link and can't redirect natively? -->
|
|
82
99
|
<NuxtLink class="option" :to="option.url">
|
|
83
100
|
{{ option.label }}
|
|
84
|
-
<a-icon :icon="option.icon" font-size="24px" />
|
|
85
101
|
</NuxtLink>
|
|
86
102
|
</div>
|
|
87
103
|
</div>
|
|
@@ -106,13 +122,44 @@ function handleKeydownAction($event: KeyboardEvent, index: number) {
|
|
|
106
122
|
align-items: center;
|
|
107
123
|
cursor: pointer;
|
|
108
124
|
}
|
|
109
|
-
.header__dropdown:focus {
|
|
125
|
+
.header__dropdown:focus-visible {
|
|
110
126
|
box-shadow: 0 0 0 2px var(--primary-white), 0 0 0 3px var(--purple);
|
|
111
127
|
}
|
|
112
|
-
.header__dropdown .avatar-container:focus {
|
|
128
|
+
.header__dropdown .avatar-container:focus-visible {
|
|
113
129
|
outline: none;
|
|
114
130
|
}
|
|
115
131
|
|
|
132
|
+
hr {
|
|
133
|
+
border: 0;
|
|
134
|
+
border-top: 1px solid var(--grey-20);
|
|
135
|
+
margin: 8px 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.user-info {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
gap: 12px;
|
|
142
|
+
padding: 8px;
|
|
143
|
+
}
|
|
144
|
+
.user-info .user-info-title {
|
|
145
|
+
font-weight: bold;
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
color: var(--grey-60);
|
|
148
|
+
text-transform: uppercase;
|
|
149
|
+
}
|
|
150
|
+
.user-info .user-info-details {
|
|
151
|
+
display: flex;
|
|
152
|
+
gap: 4px;
|
|
153
|
+
}
|
|
154
|
+
.user-info .user-info-details .user-info-details-container {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
}
|
|
158
|
+
.user-info .user-info-details .user-info-details-container .user-info-email {
|
|
159
|
+
font-size: 12px;
|
|
160
|
+
color: var(--grey-50);
|
|
161
|
+
}
|
|
162
|
+
|
|
116
163
|
.options-dropdown {
|
|
117
164
|
min-height: 100px;
|
|
118
165
|
position: absolute;
|
|
@@ -123,7 +170,7 @@ function handleKeydownAction($event: KeyboardEvent, index: number) {
|
|
|
123
170
|
border-radius: 4px;
|
|
124
171
|
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.06);
|
|
125
172
|
padding: 8px;
|
|
126
|
-
z-index:
|
|
173
|
+
z-index: v-bind(zIndex);
|
|
127
174
|
}
|
|
128
175
|
.options-dropdown .option-wrapper {
|
|
129
176
|
padding: 8px;
|
|
@@ -3,8 +3,8 @@ export declare function useDropdown(options: Array<unknown>, dropdownOptionsRef:
|
|
|
3
3
|
searchable: boolean;
|
|
4
4
|
trackBy?: string;
|
|
5
5
|
}): {
|
|
6
|
-
active: Ref<boolean>;
|
|
7
|
-
searchQuery: Ref<string>;
|
|
6
|
+
active: Ref<boolean, boolean>;
|
|
7
|
+
searchQuery: Ref<string, string>;
|
|
8
8
|
toggleDropdown: ($event: KeyboardEvent | MouseEvent) => Promise<void>;
|
|
9
9
|
openDropdown: () => void;
|
|
10
10
|
closeDropdown: () => void;
|
|
@@ -50,6 +50,9 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
break;
|
|
53
|
+
case "ArrowLeft":
|
|
54
|
+
case "ArrowRight":
|
|
55
|
+
break;
|
|
53
56
|
case " ":
|
|
54
57
|
if (active.value) {
|
|
55
58
|
closeDropdown();
|
|
@@ -141,6 +144,9 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
141
144
|
dropdownOptionsRef.value[index - 1].focus();
|
|
142
145
|
});
|
|
143
146
|
return index;
|
|
147
|
+
case "ArrowLeft":
|
|
148
|
+
case "ArrowRight":
|
|
149
|
+
break;
|
|
144
150
|
case "Home":
|
|
145
151
|
nextTick(() => {
|
|
146
152
|
dropdownOptionsRef.value[0].focus();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apptegy/nuxt-navigation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -26,24 +26,24 @@
|
|
|
26
26
|
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@nuxt/kit": "^3.
|
|
30
|
-
"@vueuse/components": "11.0.
|
|
29
|
+
"@nuxt/kit": "^3.13.1",
|
|
30
|
+
"@vueuse/components": "11.0.3",
|
|
31
31
|
"lodash": "^4.17.21"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@nuxt/devtools": "^1.
|
|
35
|
-
"@nuxt/eslint-config": "^0.5.
|
|
34
|
+
"@nuxt/devtools": "^1.4.1",
|
|
35
|
+
"@nuxt/eslint-config": "^0.5.6",
|
|
36
36
|
"@nuxt/module-builder": "^0.8.3",
|
|
37
|
-
"@nuxt/schema": "^3.
|
|
37
|
+
"@nuxt/schema": "^3.13.1",
|
|
38
38
|
"@nuxt/test-utils": "^3.14.1",
|
|
39
|
-
"@types/node": "^20.16.
|
|
39
|
+
"@types/node": "^20.16.5",
|
|
40
40
|
"changelogen": "^0.5.5",
|
|
41
|
-
"eslint": "^9.9.
|
|
42
|
-
"nuxt": "^3.
|
|
43
|
-
"sass": "^1.
|
|
41
|
+
"eslint": "^9.9.1",
|
|
42
|
+
"nuxt": "^3.13.1",
|
|
43
|
+
"sass": "^1.78.0",
|
|
44
44
|
"typescript": "^5.5.4",
|
|
45
45
|
"vitest": "^1.6.0",
|
|
46
|
-
"vue": "^3.
|
|
47
|
-
"vue-tsc": "^2.
|
|
46
|
+
"vue": "^3.5.2",
|
|
47
|
+
"vue-tsc": "^2.1.6"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
File without changes
|