@apptegy/nuxt-navigation 0.1.3 → 0.1.5

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.
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="org-selection">
3
3
  <label id="org-dropdown-label" class="sr-only">
4
- {{ orgDropdownLabel }}
4
+ {{ $t("navigation.orgDropdown") }}
5
5
  </label>
6
6
  <div
7
7
  ref="dropdownTriggerRef"
@@ -14,48 +14,43 @@
14
14
  >
15
15
  <input
16
16
  ref="inputRef"
17
- is="input"
18
- class="org-title"
19
17
  type="text"
20
- placeholder="Start typing to search"
21
- :value="active? newQuery : selectedOrg.name"
22
- @keydown="keydownActionsOnTrigger($event, orgs, optionRef, dropdownTriggerRef, true, inputRef)"
23
- @click.prevent="toggleDropdown($event, optionRef)"
24
- @input="handleDropdownSearch($event, sortedOrgs, optionRef)"
25
- />
26
- <span v-if="selectedOrg.intranet" class="intranet selected">
27
- <a-icon icon="16:lock-locked" ></a-icon>
28
- {{ $t('navigation.intranet') }}
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") }}
29
28
  </span>
30
- <!-- <Icon
31
- style="margin-bottom: 2px"
32
- :name-or-path="`dropdown${active ? '-up' : ''}`"
33
- size="tiny"
34
- reduced
35
- variant="info"
36
- /> -->
37
29
  </div>
38
- <div v-show="active" id="organizations-options-list" class="dropdown" role="listbox">
30
+ <div
31
+ v-show="active"
32
+ id="organizations-options-list"
33
+ class="dropdown"
34
+ role="listbox"
35
+ >
39
36
  <div
40
- v-for="(option, idx) in sortedOrgs"
41
- v-on-click-outside="onClickOutsideHandler"
37
+ v-for="(option) in sortedOrgs"
38
+ :key="option.id"
42
39
  ref="optionRef"
43
- :key="option.name"
40
+ v-on-click-outside="onClickOutsideHandler"
44
41
  role="option"
45
42
  class="org-option"
46
- :class="{ selected: option === selectedOrg }"
47
- :aria-selected="!!String(option === selectedOrg)"
43
+ :class="{ selected: option.id === modelValue }"
44
+ :aria-selected="option.id === modelValue"
48
45
  tabindex="-1"
49
- @keydown="handleKeydownAction(orgs, idx, $event, optionRef, dropdownTriggerRef)"
50
- @click="handleSelectedClick(idx)"
46
+ @keydown="handleSelectedOrg($event, option.id)"
47
+ @click="handleSelectedOrg($event, option.id)"
51
48
  >
52
- <div class="selected-space">
53
- <!-- <Icon v-if="option === selectedOrg" name-or-path="success" size="tiny" /> -->
54
- </div>
55
- <span :class="{ pinned: option.pinned, 'item-flex': true }">{{ optionLabel(option) }}
56
- <span v-if="option.intranet" class="intranet selected">
57
- <a-icon icon="16:lock-locked"></a-icon>
58
- {{ $t('navigation.intranet') }}
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") }}
59
54
  </span>
60
55
  </span>
61
56
  </div>
@@ -64,39 +59,50 @@
64
59
  </template>
65
60
 
66
61
  <script setup lang="ts">
67
- import { useDropdown } from '../../composables/accessibleActions.js';
68
- import { vOnClickOutside } from '@vueuse/components';
69
- import { ref, computed, useNuxtApp } from '#imports';
62
+ import { useDropdown, useNuxtApp } from "#imports";
63
+ import { vOnClickOutside } from "@vueuse/components";
64
+ import { ref, computed } from "vue";
65
+
70
66
  const { $t } = useNuxtApp();
71
67
 
