adminforth 2.4.0-next.161 → 2.4.0-next.162

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.
@@ -72,123 +72,21 @@
72
72
  </div>
73
73
  </nav>
74
74
 
75
- <aside
76
- ref="sidebarAside"
75
+ <Sidebar
77
76
  v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout"
78
- id="logo-lightSidebar" class="fixed border-none top-0 left-0 z-30 w-64 h-screen transition-transform bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder sm:translate-x-0 dark:bg-darkSidebar dark:border-darkSidebarBorder"
79
- :class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
80
- aria-label="Sidebar"
81
- >
82
- <div class="h-full px-3 pb-4 overflow-y-auto bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder">
83
- <div class="af-logo-title-wrapper flex ms-2 m-4">
84
- <img v-if="coreStore.config?.showBrandLogoInSidebar !== false" :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-logo h-8 me-3" />
85
- <span
86
- v-if="coreStore.config?.showBrandNameInSidebar"
87
- class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
88
- >
89
- {{ coreStore.config?.brandName }}
90
- </span>
91
- <div class="flex items-center gap-2 w-auto" :class="{'w-full justify-end': coreStore.config?.showBrandLogoInSidebar === false}">
92
- <component
93
- v-for="c in coreStore?.config?.globalInjections?.sidebarTop || []"
94
- :is="getCustomComponent(c)"
95
- :meta="c.meta"
96
- :adminUser="coreStore.adminUser"
97
- />
98
- </div>
99
- </div>
100
-
101
- <ul class="af-sidebar-container space-y-2 font-medium">
102
- <template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
103
- <div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
104
- <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
105
- <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
106
- ">{{ item.label }}</div>
107
- <li v-else-if="item.children" class="af-sidebar-expand-container">
108
- <button @click="clickOnMenuItem(i)" type="button" class="af-sidebar-expand-button flex items-center w-full p-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
109
- :class="opened.includes(i) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
110
- :aria-controls="`dropdown-example${i}`"
111
- :data-collapse-toggle="`dropdown-example${i}`"
112
- >
113
-
114
- <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
115
-
116
- <span class="text-ellipsis overflow-hidden flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}
117
-
118
- <span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
119
- fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
120
- <Tooltip v-if="item.badgeTooltip">
121
- {{ item.badge }}
122
- <template #tooltip>
123
- {{ item.badgeTooltip }}
124
- </template>
125
- </Tooltip>
126
- <template v-else>
127
- {{ item.badge }}
128
- </template>
129
- </span>
130
- </span>
131
-
132
- <svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
133
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
134
- </svg>
135
- </button>
136
-
137
- <ul :id="`dropdown-example${i}`" role="none" class="af-sidebar-dropdown pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
138
- <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
139
- <li class="af-sidebar-menu-link">
140
- <MenuLink :item="child" isChild="true" @click="hideSidebar"/>
141
- </li>
142
- </template>
143
- </ul>
144
- </li>
145
- <li v-else class="af-sidebar-menu-link">
146
- <MenuLink :item="item" @click="hideSidebar"/>
147
- </li>
148
- </template>
149
- </ul>
150
-
151
-
152
- <div id="dropdown-cta" class="p-4 mt-6 rounded-lg bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
153
- fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent text-sm" role="alert"
154
- v-if="ctaBadge"
155
- >
156
- <div class="flex items-center mb-3" :class="!ctaBadge.title ? 'float-right' : ''">
157
- <!-- <span class="bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity text-sm font-semibold me-2 px-2.5 py-0.5 rounded "
158
- v-if="ctaBadge.title"
159
- > -->
160
- <span>
161
- {{ctaBadge.title}}
162
- </span>
163
- <button type="button"
164
- class="ms-auto -mx-1.5 -my-1.5 bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity inline-flex justify-center items-center w-6 h-6 rounded-lg p-1 hover:brightness-110"
165
-
166
- data-dismiss-target="#dropdown-cta" aria-label="Close"
167
- v-if="ctaBadge?.closable" @click="closeCTA"
168
- >
169
- <span class="sr-only">Close</span>
170
- <svg class="w-2.5 h-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
171
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
172
- </svg>
173
- </button>
174
- </div>
175
- <p class="mb-3 text-sm " v-if="ctaBadge.html" v-html="ctaBadge.html"></p>
176
- <p class="mb-3 text-sm fill-lightNavbarText dark:fill-darkPrimary text-lightNavbarText dark:text-darkNavbarPrimary" v-else>
177
- {{ ctaBadge.text }}
178
- </p>
179
- <!-- <a class="text-sm text-lightPrimary underline font-medium hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" href="#">Turn new navigation off</a> -->
180
- </div>
181
-
182
- <component
183
- v-for="c in coreStore?.config?.globalInjections?.sidebar || []"
184
- :is="getCustomComponent(c)"
185
- :meta="c.meta"
186
- :adminUser="coreStore.adminUser"
187
- />
188
- </div>
189
- </aside>
77
+ :sideBarOpen="sideBarOpen"
78
+ @hideSidebar="hideSidebar"
79
+ @loadMenu="loadMenu"
80
+ @sidebarStateChange="handleSidebarStateChange"
81
+ />
190
82
 
