feat: polish #13
1 changed files with 135 additions and 68 deletions
|
|
@ -1,80 +1,147 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import {
|
|
||||||
Collapse,
|
|
||||||
Dropdown,
|
|
||||||
DropdownItem,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownToggle,
|
|
||||||
Nav,
|
|
||||||
NavItem,
|
|
||||||
NavLink,
|
|
||||||
Navbar,
|
|
||||||
NavbarBrand,
|
|
||||||
NavbarToggler,
|
|
||||||
} from '@sveltestrap/sveltestrap';
|
|
||||||
import { authStore } from '$auth';
|
import { authStore } from '$auth';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { t } from '$i18n';
|
import { t } from '$i18n';
|
||||||
|
|
||||||
// Auth is already refreshed by the root layout before this mounts.
|
let isNavOpen = $state(false);
|
||||||
let isOpen = $state(false);
|
let isDropdownOpen = $state(false);
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await authStore.logout();
|
await authStore.logout();
|
||||||
goto('/');
|
await goto('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAll() {
|
||||||
|
isNavOpen = false;
|
||||||
|
isDropdownOpen = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar color="light" light expand="lg" fixed="top" class="custom-navbar border-bottom">
|
<svelte:window onclick={() => { if (isDropdownOpen) isDropdownOpen = false; }} />
|
||||||
<NavbarBrand href="/" class="nav-full-height">
|
|
||||||
|
<style>
|
||||||
|
/* Stretch the container chain so every nav item reaches the full navbar height */
|
||||||
|
.container-fluid {
|
||||||
|
height: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-collapse {
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normalize <button> to match <a> nav-links exactly */
|
||||||
|
button.nav-link {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.nav-link:hover {
|
||||||
|
color: white !important;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep dropdown within viewport — clip vertically, guard horizontal edge */
|
||||||
|
.dropdown-menu {
|
||||||
|
max-height: calc(100vh - var(--navbar-height) - 4px);
|
||||||
|
overflow-y: auto;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
max-width: calc(100vw - 1.5rem);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light fixed-top custom-navbar border-bottom">
|
||||||
|
<div class="container-fluid px-3">
|
||||||
|
<a class="navbar-brand nav-full-height" href="/">
|
||||||
<img src="/logo.svg" alt="Logo" height="34" class="d-inline-block align-text-top" />
|
<img src="/logo.svg" alt="Logo" height="34" class="d-inline-block align-text-top" />
|
||||||
</NavbarBrand>
|
</a>
|
||||||
<NavbarToggler on:click={() => (isOpen = !isOpen)} />
|
|
||||||
<Collapse {isOpen} navbar expand="lg">
|
<button
|
||||||
<Nav class="me-auto mb-lg-0" navbar>
|
type="button"
|
||||||
<NavItem>
|
class="navbar-toggler"
|
||||||
<NavLink
|
aria-controls="mainNav"
|
||||||
|
aria-expanded={isNavOpen}
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
onclick={() => (isNavOpen = !isNavOpen)}>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" class:show={isNavOpen} id="mainNav">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
href="/predict"
|
href="/predict"
|
||||||
class="nav-full-height border border-top-0"
|
class="nav-link nav-full-height border border-top-0"
|
||||||
active={$page.url.pathname === '/predict'}>
|
class:active={$page.url.pathname === '/predict'}
|
||||||
|
onclick={closeAll}>
|
||||||
{$t('nav.predict')}
|
{$t('nav.predict')}
|
||||||
</NavLink>
|
</a>
|
||||||
</NavItem>
|
</li>
|
||||||
<NavItem>
|
<li class="nav-item">
|
||||||
<NavLink
|
<a
|
||||||
href="/track"
|
href="/track"
|
||||||
class="nav-full-height border border-top-0"
|
class="nav-link nav-full-height border border-top-0"
|
||||||
active={$page.url.pathname === '/track'}>
|
class:active={$page.url.pathname === '/track'}
|
||||||
|
onclick={closeAll}>
|
||||||
{$t('nav.track')}
|
{$t('nav.track')}
|
||||||
</NavLink>
|
</a>
|
||||||
</NavItem>
|
</li>
|
||||||
</Nav>
|
</ul>
|
||||||
<Nav navbar>
|
|
||||||
|
<ul class="navbar-nav">
|
||||||
{#if $authStore.status === 'authenticated' && $authStore.username}
|
{#if $authStore.status === 'authenticated' && $authStore.username}
|
||||||
<Dropdown nav inNavbar>
|
<li class="nav-item dropdown" class:show={isDropdownOpen}>
|
||||||
<DropdownToggle nav caret class="nav-full-height border border-top-0">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nav-link nav-full-height border border-top-0 dropdown-toggle"
|
||||||
|
aria-expanded={isDropdownOpen}
|
||||||
|
onclick={(e) => { e.stopPropagation(); isDropdownOpen = !isDropdownOpen; }}>
|
||||||
{$authStore.username}
|
{$authStore.username}
|
||||||
</DropdownToggle>
|
</button>
|
||||||
<DropdownMenu end>
|
<ul class="dropdown-menu dropdown-menu-end" class:show={isDropdownOpen}>
|
||||||
<DropdownItem href="/user/account">{$t('nav.account')}</DropdownItem>
|
<li><a class="dropdown-item" href="/user/account" onclick={closeAll}>{$t('nav.account')}</a></li>
|
||||||
<DropdownItem href="/user/templates">{$t('nav.scenarios')}</DropdownItem>
|
<li><a class="dropdown-item" href="/user/templates" onclick={closeAll}>{$t('nav.scenarios')}</a></li>
|
||||||
<DropdownItem href="/user/predictions">{$t('nav.predictionHistory')}</DropdownItem>
|
<li><a class="dropdown-item" href="/user/predictions" onclick={closeAll}>{$t('nav.predictionHistory')}</a></li>
|
||||||
<DropdownItem href="/user/flights">{$t('nav.trackingHistory')}</DropdownItem>
|
<li><a class="dropdown-item" href="/user/flights" onclick={closeAll}>{$t('nav.trackingHistory')}</a></li>
|
||||||
<DropdownItem divider />
|
<li><hr class="dropdown-divider" /></li>
|
||||||
<DropdownItem on:click={handleLogout}>{$t('nav.logout')}</DropdownItem>
|
<li>
|
||||||
</DropdownMenu>
|
<button type="button" class="dropdown-item" onclick={handleLogout}>
|
||||||
</Dropdown>
|
{$t('nav.logout')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
{:else if $authStore.status === 'anonymous'}
|
{:else if $authStore.status === 'anonymous'}
|
||||||
<NavItem>
|
<li class="nav-item">
|
||||||
<NavLink
|
<a
|
||||||
href="/login"
|
href="/login"
|
||||||
class="nav-full-height border border-top-0"
|
class="nav-link nav-full-height border border-top-0"
|
||||||
active={$page.url.pathname === '/login'}>
|
class:active={$page.url.pathname === '/login'}
|
||||||
|
onclick={closeAll}>
|
||||||
{$t('nav.login')}
|
{$t('nav.login')}
|
||||||
</NavLink>
|
</a>
|
||||||
</NavItem>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
</Nav>
|
</ul>
|
||||||
</Collapse>
|
</div>
|
||||||
</Navbar>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue