nav.vue (3014B)
1 <template> 2 <nav :class="isOpen ? 'open' : ''" > 3 <div class="brand"> 4 <center> 5 <img src="/assets/logo.png"> 6 <h3>Product</h3> 7 </center> 8 </div> 9 <ul> 10 <template v-for="route in $router.getRoutes()"> 11 <li v-if="route.meta.nav"> 12 <router-link :to="route.path"><icon>{{ route.meta.icon }}</icon><span>{{ route.name }}</span></router-link> 13 </li> 14 </template> 15 </ul> 16 </nav> 17 <div class="topbar"> 18 <div> 19 <a href="#!" @click.prevent="toggleMenu" v-if="isOpen"><icon>navigate_before</icon></a> 20 <a href="#!" @click.prevent="toggleMenu" v-if="!isOpen"><icon>menu</icon></a> 21 </div> 22 <div class="grow"><h4>{{ $route.name }}</h4></div> 23 </div> 24 </template> 25 26 <style scoped> 27 a { 28 text-decoration: none; 29 color: inherit; 30 } 31 ul, li { 32 list-style-type: none; 33 padding: 0; 34 margin: 0; 35 } 36 svg.feather, 37 .material-icons, 38 .material-icons-round { 39 margin-right: 0.5rem; 40 } 41 ul { 42 margin: 1rem; 43 } 44 li a { 45 border-radius: 0.25rem; 46 padding: 0.5em; 47 display: block; 48 } 49 li a > * { 50 vertical-align: middle; 51 } 52 li a.router-link-active { 53 background: #FFF3; 54 } 55 li a:hover { 56 background: #FFF6; 57 } 58 nav { 59 background: var(--col-primary); 60 color: var(--col-text-icons); 61 position: fixed; 62 overflow: auto; 63 left: -15rem; 64 top: 0; 65 height: 100vh; 66 transition: left 200ms ease; 67 width: 15rem; 68 } 69 nav.open { 70 left: 0; 71 } 72 73 .brand img { 74 width: 50%; 75 /* border-radius: 50%; */ 76 margin-top: 3rem; 77 } 78 79 .topbar { 80 background: #FFF; 81 display: flex; 82 padding: 0.5em; 83 border-bottom: 1px solid #DDD; 84 } 85 .topbar .grow { 86 flex-grow: 1; 87 } 88 .topbar .grow * { 89 margin: 0; 90 } 91 </style> 92 93 <style> 94 body { 95 position: relative; 96 transition: margin-left 200ms ease; 97 margin-left: 0; 98 } 99 body.menu-opened { 100 margin-left: 15rem; 101 } 102 </style> 103 104 <script lang="ts"> 105 106 import { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'; 107 import Icon from './icon.vue'; 108 109 export default { 110 components: {Icon}, 111 setup() { 112 // 1920 / 2 = 960 113 // Leaving room for window borders, so chose 920 114 // Allows 2 windows side-by-side in open mode 115 const initialState = window.innerWidth >= 920; 116 117 // Nicely setup & remove class 118 // This component IS NOT CLEAN 119 onMounted(() => { 120 const bdyClassList = document.body.className.split(' ').filter(n => n && n !== 'menu-opened'); 121 if (initialState) bdyClassList.push('menu-opened'); 122 document.body.className = bdyClassList.join(' '); 123 }); 124 onUnmounted(() => { 125 const bdyClassList = document.body.className.split(' ').filter(n => n && n !== 'menu-opened'); 126 document.body.className = bdyClassList.join(' '); 127 }); 128 129 return { 130 isOpen: ref(initialState), 131 132 toggleMenu() { 133 this.isOpen = !this.isOpen; 134 const bdyClassList = document.body.className.split(' ').filter(n => n && n !== 'menu-opened'); 135 if (this.isOpen) bdyClassList.push('menu-opened'); 136 document.body.className = bdyClassList.join(' '); 137 }, 138 139 }; 140 }, 141 142 }; 143 </script>