Open History to see the last commit
1import { WorkspaceNoTabs } from "./WorkspaceNoTabs";
2import { WORKSPACE_TAB_LABELS, useEnabledNavigationTabs } from "./navigation-tabs";
3import { WorkspaceExtraTab } from "./WorkspaceExtraTab";
4import { WorkspaceSettingsList } from "../../settings/SettingsRouteScreen";
5import { WorkspaceSectionToolbar } from "./WorkspaceSectionToolbar";
6import { useNavigation } from "@react-navigation/native";
7import { use } from "react";
8import { View, useWindowDimensions } from "react-native";
9import { useKeyboardState } from "react-native-keyboard-controller";
10import { SafeAreaInsetsContext, useSafeAreaInsets } from "react-native-safe-area-context";
11
12import { CompactBrandTitle } from "../../../components/CompactBrandTitle";
13import { HeaderItemsWidthContext } from "../../../native/header-items-width";
14import { NativeStackScreenOptions } from "../../../native/StackHeader";
15import { NativePrimaryColumnContext } from "../../../native/v5-workspace-context";
16import { isDuoDevice } from "../../../lib/device-type";
17import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../../native/native-glass";
18import {
19 useDeviceType,
20 useNativeColumnLayoutMetrics,
21 useNativeLayoutMetrics,
22} from "../../../native/native-layout-metrics";
23import { HomeRouteScreen } from "../../home/HomeRouteScreen";
24import { WorkspaceConnectionTitle } from "../../home/WorkspaceConnectionTitle";
25import { useAdaptiveWorkspaceLayout } from "../AdaptiveWorkspaceLayout";
26import { PullRequestsScreen } from "../../pull-requests/PullRequestsScreen";
27import {
28 WorkspaceTabsContext,
29 WorkspaceTabsProvider,
30 useWorkspaceTabs,
31} from "./WorkspaceTabsContext";
32import { WorkspaceToolbar } from "./WorkspaceToolbar";
33import { WORKSPACE_DOCK, workspaceDockBottomClearance } from "./workspace-tabs-state";
34import { WorkspaceSidebarTabs } from "./WorkspaceSidebarTabs";
35import {
36 useCompactWorkspaceTabs,
37 useSidebarWorkspaceTabs,
38 useSystemWorkspaceTabBar,
39} from "./useSystemWorkspaceTabBar";
40import { openSettings } from "../../settings/settings-navigation";
41
42const TAB_TITLES = WORKSPACE_TAB_LABELS;
43
44function WorkspaceRootTitle(props: {
45 readonly columnWidth: number;
46 readonly onOpenEnvironments: () => void;
47}) {
48 const { state, settingsOpen } = useWorkspaceTabs()!;
49 const maxWidth = Math.max(0, props.columnWidth - (use(HeaderItemsWidthContext) ?? 160));
50 const enabled = useEnabledNavigationTabs();
51 const tabTitle = settingsOpen
52 ? "Settings"
53 : enabled.length
54 ? TAB_TITLES[state.selectedTab]
55 : "Navigation";
56 return (
57 <View style={{ width: maxWidth, minHeight: 32, justifyContent: "center" }}>
58 <WorkspaceConnectionTitle
59 brand={<CompactBrandTitle accessibilityLabel={`T3 Code, ${tabTitle}`} fit />}
60 maxWidth={maxWidth}
61 onPress={props.onOpenEnvironments}
62 />
63 </View>
64 );
65}
66
67function WorkspaceRootContent() {
68 const navigation = useNavigation();
69 const { width: windowWidth } = useWindowDimensions();
70 const { layout, startNewTask } = useAdaptiveWorkspaceLayout();
71 const primaryColumn = use(NativePrimaryColumnContext);
72 const columnMetrics = useNativeColumnLayoutMetrics();
73 const nativeMetrics = useNativeLayoutMetrics();
74 const deviceType = useDeviceType();
75 const systemTabBar = useSystemWorkspaceTabBar();
76 const compactTabs = useCompactWorkspaceTabs();
77 const enabled = useEnabledNavigationTabs();
78 const sidebarTabs = useSidebarWorkspaceTabs();
79 const sidebarPicker = sidebarTabs === "picker" || (deviceType === "pad" && layout.usesSplitView);
80 const sidebarNavigation =
81 primaryColumn !== null &&
82 (layout.usesSplitView ||
83 deviceType === "pad" ||
84 (nativeMetrics !== null && nativeMetrics.verticalBarEdge !== "none"));
85 const width = columnMetrics?.width ?? windowWidth;
86 const insets = useSafeAreaInsets();
87 const keyboardHeight = useKeyboardState((keyboard) => keyboard.height);
88 const workspace = useWorkspaceTabs()!;
89 const { state } = workspace;
90 // Settings is a workspace tab too; its list takes the place of the selected tab's.
91 const settingsOpen = workspace.settingsOpen;
92 const renderHeaderTitle = () => (
93 <WorkspaceRootTitle
94 columnWidth={width}
95 onOpenEnvironments={() => openSettings(navigation, "SettingsEnvironments")}
96 />
97 );
98 const tab = state.tabs[settingsOpen ? "settings" : state.selectedTab];
99 const bottomClearance =
100 layout.variant === "medium" && state.selectedTab === "usage"
101 ? workspaceDockBottomClearance({ bottomInset: insets.bottom, keyboardHeight })
102 : isDuoDevice(deviceType)
103 ? // The column's safe area already holds the Duo's search toolbar and keyboard.
104 Math.max(insets.bottom, keyboardHeight) + 16
105 : compactTabs || deviceType === "pad"
106 ? workspaceDockBottomClearance({ bottomInset: insets.bottom, keyboardHeight })
107 : Math.max(insets.bottom, keyboardHeight) +
108 (systemTabBar || sidebarTabs || tab.searchMode !== "resting"
109 ? sidebarPicker
110 ? 80
111 : 16
112 : WORKSPACE_DOCK.height + WORKSPACE_DOCK.bottomGap + 16);
113 const context = { ...workspace, bottomClearance };
114
115 return (
116 <WorkspaceTabsContext value={context}>
117 <View className={sidebarPicker ? "flex-1 bg-drawer" : "flex-1 bg-screen"}>
118 <NativeStackScreenOptions
119 optionsVersion={[
120 settingsOpen,
121 state.selectedTab,
122 state.selectionRevision,
123 width,
124 sidebarNavigation,
125 enabled.length,
126 ]}
127 options={{
128 title: settingsOpen
129 ? "Settings"
130 : enabled.length
131 ? TAB_TITLES[state.selectedTab]
132 : "Navigation",
133 headerShown: true,
134 headerTransparent: NATIVE_LIQUID_GLASS_SUPPORTED,
135 headerStyle: NATIVE_LIQUID_GLASS_SUPPORTED
136 ? { backgroundColor: "transparent" }
137 : undefined,
138 contentStyle: undefined,
139 ...(enabled.length === 0
140 ? { headerSearchBarOptions: undefined, unstable_headerRightItems: () => [] }
141 : {}),
142 unstable_headerLeftItems: () => [],
143 // Beside a detail column the tab bar names the tab and the detail header the page,
144 // so the sidebar keeps the brand.
145 headerTitle:
146 sidebarNavigation || (!settingsOpen && state.selectedTab !== "usage")
147 ? renderHeaderTitle
148 : undefined,
149 }}
150 />
151 <SafeAreaInsetsContext value={compactTabs ? { ...insets, bottom: 0 } : insets}>
152 {settingsOpen ? (
153 <WorkspaceSettingsList />
154 ) : enabled.length === 0 ? (
155 <WorkspaceNoTabs />
156 ) : state.selectedTab === "threads" ? (
157 <HomeRouteScreen />
158 ) : state.selectedTab === "pullRequests" ? (
159 <PullRequestsScreen />
160 ) : (
161 <WorkspaceExtraTab tab={state.selectedTab} />
162 )}
163 </SafeAreaInsetsContext>
164 {deviceType !== "pad" && deviceType !== "phone" && sidebarPicker ? (
165 <WorkspaceSidebarTabs />
166 ) : null}
167 {enabled.length === 0 && !settingsOpen ? null : settingsOpen ||
168 state.selectedTab === "threads" ||
169 state.selectedTab === "pullRequests" ? (
170 <WorkspaceToolbar sidebar={sidebarNavigation} onNewTask={startNewTask} />
171 ) : (
172 <WorkspaceSectionToolbar onNewTask={startNewTask} showCompose={false} />
173 )}
174 </View>
175 </WorkspaceTabsContext>
176 );
177}
178
179export function WorkspaceRootScreen() {
180 const workspace = useWorkspaceTabs();
181 return workspace ? (
182 <WorkspaceRootContent />
183 ) : (
184 <WorkspaceTabsProvider>
185 <WorkspaceRootContent />
186 </WorkspaceTabsProvider>
187 );
188}