120 lines
3.6 KiB
Svelte
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>
|