summaryrefslogtreecommitdiffhomepage
path: root/src/features/tabs/ui/TabBar.svelte
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-07 14:35:53 +0900
committerAdam Malczewski <[email protected]>2026-06-07 14:35:53 +0900
commit34bed91f2e0ee2484df6be9d2fc8742f64b24941 (patch)
treebabf67ec1b744ea0f8845627ec54bb55c100cdf4 /src/features/tabs/ui/TabBar.svelte
parent7769c61faea446d4bd2ae6d400314aa433d405e8 (diff)
downloaddispatch-web-34bed91f2e0ee2484df6be9d2fc8742f64b24941.tar.gz
dispatch-web-34bed91f2e0ee2484df6be9d2fc8742f64b24941.zip
feat(tabs): extract TabBar component with horizontal scroll + sticky end '+'
Move inline tab-bar markup from the composition root into a thin presentational TabBar in the tabs feature (feature-as-a-library: pure reducer -> reactive store -> UI). Adds overflow-x scroll (min-w-max strip) and a sticky right-pinned new-chat '+' that floats over scrolling tabs. Draft-on-select / create-on-send behavior unchanged.
Diffstat (limited to 'src/features/tabs/ui/TabBar.svelte')
-rw-r--r--src/features/tabs/ui/TabBar.svelte54
1 files changed, 54 insertions, 0 deletions
diff --git a/src/features/tabs/ui/TabBar.svelte b/src/features/tabs/ui/TabBar.svelte
new file mode 100644
index 0000000..76fab05
--- /dev/null
+++ b/src/features/tabs/ui/TabBar.svelte
@@ -0,0 +1,54 @@
+<script lang="ts">
+ import type { Tab } from "../tabs";
+
+ let {
+ tabs,
+ activeConversationId,
+ onSelect,
+ onClose,
+ onNewDraft,
+ }: {
+ tabs: readonly Tab[];
+ activeConversationId: string | null;
+ onSelect: (conversationId: string) => void;
+ onClose: (conversationId: string) => void;
+ onNewDraft: () => void;
+ } = $props();
+</script>
+
+<div class="overflow-x-auto border-b border-base-300">
+ <div class="tabs tabs-border min-w-max">
+ {#each tabs as tab (tab.conversationId)}
+ <div
+ class="tab"
+ class:tab-active={tab.conversationId === activeConversationId}
+ role="tab"
+ tabindex="0"
+ onclick={() => onSelect(tab.conversationId)}
+ onkeydown={(e) => {
+ if (e.key === "Enter") onSelect(tab.conversationId);
+ }}
+ >
+ <span class="max-w-[120px] truncate">{tab.title}</span>
+ <button
+ class="btn btn-ghost btn-xs ml-1"
+ aria-label="Close tab"
+ onclick={(e) => {
+ e.stopPropagation();
+ onClose(tab.conversationId);
+ }}
+ >
+ &times;
+ </button>
+ </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-active={activeConversationId === null}
+ aria-label="New chat"
+ onclick={() => onNewDraft()}
+ >
+ +
+ </button>
+ </div>
+</div>