feat: mobile friendly menu
This commit is contained in:
		
							parent
							
								
									f872972e69
								
							
						
					
					
						commit
						1e67fa6e0b
					
				
							
								
								
									
										47
									
								
								src/components/MenuLayout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/MenuLayout.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | <template> | ||||||
|  |     <n-layout has-sider> | ||||||
|  |         <n-layout-sider bordered collapse-mode="width" :collapsed-width="0" :width="240" :collapsed="isMenuCollapsed" | ||||||
|  |             @collapse="isMenuCollapsed = true" @expand="isMenuCollapsed = false" :show-trigger="false" | ||||||
|  |             :native-scrollbar="false" :position="siderPosition"> | ||||||
|  |             <slot name="sider" /> | ||||||
|  |         </n-layout-sider> | ||||||
|  |         <n-layout class="content"> | ||||||
|  |             <slot name="content" /> | ||||||
|  |             <div class="overlay" v-show="isSmallScreen && !isMenuCollapsed" @click="isMenuCollapsed = true" /> | ||||||
|  |         </n-layout> | ||||||
|  |     </n-layout> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { useStyleStore } from '@/stores/style.store'; | ||||||
|  | import { toRefs } from 'vue'; | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | 
 | ||||||
|  | const styleStore = useStyleStore() | ||||||
|  | const { isMenuCollapsed, isSmallScreen } = toRefs(styleStore) | ||||||
|  | const siderPosition = computed(() => isSmallScreen.value ? 'absolute' : 'static') | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="less" scoped> | ||||||
|  | .overlay { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     background-color: #00000080; | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .content { | ||||||
|  | 
 | ||||||
|  |     // background-color: #f1f5f9; | ||||||
|  |     ::v-deep(.n-layout-scroll-container) { | ||||||
|  |         padding: 26px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .n-layout { | ||||||
|  |     height: 100vh; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @ -2,15 +2,15 @@ | |||||||
| import { NIcon } from 'naive-ui'; | import { NIcon } from 'naive-ui'; | ||||||
| import { h, ref, type Component } from 'vue'; | import { h, ref, type Component } from 'vue'; | ||||||
| import { RouterLink, useRoute } from 'vue-router'; | import { RouterLink, useRoute } from 'vue-router'; | ||||||
| import { Heart, BrandGithub, BrandTwitter, Moon, Sun } from '@vicons/tabler' | import { Heart, BrandGithub, BrandTwitter, Moon, Sun, Menu2, Home2 } from '@vicons/tabler' | ||||||
| import { toolsByCategory } from '@/tools'; | import { toolsByCategory } from '@/tools'; | ||||||
| import SearchBar from '../components/SearchBar.vue'; | import SearchBar from '../components/SearchBar.vue'; | ||||||
| import { useStyleStore } from '@/stores/style.store'; | import { useStyleStore } from '@/stores/style.store'; | ||||||
| import HeroGradient from '../assets/hero-gradient.svg?component' | import HeroGradient from '../assets/hero-gradient.svg?component' | ||||||
| import { useThemeVars } from 'naive-ui' | import { useThemeVars } from 'naive-ui' | ||||||
|  | import MenuLayout from '../components/MenuLayout.vue' | ||||||
| 
 | 
 | ||||||
| const themeVars = useThemeVars() | const themeVars = useThemeVars() | ||||||
| const collapsed = ref(false) |  | ||||||
| const activeKey = ref(null) | const activeKey = ref(null) | ||||||
| const route = useRoute() | const route = useRoute() | ||||||
| const styleStore = useStyleStore() | const styleStore = useStyleStore() | ||||||
| @ -32,10 +32,9 @@ const m = toolsByCategory.map(category => ({ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <n-layout has-sider> |     <menu-layout class="menu-layout" :class="{ isSmallScreen: styleStore.isSmallScreen }"> | ||||||
|         <n-layout-sider bordered collapse-mode="width" :collapsed-width="64" :width="260" :collapsed="collapsed" |  | ||||||
|             @collapse="collapsed = true" @expand="collapsed = false" :show-trigger="false" :native-scrollbar="false"> |  | ||||||
| 
 | 
 | ||||||
|  |         <template v-slot:sider> | ||||||
|             <router-link to="/" class="hero-wrapper"> |             <router-link to="/" class="hero-wrapper"> | ||||||
|                 <hero-gradient class="gradient" /> |                 <hero-gradient class="gradient" /> | ||||||
|                 <div class="text-wrapper"> |                 <div class="text-wrapper"> | ||||||
| @ -45,34 +44,72 @@ const m = toolsByCategory.map(category => ({ | |||||||
|                 </div> |                 </div> | ||||||
|             </router-link> |             </router-link> | ||||||
| 
 | 
 | ||||||
|             <n-menu :value="route.name" class="menu" :collapsed="collapsed" :collapsed-width="64" |             <div class="sider-content"> | ||||||
|                 :collapsed-icon-size="22" :options="m" v-model:value="activeKey" /> |                 <n-space v-if="styleStore.isSmallScreen" justify="center"> | ||||||
|  |                     <n-button size="large" circle quaternary tag="a" href="https://github.com/CorentinTh/it-tools" | ||||||
|  |                         rel="noopener" target="_blank"> | ||||||
|  |                         <n-icon size="25" :component="BrandGithub" /> | ||||||
|  |                     </n-button> | ||||||
|  |                     <n-button size="large" circle quaternary tag="a" href="https://twitter.com/cthmsst" rel="noopener" | ||||||
|  |                         target="_blank"> | ||||||
|  |                         <n-icon size="25" :component="BrandTwitter" /> | ||||||
|  |                     </n-button> | ||||||
|  |                     <n-button size="large" circle quaternary @click="styleStore.isDarkTheme = !styleStore.isDarkTheme"> | ||||||
|  |                         <n-icon size="25" v-if="styleStore.isDarkTheme" :component="Sun" /> | ||||||
|  |                         <n-icon size="25" v-else :component="Moon" /> | ||||||
|  |                     </n-button> | ||||||
|  |                 </n-space> | ||||||
|  | 
 | ||||||
|  |                 <n-menu :value="route.name" class="menu" :collapsed-width="64" :collapsed-icon-size="22" :options="m" | ||||||
|  |                     v-model:value="activeKey" :indent="20" /> | ||||||
|  | 
 | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         </template> | ||||||
|  | 
 | ||||||
|  |         <template v-slot:content> | ||||||
|  |             <div class="navigation"> | ||||||
|  |                 <n-button :size="styleStore.isSmallScreen ? 'medium' : 'large'" circle quaternary | ||||||
|  |                     @click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"> | ||||||
|  |                     <n-icon size="25" :component="Menu2" /> | ||||||
|  |                 </n-button> | ||||||
|  | 
 | ||||||
|  |                 <router-link to="/" #="{ navigate, href }" custom> | ||||||
|  |                     <n-button tag="a" :href="href" @click="navigate" | ||||||
|  |                         :size="styleStore.isSmallScreen ? 'medium' : 'large'" circle quaternary> | ||||||
|  |                         <n-icon size="25" :component="Home2" /> | ||||||
|  |                     </n-button> | ||||||
|  |                 </router-link> | ||||||
| 
 | 
 | ||||||
|         </n-layout-sider> |  | ||||||
|         <n-layout class="content"> |  | ||||||
|             <div class="bar-wrapper"> |  | ||||||
|                 <search-bar /> |                 <search-bar /> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|                 <n-button type="primary" tag="a" href="https://github.com/sponsors/CorentinTh" rel="noopener" |                 <n-button type="primary" tag="a" href="https://github.com/sponsors/CorentinTh" rel="noopener" | ||||||
|                     target="_blank"> |                     target="_blank"> | ||||||
|                     <n-icon :component="Heart" />  |                     <n-icon :component="Heart" style="margin-right: 5px;" v-if="!styleStore.isSmallScreen" /> | ||||||
|                     Sponsor |                     Sponsor | ||||||
|                 </n-button> |                 </n-button> | ||||||
|                 <n-button size="large" circle quaternary tag="a" href="https://github.com/CorentinTh/it-tools" |                 <n-button size="large" circle quaternary tag="a" href="https://github.com/CorentinTh/it-tools" | ||||||
|                     rel="noopener" target="_blank"> |                     rel="noopener" target="_blank" v-if="!styleStore.isSmallScreen"> | ||||||
|                     <n-icon size="25" :component="BrandGithub" /> |                     <n-icon size="25" :component="BrandGithub" /> | ||||||
|                 </n-button> |                 </n-button> | ||||||
|                 <n-button size="large" circle quaternary tag="a" href="https://twitter.com/cthmsst" rel="noopener" |                 <n-button size="large" circle quaternary tag="a" href="https://twitter.com/cthmsst" rel="noopener" | ||||||
|                     target="_blank"> |                     target="_blank" v-if="!styleStore.isSmallScreen"> | ||||||
|                     <n-icon size="25" :component="BrandTwitter" /> |                     <n-icon size="25" :component="BrandTwitter" /> | ||||||
|                 </n-button> |                 </n-button> | ||||||
|                 <n-button size="large" circle quaternary @click="styleStore.isDarkTheme = !styleStore.isDarkTheme"> |                 <n-button size="large" circle quaternary @click="styleStore.isDarkTheme = !styleStore.isDarkTheme" | ||||||
|  |                     v-if="!styleStore.isSmallScreen"> | ||||||
|                     <n-icon size="25" v-if="styleStore.isDarkTheme" :component="Sun" /> |                     <n-icon size="25" v-if="styleStore.isDarkTheme" :component="Sun" /> | ||||||
|                     <n-icon size="25" v-else :component="Moon" /> |                     <n-icon size="25" v-else :component="Moon" /> | ||||||
|                 </n-button> |                 </n-button> | ||||||
|  | 
 | ||||||
|             </div> |             </div> | ||||||
|             <slot /> |             <slot /> | ||||||
|         </n-layout> | 
 | ||||||
|     </n-layout> |         </template> | ||||||
|  | 
 | ||||||
|  |     </menu-layout> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style lang="less" scoped> | <style lang="less" scoped> | ||||||
| @ -87,7 +124,7 @@ const m = toolsByCategory.map(category => ({ | |||||||
| //     background-size: @size @size; | //     background-size: @size @size; | ||||||
| // } | // } | ||||||
| 
 | 
 | ||||||
| .n-menu { | .sider-content { | ||||||
|     padding-top: 160px; |     padding-top: 160px; | ||||||
|     padding-bottom: 200px; |     padding-bottom: 200px; | ||||||
| } | } | ||||||
| @ -99,9 +136,10 @@ const m = toolsByCategory.map(category => ({ | |||||||
|     left: 0; |     left: 0; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     z-index: 10; |     z-index: 10; | ||||||
|  |     overflow: hidden; | ||||||
| 
 | 
 | ||||||
|     .gradient { |     .gradient { | ||||||
|         margin-top: -80px; |         margin-top: -65px; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .text-wrapper { |     .text-wrapper { | ||||||
| @ -131,29 +169,24 @@ const m = toolsByCategory.map(category => ({ | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .bar-wrapper { | .navigation { | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     justify-content: center; |     justify-content: center; | ||||||
|  |     flex-direction: row; | ||||||
| 
 | 
 | ||||||
|     &>*:not(:first-child) { |     &>*:not(:first-child) { | ||||||
|         margin-left: 15px; |         margin-left: 10px; | ||||||
|  | 
 | ||||||
|  |         .isSmallScreen & { | ||||||
|  |             margin-left: 5px; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     .search-bar { |     .search-bar { | ||||||
|  |         // width: 100%; | ||||||
|         flex-grow: 1; |         flex-grow: 1; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| .content { |  | ||||||
| 
 |  | ||||||
|     // background-color: #f1f5f9; |  | ||||||
|     ::v-deep(.n-layout-scroll-container) { |  | ||||||
|         padding: 26px; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .n-layout { |  | ||||||
|     height: 100vh; |  | ||||||
| } |  | ||||||
| </style> | </style> | ||||||
| @ -45,19 +45,23 @@ useHead(head) | |||||||
| 
 | 
 | ||||||
|     .tool-header { |     .tool-header { | ||||||
|         padding: 40px 0; |         padding: 40px 0; | ||||||
|  | 
 | ||||||
|         .n-h1 { |         .n-h1 { | ||||||
|             opacity: 0.9; |             opacity: 0.9; | ||||||
|             font-size: 40px; |             font-size: 40px; | ||||||
|             font-weight: 400; |             font-weight: 400; | ||||||
|             margin: 0; |             margin: 0; | ||||||
|  |             line-height: 1; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         .separator { |         .separator { | ||||||
|             width: 200px; |             width: 200px; | ||||||
|             height: 2px; |             height: 2px; | ||||||
|             background: rgb(161, 161, 161); |             background: rgb(161, 161, 161); | ||||||
| 
 | 
 | ||||||
|             margin-bottom: 10px; |             margin: 10px 0; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         .description { |         .description { | ||||||
|             margin: 0; |             margin: 0; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| import { useStorage } from '@vueuse/core'; | import { useMediaQuery, useStorage } from '@vueuse/core'; | ||||||
| import { defineStore } from 'pinia'; | import { defineStore } from 'pinia'; | ||||||
| import type { Ref } from 'vue'; | import type { Ref } from 'vue'; | ||||||
| 
 | 
 | ||||||
| export const useStyleStore = defineStore('style', { | export const useStyleStore = defineStore('style', { | ||||||
|   state: () => ({ |   state: () => ({ | ||||||
|     isDarkTheme: useStorage('useDarkTheme', false) as Ref<boolean>, |     isDarkTheme: useStorage('isDarkTheme', true) as Ref<boolean>, | ||||||
|  |     isMenuCollapsed: useStorage('isMenuCollapsed', false) as Ref<boolean>, | ||||||
|  |     isSmallScreen: useMediaQuery('(max-width: 700px)'), | ||||||
|   }), |   }), | ||||||
| }); | }); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user