191
- <div class="sm:ml-64 max-w-[100vw] sm:max-w-[calc(100%-16rem)]"
83
+ <div class="transition-all duration-300 ease-in-out max-w-[100vw]"
84
+ :class="{
85
+ 'sm:ml-18': isSidebarIconOnly,
86
+ 'sm:ml-64': !isSidebarIconOnly,
87
+ 'sm:max-w-[calc(100%-4.5rem)]': isSidebarIconOnly,
88
+ 'sm:max-w-[calc(100%-16rem)]': !isSidebarIconOnly
89
+ }"
192
90
  v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout">
193
91
  <div class="p-0 dark:border-gray-700 mt-14">
194
92
  <RouterView/>
@@ -248,10 +146,20 @@
248
146
  @apply opacity-100;
249
147
  }
250
148
 
149
+ @media (min-width: 640px) {
150
+ .sm\:ml-18 {
151
+ margin-left: 4.5rem;
152
+ }
153
+ .sm\:max-w-\[calc\(100\%-4\.5rem\)\] {
154
+ max-width: calc(100% - 4.5rem);
155
+ }
156
+ }
157
+
158
+
251
159
  </style>
252
160
 
253
161
  <script setup lang="ts">
254
- import { computed, onMounted, ref, watch, onBeforeMount, nextTick, type Ref } from 'vue';
162
+ import { computed, onMounted, ref, watch, onBeforeMount } from 'vue';
255
163
  import { RouterView } from 'vue-router';
256
164
  import { Dropdown } from 'flowbite'
257
165
  import './index.scss'
@@ -259,16 +167,12 @@ import { useCoreStore } from '@/stores/core';
259
167
  import { useUserStore } from '@/stores/user';
260
168
  import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
261
169
  import AcceptModal from './components/AcceptModal.vue';
262
- import MenuLink from './components/MenuLink.vue';
170
+ import Sidebar from './components/Sidebar.vue';
263
171
  import { useRoute, useRouter } from 'vue-router';
264
- import { getIcon, verySimpleHash } from '@/utils';
265
172
  import { createHead } from 'unhead'
266
- import { loadFile } from '@/utils';
173
+ import { getCustomComponent } from '@/utils';
267
174
  import Toast from './components/Toast.vue';
268
175
  import {useToastStore} from '@/stores/toast';
269
- import { getCustomComponent } from '@/utils';
270
- import type { AdminForthConfigMenuItem, AnnouncementBadgeResponse } from './types/Common';
271
- import { Tooltip } from '@/afcl';
272
176
  import { initFrontedAPI } from '@/adminforth';
273
177
  import adminforth from '@/adminforth';
274
178
  import UserMenuSettingsButton from './components/UserMenuSettingsButton.vue';
@@ -284,16 +188,15 @@ const sideBarOpen = ref(false);
284
188
  const defaultLayout = ref(true);
