返回 DeepSeek-Reasonix
TabContainer.tsx
根目录 / desktop / frontend / src / components / TabContainer / TabContainer.tsx
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 import "../WorkspacePanelStability.css";
15
16 interface TabContainerProps {
17 /** App-provided panel renderer for a given tab. */
18 renderTab: (tab: TabItem) => ReactNode;
19 /** Opens (or activates) the view a tab-picker entry stands for. */
20 onPickEntry: (entryId: string) => void;
21 onClosePanel?: () => void;
22 }
23
24 export function TabContainer({ renderTab, onPickEntry, onClosePanel }: TabContainerProps) {
25 const tabs = useActivityBarStore((s) => s.tabs);
26 const activeTabId = useActivityBarStore((s) => s.activeTabId);
27 const addMenuOpen = useActivityBarStore((s) => s.addMenuOpen);
28 const addTab = useActivityBarStore((s) => s.addTab);
29 const closeTab = useActivityBarStore((s) => s.closeTab);
30 const activateTab = useActivityBarStore((s) => s.activateTab);
31 const moveTab = useActivityBarStore((s) => s.moveTab);
32 const setAddMenuOpen = useActivityBarStore((s) => s.setAddMenuOpen);
33 const addButtonRef = useRef<HTMLButtonElement>(null);
34 const activeTab = tabs.find((tab) => tab.id === activeTabId) ?? null;
35 // Closing the add menu on outside click / Escape lives inside TabAddMenu
36 // (it knows its own bounds); the panel itself must not swallow those clicks.
37
38 // Every panel type can be added repeatedly, so the pick handler always
39 // appends a fresh tab (no dedup via openEntry).
40 const handlePickTab = useCallback(
41 (type: TabType, label: string) => {
42 addTab(type, label);
43 },
44 [addTab],
45 );
46
47 return (
48 <div className="tab-container">
49 <TabBar
50 tabs={tabs}
51 activeTabId={activeTabId}
52 onActivate={activateTab}
53 onClose={closeTab}
54 onMoveTab={moveTab}
55 onAdd={() => setAddMenuOpen(!addMenuOpen)}
56 addButtonRef={addButtonRef}
57 onClosePanel={onClosePanel}
58 />
59 {addMenuOpen && <TabAddMenu anchorRef={addButtonRef} onPick={handlePickTab} onClose={() => setAddMenuOpen(false)} />}
60 <div className="tab-container__content">
61 <TabContent activeTab={activeTab} renderTab={renderTab} onPickEntry={onPickEntry} />
62 </div>
63 </div>
64 );
65 }
66
66 lines Plain Text