diff options
| author | Adam Malczewski <[email protected]> | 2026-06-07 14:35:53 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-07 14:35:53 +0900 |
| commit | 0cb08678ffead285afb1f93ba50cd5a144ed5e7d (patch) | |
| tree | cf1396f1d7a065b5777ede0fd64f67ae8d6063ec /src/features/tabs/ui/TabBar.svelte | |
| parent | 2663fe7f7b7eb438dc295fe9dea221aa8b8b8f81 (diff) | |
| download | dispatch-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/TabBar.svelte')
| -rw-r--r-- | src/features/tabs/ui/TabBar.svelte | 54 |
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); + }} + > + × + </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> |
