[FEAT] Improved menu

This commit is contained in:
2026-01-01 18:33:54 +01:00
parent 7e51896d00
commit 9e19237311
7 changed files with 253 additions and 92 deletions

View File

@@ -1,73 +1,42 @@
<template> <template>
<nav class="sticky top-0 z-50 w-full glass border-b border-gray-200/50 dark:border-gray-800/50"> <nav class="sticky top-0 z-50 w-full glass border-b border-gray-200/50 dark:border-gray-800/50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="w-full px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16"> <div class="flex items-center justify-between h-16">
<!-- Logo & Brand --> <!-- Left Side: Logo & Navigation -->
<div class="flex-shrink-0 flex items-center gap-3"> <div class="flex items-center gap-8">
<router-link to="/" class="flex items-center gap-2 group"> <NavbarLogo />
<div class="w-8 h-8 rounded-lg bg-gray-900 dark:bg-white flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform duration-200">
<i class="fas fa-layer-group text-white dark:text-gray-900 text-sm"></i>
</div>
<span class="font-bold text-xl tracking-tight text-gray-900 dark:text-white group-hover:opacity-80 transition-opacity"> Spritesheet generator </span>
</router-link>
</div>
<!-- Desktop Navigation --> <!-- Desktop Navigation -->
<div class="hidden md:flex items-center gap-6"> <div class="hidden md:flex items-center">
<div class="flex items-center gap-4 text-sm font-medium text-gray-600 dark:text-gray-400"> <NavbarLinks />
<router-link to="/" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">Generator</router-link>
<router-link to="/blog" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">Blog</router-link>
<router-link to="/about" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">About</router-link>
<router-link to="/faq" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">FAQ</router-link>
</div> </div>
</div>
<!-- Right Side: Actions & User -->
<div class="hidden md:flex items-center gap-5">
<!-- Auth & Projects -->
<template v-if="authStore.user">
<NavbarProjectActions
@open-save-modal="openSaveModal"
@open-project-list="isProjectListOpen = true"
/>
<div class="h-4 w-px bg-gray-200 dark:bg-gray-700"></div> <div class="h-4 w-px bg-gray-200 dark:bg-gray-700"></div>
<!-- Auth & Projects --> <!-- User Dropdown -->
<div v-if="authStore.user" class="flex items-center gap-3"> <NavbarUserMenu @open-auth-modal="isAuthModalOpen = true" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-300 truncate max-w-[150px]"> </template>
{{ authStore.user.email }}
</span>
<button @click="openSaveModal" class="btn btn-primary btn-sm">
<i class="fas fa-save mr-1.5"></i> Save
</button>
<button @click="isProjectListOpen = true" class="btn btn-secondary btn-sm">
<i class="fas fa-folder-open mr-1.5"></i> Projects
</button>
<button @click="authStore.logout()" class="text-sm text-gray-500 hover:text-red-500 transition-colors" title="Logout">
<i class="fas fa-sign-out-alt"></i>
</button>
</div>
<div v-else> <div v-else>
<button @click="isAuthModalOpen = true" class="btn btn-primary btn-sm"> <button @click="isAuthModalOpen = true" class="btn btn-primary btn-sm shadow-indigo-500/20 shadow-lg">
Login / Register Login / Register
</button> </button>
</div> </div>
<div class="h-4 w-px bg-gray-200 dark:bg-gray-700"></div> <div class="h-4 w-px bg-gray-200 dark:bg-gray-700"></div>
<div class="flex items-center gap-3"> <NavbarSocials @open-help="$emit('open-help')" />
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" rel="noopener noreferrer" class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors" title="Source Code">
<i class="fab fa-github text-lg"></i>
</a>
<a href="https://discord.gg/JTev3nzeDa" target="_blank" rel="noopener noreferrer" class="text-gray-500 hover:text-[#5865F2] dark:text-gray-400 dark:hover:text-[#5865F2] transition-colors" title="Discord Community">
<i class="fab fa-discord text-lg"></i>
</a>
<button @click="$emit('open-help')" class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400 transition-colors" title="Help">
<i class="fas fa-question-circle text-lg"></i>
</button>
<DarkModeToggle />
</div> </div>
</div>
<AuthModal :is-open="isAuthModalOpen" @close="isAuthModalOpen = false" />
<ProjectList :is-open="isProjectListOpen" @close="isProjectListOpen = false" @open-project="handleOpenProject" />
<SaveProjectModal
:is-open="isSaveProjectModalOpen"
:initial-name="projectStore.currentProject?.name"
@close="isSaveProjectModalOpen = false"
@save="handleSaveProject"
/>
<!-- Mobile Menu Button --> <!-- Mobile Menu Button -->
<div class="flex md:hidden"> <div class="flex md:hidden">
@@ -80,37 +49,16 @@
</div> </div>
<!-- Mobile Menu --> <!-- Mobile Menu -->
<div v-show="isMobileMenuOpen" class="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white/95 dark:bg-gray-950/95 backdrop-blur-xl"> <NavbarMobileMenu
<div class="px-2 pt-2 pb-3 space-y-1"> :is-open="isMobileMenuOpen"
<router-link to="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="isMobileMenuOpen = false">Generator</router-link> @close="isMobileMenuOpen = false"
<router-link to="/blog" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="isMobileMenuOpen = false">Blog</router-link> @open-help="$emit('open-help')"
<router-link to="/about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="isMobileMenuOpen = false">About</router-link> />
<router-link to="/faq" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="isMobileMenuOpen = false">FAQ</router-link>
<div class="border-t border-gray-200 dark:border-gray-800 my-2 pt-2 flex gap-4 px-3">
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white">
<i class="fab fa-github text-xl"></i>
</a>
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="text-gray-500 hover:text-[#5865F2] dark:text-gray-400 dark:hover:text-[#5865F2]">
<i class="fab fa-discord text-xl"></i>
</a>
<button
@click="
$emit('open-help');
isMobileMenuOpen = false;
"
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
>
<i class="fas fa-question-circle text-xl"></i>
</button>
</div>
</div>
</div>
</nav> </nav>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref, toRef } from 'vue';
import { RouterLink } from 'vue-router';
import DarkModeToggle from '../utilities/DarkModeToggle.vue'; import DarkModeToggle from '../utilities/DarkModeToggle.vue';
import { useAuthStore } from '@/stores/useAuthStore'; import { useAuthStore } from '@/stores/useAuthStore';
import AuthModal from '@/components/auth/AuthModal.vue'; import AuthModal from '@/components/auth/AuthModal.vue';
@@ -120,9 +68,16 @@
import { useSettingsStore } from '@/stores/useSettingsStore'; import { useSettingsStore } from '@/stores/useSettingsStore';
import { useExportLayers } from '@/composables/useExportLayers'; import { useExportLayers } from '@/composables/useExportLayers';
import { useLayers } from '@/composables/useLayers'; import { useLayers } from '@/composables/useLayers';
import { toRef } from 'vue';
defineEmits(['open-help']); // Removed 'open-project' since we handle it here // Sub-components
import NavbarLogo from './navbar/NavbarLogo.vue';
import NavbarLinks from './navbar/NavbarLinks.vue';
import NavbarProjectActions from './navbar/NavbarProjectActions.vue';
import NavbarUserMenu from './navbar/NavbarUserMenu.vue';
import NavbarSocials from './navbar/NavbarSocials.vue';
import NavbarMobileMenu from './navbar/NavbarMobileMenu.vue';
defineEmits(['open-help']);
const isMobileMenuOpen = ref(false); const isMobileMenuOpen = ref(false);
const isAuthModalOpen = ref(false); const isAuthModalOpen = ref(false);
@@ -165,14 +120,8 @@
try { try {
const data = await generateProjectJSON(); const data = await generateProjectJSON();
if (projectStore.currentProject && projectStore.currentProject.name === name) { // Simple check, ideally check ID but UI only exposes name edit on save for new projects or overwrite? if (projectStore.currentProject && projectStore.currentProject.name === name) {
// Actually SaveProjectModal allows editing name.
// If it's the same project, we update.
await projectStore.updateProject(projectStore.currentProject.id, data); await projectStore.updateProject(projectStore.currentProject.id, data);
// Update name if changed? updateProject signature might need name.
// current implementation of updateProject only updates data.
// To update name we need to change store or backend.
// For now, let's just update data.
} else { } else {
await projectStore.createProject(name, data); await projectStore.createProject(name, data);
} }
@@ -182,3 +131,4 @@
} }
}; };
</script> </script>