285
189
  const route = useRoute();
286
190
  const router = useRouter();
287
- //create a ref to store the opened menu items with ts type;
288
- const opened = ref<(string|number)[]>([]);
289
191
  const publicConfigLoaded = ref(false);
290
192
  const dropdownUserButton = ref(null);
291
193
 
292
- const sidebarAside = ref(null);
293
194
 
294
195
  const routerIsReady = ref(false);
295
196
  const loginRedirectCheckIsReady = ref(false);
296
197
 
198
+ const isSidebarIconOnly = ref(localStorage.getItem('afIconOnlySidebar') === 'true');
199
+
297
200
  const loggedIn = computed(() => !!coreStore?.adminUser);
298
201
 
299
202
  const theme = ref('light');
@@ -302,19 +205,16 @@ function hideSidebar(): void {
302
205
  sideBarOpen.value = false;
303
206
  }
304
207
 
208
+ function handleSidebarStateChange(state: { isSidebarIconOnly: boolean }) {
209
+ isSidebarIconOnly.value = state.isSidebarIconOnly;
210
+ }
211
+
212
+
305
213
  function toggleTheme() {
306
214
  theme.value = theme.value === 'light' ? 'dark' : 'light';
307
215
  coreStore.toggleTheme();
308
216
  }
309
217
 
310
- function clickOnMenuItem(label: string | number) {
311
- if (opened.value.includes(label)) {
312
- opened.value = opened.value.filter((item) => item !== label);
313
- } else {
314
- opened.value.push(label);
315
- }
316
-
317
- }
318
218
 
319
219
  async function logout() {
320
220
  userStore.unauthorize();
@@ -374,13 +274,6 @@ watch([route, () => coreStore.resourceById, () => coreStore.config], async () =>
374
274
 
375
275
  });
376
276
 
377
- watch(()=>coreStore.menu, () => {
378
- coreStore.menu.forEach((item, i) => {
379
- if (item.open) {
380
- opened.value.push(i);
381
- };
382
- });
383
- })
384
277
 
