Skip to content

Commit ad4572b

Browse files
fix(mobile): tab bar spans the screen on iOS before 26
Before iOS 26, UITabBar draws an opaque full-width bar, so the floating tab bar's edge insets and compose slot showed up as a grey block that stopped short of the screen edge. Without Liquid Glass the bar now sits flush and compose moves to the header. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 08a463e commit ad4572b

3 files changed

Lines changed: 27 additions & 10 deletions

File tree

‎apps/mobile/src/features/workspace/WorkspaceNativeTabs.ios.tsx‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import {
1818
NativeWorkspaceTabActivityContext,
1919
WorkspaceSlotOptionsContext,
2020
} from "../../native/v5-workspace-context";
21-
import { useCompactWorkspaceTabs, useSystemWorkspaceTabBar } from "./useSystemWorkspaceTabBar";
21+
import {
22+
useCompactWorkspaceTabs,
23+
useFloatingWorkspaceTabs,
24+
useSystemWorkspaceTabBar,
25+
} from "./useSystemWorkspaceTabBar";
2226
import { useControlTheme } from "../../components/ui/useControlTheme";
2327
import { useDeviceType } from "../../native/native-layout-metrics";
2428
import { WorkspaceSidebarTabs } from "./WorkspaceSidebarTabs";
@@ -80,7 +84,7 @@ export function WorkspaceNativeTabs(props: {
8084
const deviceType = useDeviceType();
8185
const compactTabs = useCompactWorkspaceTabs();
8286
const { layout, startNewTask } = useAdaptiveWorkspaceLayout();
83-
const compactSidebarTabs = deviceType === "phone" || !layout.usesSplitView;
87+
const floatingTabs = useFloatingWorkspaceTabs();
8488
const { width } = useWindowDimensions();
8589
const { colors } = useControlTheme();
8690
const selectedTab = workspace.state.selectedTab;
@@ -165,10 +169,10 @@ export function WorkspaceNativeTabs(props: {
165169
}}
166170
>
167171
<WorkspaceSidebarTabs
168-
compact={compactSidebarTabs}
169-
reserveComposeSpace={compactSidebarTabs}
172+
compact={floatingTabs}
173+
reserveComposeSpace={floatingTabs}
170174
onNewTask={
171-
compactSidebarTabs && !workspace.settingsOpen && selectedTab === "threads"
175+
floatingTabs && !workspace.settingsOpen && selectedTab === "threads"
172176
? startNewTask
173177
: undefined
174178
}

‎apps/mobile/src/features/workspace/WorkspaceToolbar.ios.tsx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
1717
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
1818
import { useWorkspaceTabs } from "./WorkspaceTabsContext";
19-
import { useCompactWorkspaceTabs, useSidebarWorkspaceTabs } from "./useSystemWorkspaceTabBar";
19+
import { useFloatingWorkspaceTabs, useSidebarWorkspaceTabs } from "./useSystemWorkspaceTabBar";
2020
import { WorkspaceTabToolbarButtons } from "./WorkspaceSectionToolbar";
2121

2222
const SEARCH_LABELS = {
@@ -33,7 +33,7 @@ export function WorkspaceToolbar(props: {
3333
const activeNewTask = useActiveNewTask();
3434
const deviceType = useDeviceType();
3535
const sidebarTabs = useSidebarWorkspaceTabs();
36-
const compactTabs = useCompactWorkspaceTabs();
36+
const floatingTabs = useFloatingWorkspaceTabs();
3737
const duoSearch = NATIVE_LIQUID_GLASS_SUPPORTED && isDuoDevice(deviceType);
3838
const bottomSearch = duoSearch && sidebarTabs !== "picker";
3939
// On the Duo, compose belongs to the threads tab, in the detail column's bar when unfolded.
@@ -45,7 +45,7 @@ export function WorkspaceToolbar(props: {
4545
activeNewTask === null;
4646
const tabActive = use(NativeWorkspaceTabActivityContext)?.active ?? true;
4747
const focused = useIsFocused();
48-
const { panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout();
48+
const { layout, panes, togglePrimarySidebar } = useAdaptiveWorkspaceLayout();
4949
const searchRef = useRef<SearchBarCommands>(null);
5050
// The Settings list takes the selected tab's place, so search follows it.
5151
const selectedTab = workspace.settingsOpen ? "settings" : workspace.state.selectedTab;
@@ -189,9 +189,9 @@ export function WorkspaceToolbar(props: {
189189
{composeButton}
190190
{selectedTab !== "threads" ||
191191
isDuoDevice(deviceType) ||
192-
deviceType === "pad" ||
192+
(deviceType === "pad" && layout.usesSplitView) ||
193193
sidebarTabs === "picker" ||
194-
compactTabs ? null : (
194+
floatingTabs ? null : (
195195
<Toolbar.Button
196196
accessibilityLabel="New chat"
197197
icon="square.and.pencil"

‎apps/mobile/src/features/workspace/useSystemWorkspaceTabBar.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isDuoDevice } from "../../lib/device-type";
22
import { Platform } from "react-native";
3+
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
34
import { useAdaptiveWorkspaceLayout } from "../layout/adaptive-workspace-context";
45
import { useDeviceType, useNativeLayoutMetrics } from "../../native/native-layout-metrics";
56
import { useWorkspaceTabs } from "./WorkspaceTabsContext";
@@ -37,6 +38,18 @@ export function useCompactWorkspaceTabs() {
3738
);
3839
}
3940

41+
/** The workspace tab bar floats with compose beside it. Before iOS 26 UITabBar is opaque and
42+
* full width, so it sits flush and compose stays in the header. */
43+
export function useFloatingWorkspaceTabs() {
44+
const { layout } = useAdaptiveWorkspaceLayout();
45+
const deviceType = useDeviceType();
46+
const compactTabs = useCompactWorkspaceTabs();
47+
return (
48+
NATIVE_LIQUID_GLASS_SUPPORTED &&
49+
(compactTabs || (deviceType === "pad" && !layout.usesSplitView))
50+
);
51+
}
52+
4053
export function useSystemWorkspaceTabBar() {
4154
const deviceType = useDeviceType();
4255
const { layout } = useAdaptiveWorkspaceLayout();

0 commit comments

Comments
 (0)