View File

@@ -0,0 +1,12 @@
<template>
<div class="flex items-center gap-4 text-sm font-medium text-gray-600 dark:text-gray-400">
<router-link to="/" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">Generator</router-link>
<router-link to="/blog" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">Blog</router-link>
<router-link to="/about" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">About</router-link>
<router-link to="/faq" class="hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors">FAQ</router-link>
</div>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router';
</script>

View File

@@ -0,0 +1,14 @@
<template>
<div class="flex-shrink-0 flex items-center gap-3">
<router-link to="/" class="flex items-center gap-2 group">
<div class="w-8 h-8 rounded-lg bg-gray-900 dark:bg-white flex items-center justify-center shadow-lg group-hover:scale-105 transition-transform duration-200">
<i class="fas fa-layer-group text-white dark:text-gray-900 text-sm"></i>
</div>
<span class="font-bold text-xl tracking-tight text-gray-900 dark:text-white group-hover:opacity-80 transition-opacity"> Spritesheet generator </span>
</router-link>
</div>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router';
</script>

View File

@@ -0,0 +1,46 @@
<template>
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="transform opacity-0 -translate-y-2"
enter-to-class="transform opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-150"
leave-from-class="transform opacity-100 translate-y-0"
leave-to-class="transform opacity-0 -translate-y-2"
>
<div v-show="isOpen" class="md:hidden border-t border-gray-200 dark:border-gray-800 bg-white/95 dark:bg-gray-950/95 backdrop-blur-xl absolute top-16 left-0 w-full z-40 shadow-lg">
<div class="px-2 pt-2 pb-3 space-y-1">
<router-link to="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="$emit('close')">Generator</router-link>
<router-link to="/blog" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="$emit('close')">Blog</router-link>
<router-link to="/about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="$emit('close')">About</router-link>
<router-link to="/faq" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors" @click="$emit('close')">FAQ</router-link>
<div class="border-t border-gray-200 dark:border-gray-800 my-2 pt-2 flex gap-4 px-3">
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white">
<i class="fab fa-github text-xl"></i>
</a>
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="text-gray-500 hover:text-[#5865F2] dark:text-gray-400 dark:hover:text-[#5865F2]">
<i class="fab fa-discord text-xl"></i>
</a>
<button
@click="
$emit('open-help');
$emit('close');
"
class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400"
>
<i class="fas fa-question-circle text-xl"></i>
</button>
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router';
defineProps<{
isOpen: boolean;
}>();
defineEmits(['close', 'open-help']);
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div class="flex items-center gap-3 mr-2">
<div class="flex items-center gap-2 group">
<div class="text-right hidden lg:block">
<p class="text-[10px] uppercase tracking-wider font-bold text-gray-400 dark:text-gray-500 mb-[-2px]">Current Project</p>
<button
@click="$emit('open-save-modal')"
class="text-sm font-bold text-gray-800 dark:text-gray-200 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors truncate max-w-[160px]"
title="Rename Project"
>
{{ projectStore.currentProject?.name || 'Untitled Project' }}
</button>
</div>
<!-- Mobile/Tablet simplified view -->
<button
@click="$emit('open-save-modal')"
class="lg:hidden text-sm font-bold text-gray-800 dark:text-gray-200 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors truncate max-w-[120px]"
>
{{ projectStore.currentProject?.name || 'Untitled' }}
</button>
</div>
<div class="h-8 w-px bg-gray-200 dark:bg-gray-700 mx-1"></div>
<div class="flex items-center gap-1">
<button @click="$emit('open-save-modal')" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-500 hover:text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/30 transition-all" title="Save Project">
<i class="fas fa-save"></i>
</button>
<button @click="$emit('open-project-list')" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-500 hover:text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/30 transition-all" title="My Projects">
<i class="fas fa-folder-open"></i>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useProjectStore } from '@/stores/useProjectStore';
defineEmits(['open-save-modal', 'open-project-list']);
const projectStore = useProjectStore();
</script>