385
278
  watch(dropdownUserButton, (dropdownUserButton) => {
386
279
  if (dropdownUserButton) {
@@ -399,11 +292,6 @@ async function loadPublicConfig() {
399
292
  publicConfigLoaded.value = true;
400
293
  }
401
294
 
402
- watch(sidebarAside, (sidebarAside) => {
403
- if (sidebarAside) {
404
- coreStore.fetchMenuBadges();
405
- }
406
- })
407
295
 
408
296
  // initialize components based on data attribute selectors
409
297
  onMounted(async () => {
@@ -431,32 +319,4 @@ watch(() => coreStore.config?.singleTheme, (singleTheme) => {
431
319
  }
432
320
  }, { immediate: true })
433
321
 
434
-
435
- const ctaBadge: Ref<(AnnouncementBadgeResponse & { hash: string; }) | null> = computed(() => {
436
- const badge = coreStore.config?.announcementBadge;
437
- if (!badge) {
438
- return null;
439
- }
440
- const hash = badge.closable ? verySimpleHash(JSON.stringify(badge)) : '';
441
- if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
442
- return null;
443
- }
444
- return {...badge, hash};
445
- });
446
-
447
- function closeCTA() {
448
- if (!ctaBadge.value) {
449
- return;
450
- }
451
- const hash = ctaBadge.value.hash;
452
- window.localStorage.setItem(`ctaBadge-${hash}`, '1');
453
- nextTick( async() => {
454
- loadMenu();
455
- await coreStore.fetchMenuBadges();
456
- adminforth.menu.refreshMenuBadges();
457
- })
458
-
459
- }
460
-
461
-
462
322
  </script>
@@ -1,24 +1,46 @@
1
1
  <template>
2
2
  <RouterLink
3
3
  :to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
4
- class="flex group items-center py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover" role="menuitem"
4
+ class="af-menu-link flex group relative items-center w-full py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default transition-all duration-200 ease-in-out"
5
5
  :class="{
6
- 'px-4': isChild,
7
- 'px-2': !isChild,
6
+ 'ml-1': isSidebarIconOnly && !isSidebarHovering && isChild,
7
+ 'hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover': !['divider', 'gap', 'heading'].includes(item.type),
8
+ 'px-6': (isChild && !isSidebarIconOnly && !isSidebarHovering) || (isChild && isSidebarIconOnly && isSidebarHovering),
9
+ 'px-3.5': !isChild || (isSidebarIconOnly && !isSidebarHovering),
10
+ 'max-w-13': isSidebarIconOnly && !isSidebarHovering,
8
11
  'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': item.resourceId ?
9
12
  ($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
10
13
  ($route.name === item.path)
11
14
  }"
12
15
  >
13
- <component v-if="item.icon" :is="getIcon(item.icon)" class="min-w-5 min-h-5 text-lightSidebarIcons dark:text-darkSidebarIcons transition duration-75 group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover" ></component>
14
- <span class="text-ellipsis overflow-hidden ms-3">{{ item.label }}</span>
15
- <span v-if="item.badge"
16
+ <component v-if="item.icon" :is="getIcon(item.icon)"
17
+ class="text-lightSidebarIcons dark:text-darkSidebarIcons group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover transition-all duration-200 ease-in-out"
18
+ :class="{
19
+ 'min-w-4 min-h-4': isSidebarIconOnly && !isSidebarHovering && isChild,
20
+ 'min-w-5 min-h-5': !(isSidebarIconOnly && !isSidebarHovering && isChild)
21
+ }" >
22
+ </component>
23
+ <span
24
+ class="overflow-hidden block ms-3 text-left rtl:text-right transition-all duration-200 ease-in-out"
25
+ :class="{
26
+ 'opacity-0 ms-0 translate-x-4 flex-none': isSidebarIconOnly && !isSidebarHovering,
27
+ 'opacity-100 ms-3 translate-x-0 flex-none': isSidebarIconOnly && isSidebarHovering,
28
+ 'opacity-100 ms-3 translate-x-0 flex-1': !isSidebarIconOnly
29
+ }"
30
+ :style="isSidebarIconOnly ? {
31
+ minWidth: isChild
32
+ ? 'calc(16.5rem - 0.75rem*2 - 1.5rem*2 - 1.25rem - 0.75rem)'
33
+ : 'calc(16.5rem - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)',
34
+ width: isChild
35
+ ? 'calc(16.5rem - 0.75rem*2 - 1.5rem*2 - 1.25rem - 0.75rem)'
36
+ : 'calc(16.5rem - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)'
37
+ } : {}"
16
38
  >
17
-
39
+ {{ item.label }}
40
+ <template v-if="item.badge && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
18
41
  <Tooltip v-if="item.badgeTooltip">
19
42
  <div class="af-badge inline-flex items-center justify-center h-3 py-3 px-1 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
20
43
  fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
21
-
22
44
  <template #tooltip>
23
45
  {{ item.badgeTooltip }}
24
46
  </template>
@@ -26,17 +48,19 @@
26
48
  <template v-else>
27
49
  <div class="af-badge inline-flex items-center justify-center h-3 py-3 px-1 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
28
50
  fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
29
- </template>
30
-
51
+ </template>
52
+ </template>
31
53
  </span>
32
-
54
+ <div v-if="item.badge && isSidebarIconOnly && !isSidebarHovering" class="af-badge absolute right-1 top-1/2 -translate-y-1/2 inline-flex items-center justify-center h-2 w-2 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
55
+ fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
56
+ </div>
33
57
  </RouterLink>
34
58
  </template>
35
59
 
36
- <script setup lang="ts">
60
+ <script setup lang="ts">
37
61
  import { getIcon } from '@/utils';
38
62
  import { Tooltip } from '@/afcl';
39
- const props = defineProps(['item', 'isChild']);
40
63
 
64
+ defineProps(['item', 'isChild', 'isSidebarIconOnly', 'isSidebarHovering']);
41
65
 
42
66
  </script>