diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue
index 799fa027..38108b7b 100644
--- a/src/components/NavMenu.vue
+++ b/src/components/NavMenu.vue
@@ -19,18 +19,48 @@
>
-
-
+
-
-
- {{ t(item.tooltip) }}
+ placement="right-start"
+ trigger="hover"
+ :hide-after="0"
+ :show-arrow="false"
+ :offset="0"
+ :width="navPopoverWidth"
+ transition="null"
+ @before-enter="handleSubMenuBeforeEnter()"
+ :popper-style="navPopoverStyle"
+ popper-class="nav-menu-popover-popper">
+
+
+
+
+
+ {{ t(item.tooltip) }}
+
+
-
+
+
@@ -177,20 +207,80 @@
const router = useRouter();
const navItems = [
- { index: 'feed', icon: 'ri-rss-line', tooltip: 'nav_tooltip.feed' },
- { index: 'friend-location', icon: 'ri-user-location-line', tooltip: 'nav_tooltip.friend_location' },
- { index: 'game-log', icon: 'ri-history-line', tooltip: 'nav_tooltip.game_log' },
- { index: 'player-list', icon: 'ri-group-3-line', tooltip: 'nav_tooltip.player_list' },
- { index: 'search', icon: 'ri-search-line', tooltip: 'nav_tooltip.search' },
- { index: 'favorites', icon: 'ri-star-line', tooltip: 'nav_tooltip.favorites' },
- { index: 'friend-log', icon: 'ri-contacts-line', tooltip: 'nav_tooltip.friend_log' },
- { index: 'moderation', icon: 'ri-user-forbid-line', tooltip: 'nav_tooltip.moderation' },
- { index: 'notification', icon: 'ri-notification-2-line', tooltip: 'nav_tooltip.notification' },
- { index: 'friend-list', icon: 'ri-contacts-book-3-line', tooltip: 'nav_tooltip.friend_list' },
- { index: 'charts', icon: 'ri-bar-chart-line', tooltip: 'nav_tooltip.charts' },
- { index: 'tools', icon: 'ri-tools-line', tooltip: 'nav_tooltip.tools' }
+ {
+ index: 'feed',
+ icon: 'ri-rss-line',
+ tooltip: 'nav_tooltip.feed'
+ },
+ {
+ index: 'friend-location',
+ icon: 'ri-user-location-line',
+ tooltip: 'nav_tooltip.friend_location'
+ },
+ {
+ index: 'game-log',
+ icon: 'ri-history-line',
+ tooltip: 'nav_tooltip.game_log'
+ },
+ {
+ index: 'player-list',
+ icon: 'ri-group-3-line',
+ tooltip: 'nav_tooltip.player_list'
+ },
+ {
+ index: 'search',
+ icon: 'ri-search-line',
+ tooltip: 'nav_tooltip.search'
+ },
+ {
+ index: 'favorites',
+ icon: 'ri-star-line',
+ tooltip: 'nav_tooltip.favorites'
+ },
+ {
+ index: 'social',
+ icon: 'ri-group-line',
+ tooltip: '',
+ title: 'nav_tooltip.social',
+ entries: [
+ { label: 'nav_tooltip.friend_log', path: '/friend-log' },
+ { label: 'nav_tooltip.friend_list', path: '/friend-list' },
+ { label: 'nav_tooltip.moderation', path: '/moderation' }
+ ]
+ },
+
+ {
+ index: 'notification',
+ icon: 'ri-notification-2-line',
+ tooltip: 'nav_tooltip.notification'
+ },
+
+ {
+ index: 'charts',
+ icon: 'ri-bar-chart-line',
+ tooltip: 'nav_tooltip.charts'
+ },
+ {
+ index: 'tools',
+ icon: 'ri-tools-line',
+ tooltip: 'nav_tooltip.tools'
+ }
];
+ const navPopoverWidth = 250;
+ const navPopoverStyle = {
+ zIndex: 500,
+ borderRadius: '0',
+ border: '1px solid var(--el-border-color)',
+ borderLeft: 'none',
+ borderBottom: 'none',
+ borderTop: 'none',
+ boxShadow: '0 8px 20px rgba(0,0,0,0.05)',
+ padding: '0',
+ background: 'var(--el-bg-color)',
+ height: '100vh'
+ };
+
const VRCXUpdaterStore = useVRCXUpdaterStore();
const { pendingVRCXUpdate, pendingVRCXInstall, updateInProgress, updateProgress, branch, appVersion } =
storeToRefs(VRCXUpdaterStore);
@@ -207,6 +297,7 @@
const settingsMenuVisible = ref(false);
const themeMenuVisible = ref(false);
const supportMenuVisible = ref(false);
+ const navMenuRef = ref(null);
const version = computed(() => appVersion.value?.split('VRCX ')?.[1] || '-');
const vrcxLogo = new URL('../../images/VRCX.png', import.meta.url).href;
@@ -237,20 +328,6 @@
openExternalLink('https://github.com/vrcx-team/VRCX');
};
- watch(settingsMenuVisible, (visible) => {
- if (visible) {
- supportMenuVisible.value = false;
- } else {
- themeMenuVisible.value = false;
- }
- });
-
- watch(supportMenuVisible, (visible) => {
- if (visible) {
- settingsMenuVisible.value = false;
- }
- });
-
const supportLinks = {
wiki: 'https://github.com/vrcx-team/VRCX/wiki',
github: 'https://github.com/vrcx-team/VRCX',
@@ -265,6 +342,33 @@
}
};
+ const handleSubmenuClick = (path, index) => {
+ if (path) {
+ router.push(path);
+ navMenuRef.value?.updateActiveIndex(index);
+ }
+ };
+
+ const handleSubMenuBeforeEnter = () => {
+ settingsMenuVisible.value = false;
+ supportMenuVisible.value = false;
+ themeMenuVisible.value = false;
+ };
+
+ watch(settingsMenuVisible, (visible) => {
+ if (visible) {
+ supportMenuVisible.value = false;
+ } else {
+ themeMenuVisible.value = false;
+ }
+ });
+
+ watch(supportMenuVisible, (visible) => {
+ if (visible) {
+ settingsMenuVisible.value = false;
+ }
+ });
+
onMounted(() => {
if (!sentryErrorReporting.value) return;
const feedback = Sentry.getFeedback();
@@ -283,6 +387,10 @@
flex-direction: column;
align-items: center;
justify-content: space-between;
+ z-index: 600;
+ background-color: var(--el-bg-color);
+ border-right: 1px solid var(--el-border-color);
+ box-shadow: none;
.el-menu {
background: 0;
border: 0;
@@ -321,6 +429,86 @@
}
}
+ .nav-menu-popover {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-width: 240px;
+ background-color: var(--el-bg-color);
+ border-left: 1px solid var(--el-border-color);
+ overflow: hidden;
+
+ .nav-menu-popover__header {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ min-height: 52px;
+ padding: 0 20px;
+ border-bottom: 1px solid var(--el-border-color-light, var(--el-border-color));
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--el-text-color-primary);
+ }
+
+ .nav-menu-popover__header i {
+ font-size: 18px;
+ color: var(--el-color-primary);
+ }
+
+ .nav-menu-popover__menu {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 6px;
+ padding: 12px 12px 16px;
+ overflow-y: auto;
+ scrollbar-width: thin;
+ }
+
+ .nav-menu-popover__menu::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ .nav-menu-popover__menu::-webkit-scrollbar-thumb {
+ background-color: rgba(0, 0, 0, 0.18);
+ border-radius: 3px;
+ }
+
+ .nav-menu-popover__menu::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ .nav-menu-popover__menu-item {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 4px;
+ width: 100%;
+ padding: 10px 12px;
+ border: none;
+ background: transparent;
+ text-align: left;
+ color: var(--el-text-color-primary);
+ font-size: 13px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background-color var(--el-transition-duration);
+ }
+
+ .nav-menu-popover__menu-item:hover {
+ background-color: var(--el-menu-hover-bg-color);
+ }
+
+ .nav-menu-popover__menu-item:focus-visible {
+ outline: 2px solid var(--el-color-primary);
+ outline-offset: 2px;
+ }
+
+ .nav-menu-popover__menu-label {
+ font-weight: 600;
+ }
+ }
+
.nav-menu-settings {
display: flex;
flex-direction: column;
diff --git a/src/localization/en/en.json b/src/localization/en/en.json
index 0acc8a4a..2d4dc8a7 100644
--- a/src/localization/en/en.json
+++ b/src/localization/en/en.json
@@ -8,6 +8,7 @@
"player_list": "Player List",
"search": "Search",
"favorites": "Favorites",
+ "social": "Social",
"friend_log": "Friend Log",
"moderation": "Moderation",
"notification": "Notification",
diff --git a/src/plugin/router.js b/src/plugin/router.js
index 76f28e86..825514ea 100644
--- a/src/plugin/router.js
+++ b/src/plugin/router.js
@@ -50,3 +50,10 @@ export const router = createRouter({
export function initRouter(app) {
app.use(router);
}
+
+router.beforeEach((to, from) => {
+ if (to.path === '/social') {
+ return false;
+ }
+ return true;
+});