Skip to content

Commit

Permalink
✨ implement mobile menu
Browse files Browse the repository at this point in the history
  • Loading branch information
doroudi committed Jan 30, 2025
1 parent 071e099 commit 9c7a004
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 169 deletions.
3 changes: 0 additions & 3 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ declare module 'vue' {
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
Expand All @@ -56,7 +55,6 @@ declare module 'vue' {
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
Notifications: typeof import('./components/Navbar/Notifications.vue')['default']
NPageHeader: typeof import('naive-ui')['NPageHeader']
Expand All @@ -66,7 +64,6 @@ declare module 'vue' {
NTag: typeof import('naive-ui')['NTag']
NTooltip: typeof import('naive-ui')['NTooltip']
NTreeSelect: typeof import('naive-ui')['NTreeSelect']
NUpload: typeof import('naive-ui')['NUpload']
OrderManagement: typeof import('./components/Orders/OrderManagement.vue')['default']
PersianIcon: typeof import('./components/CustomIcons/PersianIcon.vue')['default']
ProductsManagement: typeof import('./components/Products/ProductsManagement.vue')['default']
Expand Down
2 changes: 1 addition & 1 deletion src/components/Category/CategoryManagement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { renderDeleteActionButton, renderActionButton } = useRender()
const layout = useLayoutStore()
const { dialogPlacement } = storeToRefs(layout)
const { t } = useI18n()
const collapsed = ref(false)
const collapsed = ref(useWindowSize().width.value < 600)
const store = useCategoryStore()
const message = useMessage()
Expand Down
27 changes: 18 additions & 9 deletions src/components/Navbar/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,35 @@
import {
PanelLeftContract24Regular as CollapseIcon,
PanelLeftExpand20Regular as ExpandIcon,
Navigation20Regular as MenuIcon,
} from '@vicons/fluent'
import { storeToRefs } from 'pinia'
const layoutStore = useLayoutStore()
const { collapsed, isRtl } = storeToRefs(layoutStore)
const { collapsed, isRtl, mobileMode } = storeToRefs(layoutStore)
</script>

<template>
<n-page-header class="px-2 py-3 navbar">
<template #title>
<div class="flex items-center">
<n-button mx-2 size="small" quaternary circle :class="{ 'rotate-180': isRtl }" @click="layoutStore.toggleSidebar">
<template #icon>
<NIcon size="1.2rem">
<ExpandIcon v-if="collapsed" />
<CollapseIcon v-else />
</NIcon>
</template>
</n-button>
<div flex w-full justify-start items-center>
<img v-if="mobileMode" width="35" src="@/assets/images/logo.png" alt="logo" class="logo">

<n-button
mx-2 size="small" quaternary circle :class="{ 'rotate-180': isRtl }"
@click="layoutStore.toggleSidebar"
>
<template #icon>
<NIcon size="1.2rem">
<MenuIcon v-if="mobileMode" />
<ExpandIcon v-else-if="collapsed" />
<CollapseIcon v-else />
</NIcon>
</template>
</n-button>
</div>
<BreadCrumb />
</div>
</template>
Expand Down
74 changes: 45 additions & 29 deletions src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DoorArrowRight20Regular as AuthIcon,
CheckmarkStarburst16Regular as BrandsIcon,
Folder32Regular as CategoryIcon,
Dismiss20Filled as CloseIcon,
Color24Regular as ColorsIcon,
People28Regular as CustomersIcon,
Home32Regular as DashboardIcon,
Expand All @@ -22,14 +23,15 @@ import {
import { storeToRefs } from 'pinia'
const layoutStore = useLayoutStore()
const { collapsed, forceCollapsed } = storeToRefs(layoutStore)
const { collapsed, forceCollapsed, mobileMode, mobileMenuClosed } = storeToRefs(layoutStore)
const { t } = useI18n()
const { renderIcon, renderLabel } = useRender()
const isHovered = ref(false)
const effectiveCollapsed = computed(() => {
return (collapsed.value || forceCollapsed.value) && !isHovered.value
if (mobileMode.value)
return mobileMenuClosed.value
return (collapsed.value || forceCollapsed.value)
})
const menuOptions: MenuOption[] = [
Expand Down Expand Up @@ -137,31 +139,51 @@ const menuOptions: MenuOption[] = [
const route = useRoute()
const selectedMenuKey = ref('dashboard')
const menuRef = ref<MenuInst | null>(null)
onMounted(() => {
activateCurrentRoute()
})
function activateCurrentRoute() {
const keys = menuOptions.flatMap(m => m.children || m) as [{ key: string }]
if (keys !== undefined) {
selectedMenuKey.value = keys.find(k => k.key.toLowerCase() === route.name.toLowerCase())?.key ?? 'index'
menuRef.value?.showOption(selectedMenuKey.value)
}
}
const router = useRouter()
router.beforeEach(() => {
layoutStore.closeSidebar()
})
</script>

<template>
<n-layout-sider
:native-scrollbar="false" collapse-mode="width" :collapsed-width="64" :collapsed="effectiveCollapsed"
:class="{ collapsed: effectiveCollapsed }" @mouseenter="isHovered = true" @mouseleave="isHovered = false"
:native-scrollbar="false" collapse-mode="width" :collapsed-width="mobileMode ? 0 : 64"
:collapsed="effectiveCollapsed" :class="{ 'collapsed': effectiveCollapsed, 'mobile-mode': mobileMode }"
>
<div class="logo-container mb-4">
<div flex w-full justify-start items-center>
<img src="@/assets/images/logo.png" alt="logo" class="logo">
<h1 class="main-title">
{{ t('title') }}
</h1>
<div flex w-full justify-between items-center>
<div flex w-full justify-start items-center>
<img src="@/assets/images/logo.png" alt="logo" class="logo">
<h1 class="main-title">
{{ t('title') }}
</h1>
</div>

<n-button v-if="mobileMode" mx-2 size="small" tertiary circle @click="layoutStore.closeSidebar">
<template #icon>
<NIcon size="1.2rem">
<CloseIcon />
</NIcon>
</template>
</n-button>
</div>
</div>
<n-menu
ref="menuRef" v-model:value="selectedMenuKey" :collapsed-width="64" :collapsed-icon-size="22"
:options="menuOptions"
ref="menuRef" v-model:value="selectedMenuKey" :collapsed-width="mobileMode ? 0 : 64"
:collapsed-icon-size="mobileMode ? 30 : 20" :options="menuOptions"
/>
</n-layout-sider>
</template>
Expand Down Expand Up @@ -192,14 +214,22 @@ onMounted(() => {
max-width: 175px;
}
}
.mobile-mode {
max-width: 100% !important;
width: 100% !important;
}
.mobile-mode.collapsed {
max-width: 0 !important;
}
.collapsed {
.logo-container {
padding: 1.5rem 0.5rem 0.5rem .5rem;
}
.main-title {
display:none;
display: none;
}
}
Expand All @@ -212,8 +242,6 @@ onMounted(() => {
}
.collapsed {
max-width: 100px;
.p-button-label {
display: none;
}
Expand All @@ -230,25 +258,13 @@ onMounted(() => {
margin-left: 0.8rem;
margin-right: .5rem;
}
// .p-button {
// .p-button-label {
// text-align: right;
// }
// .p-button-icon-left {
// margin-right: 0;
// margin-left: 0.5rem;
// }
// }
}
.n-menu-item {
user-select: none;
}
.main-menu {
.active {
.p-button {
background: #f4f4f5;
Expand All @@ -265,7 +281,7 @@ onMounted(() => {
}
.separator {
border-bottom: solid 1px rgb(224, 224, 224);
border-bottom: solid 1px #f4f4f5;
margin-bottom: .5rem;
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,13 @@ app.config.globalProperties.$filters = {}
Object.values(import.meta.glob<any>('./common/filters/*.filter.ts', { eager: true, import: 'default' }))
.forEach(filters => Object.keys(filters).forEach(func => app.config.globalProperties.$filters[func] = filters[func]))

router.beforeEach((to, from, next) => {
router.beforeEach((to, _, next) => {
const { t } = i18n.global
let title = t('title')
if (to.meta.title)
title = `${t(`menu.${to.meta.title}`)} - ${title}`

document.title = title

next()
})

Expand Down
29 changes: 24 additions & 5 deletions src/store/layout.store.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { acceptHMRUpdate, defineStore } from 'pinia'

export const useLayoutStore = defineStore('layout', () => {
const { t, locale } = useI18n()
const collapsed = ref(false)
const forceCollapsed = ref(false)
const mobileMenuClosed = ref(true)
const mobileMode = ref(false)
const activeLanguage = ref('en')
const isRtl = ref(false)
const { t, locale } = useI18n()
const themeColor = ref('#00ad4c')
const isDark = ref(false)
const isWelcomeShown = ref(false)

const dialogPlacement = computed(() => isRtl.value ? 'left' : 'right')

watch(() => useWindowSize().width.value, (newValue: number) => {
forceCollapsed.value = newValue < 1000
})
forceCollapsed.value = newValue <= 1024
mobileMode.value = newValue < 600
}, { immediate: true })

function toggleSidebar() {
collapsed.value = !collapsed.value
if (mobileMode.value)
mobileMenuClosed.value = false
else
collapsed.value = !collapsed.value
}

function closeSidebar() {
mobileMenuClosed.value = true
}

function toggleTheme() {
Expand All @@ -38,20 +49,28 @@ export const useLayoutStore = defineStore('layout', () => {
isWelcomeShown.value = true
}

function $reset() {
mobileMode.value = false
}

return {
collapsed,
forceCollapsed,
mobileMode,
toggleSidebar,
toggleTheme,
isRtl,
activeLanguage,
changeLanguage,
forceCollapsed,
isDark,
setThemeColor,
themeColor,
dialogPlacement,
isWelcomeShown,
showWelcome,
closeSidebar,
$reset,
mobileMenuClosed,
}
}, { persist: true })

Expand Down
Loading

0 comments on commit 9c7a004

Please sign in to comment.