122 lines
3.5 KiB
Svelte
122 lines
3.5 KiB
Svelte
<script lang="ts">
|
|
import LogOutIcon from "@lucide/svelte/icons/log-out";
|
|
import PanelLeftCloseIcon from "@lucide/svelte/icons/panel-left-close";
|
|
import PanelLeftOpenIcon from "@lucide/svelte/icons/panel-left-open";
|
|
import { goto } from "$app/navigation";
|
|
import { base } from "$app/paths";
|
|
import { page } from "$app/state";
|
|
import LiveDot from "$lib/components/live-dot.svelte";
|
|
import ThemeToggle from "$lib/components/theme-toggle.svelte";
|
|
import { Button } from "$lib/components/ui/button";
|
|
import { gateway } from "$lib/gateway.svelte";
|
|
import { isActive, NAV } from "$lib/nav";
|
|
import { session } from "$lib/session.svelte";
|
|
import { ui } from "$lib/ui.svelte";
|
|
import { cn } from "$lib/utils";
|
|
|
|
const running = $derived(gateway.running.length);
|
|
const open = $derived(ui.nav);
|
|
|
|
async function signOut() {
|
|
await session.logout();
|
|
await goto(`${base}/login`);
|
|
}
|
|
</script>
|
|
|
|
<aside
|
|
class={cn(
|
|
"hidden shrink-0 flex-col border-r bg-sidebar text-sidebar-foreground transition-[width] duration-150 sm:flex",
|
|
open ? "w-52" : "w-12"
|
|
)}
|
|
>
|
|
<a
|
|
class={cn(
|
|
"flex h-12 items-center gap-2 border-b font-semibold tracking-tight",
|
|
open ? "px-4" : "justify-center"
|
|
)}
|
|
href="{base}/"
|
|
title="Beaver"
|
|
>
|
|
<span class="size-2.5 shrink-0 rounded-sm bg-primary"></span>
|
|
{#if open}
|
|
Beaver
|
|
{/if}
|
|
</a>
|
|
<nav aria-label="Sections" class="flex flex-1 flex-col gap-0.5 p-2">
|
|
{#each NAV as item (item.href)}
|
|
{@const active = isActive(page.url.pathname, base, item.href)}
|
|
<a
|
|
aria-current={active ? "page" : undefined}
|
|
aria-label={item.label}
|
|
class={cn(
|
|
"relative flex h-8 items-center gap-2.5 rounded-md text-sm transition-colors",
|
|
open ? "px-2.5" : "justify-center",
|
|
active
|
|
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent/60 hover:text-sidebar-foreground"
|
|
)}
|
|
href="{base}{item.href}"
|
|
title={open ? undefined : item.label}
|
|
>
|
|
<item.icon class="size-4 shrink-0 text-icon" />
|
|
{#if open}
|
|
<span class="flex-1">{item.label}</span>
|
|
{/if}
|
|
{#if item.href === "/" && running > 0}
|
|
{#if open}
|
|
<span
|
|
class="tabular rounded-full bg-signal/12 px-1.5 font-medium text-signal text-xs"
|
|
>
|
|
{running}
|
|
</span>
|
|
{:else}
|
|
<span
|
|
class="absolute top-1 right-1 size-1.5 rounded-full bg-signal"
|
|
></span>
|
|
{/if}
|
|
{/if}
|
|
</a>
|
|
{/each}
|
|
</nav>
|
|
<div
|
|
class={cn(
|
|
"flex items-center gap-1 border-t",
|
|
open ? "px-3 py-2" : "flex-col px-1 py-2"
|
|
)}
|
|
>
|
|
<span
|
|
class={cn(open && "mr-auto")}
|
|
title="event stream from the gateway (SSE)"
|
|
>
|
|
<LiveDot
|
|
detail={gateway.live.detail}
|
|
label={open}
|
|
state={gateway.live.state}
|
|
/>
|
|
</span>
|
|
<ThemeToggle />
|
|
<Button
|
|
aria-label="Sign out"
|
|
onclick={signOut}
|
|
size="icon-sm"
|
|
title="Sign out"
|
|
variant="ghost"
|
|
>
|
|
<LogOutIcon class="size-4" />
|
|
</Button>
|
|
<Button
|
|
aria-label={open ? "Collapse sidebar" : "Expand sidebar"}
|
|
onclick={() => ui.toggleNav()}
|
|
size="icon-sm"
|
|
title="{open ? 'Collapse' : 'Expand'} sidebar (⌘B)"
|
|
variant="ghost"
|
|
>
|
|
{#if open}
|
|
<PanelLeftCloseIcon class="size-4" />
|
|
{:else}
|
|
<PanelLeftOpenIcon class="size-4" />
|
|
{/if}
|
|
</Button>
|
|
</div>
|
|
</aside>
|