| 1 | // TabContainer is the right dock's tab shell: the tab strip on top (with the |
| 2 | // + add menu) and the active tab's panel below, routed through TabContent. |
| 3 | // The ActivityBar sits to its left inside the workbench-dock (rendered by |
| 4 | // App); panel content is provided as a renderer because the panels need App's |
| 5 | // props. When the last tab is closed the container collapses (activityBarOpen |
| 6 | // flips false in the store), so this component only renders while tabs exist. |
| 7 | |
| 8 | import { useCallback, useRef } from "react"; |
| 9 | import type { ReactNode } from "react"; |
| 10 | import { useActivityBarStore, type TabItem, type TabType } from "../../store/activityBar"; |
| 11 | import { TabAddMenu } from "./TabAddMenu"; |
| 12 | import { TabBar } from "./TabBar"; |
| 13 | import { TabContent } from "./TabContent"; |
| 14 | |
| 15 | interface TabContainerProps { |
| 16 | /** App-provided panel renderer for a given tab. */ |
| 17 | renderTab: (tab: TabItem) => ReactNode; |
| 18 | /** Opens (or activates) the view a tab-picker entry stands for. */ |
| 19 | onPickEntry: (entryId: string) => void; |
| 20 | } |
| 21 | |
| 22 | export function TabContainer({ renderTab, onPickEntry }: TabContainerProps) { |
| 23 | const tabs = useActivityBarStore((s) => s.tabs); |
| 24 | const activeTabId = useActivityBarStore((s) => s.activeTabId); |
| 25 | const addMenuOpen = useActivityBarStore((s) => s.addMenuOpen); |
| 26 | const addTab = useActivityBarStore((s) => s.addTab); |
| 27 | const closeTab = useActivityBarStore((s) => s.closeTab); |
| 28 | const activateTab = useActivityBarStore((s) => s.activateTab); |
| 29 | const moveTab = useActivityBarStore((s) => s.moveTab); |
| 30 | const setAddMenuOpen = useActivityBarStore((s) => s.setAddMenuOpen); |
| 31 | const addButtonRef = useRef<HTMLButtonElement>(null); |
| 32 | const activeTab = tabs.find((tab) => tab.id === activeTabId) ?? null; |
| 33 | // Closing the add menu on outside click / Escape lives inside TabAddMenu |
| 34 | // (it knows its own bounds); the panel itself must not swallow those clicks. |
| 35 | |
| 36 | // Every panel type can be added repeatedly, so the pick handler always |
| 37 | // appends a fresh tab (no dedup via openEntry). |
| 38 | const handlePickTab = useCallback( |
| 39 | (type: TabType, label: string) => { |
| 40 | addTab(type, label); |
| 41 | }, |
| 42 | [addTab], |
| 43 | ); |
| 44 | |
| 45 | return ( |
| 46 | <div className="tab-container"> |
| 47 | <TabBar |
| 48 | tabs={tabs} |
| 49 | activeTabId={activeTabId} |
| 50 | onActivate={activateTab} |
| 51 | onClose={closeTab} |
| 52 | onMoveTab={moveTab} |
| 53 | onAdd={() => setAddMenuOpen(!addMenuOpen)} |
| 54 | addButtonRef={addButtonRef} |
| 55 | /> |
| 56 | {addMenuOpen && <TabAddMenu anchorRef={addButtonRef} onPick={handlePickTab} onClose={() => setAddMenuOpen(false)} />} |
| 57 | <div className="tab-container__content"> |
| 58 | <TabContent activeTab={activeTab} renderTab={renderTab} onPickEntry={onPickEntry} /> |
| 59 | </div> |
| 60 | </div> |
| 61 | ); |
| 62 | } |
| 63 |