Files
beaver-gateway/ui/src/lib/panel/panel-bar.svelte
T

120 lines
3.6 KiB
Svelte

<script lang="ts">
import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
import Link2Icon from "@lucide/svelte/icons/link-2";
import Link2OffIcon from "@lucide/svelte/icons/link-2-off";
import type { ApiClient } from "$lib/api/client";
import KindBadge from "$lib/components/kind-badge.svelte";
import LiveDot from "$lib/components/live-dot.svelte";
import { Button } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover";
import { clip, shortId } from "$lib/format";
import ConversationPicker from "./conversation-picker.svelte";
import type { ConversationIndex } from "./index.svelte";
let {
client,
index,
selected = null,
onOpen,
follow = $bindable(false),
showFollow = false,
}: {
client: ApiClient;
index: ConversationIndex;
selected?: string | null;
onOpen: (id: string) => void;
follow?: boolean;
showFollow?: boolean;
} = $props();
const TITLE_MAX = 48;
let switcherOpen = $state(false);
const current = $derived(selected ? index.byId(selected) : undefined);
const running = $derived(index.running.length);
</script>
<div class="flex items-center gap-1 border-b px-2 py-1.5">
<Popover.Root bind:open={switcherOpen}>
<Popover.Trigger>
{#snippet child({ props })}
<button
{...props}
aria-label="Switch conversation"
class="row-hover flex h-8 min-w-0 flex-1 items-center gap-2 rounded-md px-2 text-left text-sm aria-expanded:bg-muted"
type="button"
>
{#if current}
<KindBadge kind={current.kind} />
<span class="min-w-0 flex-1 truncate font-medium">
{current.title
? clip(current.title, TITLE_MAX)
: `${current.kind} · ${shortId(current.id)}`}
</span>
{:else if selected}
<span class="tabular min-w-0 flex-1 truncate font-medium">
{shortId(selected)}
</span>
{:else}
<span class="min-w-0 flex-1 truncate text-muted-foreground">
Pick a conversation
</span>
{/if}
{#if running > 0 && !current?.running_turn}
<span
class="tabular shrink-0 rounded-full bg-signal/12 px-1.5 font-medium text-signal text-xs"
title="{running} running elsewhere"
>
{running}
</span>
{/if}
<ChevronDownIcon class="size-3.5 shrink-0 text-icon" />
</button>
{/snippet}
</Popover.Trigger>
<Popover.Content
align="start"
class="w-[min(24rem,calc(100vw-1rem))] gap-0 overflow-hidden p-0"
sideOffset={6}
>
<div class="flex max-h-[min(70vh,32rem)] flex-col">
<ConversationPicker
autofocus
{client}
{index}
onOpen={(id) => {
switcherOpen = false;
onOpen(id);
}}
{selected}
/>
</div>
</Popover.Content>
</Popover.Root>
<LiveDot
class="px-1"
detail={index.live.detail}
label={false}
state={index.live.state}
/>
{#if showFollow}
<Button
aria-label="Follow the active note"
aria-pressed={follow}
onclick={() => {
follow = !follow;
}}
size="icon-sm"
title={follow
? "Following the active note - click to pin this conversation"
: "Pinned - click to follow the active note"}
variant={follow ? "secondary" : "ghost"}
>
{#if follow}
<Link2Icon class="size-4" />
{:else}
<Link2OffIcon class="size-4" />
{/if}
</Button>
{/if}
</div>