@apptegy/nuxt-navigation 0.1.76 → 0.1.78
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.json
CHANGED
|
@@ -6,6 +6,7 @@ const appConfig = useAppConfig();
|
|
|
6
6
|
const zIndex = computed(() => appConfig?.navigation?.zIndex || 100);
|
|
7
7
|
const emit = defineEmits(["update:model-value"]);
|
|
8
8
|
const dropdownTriggerRef = ref();
|
|
9
|
+
const inputRef = ref();
|
|
9
10
|
const optionRef = ref();
|
|
10
11
|
const selectedOrg = computed(() => {
|
|
11
12
|
return props.orgs.find(({ id }) => id == props.modelValue);
|
|
@@ -17,13 +18,14 @@ const props = defineProps({
|
|
|
17
18
|
const {
|
|
18
19
|
active,
|
|
19
20
|
searchQuery,
|
|
21
|
+
highlightedIndex,
|
|
20
22
|
toggleDropdown,
|
|
21
23
|
closeDropdown,
|
|
22
24
|
onClickOutsideHandler,
|
|
23
25
|
keydownActionsOnTrigger,
|
|
24
26
|
handleKeydown,
|
|
25
27
|
handleDropdownSearch
|
|
26
|
-
} = useDropdown(props.orgs, optionRef,
|
|
28
|
+
} = useDropdown(props.orgs, optionRef, inputRef, {
|
|
27
29
|
searchable: true,
|
|
28
30
|
trackBy: "name"
|
|
29
31
|
});
|
|
@@ -38,6 +40,12 @@ const sortedOrgs = computed(() => {
|
|
|
38
40
|
}
|
|
39
41
|
});
|
|
40
42
|
});
|
|
43
|
+
const highlightedOrg = computed(() => {
|
|
44
|
+
if (highlightedIndex.value >= 0 && highlightedIndex.value < props.orgs.length) {
|
|
45
|
+
return props.orgs[highlightedIndex.value];
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
});
|
|
41
49
|
function handleSelectedOrg($event, orgId) {
|
|
42
50
|
if ($event.type == "click") {
|
|
43
51
|
emit("update:model-value", orgId);
|
|
@@ -97,7 +105,10 @@ function handleSelectedOrg($event, orgId) {
|
|
|
97
105
|
v-on-click-outside="onClickOutsideHandler"
|
|
98
106
|
role="option"
|
|
99
107
|
class="org-option"
|
|
100
|
-
:class="{
|
|
108
|
+
:class="{
|
|
109
|
+
selected: option.id === modelValue,
|
|
110
|
+
highlighted: highlightedOrg && option.id === highlightedOrg.id
|
|
111
|
+
}"
|
|
101
112
|
:aria-selected="option.id === modelValue"
|
|
102
113
|
tabindex="-1"
|
|
103
114
|
@keydown="handleSelectedOrg($event, option.id)"
|
|
@@ -179,6 +190,9 @@ function handleSelectedOrg($event, orgId) {
|
|
|
179
190
|
background-position: center left 15px;
|
|
180
191
|
color: var(--purple);
|
|
181
192
|
}
|
|
193
|
+
.org-selection .dropdown .org-option.highlighted {
|
|
194
|
+
background-color: #f8f6fc;
|
|
195
|
+
}
|
|
182
196
|
.org-selection .dropdown .org-option:focus-visible, .org-selection .dropdown .org-option:hover {
|
|
183
197
|
background-color: #f8f6fc;
|
|
184
198
|
}
|
|
@@ -20,21 +20,21 @@
|
|
|
20
20
|
<div :class="['header-actions', { 'collapsed': !expanded && !isMobile }]">
|
|
21
21
|
<NuxtLink v-if="expanded" class="no-link-style" :to="homepageUrl">
|
|
22
22
|
<slot name="logo">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</div>
|
|
23
|
+
<div class="navbar--logo" :class="{ 'big-logo': _brandLogoWidth > 80 }">
|
|
24
|
+
<img v-if="brandLogo" ref="brandLogoRef" :src="brandLogo" :alt="$t('navigation.brandLogoAlt')">
|
|
25
|
+
<component
|
|
26
|
+
:is="ApptegyLogo"
|
|
27
|
+
v-else
|
|
28
|
+
:alt="$t('navigation.brandLogoAlt')"
|
|
29
|
+
:use-light-logo="isAdmin || Object.values(getBrandingStyles).length === 0"
|
|
30
|
+
/>
|
|
31
|
+
<div v-if="!brand?.hide_school_name">
|
|
32
|
+
<div class="school-name"> {{ brand?.school_name }} </div>
|
|
33
|
+
<div v-if="brand?.school_tag_line" class="school-tag">{{ brand.school_tag_line }}</div>
|
|
35
34
|
</div>
|
|
36
|
-
|
|
37
|
-
</
|
|
35
|
+
</div>
|
|
36
|
+
</slot>
|
|
37
|
+
</NuxtLink>
|
|
38
38
|
<div v-if="!isMobile && showCollapseButton">
|
|
39
39
|
<button v-if="lockedExpanded && !allowCollapse && expanded" class="hamburger-button" @click="closeSidebar" v-tooltip.bottom="'Unpin menu'">
|
|
40
40
|
<Pinned />
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
</template>
|
|
92
92
|
|
|
93
93
|
<script setup>
|
|
94
|
-
import { useSidebar, useBranding, useNuxtApp, ref, watch } from "#imports";
|
|
94
|
+
import { useSidebar, useBranding, useNuxtApp, ref, watch, computed, onMounted, onBeforeUnmount } from "#imports";
|
|
95
95
|
import ApptegyLogo from "./Icons/ApptegyLogo.vue";
|
|
96
96
|
import { useBreakpoints, breakpointsTailwind } from "@vueuse/core";
|
|
97
97
|
import Unpinned from "./Icons/Unpinned.vue";
|
|
@@ -128,10 +128,30 @@ const emit = defineEmits(["toggle-expand"]);
|
|
|
128
128
|
const { state: expanded } = useSidebar();
|
|
129
129
|
const { brand, getBrandingStyles } = await useBranding(props.clientId);
|
|
130
130
|
const brandLogo = ref(brand.value?.logo_light);
|
|
131
|
+
const brandLogoRef = ref(null);
|
|
132
|
+
const brandLogoWidthPx = ref(0);
|
|
133
|
+
let brandLogoResizeObserver = null;
|
|
134
|
+
const _brandLogoWidth = computed(() => brandLogoWidthPx.value);
|
|
131
135
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
132
136
|
const isMobile = breakpoints.smaller("md");
|
|
133
137
|
const lockedExpanded = ref(true);
|
|
134
138
|
const allowCollapse = ref(false);
|
|
139
|
+
function updateBrandLogoWidth() {
|
|
140
|
+
brandLogoWidthPx.value = brandLogoRef.value?.clientWidth ?? 0;
|
|
141
|
+
}
|
|
142
|
+
onMounted(() => {
|
|
143
|
+
updateBrandLogoWidth();
|
|
144
|
+
if (brandLogoRef.value) {
|
|
145
|
+
brandLogoResizeObserver = new ResizeObserver(() => {
|
|
146
|
+
updateBrandLogoWidth();
|
|
147
|
+
});
|
|
148
|
+
brandLogoResizeObserver.observe(brandLogoRef.value);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
onBeforeUnmount(() => {
|
|
152
|
+
brandLogoResizeObserver?.disconnect();
|
|
153
|
+
brandLogoResizeObserver = null;
|
|
154
|
+
});
|
|
135
155
|
watch(expanded, (value) => {
|
|
136
156
|
emit("toggle-expand", value ? 264 : 64);
|
|
137
157
|
});
|
|
@@ -223,6 +243,10 @@ function lockSidebar() {
|
|
|
223
243
|
align-items: center;
|
|
224
244
|
gap: 8px;
|
|
225
245
|
}
|
|
246
|
+
.navbar--logo.big-logo {
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
align-items: flex-start;
|
|
249
|
+
}
|
|
226
250
|
.navbar--logo .school-name {
|
|
227
251
|
font-size: 16px;
|
|
228
252
|
font-style: normal;
|
|
@@ -6,13 +6,14 @@ export declare function useDropdown(options: Array<OptionItem>, dropdownOptionsR
|
|
|
6
6
|
}): {
|
|
7
7
|
active: Ref<boolean, boolean>;
|
|
8
8
|
searchQuery: Ref<string, string>;
|
|
9
|
+
highlightedIndex: Ref<number, number>;
|
|
9
10
|
toggleDropdown: ($event: KeyboardEvent | MouseEvent) => Promise<void>;
|
|
10
11
|
openDropdown: () => void;
|
|
11
12
|
closeDropdown: () => void;
|
|
12
13
|
onClickOutsideHandler: () => void;
|
|
13
14
|
keydownActionsOnTrigger: (event: KeyboardEvent) => void;
|
|
14
15
|
handleKeydown: (event: KeyboardEvent, index: number) => number | undefined;
|
|
15
|
-
handleDropdownSearch: (inputValue:
|
|
16
|
+
handleDropdownSearch: (inputValue: Event) => void;
|
|
16
17
|
debouncedFocus: import("@vueuse/core").PromisifyFn<(idx: number) => void>;
|
|
17
18
|
};
|
|
18
19
|
export {};
|
|
@@ -3,6 +3,7 @@ import { ref, nextTick } from "#imports";
|
|
|
3
3
|
export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, config = { searchable: false, trackBy: "name" }) {
|
|
4
4
|
const active = ref(false);
|
|
5
5
|
const searchQuery = ref("");
|
|
6
|
+
const highlightedIndex = ref(-1);
|
|
6
7
|
async function toggleDropdown($event) {
|
|
7
8
|
active.value = !active.value;
|
|
8
9
|
if ($event instanceof KeyboardEvent && active.value && $event.key === "ArrowDown") {
|
|
@@ -16,6 +17,8 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
16
17
|
}
|
|
17
18
|
function closeDropdown() {
|
|
18
19
|
active.value = false;
|
|
20
|
+
searchQuery.value = "";
|
|
21
|
+
highlightedIndex.value = -1;
|
|
19
22
|
}
|
|
20
23
|
function onClickOutsideHandler() {
|
|
21
24
|
if (!active.value) return;
|
|
@@ -23,7 +26,8 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
23
26
|
}
|
|
24
27
|
function keydownActionsOnTrigger(event) {
|
|
25
28
|
if (event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
26
|
-
if (event.key
|
|
29
|
+
if (config.searchable && active.value && (event.key.length === 1 || event.key === "Backspace")) {
|
|
30
|
+
} else if (event.key !== "Tab") {
|
|
27
31
|
event.preventDefault();
|
|
28
32
|
}
|
|
29
33
|
switch (event.key) {
|
|
@@ -33,17 +37,21 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
33
37
|
break;
|
|
34
38
|
case "ArrowDown":
|
|
35
39
|
if (active.value && event.key === "ArrowDown") {
|
|
40
|
+
event.preventDefault();
|
|
36
41
|
nextTick(() => {
|
|
37
|
-
|
|
42
|
+
const targetIndex = highlightedIndex.value >= 0 ? highlightedIndex.value : 0;
|
|
43
|
+
dropdownOptionsRef.value[targetIndex]?.focus();
|
|
38
44
|
openDropdown();
|
|
39
45
|
});
|
|
40
46
|
}
|
|
41
47
|
break;
|
|
42
48
|
case "ArrowUp":
|
|
49
|
+
event.preventDefault();
|
|
43
50
|
openDropdown();
|
|
44
51
|
if (config.searchable) {
|
|
45
52
|
nextTick(() => {
|
|
46
|
-
|
|
53
|
+
const targetIndex = highlightedIndex.value >= 0 ? highlightedIndex.value : 0;
|
|
54
|
+
dropdownOptionsRef.value[targetIndex]?.focus();
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
break;
|
|
@@ -51,6 +59,9 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
51
59
|
case "ArrowRight":
|
|
52
60
|
break;
|
|
53
61
|
case " ":
|
|
62
|
+
if (config.searchable && active.value) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
54
65
|
if (active.value) {
|
|
55
66
|
closeDropdown();
|
|
56
67
|
nextTick(() => {
|
|
@@ -62,12 +73,19 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
62
73
|
break;
|
|
63
74
|
case "Enter":
|
|
64
75
|
if (active.value) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
if (highlightedIndex.value >= 0 && highlightedIndex.value < options.length) {
|
|
77
|
+
nextTick(() => {
|
|
78
|
+
dropdownOptionsRef.value[highlightedIndex.value]?.focus();
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
closeDropdown();
|
|
82
|
+
nextTick(() => {
|
|
83
|
+
dropdownTriggerRef.value.focus();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
69
86
|
} else {
|
|
70
87
|
searchQuery.value = "";
|
|
88
|
+
highlightedIndex.value = -1;
|
|
71
89
|
openDropdown();
|
|
72
90
|
}
|
|
73
91
|
break;
|
|
@@ -76,9 +94,11 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
76
94
|
dropdownTriggerRef.value.focus();
|
|
77
95
|
break;
|
|
78
96
|
case "Home":
|
|
97
|
+
event.preventDefault();
|
|
79
98
|
openDropdown();
|
|
80
99
|
nextTick(() => {
|
|
81
|
-
|
|
100
|
+
const targetIndex = highlightedIndex.value >= 0 ? highlightedIndex.value : 0;
|
|
101
|
+
dropdownOptionsRef.value[targetIndex]?.focus();
|
|
82
102
|
});
|
|
83
103
|
break;
|
|
84
104
|
case "End":
|
|
@@ -88,7 +108,6 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
88
108
|
});
|
|
89
109
|
break;
|
|
90
110
|
default:
|
|
91
|
-
handleDropdownSearch(event);
|
|
92
111
|
break;
|
|
93
112
|
}
|
|
94
113
|
}
|
|
@@ -171,32 +190,40 @@ export function useDropdown(options, dropdownOptionsRef, dropdownTriggerRef, con
|
|
|
171
190
|
}, 500);
|
|
172
191
|
function handleDropdownSearch(inputValue) {
|
|
173
192
|
if (!config.searchable) return;
|
|
174
|
-
if (inputValue.
|
|
175
|
-
searchQuery.value =
|
|
176
|
-
|
|
193
|
+
if (inputValue.target && "value" in inputValue.target) {
|
|
194
|
+
searchQuery.value = inputValue.target.value;
|
|
195
|
+
updateHighlightedIndex();
|
|
177
196
|
}
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
}
|
|
198
|
+
function updateHighlightedIndex() {
|
|
180
199
|
const searchIndex = options.findIndex((option) => {
|
|
200
|
+
if (!searchQuery.value) return false;
|
|
181
201
|
if (!config.trackBy) {
|
|
182
|
-
return typeof option === "string" && option.includes(searchQuery.value);
|
|
202
|
+
return typeof option === "string" && option.toLowerCase().includes(searchQuery.value.toLowerCase());
|
|
183
203
|
} else if (typeof option === "object" && option !== null && config.trackBy in option) {
|
|
184
204
|
const value = option[config.trackBy];
|
|
185
205
|
if (typeof value === "string") {
|
|
186
206
|
return value.toLowerCase().includes(searchQuery.value.toLowerCase());
|
|
187
207
|
} else if (value !== null && value !== void 0) {
|
|
188
|
-
return String(value).includes(searchQuery.value);
|
|
208
|
+
return String(value).toLowerCase().includes(searchQuery.value.toLowerCase());
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
211
|
return false;
|
|
192
212
|
});
|
|
193
|
-
|
|
194
|
-
|
|
213
|
+
highlightedIndex.value = searchIndex;
|
|
214
|
+
if (searchIndex >= 0 && dropdownOptionsRef.value[searchIndex]) {
|
|
215
|
+
nextTick(() => {
|
|
216
|
+
dropdownOptionsRef.value[searchIndex]?.scrollIntoView({
|
|
217
|
+
block: "nearest",
|
|
218
|
+
behavior: "smooth"
|
|
219
|
+
});
|
|
220
|
+
});
|
|
195
221
|
}
|
|
196
222
|
}
|
|
197
223
|
return {
|
|
198
224
|
active,
|
|
199
225
|
searchQuery,
|
|
226
|
+
highlightedIndex,
|
|
200
227
|
toggleDropdown,
|
|
201
228
|
openDropdown,
|
|
202
229
|
closeDropdown,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apptegy/nuxt-navigation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"vue": "^3.5.13",
|
|
47
47
|
"vue-tsc": "^3.0.3"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "0270d8823bc029e30d7e32ca8b331bba224250fb"
|
|
50
50
|
}
|