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