72
- const emit = defineEmits(['selected']);
68
+ interface IOrg {
69
+ name: string;
70
+ id: number;
71
+ intranet?: boolean;
72
+ pinned?: boolean;
73
+ }
74
+
75
+ interface IOrgSelectProps {
76
+ orgs: Array<IOrg>;
77
+ modelValue: number
78
+ }
79
+
80
+ const emit = defineEmits<{
81
+ 'update:model-value': [index: number]
82
+ }>();
83
+
84
+ const dropdownTriggerRef = ref();
85
+ const optionRef = ref();
86
+ const selectedOrg = computed(() => {
87
+ return props.orgs.find(({id}) => id == props.modelValue);
88
+ });
89
+
90
+ const props = defineProps<IOrgSelectProps>();
73
91
 
74
92
  const {
75
93
  active,
76
- newQuery,
94
+ searchQuery,
77
95
  toggleDropdown,
78
96
  closeDropdown,
79
97
  onClickOutsideHandler,
80
98
  keydownActionsOnTrigger,
81
99
  handleKeydown,
82
100
  handleDropdownSearch,
83
- } = useDropdown();
84
-
85
- interface IOrgSelectProps {
86
- orgs: Array<any>;
87
- selectedOrg: {};
88
- };
89
-
90
- const props = withDefaults(defineProps<IOrgSelectProps>(), {
91
- selectedOrg: {
92
- name: '',
93
- intranet: false
94
- }
101
+ } = useDropdown(props.orgs, optionRef, dropdownTriggerRef, {
102
+ searchable: true,
103
+ trackBy: "name",
95
104
  });
96
105
 
97
- const dropdownTriggerRef = ref();
98
- const optionRef = ref();
99
-
100
106
  const sortedOrgs = computed(() => {
101
107
  return [...props.orgs].sort((a, b) => {
102
108
  if (a.pinned && !b.pinned) {
@@ -108,22 +114,15 @@ const sortedOrgs = computed(() => {
108
114
  }
109
115
  });
110
116
  });
111
- const orgDropdownLabel = computed(() => {
112
- return $t('navigation.orgDropdown') || 'Select Organization'
113
- })
114
-
115
- function optionLabel(option) {
116
- return option?.name || option.label
117
- }
118
117
 
119
- function handleSelectedClick(index: number) {
120
- emit('selected', index);
121
- closeDropdown();
122
- }
123
-
124
- function handleKeydownAction(orgs: string | any[], idx: number, $event: { key: string | boolean; }, optionRef: { focus: () => void; }[], dropdownTriggerRef: { focus: () => void; }) {
125
- emit('selected', idx);
126
- handleKeydown(orgs, idx, $event, optionRef, dropdownTriggerRef);
118
+ function handleSelectedOrg($event: KeyboardEvent | MouseEvent, orgId: number) {
119
+ if($event.type == 'click') {
120
+ emit("update:model-value", orgId);
121
+ closeDropdown();
122
+ } else if ($event.type == 'keydown') {
123
+ emit("update:model-value", orgId);
124
+ handleKeydown($event as KeyboardEvent, orgId);
125
+ }
127
126
  }
128
127
  </script>
129
128
 
@@ -133,38 +132,44 @@ function handleKeydownAction(orgs: string | any[], idx: number, $event: { key: s
133
132
  width: 300px;
134
133
  max-height: 300px;
135
134
  overflow-y: auto;
136
- background-color: white;
135
+ background-color: var(--primary-white);
137
136
  z-index: 50;
138
137
  }
139
138
 
140
139
  .org-selection {
141
- width: 300px;
142
- margin-right: 25px;
140
+ border-radius: 4px 4px 0 0;
141
+ }
142
+ .org-selection:focus-within {
143
+ outline: 2px var(--purple) solid;
143
144
  }
144
- .org-selection .dropdown-trigger:focus-visible {
145
- outline: 2px #000FFF solid;
145
+ .org-selection .dropdown-trigger {
146
+ width: 300px;
147
+ padding: 0 8px;
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 8px;
151
+ justify-content: space-between;
152
+ border-radius: 4px 4px 0 0;
153
+ border: 0;
154
+ border-bottom: 1px solid var(--dscl-forms-underline, var(--grey-50));
155
+ background: var(--dscl-forms-bg, var(--grey-10));
146
156
  }
147
157
  .org-selection .dropdown-trigger .org-title {
148
158
  line-height: 16px;
149
- width: 300px;
159
+ width: 100%;
150
160
  padding: 12px;
151
161
  letter-spacing: 0.4px;
152
162
  touch-action: manipulation;
153
- color: #333;
163
+ color: var(--primary-dark);
154
164
  height: 40px;
155
- display: flex;
156
- justify-content: space-between;
157
- align-items: center;
158
- padding: 8px;
159
- border-radius: 4px 4px 0 0;
160
165
  border: 0;
161
- border-bottom: 1px solid var(--dscl-forms-underline, #747474);
162
- background: var(--dscl-forms-bg, #fbfbfb);
166
+ padding: 8px;
167
+ background: var(--dscl-forms-bg, var(--grey-10));
163
168
  white-space: nowrap;
164
169
  cursor: pointer;
165
170
  }
166
- .org-selection .intranet {
167
- background-color: var(--purple-50);
171
+ .org-selection .dropdown-trigger .org-title:focus {
172
+ outline: none;
168
173
  }
169
174
  .org-selection .dropdown .org-option {
170
175
  display: block;
@@ -177,30 +182,31 @@ function handleKeydownAction(orgs: string | any[], idx: number, $event: { key: s
177
182
  cursor: pointer;
178
183
  }
179
184
  .org-selection .dropdown .org-option:focus-visible, .org-selection .dropdown .org-option:hover {
180
- background-color: #F8F6FC;
185
+ background-color: #f8f6fc;
181
186
  }
182
187
  .org-selection .custom-search {
183
188
  margin: 8px 16px;
184
189
  }
185
190
  .org-selection .item-flex {
186
191
  display: flex;
187
- gap: 10px;
192
+ gap: 8px;
188
193
  align-items: center;
189
194
  width: 100%;
190
195
  justify-content: space-between;
191
196
  }
192
- .org-selection .intranet {
197
+ .org-selection .badge {
193
198
  display: inline-flex;
194
199
  align-items: center;
195
200
  background-color: var(--purple-50);
196
201
  padding: 2px 4px;
197
202
  color: white;
198
203
  border-radius: 4px;
204
+ text-transform: uppercase;
199
205
  }
200
- .org-selection .intranet svg {
206
+ .org-selection .badge svg {
201
207
  margin-right: 4px;
202
208
  }
203
- .org-selection .intranet.selected {
209
+ .org-selection .badge.selected {
204
210
  background-color: var(--grey-50);
205
211
  }
206
212
  </style>
@@ -14,6 +14,7 @@
14
14
  {{ $t('navigation.skipToContent') }}
15
15
  </a>
16
16
  <div class="navbar--content">
17
+ <slot name="switcher" />
17
18
  <ul class="navbar-applications">
18
19
  <li v-for="application of applications" :key="application.id">
19
20
  <NavSidebarSection
@@ -38,7 +39,6 @@
38
39
  <NavImpersonateInfoPanel
39
40
  v-if="impersonatedUser.show"
40
41
  :impersonated-user="impersonatedUser"
41
- :navbar-expanded="expanded"
42
42
  />
43
43
  </div>
44
44
 
@@ -84,9 +84,9 @@ interface ISidebarProps {
84
84
  expanded?: boolean;
85
85
  homepageUrl?: string;
86
86
  showHelpArticles?: boolean;
87
- helpArticleOptions?: Array<any>;
87
+ helpArticleOptions?: Array<unknown>;
88
88
  impersonatedUser?: IUser;
89
- logo: ILogo;
89
+ logo?: ILogo;
90
90
  }
91
91
 
92
92
  withDefaults(defineProps<ISidebarProps>(), {
@@ -96,18 +96,18 @@ withDefaults(defineProps<ISidebarProps>(), {
96
96
  showHelpArticles: true,
97
97
  expanded: true,
98
98
  helpArticleOptions: () => [],
99
- impersonatedUser: {
99
+ impersonatedUser: () => ({
100
100
  show: false,
101
101
  email: "staff3@lms.com",
102
- phone_number :null,
102
+ phone_number : '',
103
103
  client: "Sales Demo",
104
- admin_email: null
105
- },
106
- logo: {
104
+ admin_email: '',
105
+ }),
106
+ logo: () => ({
107
107
  alt: 'Thrillshare',
108
108
  src: 'logo.svg',
109
109
  iconSrc: 'logo.svg',
110
- }
110
+ })
111
111
  })
112
112
  </script>
113
113
 
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <button class="sidebar-toggle" @click="toggleSidebar">
3
+ <NavIconsSidebarToggle />
4
+ <span> {{ $t('navigation.sidebarToggle') }}</span>
5
+ </button>
6
+ </template>
7
+
8
+ <script setup>
9
+ const emit = defineEmits(["toggle-sidebar"]);
10
+
11
+ function toggleSidebar() {
12
+ emit("toggle-sidebar");
13
+ }
14
+ </script>
15
+
16
+ <style scoped>
17
+ .sidebar-toggle {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 4px;
21
+ background: var(--purple-20);
22
+ border: none;
23
+ border-radius: 4px;
24
+ padding: 8px 16px;
25
+ color: var(--purple);
26
+ cursor: pointer;
27
+ }
28
+ .sidebar-toggle:focus {
29
+ outline: none;
30
+ box-shadow: 0 0 0 2px var(--primary-white), 0 0 0 3px var(--purple);
31
+ }
32
+ </style>
@@ -0,0 +1,147 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import { useDropdown } from "#imports";
4
+ import { vOnClickOutside } from "@vueuse/components";
5
+
6
+ interface IUserDropdown {
7
+ user: {
8
+ name: string;
9
+ avatar?: string;
10
+ };
11
+ userDropdownOptions: Array<{ label: string; url: string; icon: string }>;
12
+ }
13
+
14
+ const props = defineProps<IUserDropdown>();
15
+ const dropdownTriggerRef = ref<HTMLDivElement>();
16
+ const dropdownOptionsRef = ref<Array<HTMLDivElement>>();
17
+
18
+ const {
19
+ active,
20
+ toggleDropdown,
21
+ closeDropdown,
22
+ keydownActionsOnTrigger,
23
+ handleKeydown,
24
+ onClickOutsideHandler,
25
+ } = useDropdown(
26
+ props.userDropdownOptions,
27
+ dropdownOptionsRef,
28
+ dropdownTriggerRef,
29
+ );
30
+
31
+ const emit = defineEmits<{
32
+ 'select-option': [index: number]
33
+ }>();
34
+
35
+ function handleSelectedClick(index: number) {
36
+ emit("select-option", index);
37
+ closeDropdown();
38
+ }
39
+
40
+ function handleKeydownAction($event: KeyboardEvent, index: number) {
41
+ if ($event.key === "Enter" || $event.key === " ") {
42
+ emit("select-option", index);
43
+ } else {
44
+ handleKeydown($event, index);
45
+ }
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <div
51
+ ref="dropdownTriggerRef"
52
+ aria-controls="user-dropdown"
53
+ :aria-expanded="active"
54
+ aria-haspopup="listbox"
55
+ role="combobox"
56
+ tabindex="0"
57
+ class="header__dropdown"
58
+ @keydown="keydownActionsOnTrigger"
59
+ @click.prevent="toggleDropdown"
60
+ >
61
+ <NavAvatar :name="user.name" :src="user.avatar" />
62
+ <NavIconsUserDropdownMenu />
63
+ <span class="sr-only">{{ $t('navigation.userDropdown') }}</span>
64
+ </div>
65
+ <div
66
+ v-show="active"
67
+ id="user-dropdown"
68
+ class="options-dropdown"
69
+ role="listbox"
70
+ >
71
+ <div
72
+ v-for="(option, idx) in userDropdownOptions"
73
+ :key="option.label"
74
+ ref="dropdownOptionsRef"
75
+ v-on-click-outside="onClickOutsideHandler"
76
+ role="option"
77
+ class="option-wrapper"
78
+ tabindex="-1"
79
+ @keydown="handleKeydownAction($event, idx)"
80
+ @click="handleSelectedClick(idx)"
81
+ >
82
+ <NuxtLink class="option" :to="option.url">
83
+ {{ option.label }}
84
+ <a-icon :icon="option.icon" font-size="24px" />
85
+ </NuxtLink>
86
+ </div>
87
+ </div>
88
+ </template>
89
+
90
+ <style scoped>
91
+ .header__dropdown {
92
+ font-size: 24px;
93
+ min-width: 64px;
94
+ width: 64px;
95
+ max-width: 64px;
96
+ border: none;
97
+ padding: 0;
98
+ outline: none;
99
+ color: var(--purple);
100
+ background: var(--purple-20);
101
+ border-radius: 16px;
102
+ overflow: hidden;
103
+ text-align: left;
104
+ position: relative;
105
+ display: flex;
106
+ align-items: center;
107
+ cursor: pointer;
108
+ }
109
+ .header__dropdown:focus {
110
+ box-shadow: 0 0 0 2px var(--primary-white), 0 0 0 3px var(--purple);
111
+ }
112
+ .header__dropdown .avatar-container:focus {
113
+ outline: none;
114
+ }
115
+
116
+ .options-dropdown {
117
+ min-height: 100px;
118
+ position: absolute;
119
+ max-width: 512px;
120
+ right: 65px;
121
+ top: 80px;
122
+ background-color: var(--primary-white);
123
+ border-radius: 4px;
124
+ box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.06);
125
+ padding: 8px;
126
+ z-index: 10;
127
+ }
128
+ .options-dropdown .option-wrapper {
129
+ padding: 8px;
130
+ border-radius: 4px;
131
+ cursor: pointer;
132
+ }
133
+ .options-dropdown .option-wrapper .option {
134
+ display: flex;
135
+ gap: 8px;
136
+ justify-content: space-between;
137
+ align-items: center;
138
+ color: var(--primary-dark);
139
+ text-decoration: none;
140
+ }
141
+ .options-dropdown .option-wrapper:focus-visible, .options-dropdown .option-wrapper:hover {
142
+ background-color: var(--purple-20);
143
+ }
144
+ .options-dropdown .option-wrapper:focus-visible .option, .options-dropdown .option-wrapper:hover .option {
145
+ color: var(--purple);
146
+ }
147
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Ref } from "vue";
2
+ export declare function useDropdown(options: Array<unknown>, dropdownOptionsRef: Ref<Array<HTMLElement>>, dropdownTriggerRef: Ref<HTMLElement>, config?: {
3
+ searchable: boolean;
4
+ trackBy?: string;
5
+ }): {
6
+ active: Ref<boolean>;
7
+ searchQuery: Ref<string>;
8
+ toggleDropdown: ($event: KeyboardEvent | MouseEvent) => Promise<void>;
9
+ openDropdown: () => void;
10
+ closeDropdown: () => void;
11
+ onClickOutsideHandler: () => void;
12
+ keydownActionsOnTrigger: (event: KeyboardEvent) => void;
13
+ handleKeydown: (event: KeyboardEvent, index: number) => number | undefined;
14
+ handleDropdownSearch: (inputValue: KeyboardEvent & InputEvent) => void;
15
+ debouncedFocus: any;
16
+ };