Split +layout.svelte (205 lines) into: - AppNav.svelte: logo + nav links with active-state styling - UserMenu.svelte: avatar button, dropdown, click-outside handler Layout drops from 205 → 80 lines. Part of #75 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.5 KiB
Svelte
83 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import './layout.css';
|
|
import { page } from '$app/state';
|
|
import { onMount } from 'svelte';
|
|
import { setLocale, getLocale } from '$lib/paraglide/runtime';
|
|
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
|
import AppNav from './AppNav.svelte';
|
|
import UserMenu from './UserMenu.svelte';
|
|
|
|
let { children, data } = $props();
|
|
|
|
const locales = ['DE', 'EN', 'ES'] as const;
|
|
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
|
|
const activeLocale = $derived(getLocale().toUpperCase());
|
|
|
|
const isAdmin = $derived(
|
|
data?.user?.groups?.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN'))
|
|
);
|
|
|
|
// Set after client-side hydration completes. Used by E2E tests to know the
|
|
// page is interactive (event handlers registered) before they interact with it.
|
|
let hydrated = $state(false);
|
|
onMount(() => {
|
|
hydrated = true;
|
|
});
|
|
|
|
const isAuthPage = $derived(
|
|
['/login', '/forgot-password', '/reset-password'].some((p) => page.url.pathname.startsWith(p))
|
|
);
|
|
|
|
const userInitials = $derived.by(() => {
|
|
const first = data?.user?.firstName?.[0];
|
|
const last = data?.user?.lastName?.[0];
|
|
if (first && last) return (first + last).toUpperCase();
|
|
return null;
|
|
});
|
|
</script>
|
|
|
|
<div class="min-h-screen bg-canvas" data-hydrated={hydrated || undefined}>
|
|
{#if !isAuthPage}
|
|
<header class="sticky top-0 z-50 border-b border-line-2 bg-surface">
|
|
<!-- De Gruyter Brill purple accent strip -->
|
|
<div class="h-1 bg-brand-purple"></div>
|
|
|
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
<div class="flex h-16 justify-between">
|
|
<!-- Logo & Nav -->
|
|
<AppNav isAdmin={isAdmin} />
|
|
|
|
<!-- Right Side -->
|
|
<div class="flex items-center gap-3">
|
|
<!-- Language selector -->
|
|
<div class="flex items-center gap-1 border-r border-line pr-3">
|
|
{#each locales as locale (locale)}
|
|
<button
|
|
type="button"
|
|
onclick={() => setLocale(localeMap[locale])}
|
|
class="px-1.5 py-1 font-sans text-xs tracking-widest transition-colors
|
|
{activeLocale === locale
|
|
? 'font-bold text-ink'
|
|
: 'font-normal text-ink-3 hover:text-ink'}"
|
|
>
|
|
{locale}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Theme toggle -->
|
|
<ThemeToggle />
|
|
|
|
<!-- User menu -->
|
|
<UserMenu userInitials={userInitials} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
{/if}
|
|
|
|
<main class={isAuthPage ? '' : 'py-6'}>
|
|
{@render children()}
|
|
</main>
|
|
</div>
|