mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 06:23:40 +02:00
feat: unify navigation with shared PillNavigation component
- Add PillNavigation and PillDropdown to @manacore/shared-ui - Features: glassmorphism design, horizontal/sidebar toggle, collapsible FAB - Configurable per app: nav items, primary color, logo, language switcher - Integrate into ManaCore, ManaDeck, and Memoro web apps - Remove old local navigation components (Sidebar, Navbar, PillNavigation) - Add navigation stores for persistent user preferences Apps now share consistent navigation UX with app-specific branding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cacbd61fe4
commit
bd869dfe09
13 changed files with 984 additions and 1520 deletions
|
|
@ -1,83 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { authStore } from '$lib/stores/authStore.svelte';
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Decks', href: '/decks', icon: '📚' },
|
||||
{ label: 'Explore', href: '/explore', icon: '🔍' },
|
||||
{ label: 'Progress', href: '/progress', icon: '📊' },
|
||||
{ label: 'Mana', href: '/subscription', icon: '⚡' },
|
||||
{ label: 'Profile', href: '/profile', icon: '👤' }
|
||||
];
|
||||
|
||||
async function handleSignOut() {
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
function isActive(href: string) {
|
||||
return $page.url.pathname.startsWith(href);
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="border-b border-border bg-surface-elevated">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center">
|
||||
<a href="/decks" class="flex items-center space-x-2">
|
||||
<span class="text-2xl">🎴</span>
|
||||
<span class="text-xl font-bold">Manadeck</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden md:flex items-center space-x-1">
|
||||
{#each navItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
||||
isActive(item.href)
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
}`}
|
||||
>
|
||||
<span class="mr-2">{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- User Menu -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{authStore.user?.email || 'Guest'}
|
||||
</div>
|
||||
<button
|
||||
onclick={handleSignOut}
|
||||
class="px-3 py-2 text-sm text-destructive hover:bg-destructive/10 rounded-md transition-colors"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation -->
|
||||
<div class="md:hidden border-t border-border">
|
||||
<div class="flex justify-around py-2">
|
||||
{#each navItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class={`flex flex-col items-center px-3 py-2 text-xs ${
|
||||
isActive(item.href) ? 'text-primary' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<span class="text-xl mb-1">{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
4
manadeck/apps/web/src/lib/stores/navigation.ts
Normal file
4
manadeck/apps/web/src/lib/stores/navigation.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const isSidebarMode = writable(false);
|
||||
export const isNavCollapsed = writable(false);
|
||||
|
|
@ -1,32 +1,140 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { authStore } from '$lib/stores/authStore.svelte';
|
||||
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { isSidebarMode as sidebarModeStore, isNavCollapsed as collapsedStore } from '$lib/stores/navigation';
|
||||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem } from '@manacore/shared-ui';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let isSidebarMode = $state(false);
|
||||
let isCollapsed = $state(false);
|
||||
|
||||
// Get theme state
|
||||
let effectiveMode = $derived(theme.effectiveMode);
|
||||
|
||||
// Navigation items for ManaDeck
|
||||
const navItems: PillNavItem[] = [
|
||||
{ href: '/decks', label: 'Decks', icon: 'archive' },
|
||||
{ href: '/explore', label: 'Explore', icon: 'search' },
|
||||
{ href: '/progress', label: 'Progress', icon: 'chart' },
|
||||
{ href: '/subscription', label: 'Mana', icon: 'mana' },
|
||||
{ href: '/profile', label: 'Profile', icon: 'user' }
|
||||
];
|
||||
|
||||
// Navigation shortcuts (Ctrl+1-5)
|
||||
const navRoutes = navItems.map(item => item.href);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
|
||||
const num = parseInt(event.key);
|
||||
if (num >= 1 && num <= navRoutes.length) {
|
||||
event.preventDefault();
|
||||
const route = navRoutes[num - 1];
|
||||
if (route) {
|
||||
goto(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleModeChange(isSidebar: boolean) {
|
||||
isSidebarMode = isSidebar;
|
||||
sidebarModeStore.set(isSidebar);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('manadeck-nav-sidebar', String(isSidebar));
|
||||
}
|
||||
}
|
||||
|
||||
function handleCollapsedChange(collapsed: boolean) {
|
||||
isCollapsed = collapsed;
|
||||
collapsedStore.set(collapsed);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('manadeck-nav-collapsed', String(collapsed));
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleTheme() {
|
||||
theme.toggleMode();
|
||||
}
|
||||
|
||||
async function handleSignOut() {
|
||||
await authStore.signOut();
|
||||
goto('/login');
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await authStore.initialize();
|
||||
|
||||
if (!authStore.isAuthenticated) {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize sidebar mode from localStorage
|
||||
const savedSidebar = localStorage.getItem('manadeck-nav-sidebar');
|
||||
if (savedSidebar === 'true') {
|
||||
isSidebarMode = true;
|
||||
sidebarModeStore.set(true);
|
||||
}
|
||||
|
||||
// Initialize collapsed state from localStorage
|
||||
const savedCollapsed = localStorage.getItem('manadeck-nav-collapsed');
|
||||
if (savedCollapsed === 'true') {
|
||||
isCollapsed = true;
|
||||
collapsedStore.set(true);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
{#if authStore.loading}
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<div class="text-center">
|
||||
<div class="inline-block animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div>
|
||||
<p class="mt-4 text-muted-foreground">Loading...</p>
|
||||
<div class="inline-block animate-spin h-8 w-8 border-4 border-primary-500 border-t-transparent rounded-full"></div>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if authStore.isAuthenticated}
|
||||
<div class="min-h-screen bg-background">
|
||||
<Navbar />
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{@render children()}
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<!-- Pill Navigation -->
|
||||
<PillNavigation
|
||||
items={navItems}
|
||||
currentPath={$page.url.pathname}
|
||||
appName="ManaDeck"
|
||||
homeRoute="/decks"
|
||||
onLogout={handleSignOut}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
isDark={effectiveMode === 'dark'}
|
||||
isSidebarMode={isSidebarMode}
|
||||
onModeChange={handleModeChange}
|
||||
isCollapsed={isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
showLanguageSwitcher={false}
|
||||
primaryColor="#6366f1"
|
||||
/>
|
||||
|
||||
<!-- Main content with dynamic padding -->
|
||||
<main
|
||||
class="transition-all duration-300 {isCollapsed ? '' : (isSidebarMode ? 'pl-[180px]' : 'pt-20')}"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue