add menu structure and off canvas on the right side

This commit is contained in:
Tilman Behrend 2025-06-02 01:03:35 +02:00
parent 9c48a17152
commit 734255aad2
3 changed files with 652 additions and 77 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
/.history

View file

@ -4,6 +4,7 @@ Eine kurze Beschreibung, was dieses Projekt macht (ein oder zwei Sätze).
## Inhaltsverzeichnis ## Inhaltsverzeichnis
- [Start des Projekts](#start)
- [Beschreibung](#beschreibung) - [Beschreibung](#beschreibung)
- [Technologie-Stack](#technologie-stack) - [Technologie-Stack](#technologie-stack)
- [Installation](#installation) - [Installation](#installation)
@ -11,6 +12,13 @@ Eine kurze Beschreibung, was dieses Projekt macht (ein oder zwei Sätze).
- [Mitwirken](#mitwirken) - [Mitwirken](#mitwirken)
- [Lizenz](#lizenz) - [Lizenz](#lizenz)
## Start
1. cd frontend
2. npm install
3. npm run dev
4. npm run build
## Beschreibung ## Beschreibung
Eine ausführlichere Beschreibung des Projekts. Was ist das Ziel? Welche Probleme löst es? Was sind die Hauptmerkmale? Eine ausführlichere Beschreibung des Projekts. Was ist das Ziel? Welche Probleme löst es? Was sind die Hauptmerkmale?
@ -85,4 +93,4 @@ Erkläre, wie andere zum Projekt beitragen können. Gibt es Richtlinien für Pul
Gib an, unter welcher Lizenz das Projekt veröffentlicht wird. Zum Beispiel: Gib an, unter welcher Lizenz das Projekt veröffentlicht wird. Zum Beispiel:
Dieses Projekt ist unter der MIT-Lizenz lizenziert - siehe die [LICENSE.md](LICENSE.md)-Datei für Details (falls vorhanden). Dieses Projekt ist unter der MIT-Lizenz lizenziert - siehe die [LICENSE.md](LICENSE.md)-Datei für Details (falls vorhanden).

View file

@ -2,41 +2,495 @@
<q-layout view="lHh Lpr lFf" class="dynamic-gradient-bg"> <q-layout view="lHh Lpr lFf" class="dynamic-gradient-bg">
<q-header elevated> <q-header elevated>
<q-toolbar> <q-toolbar>
<q-toolbar-title>
MIIUS <!-- Me You Us -->
</q-toolbar-title>
<q-btn <q-btn
flat flat
dense dense
round round
icon="menu" icon="menu"
aria-label="Menu" aria-label="Menu"
@click="toggleLeftDrawer" @click="toggleDrawer"
/> />
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
<!-- Off-Canvas Drawer -->
<q-drawer <q-drawer
v-model="leftDrawerOpen" v-model="drawer"
show-if-above show-if-above
bordered side="right"
:width="280"
:breakpoint="768"
class="bg-grey-1"
style="overflow: hidden;"
> >
<q-list> <!-- Sliding Menu Container -->
<q-item-label <div class="menu-container" :style="{ transform: `translateX(${slideOffset}px)` }">
header
> <!-- Main Menu Panel -->
Essential Links <div class="menu-panel">
</q-item-label>
<EssentialLink <!-- Main Menu List -->
v-for="link in linksList" <q-list>
:key="link.title" <q-item clickable v-ripple @click="navigateTo('life-wave')">
v-bind="link" <q-item-section avatar>
/> <q-icon name="line_axis" />
</q-list> </q-item-section>
<q-item-section>
<q-item-label>Life Wave</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('new-entry')">
<q-item-section avatar>
<q-icon name="add_circle" />
</q-item-section>
<q-item-section>
<q-item-label>New entry</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('my-people', 'My People')">
<q-item-section avatar>
<q-icon name="people" />
</q-item-section>
<q-item-section>
<q-item-label>My People</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('messages', 'Messages')">
<q-item-section avatar>
<q-icon name="mail" />
</q-item-section>
<q-item-section>
<q-item-label>Messages</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-ripple @click="openSubmenu('profile', 'Profile')">
<q-item-section avatar>
<q-icon name="person" />
</q-item-section>
<q-item-section>
<q-item-label>Profile</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('account', 'Account')">
<q-item-section avatar>
<q-icon name="account_circle" />
</q-item-section>
<q-item-section>
<q-item-label>Account</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('login', 'Login')">
<q-item-section avatar>
<q-icon name="login" />
</q-item-section>
<q-item-section>
<q-item-label>Login</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('sign-up')">
<q-item-section avatar>
<q-icon name="person_add" />
</q-item-section>
<q-item-section>
<q-item-label>Sign Up</q-item-label>
</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-ripple @click="openSubmenu('support', 'Need Help?')">
<q-item-section avatar>
<q-icon name="help" />
</q-item-section>
<q-item-section>
<q-item-label>Need Help?</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('legal', 'Legal Stuff')">
<q-item-section avatar>
<q-icon name="gavel" />
</q-item-section>
<q-item-section>
<q-item-label>Legal Stuff</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
</q-list>
</div>
<!-- Second Level Menu Panel -->
<div class="menu-panel">
<div class="q-pa-md bg-secondary text-white">
<q-btn
flat
round
icon="arrow_back"
@click="goBack"
class="q-mr-sm"
size="sm"
/>
<span class="text-h6">{{ currentSubmenuTitle }}</span>
</div>
<!-- My People Submenu -->
<q-list v-if="currentSubmenu === 'my-people'">
<q-item clickable v-ripple @click="navigateTo('relatives')">
<q-item-section avatar>
<q-icon name="family_restroom" />
</q-item-section>
<q-item-section>
<q-item-label>Relatives</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('friends')">
<q-item-section avatar>
<q-icon name="group" />
</q-item-section>
<q-item-section>
<q-item-label>Friends</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Messages Submenu -->
<q-list v-if="currentSubmenu === 'messages'">
<q-item clickable v-ripple @click="navigateTo('notifications')">
<q-item-section avatar>
<q-icon name="notifications" />
</q-item-section>
<q-item-section>
<q-item-label>Notifications</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('message-center', 'Message Center')">
<q-item-section avatar>
<q-icon name="mail" />
</q-item-section>
<q-item-section>
<q-item-label>Messages</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('groups')">
<q-item-section avatar>
<q-icon name="group_work" />
</q-item-section>
<q-item-section>
<q-item-label>Groups</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
</q-list>
<!-- Profile Submenu -->
<q-list v-if="currentSubmenu === 'profile'">
<q-item clickable v-ripple @click="navigateTo('personal-data')">
<q-item-section avatar>
<q-icon name="badge" />
</q-item-section>
<q-item-section>
<q-item-label>Personal Data</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('portrait', 'Portrait')">
<q-item-section avatar>
<q-icon name="portrait" />
</q-item-section>
<q-item-section>
<q-item-label>Portrait</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('voice', 'Voice')">
<q-item-section avatar>
<q-icon name="record_voice_over" />
</q-item-section>
<q-item-section>
<q-item-label>Voice</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
</q-list>
<!-- Account Submenu -->
<q-list v-if="currentSubmenu === 'account'">
<q-item clickable v-ripple @click="openSubmenu('plan', 'Plan')">
<q-item-section avatar>
<q-icon name="subscriptions" />
</q-item-section>
<q-item-section>
<q-item-label>Plan</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="openSubmenu('billing', 'Billing')">
<q-item-section avatar>
<q-icon name="payment" />
</q-item-section>
<q-item-section>
<q-item-label>Billing</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
</q-list>
<!-- Login Submenu -->
<q-list v-if="currentSubmenu === 'login'">
<q-item clickable v-ripple @click="navigateTo('password-reset')">
<q-item-section avatar>
<q-icon name="lock_reset" />
</q-item-section>
<q-item-section>
<q-item-label>Password reset</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="logout">
<q-item-section avatar>
<q-icon name="logout" />
</q-item-section>
<q-item-section>
<q-item-label>Logout</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Support Submenu -->
<q-list v-if="currentSubmenu === 'support'">
<q-item clickable v-ripple @click="navigateTo('video-tutorials')">
<q-item-section avatar>
<q-icon name="play_circle" />
</q-item-section>
<q-item-section>
<q-item-label>Video tutorials</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('knowledge-base')">
<q-item-section avatar>
<q-icon name="library_books" />
</q-item-section>
<q-item-section>
<q-item-label>Knowledge base</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('faq')">
<q-item-section avatar>
<q-icon name="quiz" />
</q-item-section>
<q-item-section>
<q-item-label>FAQ</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('chatbot')">
<q-item-section avatar>
<q-icon name="smart_toy" />
</q-item-section>
<q-item-section>
<q-item-label>Chatbot</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Legal Submenu -->
<q-list v-if="currentSubmenu === 'legal'">
<q-item clickable v-ripple @click="navigateTo('legal-terms')">
<q-item-section avatar>
<q-icon name="article" />
</q-item-section>
<q-item-section>
<q-item-label>Legal Terms</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('privacy-policy')">
<q-item-section avatar>
<q-icon name="privacy_tip" />
</q-item-section>
<q-item-section>
<q-item-label>Privacy Policy</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
<!-- Third Level Menu Panel -->
<div class="menu-panel">
<div class="q-pa-md bg-accent text-white">
<q-btn
flat
round
icon="arrow_back"
@click="goBack"
class="q-mr-sm"
size="sm"
/>
<span class="text-h6">{{ currentSubmenuTitle }}</span>
</div>
<!-- Message Center Submenu -->
<q-list v-if="currentSubmenu === 'message-center'">
<q-item clickable v-ripple @click="navigateTo('new-message')">
<q-item-section avatar>
<q-icon name="add" />
</q-item-section>
<q-item-section>
<q-item-label>Chat</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('direct-messages')">
<q-item-section avatar>
<q-icon name="message" />
</q-item-section>
<q-item-section>
<q-item-label>Direct Messages</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('group-messages')">
<q-item-section avatar>
<q-icon name="group" />
</q-item-section>
<q-item-section>
<q-item-label>Group Messages</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('message-requests')">
<q-item-section avatar>
<q-icon name="request_page" />
</q-item-section>
<q-item-section>
<q-item-label>Message Requests</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" />
</q-item-section>
</q-item>
</q-list>
<!-- Portrait Submenu -->
<q-list v-if="currentSubmenu === 'portrait'">
<q-item clickable v-ripple @click="navigateTo('animated-avatar')">
<q-item-section avatar>
<q-icon name="face" />
</q-item-section>
<q-item-section>
<q-item-label>Animated Avatar</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Voice Submenu -->
<q-list v-if="currentSubmenu === 'voice'">
<q-item clickable v-ripple @click="navigateTo('voice-cloning')">
<q-item-section avatar>
<q-icon name="content_copy" />
</q-item-section>
<q-item-section>
<q-item-label>Voice cloning</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Plan Submenu -->
<q-list v-if="currentSubmenu === 'plan'">
<q-item clickable v-ripple @click="navigateTo('upgrade')">
<q-item-section avatar>
<q-icon name="upgrade" />
</q-item-section>
<q-item-section>
<q-item-label>Upgrade</q-item-label>
</q-item-section>
</q-item>
</q-list>
<!-- Billing Submenu -->
<q-list v-if="currentSubmenu === 'billing'">
<q-item clickable v-ripple @click="navigateTo('billing-information')">
<q-item-section avatar>
<q-icon name="credit_card" />
</q-item-section>
<q-item-section>
<q-item-label>Billing Information</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-ripple @click="navigateTo('invoices')">
<q-item-section avatar>
<q-icon name="receipt" />
</q-item-section>
<q-item-section>
<q-item-label>Invoices</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>
@ -45,73 +499,123 @@
<q-footer elevated class="bg-white text-black font"> <q-footer elevated class="bg-white text-black font">
<q-tabs align="justify" dense> <q-tabs align="justify" dense>
<q-route-tab to="/" icon="home" label="Home" />
<q-route-tab to="/page2" icon="search" label="Suche" /> <q-route-tab to="/" icon="home" />
<q-route-tab to="/page3" icon="settings" label="Einstellungen" /> <q-route-tab to="/people" icon="people" />
<!-- Füge hier weitere Tabs nach Bedarf hinzu --> <q-route-tab to="/entry" icon="add_circle_outline" />
<q-route-tab to="/messages" icon="mail" />
<q-route-tab to="/profile" icon="person"/>
</q-tabs> </q-tabs>
</q-footer> </q-footer>
</q-layout> </q-layout>
</template> </template>
<script setup> <script>
import { ref } from 'vue' import { ref, computed } from 'vue'
import EssentialLink from 'components/EssentialLink.vue'
const linksList = [ export default {
{ name: 'MultiLevelSlidingMenu',
title: 'Docs', setup() {
caption: 'quasar.dev', const drawer = ref(false)
icon: 'school', const currentRoute = ref('home')
link: 'https://quasar.dev' const menuLevel = ref(0)
}, const currentSubmenu = ref('')
{ const currentSubmenuTitle = ref('')
title: 'Github', const menuHistory = ref([])
caption: 'github.com/quasarframework',
icon: 'code', const slideOffset = computed(() => {
link: 'https://github.com/quasarframework' return menuLevel.value * -280 // Each level slides 280px (drawer width) to the left
}, })
{
title: 'Discord Chat Channel', const toggleDrawer = () => {
caption: 'chat.quasar.dev', drawer.value = !drawer.value
icon: 'chat', // Reset menu to main level when drawer is opened
link: 'https://chat.quasar.dev' if (drawer.value) {
}, resetMenu()
{ }
title: 'Forum', }
caption: 'forum.quasar.dev',
icon: 'record_voice_over', const resetMenu = () => {
link: 'https://forum.quasar.dev' menuLevel.value = 0
}, currentSubmenu.value = ''
{ currentSubmenuTitle.value = ''
title: 'Twitter', menuHistory.value = []
caption: '@quasarframework', }
icon: 'rss_feed',
link: 'https://twitter.quasar.dev' const openSubmenu = (submenuKey, title) => {
}, console.log(`Opening submenu: ${submenuKey} - ${title}`)
{
title: 'Facebook', // Save current state to history
caption: '@QuasarFramework', menuHistory.value.push({
icon: 'public', level: menuLevel.value,
link: 'https://facebook.quasar.dev' submenu: currentSubmenu.value,
}, title: currentSubmenuTitle.value
{ })
title: 'Quasar Awesome',
caption: 'Community Quasar projects', // Navigate to new submenu
icon: 'favorite', menuLevel.value += 1
link: 'https://awesome.quasar.dev' currentSubmenu.value = submenuKey
currentSubmenuTitle.value = title
console.log(`New menu level: ${menuLevel.value}, submenu: ${currentSubmenu.value}`)
}
const goBack = () => {
console.log('Going back, history length:', menuHistory.value.length)
if (menuHistory.value.length > 0) {
const previousState = menuHistory.value.pop()
menuLevel.value = previousState.level
currentSubmenu.value = previousState.submenu
currentSubmenuTitle.value = previousState.title
console.log(`Back to level: ${menuLevel.value}, submenu: ${currentSubmenu.value}`)
} else {
resetMenu()
}
}
const navigateTo = (route) => {
console.log(`Navigating to: ${route}`)
currentRoute.value = route
// In a real app, you would use Vue Router here
// this.$router.push('/' + route)
// Close drawer and reset menu on mobile after navigation
if (window.innerWidth < 768) {
drawer.value = false
resetMenu()
}
}
const logout = () => {
console.log('Logging out...')
currentRoute.value = 'login'
drawer.value = false
resetMenu()
}
return {
drawer,
currentRoute,
menuLevel,
currentSubmenu,
currentSubmenuTitle,
menuHistory,
slideOffset,
toggleDrawer,
openSubmenu,
goBack,
navigateTo,
logout
}
} }
]
const leftDrawerOpen = ref(false)
function toggleLeftDrawer () {
leftDrawerOpen.value = !leftDrawerOpen.value
} }
</script> </script>
<style> <style scoped>
.dynamic-gradient-bg { .dynamic-gradient-bg {
/* background: linear-gradient(45deg, #541ba2, #d4890b, #fc1c98); /* background: linear-gradient(45deg, #541ba2, #d4890b, #fc1c98);
background: linear-gradient(45deg, #7317c3, #d2348b, #31e6cb); */ background: linear-gradient(45deg, #7317c3, #d2348b, #31e6cb); */
@ -141,4 +645,66 @@ function toggleLeftDrawer () {
.q-footer .q-tab__label { .q-footer .q-tab__label {
font-size: 0.7rem; /* Passe diesen Wert nach Bedarf an */ font-size: 0.7rem; /* Passe diesen Wert nach Bedarf an */
} }
/* Custom styles for the drawer */
.q-drawer {
box-shadow: -2px 0 12px rgba(0, 0, 0, 0.1);
}
/* Menu container for sliding panels */
.menu-container {
display: flex;
width: calc(100% * 3); /* Width for 3 levels */
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}
/* Individual menu panel */
.menu-panel {
width: 280px;
flex-shrink: 0;
height: 100vh;
overflow-y: auto;
}
/* Hover effects for menu items */
.q-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
/* Chevron icon styling */
.q-item-section--side .q-icon {
color: rgba(0, 0, 0, 0.54);
}
/* Back button styling */
.q-btn[round] {
min-height: 32px;
min-width: 32px;
}
/* Header gradient for different levels */
.bg-secondary {
background: linear-gradient(135deg, #9c27b0, #673ab7) !important;
}
.bg-accent {
background: linear-gradient(135deg, #ff5722, #f44336) !important;
}
/* Smooth scrolling for menu panels */
.menu-panel {
scroll-behavior: smooth;
}
/* Mobile optimizations */
@media (max-width: 768px) {
.menu-container {
width: calc(100vw * 3);
}
.menu-panel {
width: 100vw;
}
}
</style> </style>