summaryrefslogtreecommitdiffhomepage
path: root/src/features/tabs/ui
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
commit0cb08678ffead285afb1f93ba50cd5a144ed5e7d (patch)
treecf1396f1d7a065b5777ede0fd64f67ae8d6063ec /src/features/tabs/ui
parent2663fe7f7b7eb438dc295fe9dea221aa8b8b8f81 (diff)
downloaddispatch-web-0cb08678ffead285afb1f93ba50cd5a144ed5e7d.tar.gz
dispatch-web-0cb08678ffead285afb1f93ba50cd5a144ed5e7d.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')
-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>