7070 <div v-else-if =" item.type === 'heading'" class =" flex items-center justify-left px-3 pt-3 pb-1 text-xs font-medium tracking-wide text-lightSidebarHeading dark:text-darkSidebarHeading
7171 " >{{ item.label }}</div >
7272 <li v-else-if =" item.children" class =" af-sidebar-expand-container" >
73- <button @click =" clickOnMenuItem(i )" type =" button" class =" af-sidebar-expand-button flex items-center w-full px-3 py-1.5 text-sm text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover"
74- :class =" opened.includes(i ) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
73+ <button @click =" clickOnMenuItem(item )" type =" button" class =" af-sidebar-expand-button flex items-center w-full px-3 py-1.5 text-sm text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover"
74+ :class =" isGroupOpened(item ) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
7575 :aria-controls =" `dropdown-example${i}`"
7676 >
7777 <component v-if =" item.icon" :is =" getIcon(item.icon)" class =" w-4 h-4 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component >
9090 </span >
9191 </span >
9292
93- <IconChevronDown :class =" {' rotate-180' : opened . includes ( i ) } " class="w-3 h-3 text-lightSidebarIcons dark:text-darkSidebarIcons transition-transform duration-200" aria-hidden="true" />
93+ <IconChevronDown :class =" {' rotate-180' : isGroupOpened ( item ) } " class="w-3 h-3 text-lightSidebarIcons dark:text-darkSidebarIcons transition-transform duration-200" aria-hidden="true" />
9494 </button >
9595
9696 <div class =" grid transition-[grid-template-rows,opacity] duration-200 ease-in-out"
97- :class =" opened.includes(i ) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
98- :inert =" !opened.includes(i )"
97+ :class =" isGroupOpened(item ) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
98+ :inert =" !isGroupOpened(item )"
9999 >
100100 <div class =" min-h-0 overflow-hidden" >
101101 <ul :id =" `dropdown-example${i}`" role =" none" class =" af-sidebar-dropdown mt-0.5 ml-[1.25rem] pl-2 border-l border-lightSidebarDevider dark:border-darkSidebarDevider space-y-0.5" >
119119 " >{{ item.label }}</div >
120120 <div v-else-if =" item.type === 'heading' && !isSidebarHovering" class =" opacity-0 w-1 h-8" >{{ item.label }}</div >
121121 <li v-else-if =" item.children" class =" af-sidebar-expand-container" >
122- <button @click =" clickOnMenuItem(i )" type =" button" class =" af-sidebar-expand-button relative flex items-center h-9 py-2 text-sm text-lightSidebarText rounded-default group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover"
122+ <button @click =" clickOnMenuItem(item )" type =" button" class =" af-sidebar-expand-button relative flex items-center h-9 py-2 text-sm text-lightSidebarText rounded-default group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover"
123123 :class =" [
124- opened.includes(i ) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed',
124+ isGroupOpened(item ) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed',
125125 isSidebarIconOnly && !isSidebarHovering ? 'w-10 ml-1 px-3' : 'w-full px-3'
126126 ]"
127127 :aria-controls =" `dropdown-example${i}`"
155155 </span >
156156 </span >
157157
158- <IconChevronDown :class =" {' rotate-180' : opened . includes ( i ) } " class="w-3 h-3 ml-2 absolute right-3 text-lightSidebarIcons dark:text-darkSidebarIcons transition-transform duration-200" aria-hidden="true" />
158+ <IconChevronDown :class =" {' rotate-180' : isGroupOpened ( item ) } " class="w-3 h-3 ml-2 absolute right-3 text-lightSidebarIcons dark:text-darkSidebarIcons transition-transform duration-200" aria-hidden="true" />
159159 </button >
160160
161- <ul :id =" `dropdown-example${i}`" role =" none" class =" af-sidebar-dropdown pt-0.5 space-y-0.5" :class =" { 'hidden': !opened.includes(i ) }" >
161+ <ul :id =" `dropdown-example${i}`" role =" none" class =" af-sidebar-dropdown pt-0.5 space-y-0.5" :class =" { 'hidden': !isGroupOpened(item ) }" >
162162 <template v-for =" (child , j ) in item .children " :key =" ` menu-${i }-${j } ` " >
163163 <li class =" af-sidebar-menu-link" >
164164 <MenuLink :item =" child " isChild="true" @click =" $emit (' hideSidebar' )" :isSidebarIconOnly =" isSidebarIconOnly " :isSidebarHovering =" isSidebarHovering " />
@@ -314,7 +314,7 @@ import MenuLink from './MenuLink.vue';
314314import { IconPanelLeftClose , IconPanelLeftOpen , IconChevronDown , IconX , IconCircleAlert } from ' @iconify-prerendered/vue-lucide' ;
315315import { getIcon , verySimpleHash , loadFile , getCustomComponent } from ' @/utils' ;
316316import { Tooltip } from ' @/afcl' ;
317- import type { AnnouncementBadgeResponse } from ' @/types/Common' ;
317+ import type { AnnouncementBadgeResponse , AdminForthConfigMenuItem } from ' @/types/Common' ;
318318import { useAdminforth } from ' @/adminforth' ;
319319import { useI18n } from ' vue-i18n' ;
320320
@@ -338,8 +338,9 @@ const emit = defineEmits<{
338338
339339const coreStore = useCoreStore ();
340340
341- // create a ref to store the opened menu items with ts type;
342- const opened = ref <(string | number )[]>([]);
341+ const OPENED_GROUPS_LS_KEY = ' afSidebarOpenedGroups' ;
342+ // expanded/collapsed state of groups toggled by user, by itemId, untouched groups fall back to item.open
343+ const openedGroups = ref <Record <string , boolean >>(JSON .parse (localStorage .getItem (OPENED_GROUPS_LS_KEY ) || ' {}' ));
343344const sidebarAside = ref (null );
344345
345346const smQuery = window .matchMedia (' (min-width: 640px)' );
@@ -390,21 +391,14 @@ function toggleSidebar() {
390391 }, 100 );
391392}
392393
393- function clickOnMenuItem(label : string | number ) {
394- if (opened .value .includes (label )) {
395- opened .value = opened .value .filter ((item ) => item !== label );
396- } else {
397- opened .value .push (label );
398- }
394+ function isGroupOpened(item : AdminForthConfigMenuItem ) {
395+ return openedGroups .value [item .itemId ! ] ?? item .open ;
399396}
400397
401- watch (()=> coreStore .menu , () => {
402- coreStore .menu .forEach ((item , i ) => {
403- if (item .open ) {
404- opened .value .push (i );
405- };
406- });
407- })
398+ function clickOnMenuItem(item : AdminForthConfigMenuItem ) {
399+ openedGroups .value [item .itemId ! ] = ! isGroupOpened (item );
400+ localStorage .setItem (OPENED_GROUPS_LS_KEY , JSON .stringify (openedGroups .value ));
401+ }
408402
409403
410404
@@ -455,11 +449,6 @@ onMounted(() => {
455449 isSidebarIconOnly .value = false ;
456450 }
457451
458- coreStore .menu .forEach ((item , i ) => {
459- if (item .open ) {
460- opened .value .push (i );
461- };
462- });
463452 // Emit initial state
464453 emit (' sidebarStateChange' , { isSidebarIconOnly: isSidebarIconOnly .value , isSidebarHovering: isSidebarHovering .value });
465454})
0 commit comments