1import {
2 createContext,
3 use,
4 useCallback,
5 useLayoutEffect,
6 useMemo,
7 useReducer,
8 useRef,
9 type Dispatch,
10 type ReactNode,
11} from "react";
12import {
13 initialWorkspaceTabsState,
14 workspaceTabsReducer,
15 type WorkspaceTab,
16 type WorkspaceTabsAction,
17 type WorkspaceTabsState,
18} from "./workspace-tabs-state";
19
20export interface WorkspaceScrollAnchor {
21 readonly key: string;
22 readonly viewOffset: number;
23}
24
25export const WorkspaceTabsContext = createContext<{
26 readonly state: WorkspaceTabsState;
27 readonly dispatch: Dispatch<WorkspaceTabsAction>;
28 readonly isTabActive: (tab: WorkspaceTab) => boolean;
29 readonly bottomClearance: number;
30 readonly getScrollOffset: (tab: WorkspaceTab) => number;
31 readonly recordScrollOffset: (tab: WorkspaceTab, offset: number) => void;
32 readonly getScrollAnchor: (tab: WorkspaceTab) => WorkspaceScrollAnchor | undefined;
33 readonly recordScrollAnchor: (tab: WorkspaceTab, anchor: WorkspaceScrollAnchor) => void;
34} | null>(null);
35
36export function useWorkspaceTabs() {
37 return use(WorkspaceTabsContext);
38}
39
40/** The tab controller and its list screens share state across native container changes. */
41export function WorkspaceTabsProvider(props: { readonly children: ReactNode }) {
42 const [state, dispatch] = useReducer(workspaceTabsReducer, undefined, initialWorkspaceTabsState);
43 const activeTab = useRef(state.selectedTab);
44 useLayoutEffect(() => {
45 activeTab.current = state.selectedTab;
46 }, [state.selectedTab]);
47 const isTabActive = useCallback((tab: WorkspaceTab) => activeTab.current === tab, []);
48 const scrollOffsets = useRef({ threads: 0, pullRequests: 0 });
49 const scrollAnchors = useRef<Partial<Record<WorkspaceTab, WorkspaceScrollAnchor>>>({});
50 const getScrollOffset = useCallback((tab: WorkspaceTab) => scrollOffsets.current[tab], []);
51 const recordScrollOffset = useCallback((tab: WorkspaceTab, offset: number) => {
52 scrollOffsets.current[tab] = offset;
53 }, []);
54 const getScrollAnchor = useCallback((tab: WorkspaceTab) => scrollAnchors.current[tab], []);
55 const recordScrollAnchor = useCallback((tab: WorkspaceTab, anchor: WorkspaceScrollAnchor) => {
56 scrollAnchors.current[tab] = anchor;
57 }, []);
58 const context = useMemo(
59 () => ({
60 state,
61 dispatch,
62 isTabActive,
63 bottomClearance: 0,
64 getScrollOffset,
65 recordScrollOffset,
66 getScrollAnchor,
67 recordScrollAnchor,
68 }),
69 [state, isTabActive, getScrollOffset, recordScrollOffset, getScrollAnchor, recordScrollAnchor],
70 );
71 return <WorkspaceTabsContext value={context}>{props.children}</WorkspaceTabsContext>;
72}