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