diff options
Diffstat (limited to 'src/features/tabs/ui/TabBar.svelte')
| -rw-r--r-- | src/features/tabs/ui/TabBar.svelte | 52 |
1 files changed, 49 insertions, 3 deletions
diff --git a/src/features/tabs/ui/TabBar.svelte b/src/features/tabs/ui/TabBar.svelte index 76fab05..eb5b5e5 100644 --- a/src/features/tabs/ui/TabBar.svelte +++ b/src/features/tabs/ui/TabBar.svelte @@ -1,5 +1,6 @@ <script lang="ts"> import type { Tab } from "../tabs"; + import { isStuckToEnd } from "../tabs"; let { tabs, @@ -14,9 +15,47 @@ onClose: (conversationId: string) => void; onNewDraft: () => void; } = $props(); + + // The new-chat button is `position: sticky; right: 0`. It floats over the tabs + // only while the strip overflows and isn't scrolled fully right; we square its + // right edge only in that "stuck" state. Pure decision (`isStuckToEnd`) + + // DOM-measurement at the edge here. + let scrollEl = $state<HTMLDivElement>(); + let stuck = $state(false); + + function recompute(): void { + const el = scrollEl; + if (el === undefined) { + stuck = false; + return; + } + stuck = isStuckToEnd({ + scrollLeft: el.scrollLeft, + clientWidth: el.clientWidth, + scrollWidth: el.scrollWidth, + }); + } + + $effect(() => { + const el = scrollEl; + if (el === undefined) return; + // Re-evaluate when the tab set changes (overflow may appear/disappear). + void tabs; + recompute(); + + el.addEventListener("scroll", recompute, { passive: true }); + const ro = + typeof ResizeObserver !== "undefined" ? new ResizeObserver(recompute) : undefined; + ro?.observe(el); + + return () => { + el.removeEventListener("scroll", recompute); + ro?.disconnect(); + }; + }); </script> -<div class="overflow-x-auto border-b border-base-300"> +<div bind:this={scrollEl} class="overflow-x-auto border-b border-base-300"> <div class="tabs tabs-border min-w-max"> {#each tabs as tab (tab.conversationId)} <div @@ -43,12 +82,19 @@ </div> {/each} <button - class="tab sticky right-0 z-10 bg-base-200 shadow-[-2px_0_4px_-1px_rgba(0,0,0,0.2)]" + class="tab sticky right-0 z-10 bg-base-200 shadow-[-2px_0_4px_-1px_rgba(0,0,0,0.2)] {stuck + ? '!rounded-se-none !rounded-ee-none' + : ''}" class:tab-active={activeConversationId === null} aria-label="New chat" onclick={() => onNewDraft()} > - + + {#if activeConversationId === null} + <span class="max-w-[120px] truncate">New Chat</span> + <span class="btn btn-ghost btn-xs ml-1" aria-hidden="true">+</span> + {:else} + + + {/if} </button> </div> </div> |
