1<template> 2 <div> 3 <div class="nav-container" :class="{ open: isNavigationOpen }"> 4 <nav ref="nav" :aria-label="$t('appNavigation.primaryNavigation')"> 5 <b-nav vertical class="mb-4"> 6 <template v-for="(navItem, index) in navigationItems"> 7 <!-- Navigation items with no children --> 8 <b-nav-item 9 v-if="!navItem.children" 10 :key="index" 11 :to="navItem.route" 12 :data-test-id="`nav-item-${navItem.id}`" 13 > 14 <component :is="navItem.icon" /> 15 {{ navItem.label }} 16 </b-nav-item> 17 18 <!-- Navigation items with children --> 19 <li v-else :key="index" class="nav-item"> 20 <b-button 21 v-b-toggle="`${navItem.id}`" 22 variant="link" 23 :data-test-id="`nav-button-${navItem.id}`" 24 > 25 <component :is="navItem.icon" /> 26 {{ navItem.label }} 27 <icon-expand class="icon-expand" /> 28 </b-button> 29 <b-collapse :id="navItem.id" tag="ul" class="nav-item__nav"> 30 <li class="nav-item"> 31 <router-link 32 v-for="(subNavItem, i) of filteredNavItem(navItem.children)" 33 :key="i" 34 :to="subNavItem.route" 35 :data-test-id="`nav-item-${subNavItem.id}`" 36 class="nav-link" 37 > 38 {{ subNavItem.label }} 39 </router-link> 40 </li> 41 </b-collapse> 42 </li> 43 </template> 44 </b-nav> 45 </nav> 46 </div> 47 <transition name="fade"> 48 <div 49 v-if="isNavigationOpen" 50 id="nav-overlay" 51 class="nav-overlay" 52 @click="toggleIsOpen" 53 ></div> 54 </transition> 55 </div> 56</template> 57 58<script> 59//Do not change Mixin import. 60//Exact match alias set to support 61//dotenv customizations. 62import AppNavigationMixin from './AppNavigationMixin'; 63 64export default { 65 name: 'AppNavigation', 66 mixins: [AppNavigationMixin], 67 data() { 68 return { 69 isNavigationOpen: false, 70 currentUserRole: null, 71 }; 72 }, 73 watch: { 74 $route: function () { 75 this.isNavigationOpen = false; 76 }, 77 isNavigationOpen: function (isNavigationOpen) { 78 this.$root.$emit('change-is-navigation-open', isNavigationOpen); 79 }, 80 }, 81 mounted() { 82 this.getPrivilege(); 83 this.$root.$on('toggle-navigation', () => this.toggleIsOpen()); 84 }, 85 methods: { 86 toggleIsOpen() { 87 this.isNavigationOpen = !this.isNavigationOpen; 88 }, 89 getPrivilege() { 90 this.currentUserRole = this.$store?.getters['global/userPrivilege']; 91 }, 92 filteredNavItem(navItem) { 93 if (this.currentUserRole) { 94 return navItem.filter(({ exclusiveToRoles }) => { 95 if (!exclusiveToRoles?.length) return true; 96 return exclusiveToRoles.includes(this.currentUserRole); 97 }); 98 } else return navItem; 99 }, 100 }, 101}; 102</script> 103 104<style scoped lang="scss"> 105svg { 106 fill: currentColor; 107 height: 1.2rem; 108 width: 1.2rem; 109 margin-left: 0 !important; //!important overriding button specificity 110 vertical-align: text-bottom; 111 &:not(.icon-expand) { 112 margin-right: $spacer; 113 } 114} 115 116.nav { 117 padding-top: $spacer / 4; 118 @include media-breakpoint-up($responsive-layout-bp) { 119 padding-top: $spacer; 120 } 121} 122 123.nav-item__nav { 124 list-style: none; 125 padding-left: 0; 126 margin-left: 0; 127 128 .nav-item { 129 outline: none; 130 } 131 132 .nav-link { 133 padding-left: $spacer * 4; 134 outline: none; 135 136 &:not(.nav-link--current) { 137 font-weight: normal; 138 } 139 } 140} 141 142.btn-link { 143 display: inline-block; 144 width: 100%; 145 text-align: left; 146 text-decoration: none !important; 147 border-radius: 0; 148 149 &.collapsed { 150 .icon-expand { 151 transform: rotate(180deg); 152 } 153 } 154} 155 156.icon-expand { 157 float: right; 158 margin-top: $spacer / 4; 159} 160 161.btn-link, 162.nav-link { 163 position: relative; 164 font-weight: $headings-font-weight; 165 padding-left: $spacer; // defining consistent padding for links and buttons 166 padding-right: $spacer; 167 color: theme-color('secondary'); 168 169 &:hover { 170 background-color: theme-color-level(dark, -10.5); 171 color: theme-color('dark'); 172 } 173 174 &:focus { 175 background-color: theme-color-level(light, 0); 176 box-shadow: inset 0 0 0 2px theme-color('primary'); 177 color: theme-color('dark'); 178 outline: 0; 179 } 180 181 &:active { 182 background-color: theme-color('secondary'); 183 color: $white; 184 } 185} 186 187.nav-link--current { 188 font-weight: $headings-font-weight; 189 background-color: theme-color('secondary'); 190 color: theme-color('light'); 191 cursor: default; 192 box-shadow: none; 193 194 &::before { 195 content: ''; 196 position: absolute; 197 top: 0; 198 bottom: 0; 199 left: 0; 200 width: 4px; 201 background-color: theme-color('primary'); 202 } 203 204 &:hover, 205 &:focus { 206 background-color: theme-color('secondary'); 207 color: theme-color('light'); 208 } 209} 210 211.nav-container { 212 position: fixed; 213 width: $navigation-width; 214 top: $header-height; 215 bottom: 0; 216 left: 0; 217 z-index: $zindex-fixed; 218 overflow-y: auto; 219 background-color: theme-color('light'); 220 transform: translateX(-$navigation-width); 221 transition: transform $exit-easing--productive $duration--moderate-02; 222 border-right: 1px solid theme-color-level('light', 2.85); 223 224 @include media-breakpoint-down(md) { 225 z-index: $zindex-fixed + 2; 226 } 227 228 &.open, 229 &:focus-within { 230 transform: translateX(0); 231 transition-timing-function: $entrance-easing--productive; 232 } 233 234 @include media-breakpoint-up($responsive-layout-bp) { 235 transition-duration: $duration--fast-01; 236 transform: translateX(0); 237 } 238} 239 240.nav-overlay { 241 position: fixed; 242 top: $header-height; 243 bottom: 0; 244 left: 0; 245 right: 0; 246 z-index: $zindex-fixed + 1; 247 background-color: $black; 248 opacity: 0.5; 249 250 &.fade-enter-active { 251 transition: opacity $duration--moderate-02 $entrance-easing--productive; 252 } 253 254 &.fade-leave-active { 255 transition: opacity $duration--fast-02 $exit-easing--productive; 256 } 257 258 &.fade-enter, // Remove this vue2 based only class when switching to vue3 259 &.fade-enter-from, // This is vue3 based only class modified from 'fade-enter' 260 &.fade-leave-to { 261 opacity: 0; 262 } 263 264 @include media-breakpoint-up($responsive-layout-bp) { 265 display: none; 266 } 267} 268</style> 269