返回 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
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
63 lines Plain Text