Skip to content
Source
Sign inSign up

workspace-inspector-sheet.tsx

75 lines2.8 KB

1import { createContext, useRef, useState } from "react";
2import { SafeAreaInsetsContext, useSafeAreaInsets } from "react-native-safe-area-context";
3​
4import { PaneContext } from "../../components/ui/Pane";
5import { PaneSheet } from "../../components/ui/PaneSheet";
6import { RoutedFormSheetContext } from "../../components/ui/RoutedFormSheetContext";
7import { formSheetDetents } from "../../native/sheet-surface";
8import { InspectorPanes } from "./inspector-host";
9import type { InspectorContent } from "./inspector-panes";
10​
11/**
12 * Closes the inspector sheet around the inspector, then runs `then` once it is gone: a route
13 * presented over a sheet that isn't one of the stack's own would race its dismissal.
14 */
15export const InspectorSheetContext = createContext<((then?: () => void) => void) | null>(null);
16​
17/** The inspector where the window has no column for it. Its panes draw their own bars. */
18export function WorkspaceInspectorSheet(props: {
19 readonly open: boolean;
20 readonly onClose: () => void;
21 readonly content: InspectorContent | undefined;
22}) {
23 const insets = useSafeAreaInsets();
24 // Stays mounted until the sheet has finished closing, so it doesn't empty as it slides away.
25 // Each opening gets a fresh native sheet, even one that starts while the last is closing.
26 const [presented, setPresented] = useState({
27 open: props.open,
28 mounted: props.open,
29 session: 0,
30 });
31 if (presented.open !== props.open) {
32 setPresented({
33 open: props.open,
34 mounted: presented.mounted || props.open,
35 session: props.open ? presented.session + 1 : presented.session,
36 });
37 }
38 const afterClose = useRef<(() => void) | null>(null);
39 const closed = () => {
40 setPresented((current) => ({ ...current, mounted: false }));
41 const then = afterClose.current;
42 afterClose.current = null;
43 then?.();
44 };
45 const close = (then?: () => void) => {
46 afterClose.current = then ?? null;
47 props.onClose();
48 };
49 if (!presented.mounted) return null;
50 return (
51 <PaneSheet
52 key={presented.session}
53 isOpen={props.open}
54 detents={formSheetDetents(0.6, 0.95)}
55 // The inspector's bar holds its actions, so the Duo needs no rail inside this sheet.
56 prefersHorizontalBars
57 onDismiss={closed}
58 onNativeDismiss={() => {
59 props.onClose();
60 closed();
61 }}
62 >
63 <InspectorSheetContext value={close}>
64 <RoutedFormSheetContext value={false}>
65 {/* The sheet starts below the status bar. */}
66 <SafeAreaInsetsContext value={{ ...insets, top: 0 }}>
67 <PaneContext value="inspector">
68 <InspectorPanes content={props.content} onClose={() => close()} />
69 </PaneContext>
70 </SafeAreaInsetsContext>
71 </RoutedFormSheetContext>
72 </InspectorSheetContext>
73 </PaneSheet>
74 );