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 <b-nav-item 31 v-for="(subNavItem, i) of navItem.children" 32 :key="i" 33 :to="subNavItem.route" 34 :data-test-id="`nav-item-${subNavItem.id}`" 35 > 36 {{ subNavItem.label }} 37 </b-nav-item> 38 </b-collapse> 39 </li> 40 </template> 41 </b-nav> 42 </nav> 43 </div> 44 <transition name="fade"> 45 <div 46 v-if="isNavigationOpen" 47 id="nav-overlay" 48 class="nav-overlay" 49 @click="toggleIsOpen" 50 ></div> 51 </transition> 52 </div> 53</template> 54 55<script> 56import AppNavigationMixin from './AppNavigationMixin'; 57 58export default { 59 name: 'AppNavigation', 60 mixins: [AppNavigationMixin], 61 data() { 62 return { 63 isNavigationOpen: false 64 }; 65 }, 66 watch: { 67 $route: function() { 68 this.isNavigationOpen = false; 69 }, 70 isNavigationOpen: function(isNavigationOpen) { 71 this.$root.$emit('change:isNavigationOpen', isNavigationOpen); 72 } 73 }, 74 mounted() { 75 this.$root.$on('toggle:navigation', () => this.toggleIsOpen()); 76 }, 77 methods: { 78 toggleIsOpen() { 79 this.isNavigationOpen = !this.isNavigationOpen; 80 } 81 } 82}; 83</script> 84 85<style scoped lang="scss"> 86svg { 87 fill: currentColor; 88 height: 1.2rem; 89 width: 1.2rem; 90 margin-left: 0 !important; //!important overriding button specificity 91 vertical-align: text-bottom; 92 &:not(.icon-expand) { 93 margin-right: $spacer; 94 } 95} 96 97.nav { 98 padding-top: $spacer / 4; 99 @include media-breakpoint-up($responsive-layout-bp) { 100 padding-top: $spacer; 101 } 102} 103 104.nav-item__nav { 105 list-style: none; 106 padding-left: 0; 107 margin-left: 0; 108 109 .nav-item { 110 outline: none; 111 } 112 113 .nav-link { 114 padding-left: $spacer * 4; 115 outline: none; 116 117 &:not(.nav-link--current) { 118 font-weight: normal; 119 } 120 } 121} 122 123.btn-link { 124 width: 100%; 125 text-align: left; 126 text-decoration: none !important; 127 border-radius: 0; 128 129 &.collapsed { 130 .icon-expand { 131 transform: rotate(180deg); 132 } 133 } 134} 135 136.icon-expand { 137 float: right; 138 margin-top: $spacer / 4; 139} 140 141.btn-link, 142.nav-link { 143 position: relative; 144 font-weight: $headings-font-weight; 145 padding-left: $spacer; // defining consistent padding for links and buttons 146 padding-right: $spacer; 147 color: theme-color('secondary'); 148 149 &:hover { 150 background-color: gray('300'); 151 color: theme-color('dark'); 152 } 153 154 &:focus { 155 box-shadow: $btn-focus-box-shadow; 156 color: theme-color('dark'); 157 } 158} 159 160.nav-link--current, 161.nav-link--current:hover, 162.nav-link--current:focus { 163 font-weight: $headings-font-weight; 164 background-color: theme-color('secondary'); 165 color: theme-color('light'); 166 cursor: default; 167 168 &::before { 169 content: ''; 170 position: absolute; 171 top: 0; 172 bottom: 0; 173 left: 0; 174 width: 4px; 175 background-color: theme-color('primary'); 176 } 177} 178 179.nav-container { 180 position: fixed; 181 width: $navigation-width; 182 top: $header-height; 183 bottom: 0; 184 left: 0; 185 z-index: $zindex-fixed; 186 overflow-y: auto; 187 background-color: gray('100'); 188 transform: translateX(-$navigation-width); 189 transition: transform $exit-easing--productive $duration--moderate-02; 190 @include media-breakpoint-down(md) { 191 z-index: $zindex-fixed + 2; 192 } 193 194 &.open, 195 &:focus-within { 196 transform: translateX(0); 197 transition-timing-function: $entrance-easing--productive; 198 } 199 200 @include media-breakpoint-up($responsive-layout-bp) { 201 transition-duration: $duration--fast-01; 202 transform: translateX(0); 203 } 204} 205 206.nav-overlay { 207 position: fixed; 208 top: $header-height; 209 bottom: 0; 210 left: 0; 211 right: 0; 212 z-index: $zindex-fixed + 1; 213 background-color: $black; 214 opacity: 0.5; 215 216 &.fade-enter-active { 217 transition: opacity $duration--moderate-02 $entrance-easing--productive; 218 } 219 220 &.fade-leave-active { 221 transition: opacity $duration--fast-02 $exit-easing--productive; 222 } 223 224 &.fade-enter, 225 &.fade-leave-to { 226 opacity: 0; 227 } 228 229 @include media-breakpoint-up($responsive-layout-bp) { 230 display: none; 231 } 232} 233</style> 234