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'; 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 lang="scss" scoped> 105@import '@/assets/styles/bmc/helpers/_index.scss'; 106@import '@/assets/styles/bootstrap/_helpers.scss'; 107 108svg { 109 fill: currentColor; 110 height: 1.2rem; 111 width: 1.2rem; 112 margin-left: 0 !important; //!important overriding button specificity 113 vertical-align: text-bottom; 114 &:not(.icon-expand) { 115 margin-right: $spacer; 116 } 117} 118 119.nav { 120 padding-top: $spacer / 4; 121 @include media-breakpoint-up($responsive-layout-bp) { 122 padding-top: $spacer; 123 } 124} 125 126.nav-item__nav { 127 list-style: none; 128 padding-left: 0; 129 margin-left: 0; 130 131 .nav-item { 132 outline: none; 133 } 134 135 .nav-link { 136 padding-left: $spacer * 4; 137 outline: none; 138 139 &:not(.nav-link--current) { 140 font-weight: normal; 141 } 142 } 143} 144 145.btn-link { 146 display: inline-block; 147 width: 100%; 148 text-align: left; 149 text-decoration: none !important; 150 border-radius: 0; 151 152 &.collapsed { 153 .icon-expand { 154 transform: rotate(180deg); 155 } 156 } 157} 158 159.icon-expand { 160 float: right; 161 margin-top: $spacer / 4; 162} 163 164.btn-link, 165.nav-link { 166 position: relative; 167 font-weight: $headings-font-weight; 168 padding-left: $spacer; // defining consistent padding for links and buttons 169 padding-right: $spacer; 170 color: theme-color('secondary'); 171 172 &:hover { 173 background-color: theme-color-level(dark, -10.5); 174 color: theme-color('dark'); 175 } 176 177 &:focus { 178 background-color: theme-color-level(light, 0); 179 box-shadow: inset 0 0 0 2px theme-color('primary'); 180 color: theme-color('dark'); 181 outline: 0; 182 } 183 184 &:active { 185 background-color: theme-color('secondary'); 186 color: $white; 187 } 188} 189 190.nav-link--current { 191 font-weight: $headings-font-weight; 192 background-color: theme-color('secondary'); 193 color: theme-color('light'); 194 cursor: default; 195 box-shadow: none; 196 197 &::before { 198 content: ''; 199 position: absolute; 200 top: 0; 201 bottom: 0; 202 left: 0; 203 width: 4px; 204 background-color: theme-color('primary'); 205 } 206 207 &:hover, 208 &:focus { 209 background-color: theme-color('secondary'); 210 color: theme-color('light'); 211 } 212} 213 214.nav-container { 215 position: fixed; 216 width: $navigation-width; 217 top: $header-height; 218 bottom: 0; 219 left: 0; 220 z-index: $zindex-fixed; 221 overflow-y: auto; 222 background-color: theme-color('light'); 223 transform: translateX(-$navigation-width); 224 transition: transform $exit-easing--productive $duration--moderate-02; 225 border-right: 1px solid theme-color-level('light', 2.85); 226 227 @include media-breakpoint-down(md) { 228 z-index: $zindex-fixed + 2; 229 } 230 231 &.open, 232 &:focus-within { 233 transform: translateX(0); 234 transition-timing-function: $entrance-easing--productive; 235 } 236 237 @include media-breakpoint-up($responsive-layout-bp) { 238 transition-duration: $duration--fast-01; 239 transform: translateX(0); 240 } 241} 242 243.nav-overlay { 244 position: fixed; 245 top: $header-height; 246 bottom: 0; 247 left: 0; 248 right: 0; 249 z-index: $zindex-fixed + 1; 250 background-color: $black; 251 opacity: 0.5; 252 253 &.fade-enter-active { 254 transition: opacity $duration--moderate-02 $entrance-easing--productive; 255 } 256 257 &.fade-leave-active { 258 transition: opacity $duration--fast-02 $exit-easing--productive; 259 } 260 261 &.fade-enter, // Remove this vue2 based only class when switching to vue3 262 &.fade-enter-from, // This is vue3 based only class modified from 'fade-enter' 263 &.fade-leave-to { 264 opacity: 0; 265 } 266 267 @include media-breakpoint-up($responsive-layout-bp) { 268 display: none; 269 } 270} 271</style> 272