View File

@@ -0,0 +1,20 @@
<template>
<div class="flex items-center gap-3">
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" rel="noopener noreferrer" class="text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors" title="Source Code">
<i class="fab fa-github text-lg"></i>
</a>
<a href="https://discord.gg/JTev3nzeDa" target="_blank" rel="noopener noreferrer" class="text-gray-500 hover:text-[#5865F2] dark:text-gray-400 dark:hover:text-[#5865F2] transition-colors" title="Discord Community">
<i class="fab fa-discord text-lg"></i>
</a>
<button @click="$emit('open-help')" class="text-gray-500 hover:text-indigo-600 dark:text-gray-400 dark:hover:text-indigo-400 transition-colors" title="Help">
<i class="fas fa-question-circle text-lg"></i>
</button>
<DarkModeToggle />
</div>
</template>
<script setup lang="ts">
import DarkModeToggle from '../../utilities/DarkModeToggle.vue';
defineEmits(['open-help']);
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div v-if="authStore.user" class="flex items-center gap-4">
<div class="relative" ref="userMenuRef">
<button
@click="isUserMenuOpen = !isUserMenuOpen"
class="w-9 h-9 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 text-white font-bold text-sm flex items-center justify-center shadow-md hover:shadow-lg transition-all border-2 border-white dark:border-gray-800"
>
{{ userInitials }}
</button>
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<div v-if="isUserMenuOpen" class="absolute right-0 mt-2 w-56 bg-white dark:bg-gray-900 rounded-xl shadow-xl border border-gray-200 dark:border-gray-800 overflow-hidden z-50">
<div class="p-4 border-b border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/50">
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase font-semibold tracking-wider mb-1">Signed in as</p>
<p class="text-sm font-medium text-gray-900 dark:text-white truncate" :title="authStore.user.email">{{ authStore.user.email }}</p>
</div>
<div class="p-1">
<button
@click="handleLogout"
class="w-full text-left px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600 dark:hover:text-red-400 rounded-lg transition-colors flex items-center gap-2"
>
<i class="fas fa-sign-out-alt"></i> Sign out
</button>
</div>
</div>
</transition>
</div>
</div>
<div v-else>
<button @click="$emit('open-auth-modal')" class="btn btn-primary btn-sm shadow-indigo-500/20 shadow-lg">
Login / Register
</button>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useAuthStore } from '@/stores/useAuthStore';
defineEmits(['open-auth-modal']);
const authStore = useAuthStore();
const isUserMenuOpen = ref(false);
const userMenuRef = ref<HTMLElement | null>(null);
const userInitials = computed(() => {
if (!authStore.user?.email) return '??';
return authStore.user.email.substring(0, 2).toUpperCase();
});
const closeUserMenu = (e: MouseEvent) => {
if (userMenuRef.value && !userMenuRef.value.contains(e.target as Node)) {
isUserMenuOpen.value = false;
}
};
const handleLogout = () => {
isUserMenuOpen.value = false;
authStore.logout();
};
onMounted(() => {
document.addEventListener('click', closeUserMenu);
});
onUnmounted(() => {
document.removeEventListener('click', closeUserMenu);
});
</script>