1import { createV5StackNavigator as createNativeStackNavigator } from "../../native/createV5StackNavigator";
2import { NavigationContainer, NavigationIndependentTree } from "@react-navigation/native";
3import { type NativeStackNavigationOptions } from "@react-navigation/native-stack";
4import type { ReactNode } from "react";
5import { Platform } from "react-native";
6
7import { getCompactBrandHeaderOptions } from "../../components/CompactBrandTitle";
8import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
9import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
10import { useMobileNavigationTheme } from "../../lib/useMobileNavigationTheme";
11
12const SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version);
13
14type SidebarScreenOptions = NativeStackNavigationOptions & {
15 // Same patched RNS option the GLASS/SOLID presets in Stack.tsx use — the
16 // iOS 26 "editor" navigation-item style leading-aligns the inline title.
17 readonly unstable_navigationItemStyle?: "editor";
18};
19
20/**
21 * Static chrome for the sidebar column: a real UINavigationBar with a fixed
22 * inline title (no large title — saves vertical space, left-aligned via the
23 * editor item style) and the search bar pinned below it, scroll-edge blur
24 * sampling the list. Only genuinely dynamic values (search callbacks, header
25 * items) are set by the screen content via NativeStackScreenOptions.
26 */
27const SIDEBAR_SCREEN_OPTIONS: SidebarScreenOptions = {
28 contentStyle: { backgroundColor: "transparent" },
29 headerLargeTitle: false,
30 headerShadowVisible: false,
31 headerShown: true,
32 headerStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? { backgroundColor: "transparent" } : undefined,
33 ...getCompactBrandHeaderOptions({ fontSize: 18, fontWeight: "800" }),
34 headerTransparent: NATIVE_LIQUID_GLASS_SUPPORTED,
35 scrollEdgeEffects: NATIVE_LIQUID_GLASS_SUPPORTED ? SCROLL_EDGE_EFFECTS : undefined,
36 unstable_navigationItemStyle: NATIVE_LIQUID_GLASS_SUPPORTED ? "editor" : undefined,
37};
38
39const SidebarStack = createNativeStackNavigator();
40
41/**
42 * Hosts the iPad sidebar pane inside its own single-screen native stack.
43 *
44 * The stack is navigation-inert — nothing is ever pushed onto it. It exists so
45 * the sidebar column owns a real UINavigationBar (large title, native bar
46 * button items, UISearchController), mirroring how each column of a
47 * UISplitViewController has its own UINavigationController. All real
48 * navigation still flows through the root stack via callbacks minted in
49 * AdaptiveWorkspaceLayout; NavigationIndependentTree only isolates the
50 * navigation hooks used for header configuration inside the pane.
51 */
52export function SidebarNavigationShell(props: { readonly children: ReactNode }) {
53 const navigationTheme = useMobileNavigationTheme("sidebar");
54
55 return (
56 <NavigationIndependentTree>
57 <NavigationContainer theme={navigationTheme}>
58 <SidebarStack.Navigator
59 screenOptions={SIDEBAR_SCREEN_OPTIONS}
60 initialRouteName="SidebarThreads"
61 >
62 <SidebarStack.Screen name="SidebarThreads">{() => props.children}</SidebarStack.Screen>
63 </SidebarStack.Navigator>
64 </NavigationContainer>
65 </NavigationIndependentTree>
66 );
67}